// 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 (
openForm()}>Nouvelle tâche} /> {/* Search + date scope */}
setSearch(e.target.value)} placeholder="Rechercher une tâche, un événement…" style={{ flex: 1, border: 0, outline: "none", background: "transparent", fontSize: 16 }} /> {search && }
{[ ["today", "Aujourd'hui"], ["week", "Semaine"], ["upcoming", "À venir"], ["overdue", "En retard"], ["all", "Tout"], ].map(([k, l]) => ( ))}
État
{Object.entries(STATES).map(([k, s]) => ( toggle(statusFilter, k, setStatusFilter)} /> ))}
Priorité
{Object.entries(PRIORITIES).map(([k, p]) => ( toggle(priorityFilter, k, setPriorityFilter)} /> ))}
Assigné
{Object.entries(USERS).map(([k, u]) => ( setAssigneeFilter(assigneeFilter === k ? null : k)} /> ))}
Type
{Object.entries(KINDS).map(([k, kk]) => ( toggle(kindFilter, k, setKindFilter)} /> ))} {totalActive > 0 && ( )}
{/* Groups */} {groupKeys.length === 0 ? ( openForm()}>Nouvelle tâche} /> ) : ( groupKeys.map(k => { const { date, items: its } = groups[k]; const isToday = sameDay(date, TODAY); return (
{DAYS_LONG[date.getDay()].charAt(0).toUpperCase() + DAYS_LONG[date.getDay()].slice(1)}{" "} {date.getDate()} {MONTHS[date.getMonth()]}
{fmtRel(date)} {its.length} élément{its.length > 1 ? "s" : ""}
{its.map(it => )}
); }) )}
); } Object.assign(window, { TasksView, FilterButton });