(function(){ // ============================================================ // agenda.jsx — Universelle Traktandenliste (wiederkehrende Besprechungspunkte) // Eigenständige Entität, getrennt von Todos/Habits/Exams. // Depends on (window): todayISO, localISO, fmtDate, nextInternalRuleDate, DatePicker // ============================================================ const { useState } = React; const WEEKDAY_LABELS = [{ dow: 1, l: "Mo" }, { dow: 2, l: "Di" }, { dow: 3, l: "Mi" }, { dow: 4, l: "Do" }, { dow: 5, l: "Fr" }, { dow: 6, l: "Sa" }, { dow: 7, l: "So" }]; function anchorLabel(a){ if (!a) return ""; if (a.anchorType === "outlook") return a.anchor?.subject ? `Outlook · ${a.anchor.subject}` : "Outlook"; if (a.anchorType === "internalRule") { const days = (a.anchor?.days || []).map(d => WEEKDAY_LABELS.find(w => w.dow === d)?.l).filter(Boolean).join("/"); const everyN = a.anchor?.everyNWeeks > 1 ? `alle ${a.anchor.everyNWeeks} Wochen` : "wöchentlich"; return `${everyN}${days ? " · " + days : ""}`; } return "Manuell"; } function computeSuggestedNextDate(agenda){ if (agenda.anchorType === "internalRule") return nextInternalRuleDate(agenda.anchor, todayISO()); return null; } // ── Anker-Auswahl beim Anlegen ─────────────────────────────────────── function AnchorTypePicker({ value, onChange }){ const types = [ { id: "outlook", label: "Outlook-Termin" }, { id: "internalRule", label: "Eigene Regel" }, { id: "manual", label: "Manuell" }, ]; return (
{types.map(ty => ( ))}
); } function OutlookEventPicker({ events, value, onChange }){ const upcoming = (events || []).filter(ev => ev.start?.dateTime).slice(0, 30); if (upcoming.length === 0) { return

Keine Outlook-Termine geladen (nächste 14 Tage).

; } return (
{upcoming.map(ev => { const sel = value === ev.id; const dt = ev.start?.dateTime ? new Date(ev.start.dateTime) : null; return ( ); })}
); } function WeekdayPicker({ value, onChange }){ return (
{WEEKDAY_LABELS.map(w => { const sel = (value || []).includes(w.dow); return ( ); })}
); } // ── Neue Traktandenliste anlegen ───────────────────────────────────── function NewAgendaForm({ calEvents, onCreate, onCancel }){ const [title, setTitle] = useState(""); const [anchorType, setAnchorType] = useState("manual"); const [outlookEv, setOutlookEv] = useState(null); const [ruleDays, setRuleDays] = useState([1]); const [everyNWeeks, setEveryNWeeks] = useState(1); const [manualDate, setManualDate] = useState(null); function create(){ if (!title.trim()) return; let anchor = {}; if (anchorType === "outlook" && outlookEv) { anchor = { eventId: outlookEv.id, seriesMasterId: outlookEv.seriesMasterId || null, subject: outlookEv.subject || null }; } else if (anchorType === "internalRule") { anchor = { days: ruleDays, everyNWeeks: everyNWeeks || 1 }; } const nextDate = anchorType === "internalRule" ? nextInternalRuleDate(anchor, todayISO()) : anchorType === "manual" ? manualDate : (outlookEv?.start?.dateTime ? outlookEv.start.dateTime.slice(0, 10) : null); onCreate({ id: Date.now() + Math.random(), title: title.trim(), anchorType, anchor, nextDate, items: [], createdDate: todayISO(), createdAt: Date.now(), updatedAt: Date.now(), deletedAt: null, }); } return (
Neue Traktandenliste
setTitle(e.target.value)} style={{ width: "100%", boxSizing: "border-box", fontSize: 12, marginBottom: 8 }} autoFocus />
{anchorType === "outlook" &&
} {anchorType === "internalRule" && (
alle setEveryNWeeks(Math.max(1, parseInt(e.target.value) || 1))} style={{ width: 50, fontSize: 12 }} /> Wochen
)} {anchorType === "manual" &&
}
); } // ── Verschieben-Popup beim Abschliessen ────────────────────────────── function MoveOpenItemsPopup({ agenda, calEvents, openCount, onConfirm, onClose }){ const suggested = computeSuggestedNextDate(agenda); const [date, setDate] = useState(suggested); const [outlookEv, setOutlookEv] = useState(null); function confirm(){ if (agenda.anchorType === "outlook" && outlookEv) { onConfirm({ nextDate: outlookEv.start?.dateTime?.slice(0, 10) || null, anchor: { ...agenda.anchor, eventId: outlookEv.id, seriesMasterId: outlookEv.seriesMasterId || null, subject: outlookEv.subject || null } }); } else { onConfirm({ nextDate: date, anchor: agenda.anchor }); } } return (
e.stopPropagation()}>
{openCount} Punkt{openCount === 1 ? "" : "e"} nicht besprochen

