// Tasks view: filters by date, status, priority, kind. Group by date. function FilterButton({ label, active, onClick, color }) { return ( ); } function TasksView({ items, openForm, onStatusChange, onEdit }) { const [search, setSearch] = React.useState(""); const [statusFilter, setStatusFilter] = React.useState(new Set()); // empty = all const [priorityFilter, setPriorityFilter] = React.useState(new Set()); const [kindFilter, setKindFilter] = React.useState(new Set()); const [dateFilter, setDateFilter] = React.useState("upcoming"); // today | upcoming | week | overdue | all const [assigneeFilter, setAssigneeFilter] = React.useState(null); // null = tous, "baptiste" | "imelda" const toggle = (set, key, setter) => { const ns = new Set(set); if (ns.has(key)) ns.delete(key); else ns.add(key); setter(ns); }; // Build occurrences for a window of days const today = TODAY; const range = (() => { if (dateFilter === "today") return [today, today]; if (dateFilter === "week") return [today, addDays(today, 6)]; if (dateFilter === "upcoming") return [today, addDays(today, 30)]; if (dateFilter === "overdue") return [addDays(today, -60), addDays(today, -1)]; return [addDays(today, -60), addDays(today, 60)]; })(); const occurrences = []; let d = new Date(range[0]); while (d <= range[1]) { const its = itemsOnDate(items, d); its.forEach(it => occurrences.push({ item: it, date: new Date(d) })); d = addDays(d, 1); } // Filter const filtered = occurrences.filter(({ item, date }) => { if (search && !item.title.toLowerCase().includes(search.toLowerCase())) return false; if (kindFilter.size > 0 && !kindFilter.has(item.kind)) return false; if (priorityFilter.size > 0 && !priorityFilter.has(item.priority)) return false; if (assigneeFilter) { if (item.assignee && item.assignee !== assigneeFilter) return false; } if (statusFilter.size > 0) { if (item.kind !== "task") return false; const s = statusOn(item, date); if (!statusFilter.has(s)) return false; } if (dateFilter === "overdue") { if (item.kind !== "task") return false; if ((item.recurrence?.kind || "none") !== "none") return false; const s = statusOn(item, date); if (["done", "cancelled"].includes(s)) return false; } return true; }); // Group by date const groups = {}; filtered.forEach(({ item, date }) => { const k = ymd(date); if (!groups[k]) groups[k] = { date, items: [] }; groups[k].items.push(item); }); const groupKeys = Object.keys(groups).sort(); const totalActive = statusFilter.size + priorityFilter.size + kindFilter.size + (search ? 1 : 0) + (assigneeFilter ? 1 : 0); return (