// Alternative views — Mijn dag, Per klant, Lijst, Team const { useMemo: useMemoViews, useState: useStateViews } = React; // ═══ MIJN DAG ═══════════════════════════════════════════ const MijnDag = ({ onPick }) => { const tasks = window.getMyTasks(window.CURRENT_USER_ID); const today = window.iso(window.today); const tomorrow = window.iso(window.addDays(window.today, 1)); const groupBy = (pred) => tasks.filter(pred); const overdue = groupBy(t => window.daysUntil(t.deadline) < 0); const todayT = groupBy(t => t.deadline === today); const tomor = groupBy(t => t.deadline === tomorrow); const week = groupBy(t => { const d = window.daysUntil(t.deadline); return d > 1 && d <= 7; }); const later = groupBy(t => window.daysUntil(t.deadline) > 7); const u = window.medewerkerById(window.CURRENT_USER_ID); return (
{/* Greeting banner */}
ZATERDAG · 18 APRIL 2026
Goedemorgen, Sanne.
Je hebt {todayT.length} taken voor vandaag {overdue.length > 0 && <>, en {overdue.length} stap{overdue.length>1?'pen':''} loopt te laat}.
{overdue.length > 0 && ( )}
); }; const StatTile = ({ label, value, tone }) => { const c = tone === 'hot' ? 'oklch(0.55 0.18 30)' : tone === 'alert' ? 'oklch(0.48 0.18 25)' : 'var(--ink-1)'; return (
{label.toUpperCase()}
{value}
); }; const TaskSection = ({ title, tasks, onPick, tone, defaultOpen = true }) => { const [open, setOpen] = useStateViews(defaultOpen); if (tasks.length === 0) return null; return (
{open && (
{tasks.map((t, i) => )}
)}
); }; const TaskRow = ({ t, onPick }) => { const p = window.PROJECTEN.find(x => x.id === t.projectId); const b = window.bucketById(t.bucketId); const klant = window.klantById(t.klant); const overdue = window.daysUntil(t.deadline) < 0; return (
onPick(p)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '11px 14px', background: 'var(--paper)', border: '1px solid var(--line)', borderLeft: `3px solid ${overdue ? 'oklch(0.65 0.18 25)' : klant.kleur}`, borderRadius: 8, cursor: 'pointer', }}> e.stopPropagation()} />
{b.naam}
{t.projectId} · {t.projectNaam} · {klant.naam}
); }; // ═══ PER KLANT ═══════════════════════════════════════════ const PerKlant = ({ onPick }) => { const [expanded, setExpanded] = useStateViews(() => new Set(['hak','remia'])); const grouped = useMemoViews(() => { const m = {}; window.PROJECTEN.forEach(p => { if (p.huidigeBucket === 'afgerond' || p.huidigeBucket === 'niet-ok') return; (m[p.klant] = m[p.klant] || []).push(p); }); return m; }, []); const klantIds = Object.keys(grouped).sort((a, b) => grouped[b].length - grouped[a].length); return (
{klantIds.map(kid => { const klant = window.klantById(kid); const projs = grouped[kid]; const isOpen = expanded.has(kid); const toggle = () => { const s = new Set(expanded); s.has(kid) ? s.delete(kid) : s.add(kid); setExpanded(s); }; const urgent = projs.filter(p => p.prio === 'dringend').length; return (
{isOpen && (
)}
); })}
); }; const KlantProjectTable = ({ projs, onPick, klantKleur }) => { return (
{projs.map(p => (
onPick(p)} style={{ padding: '12px 4px', borderBottom: '1px solid var(--line)', cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 8, }}>
{p.id} {p.naam} deadline {window.fmtDate(p.einde)}
{p.sauzen.map((s, i) => { const b = window.bucketById(s.bucketId); const prog = window.sausProgress(p, s.bucketId); return (
{s.naam}
{b.naam}
); })}
))}
); }; // ═══ LIJST ══════════════════════════════════════════════ const Lijst = ({ onPick }) => { const [sort, setSort] = useStateViews('deadline'); const projs = useMemoViews(() => { const arr = [...window.PROJECTEN]; arr.sort((a, b) => { if (sort === 'deadline') return a.einde.localeCompare(b.einde); if (sort === 'klant') return window.klantById(a.klant).naam.localeCompare(window.klantById(b.klant).naam); if (sort === 'prio') { const order = { dringend: 0, belangrijk: 1, gemiddeld: 2, laag: 3 }; return order[a.prio] - order[b.prio]; } return 0; }); return arr; }, [sort]); return (
Sorteer: {['deadline','klant','prio'].map(s => ( ))}
PROJECT ID NAAM KLANT HUIDIGE STAP PRIO TEAM DEADLINE
{projs.map(p => { const klant = window.klantById(p.klant); const b = window.bucketById(p.huidigeBucket); return (
onPick(p)} style={{ display: 'grid', gridTemplateColumns: '98px 1.6fr 1fr 180px 110px 90px 120px', padding: '10px 16px', fontSize: 12.5, borderBottom: '1px solid var(--line)', cursor: 'pointer', alignItems: 'center', }} onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-1)'} onMouseLeave={e => e.currentTarget.style.background = 'var(--paper)'} > {p.id} {p.naam} {klant.naam} {b.naam}
); })}
); }; // ═══ TEAM WORKLOAD ══════════════════════════════════════ const Team = ({ onPick }) => { const workload = useMemoViews(() => { const m = {}; window.MEDEWERKERS.forEach(med => m[med.id] = { open: 0, overdue: 0, deze_week: 0, vandaag: 0 }); window.PROJECTEN.forEach(p => { Object.entries(p.plan).forEach(([bid, step]) => { if (step.done || !step.owner) return; const d = window.daysUntil(step.deadline); const bucket = m[step.owner]; if (!bucket) return; bucket.open++; if (d < 0) bucket.overdue++; if (d === 0) bucket.vandaag++; if (d >= 0 && d <= 7) bucket.deze_week++; }); }); return m; }, []); const maxOpen = Math.max(...Object.values(workload).map(w => w.open), 1); return (
Openstaande stappen per medewerker, gesorteerd op bezetting. Rode balk = te laat.
{window.MEDEWERKERS .map(m => ({ m, w: workload[m.id] })) .sort((a, b) => b.w.open - a.w.open) .map(({ m, w }) => (
{m.naam}
{w.overdue > 0 && (
)} {w.vandaag > 0 && (
)} {Math.max(w.open - w.overdue - w.vandaag, 0) > 0 && (
)}
{w.open}
OPEN
{w.overdue}
TE LAAT
{w.deze_week}
DEZE WEEK
))}
); }; Object.assign(window, { MijnDag, PerKlant, Lijst, Team });