"use client"; import { useMemo, useState, useTransition } from "react"; import { AlertTriangle, CalendarClock, CheckCircle2, HelpCircle, Plus, Power, Trash2, } from "lucide-react"; import { upsertCadence, toggleCadence, removeCadence, type CadenceView, type CadenceStatut, } from "@/features/commandes-auto/actions"; const SITES: { code: string; label: string }[] = [ { code: "292", label: "292 — Frouard / Nancy" }, { code: "579", label: "579 — Houdemont" }, ]; function fmtDate(iso: string | null): string { if (!iso) return "—"; return new Date(iso).toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric" }); } function tempsRestant(jours: number | null): string { if (jours === null) return "Réception inconnue"; if (jours === 0) return "Aujourd'hui"; if (jours < 0) return `En retard de ${Math.abs(jours)} j`; if (jours === 1) return "Demain"; return `Dans ${jours} j`; } const STATUT_META: Record = { a_commander: { label: "À commander", cls: "bg-red-100 text-red-700", Icon: AlertTriangle }, bientot: { label: "Bientôt", cls: "bg-amber-100 text-amber-700", Icon: CalendarClock }, ok: { label: "OK", cls: "bg-emerald-100 text-emerald-700", Icon: CheckCircle2 }, inconnu: { label: "Inconnu", cls: "bg-gray-100 text-gray-500", Icon: HelpCircle }, }; function StatutBadge({ statut }: { statut: CadenceStatut }) { const { label, cls, Icon } = STATUT_META[statut]; return ( {label} ); } /** Tri : à commander d'abord (plus en retard en tête), puis bientôt, ok, inconnu, et inactifs en dernier. */ function trier(rows: CadenceView[]): CadenceView[] { const rank: Record = { a_commander: 0, bientot: 1, ok: 2, inconnu: 3 }; return [...rows].sort((a, b) => { if (a.actif !== b.actif) return a.actif ? -1 : 1; if (a.statut !== b.statut) return rank[a.statut] - rank[b.statut]; const ja = a.joursRestants ?? Number.POSITIVE_INFINITY; const jb = b.joursRestants ?? Number.POSITIVE_INFINITY; return ja - jb; }); } function AddCadenceForm({ site, fournisseurs, existingCodes, onDone, pending, }: { site: string; fournisseurs: { code: string; nom: string }[]; existingCodes: Set; onDone: (input: { codefou: string; nomfou: string; semaines: number }) => void; pending: boolean; }) { const [value, setValue] = useState(""); const [semaines, setSemaines] = useState(4); const [err, setErr] = useState(null); const listId = `fou-list-${site}`; // label affiché dans le datalist → fournisseur const byLabel = useMemo(() => { const m = new Map(); for (const f of fournisseurs) m.set(`${f.nom} — ${f.code}`, f); return m; }, [fournisseurs]); function submit() { setErr(null); const f = byLabel.get(value.trim()); if (!f) { setErr("Sélectionnez un fournisseur dans la liste."); return; } if (existingCodes.has(f.code)) { setErr("Ce fournisseur est déjà cadencé sur ce magasin."); return; } if (!Number.isFinite(semaines) || semaines < 1 || semaines > 104) { setErr("Intervalle : 1 à 104 semaines."); return; } onDone({ codefou: f.code, nomfou: f.nom, semaines }); setValue(""); setSemaines(4); } return (
setValue(e.target.value)} placeholder="Rechercher…" className="min-w-[260px] rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" /> {fournisseurs.map((f) => (
setSemaines(parseInt(e.target.value, 10))} className="w-28 rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
{err && {err}}
); } function SiteSection({ site, label, rows, fournisseurs, }: { site: string; label: string; rows: CadenceView[]; fournisseurs: { code: string; nom: string }[]; }) { const [pending, startTransition] = useTransition(); const sorted = useMemo(() => trier(rows), [rows]); const existingCodes = useMemo(() => new Set(rows.map((r) => r.codefou)), [rows]); const nbACommander = rows.filter((r) => r.actif && r.statut === "a_commander").length; // Pas de router.refresh() : les actions appellent déjà revalidatePath, dont // la réponse rafraîchit la page. Les deux ensemble rechargeaient tout deux fois. function run(fn: () => Promise) { startTransition(async () => { await fn(); }); } const add = (input: { codefou: string; nomfou: string; semaines: number }) => run(() => upsertCadence({ codefou: input.codefou, nomfou: input.nomfou, site, intervalleSemaines: input.semaines, actif: true, }), ); const changeSemaines = (r: CadenceView, semaines: number) => run(() => upsertCadence({ codefou: r.codefou, nomfou: r.nomfou, site, intervalleSemaines: semaines, actif: r.actif, }), ); return (

{label}

{rows.length} cadencé{rows.length !== 1 ? "s" : ""} {nbACommander > 0 && ( {nbACommander} à commander )}
{sorted.length === 0 && ( )} {sorted.map((r) => { const urgent = r.actif && r.statut === "a_commander"; return ( ); })}
Code Fournisseur Toutes les Dernière réception Prochaine commande Temps restant Statut Actions
Aucun fournisseur cadencé sur ce magasin. Ajoutez-en un ci-dessus.
{r.codefou} {r.nomfou} { const v = parseInt(e.target.value, 10); if (v && v !== r.intervalleSemaines && v >= 1 && v <= 104) changeSemaines(r, v); }} className="w-16 rounded-md border border-gray-200 px-2 py-1 text-center text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" /> sem. {fmtDate(r.derniereReception)} {fmtDate(r.echeance)} {tempsRestant(r.joursRestants)}
); } export function CadencierClient({ cadences, fournisseurs, }: { cadences: CadenceView[]; fournisseurs: { code: string; nom: string }[]; }) { const bySite = useMemo(() => { const m = new Map(); for (const c of cadences) { if (!m.has(c.site)) m.set(c.site, []); m.get(c.site)!.push(c); } return m; }, [cadences]); return (

Activez un fournisseur en alerte de commande sur un magasin et définissez sa fréquence (toutes les X semaines). L'échéance est calculée à partir de la{" "} dernière réception du fournisseur sur le magasin.

{SITES.map(({ code, label }) => ( ))}
); }