// Dashboard view: hero + today + overdue + upcoming events
function StatCard({ icon, label, value, hint, accent = "var(--terracotta)", bg = "var(--terracotta-tint)" }) {
return (
{label}
{value}
{hint &&
{hint}
}
);
}
function DashboardHero({ items, openForm, user }) {
const userInfo = (typeof USERS !== "undefined" && USERS[user]) || { name: user || "vous" };
const today = TODAY;
const greeting = (() => {
const h = today.getHours();
if (h < 12) return "Bonjour";
if (h < 18) return "Bon après-midi";
return "Bonsoir";
})();
const todayItems = itemsOnDate(items, today);
const tasksToday = todayItems.filter(x => x.kind === "task");
const remaining = tasksToday.filter(x => {
const s = statusOn(x, today);
return s === "todo" || s === "progress";
}).length;
const done = tasksToday.filter(x => statusOn(x, today) === "done").length;
const total = tasksToday.length;
return (
{/* Decorative arc */}
{fmtFull(today)}
{greeting}, {userInfo.name}.
{remaining > 0
? `${remaining} chose${remaining > 1 ? "s" : ""} à faire`
: total > 0 ? "tout est sous contrôle." : "rien de prévu aujourd'hui."}
Aujourd'hui
{total === 0 ? "Aucune tâche" : `${done} terminée${done > 1 ? "s" : ""} sur ${total}`}
);
}
function TaskRow({ item, date, onStatusChange, onEdit, dense }) {
const isTask = item.kind === "task";
const status = statusOn(item, date);
const s = STATES[status];
const k = KINDS[item.kind];
const cycleStatus = () => {
if (!isTask) return;
const order = ["todo", "progress", "done"];
const i = order.indexOf(status);
const next = i === -1 || i === order.length - 1 ? "todo" : order[i + 1];
onStatusChange(item.id, ymd(date), next);
};
return (
{/* Checkbox or dot */}
{/* Title + meta */}
{item.title}
{!isTask && }
{item.recurrence?.kind && item.recurrence.kind !== "none" && (
{recurrenceLabel(item.recurrence)}
)}
{item.notify && (
rappel
)}
{item.assignee && (
{(USERS[item.assignee]?.avatar || item.assignee[0].toUpperCase())}
{USERS[item.assignee]?.name || item.assignee}
)}
{item.notes && {item.notes}}
{/* Right side */}
{isTask &&
}
onEdit(item)} />
);
}
function recurrenceLabel(r) {
if (!r || r.kind === "none") return "Une fois";
if (r.kind === "daily") return "Tous les jours";
if (r.kind === "weekly") return "Chaque semaine";
if (r.kind === "monthly") return "Chaque mois";
if (r.kind === "custom") return `${(r.days || []).length} dates`;
return "";
}
function DashboardView({ items, openForm, onStatusChange, onEdit, navigate, user }) {
const today = TODAY;
const todayItems = itemsOnDate(items, today);
const todayTasks = todayItems.filter(x => x.kind === "task");
const todayEvents = todayItems.filter(x => x.kind !== "task");
// Overdue: non-recurring tasks before today, not done/cancelled
const overdue = items.filter(x => {
if (x.kind !== "task") return false;
if ((x.recurrence?.kind || "none") !== "none") return false;
if (!isBefore(fromYmd(x.date), today)) return false;
return !["done", "cancelled"].includes(x.status);
}).sort((a, b) => a.date.localeCompare(b.date));
// Upcoming events (next 14 days, not today)
const upcoming = [];
for (let i = 1; i <= 14; i++) {
const d = addDays(today, i);
const its = itemsOnDate(items, d).filter(x => x.kind !== "task");
its.forEach(it => upcoming.push({ item: it, date: d }));
}
// Counts
const remainingToday = todayTasks.filter(x => ["todo", "progress"].includes(statusOn(x, today))).length;
const doneToday = todayTasks.filter(x => statusOn(x, today) === "done").length;
return (
{/* Stats grid */}
(x.recurrence?.kind || "none") !== "none").length} hint="actives" accent="var(--done)" bg="var(--done-bg)" />
{/* Today's tasks */}
navigate("tasks")}>Tout voir}
/>
{todayTasks.length === 0 ? (
Nouvelle tâche} />
) : (
todayTasks.map(it => )
)}
{overdue.length > 0 && (
<>
{overdue.slice(0, 4).map(it => )}
>
)}
{/* Right column */}
{todayEvents.length === 0 ? (
Aucun événement aujourd'hui
) : (
todayEvents.map(it =>
)
)}
{upcoming.length === 0 ? (
Pas d'événement prévu
) : (
upcoming.slice(0, 6).map(({ item, date }, i) => {
const k = KINDS[item.kind];
return (
{DAYS_SHORT[date.getDay()].replace(".", "")}
{date.getDate()}
{item.title}
{item.notes &&
{item.notes}
}
onEdit(item)} />
);
})
)}
);
}
Object.assign(window, { DashboardView, TaskRow, recurrenceLabel });