"use client"; import { useCallback, useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { Calendar, ChevronRight, FileDown, History, Loader2, PencilLine, Save, Trash2, } from "lucide-react"; import { getSnapshots } from "../api/get-snapshots"; import { deleteSnapshot } from "../api/delete-snapshot"; import { useGridStore } from "@/features/grid/store/use-grid-store"; import { Button } from "@/components/ui/button"; import { StoreBadge } from "@/components/ui/badge"; import { EmptyState, ErrorState, Skeleton } from "@/components/ui/states"; import { confirmer, toast } from "@/components/ui/feedback"; import { fmtDecimal1, fmtEntier, fmtEur0 } from "@/lib/format"; import { couleurMarge } from "@/lib/marge"; import { isStaleServerActionError, STALE_ACTION_MESSAGE } from "@/lib/stale-action"; type Snapshot = Awaited>[number]; /** Modifications enregistrées : codein → gamme avant / après. */ type Changements = Record; interface Resume { totalCa?: number; tauxMargeGlobal?: number } const fmtDateLongue = new Intl.DateTimeFormat("fr-FR", { day: "numeric", month: "long", year: "numeric", hour: "2-digit", minute: "2-digit", }); function changementsDe(s: Snapshot): Changements { return (s.changes ?? {}) as Changements; } function resumeDe(s: Snapshot): Resume | null { return (s.summaryJson as Resume | null) ?? null; } function titreDe(s: Snapshot): string { return s.label ?? s.nomFournisseur ?? s.codeFournisseur; } /** * Onglet resté ouvert pendant un déploiement : les identifiants de Server * Action ont changé côté serveur. On propose le rechargement au lieu * d'afficher une erreur technique incompréhensible. */ async function proposerRechargement() { const ok = await confirmer({ titre: "Page à recharger", message: STALE_ACTION_MESSAGE, libelleConfirmer: "Recharger la page", }); if (ok) window.location.reload(); } interface SnapshotListProps { type?: "snapshot" | "export"; } export function SnapshotList({ type }: SnapshotListProps) { const [snapshots, setSnapshots] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<{ perime: boolean; detail: string } | null>(null); const [isDeleting, setIsDeleting] = useState(null); const restoreSnapshot = useGridStore((state) => state.restoreSnapshot); const router = useRouter(); const estExport = type === "export"; // Aucun setState avant le premier await : appelable depuis l'effet de montage. const fetchSnapshots = useCallback(async () => { try { const data = await getSnapshots(type); setSnapshots(data); setError(null); } catch (err) { setError({ perime: isStaleServerActionError(err), detail: err instanceof Error ? err.message : String(err), }); } finally { setLoading(false); } }, [type]); useEffect(() => { void fetchSnapshots(); }, [fetchSnapshots]); const reessayer = () => { setLoading(true); void fetchSnapshots(); }; const handleDelete = async (s: Snapshot) => { const ok = await confirmer({ titre: estExport ? "Retirer cet export de l'historique ?" : "Supprimer cette session ?", message: estExport ? `« ${titreDe(s)} » sera retiré définitivement de votre historique. Le fichier Excel déjà téléchargé n'est pas concerné.` : `« ${titreDe(s)} » sera supprimée définitivement : vous ne pourrez plus la reprendre dans la Grille.`, libelleConfirmer: "Supprimer", danger: true, }); if (!ok) return; setIsDeleting(s.id); try { const res = await deleteSnapshot(s.id); if (res.success) { setSnapshots((prev) => prev.filter((x) => x.id !== s.id)); toast.succes(estExport ? "L'export a été retiré de votre historique." : "La session a été supprimée de votre historique."); } else { toast.erreur(`La suppression a échoué : ${(res.error ?? "erreur inconnue").replace(/\.$/, "")}.`); } } catch (err) { if (isStaleServerActionError(err)) void proposerRechargement(); else toast.erreur("Une erreur est survenue pendant la suppression. Réessayez dans un instant."); } finally { setIsDeleting(null); } }; const handleLoad = async (s: Snapshot) => { const ok = await confirmer({ titre: "Reprendre dans la Grille ?", message: `Vos modifications en cours dans la Grille seront remplacées par celles de ${estExport ? "cet export" : "cette session"}.`, libelleConfirmer: "Reprendre dans la Grille", }); if (!ok) return; const changes: Record = {}; Object.entries(changementsDe(s)).forEach(([codein, delta]) => { changes[codein] = delta.after; }); restoreSnapshot(changes); const query = new URLSearchParams(); if (s.codeFournisseur) query.set("fournisseur", s.codeFournisseur); if (s.magasin) query.set("magasin", s.magasin); router.push(`/grid?${query.toString()}`); }; if (loading) { return (
{Array.from({ length: 4 }, (_, i) => )}
); } if (error && snapshots.length === 0) { return error.perime ? ( window.location.reload()}>Recharger la page} /> ) : ( Réessayer} /> ); } if (snapshots.length === 0) { return estExport ? ( ) : ( ); } return (
    {snapshots.map((s) => { const nbModifs = Object.keys(changementsDe(s)).length; const resume = resumeDe(s); const taux = resume?.tauxMargeGlobal; const IconeType = estExport ? FileDown : Save; return (
  • {titreDe(s)}

    {s.createdAt && ( {fmtDateLongue.format(new Date(s.createdAt))} )} {fmtEntier(nbModifs)} changement{nbModifs > 1 ? "s" : ""} de gamme
    {resume && (
    Chiffre d'affaires
    {fmtEur0(resume.totalCa ?? 0)}
    {taux != null && Number.isFinite(taux) && (
    Taux de marge
    {fmtDecimal1(taux)} %
    )}
    )}
  • ); })}
); }