(function(){ // ============================================================ // views.jsx — SettingsPanel, ReviewModal, CalendarWeekView, // SlotAdder, downloadICS // Depends on (window): THEMES, ACCENT_PRESETS, GLASS_PRESETS, applyTheme, applyGlass, // todayISO, localISO, getCatColor, prioCol, hexToRgba // ============================================================ const { useState, useEffect } = React; // ── NotesSendButton ────────────────────────────────────────────────── function NotesSendButton({ notes, onClear, profile }){ const [sendState, setSendState] = useState("idle"); const send = async () => { if(!notes.trim())return; setSendState("sending"); try{ const uq=profile?`?user=${encodeURIComponent(profile)}`:''; const r=await fetch('/api/notes'+uq,{method:'POST',headers:{'Content-Type':'application/json',...authHeaders()},body:JSON.stringify({text:notes,ts:Date.now()})}); setSendState(r.ok?"ok":"err"); if(r.ok&&onClear)onClear(); }catch{setSendState("err");} setTimeout(()=>setSendState("idle"),3000); }; return( ); } // ── SettingsPanel ──────────────────────────────────────────────────── function SettingsPanel({ settings, onSave, onClose }){ const [s, setS] = useState(settings); const [openFeat, setOpenFeat] = useState(null); const [settingsTab, setSettingsTab] = useState("aussehen"); // FIXED: Einstellungen tabs added const notesKey = "planer_notes_" + (settings.profile || ""); const [notes, setNotes] = useState(() => localStorage.getItem("planer_notes_" + (settings.profile || "")) || ""); const [piAuth, setPiAuth] = useState({connected:false,pending:false,userCode:null,verUri:null}); const [pwOpen, setPwOpen] = useState(false); const [pw, setPw] = useState({cur:"",nw:"",status:"",msg:""}); const [unOpen, setUnOpen] = useState(false); const [un, setUn] = useState({nw:"",pw:"",status:"",msg:""}); const [emOpen, setEmOpen] = useState(false); const [em, setEm] = useState({val:"",status:"",msg:""}); const [reportSt, setReportSt] = useState(""); const [usersModal, setUsersModal] = useState(null); const [overviewOpen, setOverviewOpen] = useState(false); const [overview, setOverview] = useState(null); // ?user= for per-profile Outlook tokens — uses SAVED settings.profile, not unsaved local state const uq = () => settings.profile ? `?user=${encodeURIComponent(settings.profile)}` : ''; useEffect(()=>{ fetch('/api/outlook-status'+uq(),{headers:{...authHeaders()}}).then(r=>r.json()).then(d=>{ // FIXED: add authHeaders setPiAuth(p=>({...p,connected:d.connected})); }).catch(()=>{}); },[]); // FIXED: re-register push subscription on mount if permission already granted // (subscriptions can expire/become invalid; this keeps the server-side copy fresh) useEffect(()=>{ if(typeof Notification!=='undefined'&&Notification.permission==='granted')subscribePush(); },[]); async function subscribePush(){ if(typeof Notification==='undefined'||!('serviceWorker' in navigator))return false; try{ const reg = await navigator.serviceWorker.ready; const keyRes = await fetch('/api/push/key').then(r=>r.json()); const raw = keyRes.publicKey; const padding = '='.repeat((4 - raw.length % 4) % 4); const base64 = (raw + padding).replace(/-/g,'+').replace(/_/g,'/'); const rawData = atob(base64); const key = new Uint8Array([...rawData].map(c=>c.charCodeAt(0))); const sub = await reg.pushManager.subscribe({userVisibleOnly:true, applicationServerKey:key}); const r = await fetch('/api/push/subscribe',{method:'POST',headers:{'Content-Type':'application/json',...authHeaders()},body:JSON.stringify(sub)}); return r.ok; }catch(e){console.error('Push subscribe:',e);return false;} } useEffect(()=>{ if(!piAuth.pending)return; const iv=setInterval(async()=>{ try{ const d=await fetch('/api/outlook-status'+uq(),{headers:{...authHeaders()}}).then(r=>r.json()); // FIXED: add authHeaders if(d.connected){setPiAuth({connected:true,pending:false,userCode:null,verUri:null});clearInterval(iv);} else if(!d.pending){setPiAuth(p=>({...p,pending:false}));clearInterval(iv);} }catch{} },3000); return()=>clearInterval(iv); },[piAuth.pending]); async function startPiAuth(){ if(!s.azureClientId){alert(window.t("settings.azureMissing"));return;} try{ const d=await fetch('/api/outlook-auth'+uq(),{method:'POST',headers:{'Content-Type':'application/json',...authHeaders()},body:JSON.stringify({clientId:s.azureClientId})}).then(r=>r.json()); // FIXED: add authHeaders if(d.user_code)setPiAuth({connected:false,pending:true,userCode:d.user_code,verUri:d.verification_uri}); else alert(d.error||"Fehler"); }catch(e){alert("Fehler: "+e.message);} } async function disconnectPiAuth(){ await fetch('/api/outlook-auth'+uq(),{method:'DELETE',headers:{...authHeaders()}}).catch(()=>{}); // FIXED: add authHeaders setPiAuth({connected:false,pending:false,userCode:null,verUri:null}); } async function doChangePw(){ if(!pw.cur||!pw.nw)return; setPw(x=>({...x,status:"saving"})); try{ const r=await fetch('/api/change-password',{method:'POST',headers:{'Content-Type':'application/json',...authHeaders()},body:JSON.stringify({currentPassword:pw.cur,newPassword:pw.nw})}); const d=await r.json(); if(r.ok)setPw({cur:"",nw:"",status:"ok",msg:window.t("settings.passwordChanged")}); else setPw(x=>({...x,status:"err",msg:d.error||window.t("settings.error")})); }catch{setPw(x=>({...x,status:"err",msg:window.t("settings.networkError")}));} } async function doChangeUn(){ if(!un.nw||!un.pw)return; setUn(x=>({...x,status:"saving"})); try{ const r=await fetch('/api/change-username',{method:'POST',headers:{'Content-Type':'application/json',...authHeaders()},body:JSON.stringify({newUsername:un.nw,currentPassword:un.pw})}); const d=await r.json(); if(r.ok){window.setAuthToken(d.token,d.username);window.location.reload();} else setUn(x=>({...x,status:"err",msg:d.error||window.t("settings.error")})); }catch{setUn(x=>({...x,status:"err",msg:window.t("settings.networkError")}));} } async function loadOverview(){ if(overviewOpen){setOverviewOpen(false);return;} setOverviewOpen(true); if(overview)return; try{ const r=await fetch('/api/admin/overview',{headers:{...authHeaders()}}); const d=await r.json(); setOverview(Array.isArray(d)?d:[]); }catch{setOverview([]);} } async function openUsersModal(){ setUsersModal("loading"); try{ const r=await fetch('/api/admin/users',{headers:{...authHeaders()}}); const d=await r.json(); setUsersModal(Array.isArray(d)?d:[]); }catch{setUsersModal([]);} } async function doSendReport(){ setReportSt("sending"); try{ const r=await fetch('/api/admin/send-report',{headers:{...authHeaders()}}); setReportSt(r.ok?"ok":"err"); }catch{setReportSt("err");} } async function doChangeEm(){ setEm(x=>({...x,status:"saving"})); try{ const r=await fetch('/api/change-email',{method:'POST',headers:{'Content-Type':'application/json',...authHeaders()},body:JSON.stringify({email:em.val.trim()})}); const d=await r.json(); if(r.ok)setEm(x=>({...x,status:"ok",msg:d.email?window.t("settings.savedEmail",{email:d.email}):window.t("settings.emailRemoved")})); else setEm(x=>({...x,status:"err",msg:d.error||window.t("settings.error")})); }catch{setEm(x=>({...x,status:"err",msg:window.t("settings.networkError")}));} } const secTitle = (label) => (
{t("settings.azureHelp")}
{t("pruef.noExams")}
{t("pruef.noExamsHint")}
{t("pruef.noBlocks")}
)}