Auf den nächsten Termin verschieben?

{agenda.anchorType === "outlook" ? : }
); } // ── Detailansicht ───────────────────────────────────────────────────── function AgendaDetail({ agenda, calEvents, onUpdate, onDelete, onClose, isMobile }){ const [newItem, setNewItem] = useState(""); const [showMovePopup, setShowMovePopup] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const items = agenda.items || []; const openItems = items.filter(i => !i.done); const doneItems = items.filter(i => i.done); const outlookMissing = agenda.anchorType === "outlook" && agenda.anchor?.eventId && !(calEvents || []).some(ev => String(ev.id) === String(agenda.anchor.eventId)); function addItem(){ if (!newItem.trim()) return; onUpdate({ ...agenda, items: [...items, { id: Date.now() + Math.random(), text: newItem.trim(), done: false, createdDate: todayISO() }], updatedAt: Date.now() }); setNewItem(""); } function toggleItem(id){ onUpdate({ ...agenda, items: items.map(i => String(i.id) === String(id) ? { ...i, done: !i.done } : i), updatedAt: Date.now() }); } function deleteItem(id){ onUpdate({ ...agenda, items: items.filter(i => String(i.id) !== String(id)), updatedAt: Date.now() }); } function finishMeeting(){ if (openItems.length > 0) { setShowMovePopup(true); return; } setShowMovePopup(true); } function applyMove({ nextDate, anchor }){ onUpdate({ ...agenda, nextDate, anchor, updatedAt: Date.now() }); setShowMovePopup(false); } function decoupleFromOutlook(){ onUpdate({ ...agenda, anchorType: "manual", anchor: { nextDate: null }, updatedAt: Date.now() }); } return (
{agenda.title}
{anchorLabel(agenda)}
{agenda.nextDate &&
Nächster Termin: {fmtDate(agenda.nextDate)}
} {outlookMissing && (
Termin existiert nicht mehr in Outlook.
)}
setNewItem(e.target.value)} onKeyDown={e => { if (e.key === "Enter") addItem(); }} style={{ flex: 1, fontSize: 12 }} />
{openItems.map(i => (
))} {openItems.length === 0 &&

Keine offenen Punkte.

}
{doneItems.length > 0 && (
{doneItems.map(i => (
toggleItem(i.id)} style={{ display: "flex", alignItems: "center", gap: 6, cursor: "pointer", opacity: 0.55 }}> {i.text}
))}
)}
{!confirmDelete ? : }
{showMovePopup && setShowMovePopup(false)} />}
); } // ── Hauptansicht ────────────────────────────────────────────────────── function AgendaView({ agendas, calEvents, isMobile, onAdd, onUpdate, onDelete }){ const [selectedId, setSelectedId] = useState(null); const [showNew, setShowNew] = useState(false); const list = agendas || []; const sel = list.find(a => String(a.id) === String(selectedId)) || null; return (
Traktandenlisten ({list.length})
{showNew && { onAdd(a); setShowNew(false); setSelectedId(a.id); }} onCancel={() => setShowNew(false)} />} {list.length === 0 && !showNew && (

Noch keine Traktandenliste.

Lege eine neue Liste an, z.B. für eine wiederkehrende Besprechung.

)}
{list.map(a => { const isSel = sel?.id === a.id; const openCount = (a.items || []).filter(i => !i.done).length; return (
setSelectedId(isSel ? null : a.id)} style={{ padding: "10px 12px", borderRadius: 8, cursor: "pointer", background: isSel ? "var(--col-accent-wash)" : "var(--col-bg)", border: isSel ? "1.5px solid var(--col-accent)" : "1px solid var(--col-border-soft)", }}>
{a.title} {a.nextDate && {fmtDate(a.nextDate)}}
{anchorLabel(a)} {openCount > 0 && {openCount} offen}
); })}
{sel && isMobile &&
setSelectedId(null)} style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.35)", zIndex: 300 }} />} {sel && setSelectedId(null)} isMobile={isMobile} />}
); } Object.assign(window, { AgendaView }); })();