"use client";
import React from "react";
import { useGridStore } from "@/features/grid/store/use-grid-store";
import { SyncQlikButton } from "./sync-qlik-button";
import { useSaveDrafts } from "@/features/grid/hooks/use-save-drafts";
import { ChevronDown, Download, FileSpreadsheet, FileText, History, Loader2, RotateCcw, Save, Table2 } from "lucide-react";
import { useMemo, useState, useTransition } from "react";
import { saveSnapshot } from "@/features/snapshots/api/save-snapshot";
import { isStaleServerActionError, STALE_ACTION_MESSAGE } from "@/lib/stale-action";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { confirmer, toast } from "@/components/ui/feedback";
import { fmtDecimal1, fmtEntier, fmtEur0 } from "@/lib/format";
import { couleurMarge } from "@/lib/marge";
import {
exporterFichierGammes,
exporterGammeA,
exporterTousProduits,
imprimerPdf,
lignesModifiees,
} from "@/features/grid/lib/exports";
function Stat({ label, value, sub, subColor }: { label: string; value: string; sub?: string; subColor?: string }) {
return (
{label}
{value}
{sub && {sub}}
);
}
function messageErreur(err: unknown): string {
if (isStaleServerActionError(err)) return STALE_ACTION_MESSAGE;
return err instanceof Error ? err.message : String(err);
}
/**
* Barre du bas de la Grille : totaux du périmètre affiché et TOUTES les actions
* sur les gammes (annuler, enregistrer, exporter). Il y avait des doublons dans
* l'en-tête, avec des droits différents.
*/
function FloatingSummaryBarInner({ isAdmin, nomFournisseur }: { isAdmin: boolean; nomFournisseur: string }) {
const summary = useGridStore((s) => s.summary);
const resetDrafts = useGridStore((s) => s.resetDrafts);
const rows = useGridStore((s) => s.rows);
const filterFournisseur = useGridStore((s) => s.filters.codeFournisseur);
// Magasin affiché (bascule de la Grille). `filters.magasin` n'est jamais
// renseigné : les sessions et validations étaient toutes notées « TOTAL ».
const activeMagasin = useGridStore((s) => s.activeMagasin);
const [isPending, startTransition] = useTransition();
const [enCours, setEnCours] = useState(null);
const { save, hasDrafts, count } = useSaveDrafts(activeMagasin || "TOTAL");
const supplierCode = filterFournisseur || rows[0]?.codeFournisseur;
const lastQlikUpdate = useMemo(() => {
let max: string | null = null;
for (const r of rows) {
if (r.networkFetchedAt && (!max || r.networkFetchedAt > max)) max = r.networkFetchedAt;
}
return max;
}, [rows]);
const handleSave = () => {
startTransition(async () => {
try {
const result = await save();
if (result.success) {
toast.succes(`${result.saved} gamme${result.saved > 1 ? "s" : ""} enregistrée${result.saved > 1 ? "s" : ""}.`);
} else {
toast.erreur(`Enregistrement impossible : ${result.error ?? "erreur inconnue"}`);
}
} catch (err) {
toast.erreur(`Enregistrement impossible : ${messageErreur(err)}`);
}
});
};
const handleReset = async () => {
const ok = await confirmer({
titre: "Annuler les modifications ?",
message: `Les ${count} gamme${count > 1 ? "s" : ""} modifiée${count > 1 ? "s" : ""} et non enregistrée${count > 1 ? "s" : ""} reviendront à leur valeur précédente.`,
libelleConfirmer: "Annuler les modifications",
danger: true,
});
if (ok) {
resetDrafts();
toast.info("Modifications annulées.");
}
};
/**
* Enregistre une copie de la session dans l'Historique.
* Renvoie l'identifiant créé, ou `null` en cas d'échec (déjà signalé).
*/
const handleSnapshot = async (labelOverride?: string, type: "snapshot" | "export" = "snapshot") => {
if (rows.length === 0) return null;
const { draftChanges } = useGridStore.getState();
// Tous les changements : brouillons + gammes déjà enregistrées (≠ FF)
const changes = Object.fromEntries(
lignesModifiees().map(r => [
r.codein,
{ before: r.codeGammeInit, after: (draftChanges[r.codein] ?? r.codeGamme) as string },
])
);
try {
const res = await saveSnapshot({
codeFournisseur: filterFournisseur || rows[0].codeFournisseur,
nomFournisseur: rows[0].nomFournisseur,
magasin: activeMagasin || "TOTAL",
label: labelOverride || `Session ${rows[0].nomFournisseur} — ${new Date().toLocaleString("fr-FR", { dateStyle: "short", timeStyle: "short" })}`,
changes,
type,
summary: {
totalRows: summary.totalRows,
totalCa: summary.totalCa,
totalMarge: summary.totalMarge,
tauxMargeGlobal: summary.tauxMargeGlobal,
},
});
if (!res.success) throw new Error(res.error);
if (!labelOverride) toast.succes("Session enregistrée dans l'Historique.");
return res.snapshotId;
} catch (err) {
if (!labelOverride) toast.erreur(`La session n'a pas pu être enregistrée : ${messageErreur(err)}`);
return null;
}
};
/** Lance un export avec indicateur et messages d'erreur communs. */
const lancer = async (cle: string, action: () => Promise, vide: string) => {
if (enCours) return;
setEnCours(cle);
try {
const ok = await action();
if (!ok) toast.info(vide);
} catch (err) {
toast.erreur(`L'export a échoué : ${messageErreur(err)}`);
} finally {
setEnCours(null);
}
};
return (
{isAdmin && }
Fichier d'import des gammes lancer("modifs", async () => {
if (lignesModifiees().length === 0) return false;
const nom = lignesModifiees()[0].nomFournisseur;
// Copie dans l'Historique, onglet « Exports »
await handleSnapshot(`Export ${nom} — ${new Date().toLocaleString("fr-FR", { dateStyle: "short", timeStyle: "short" })}`, "export");
return exporterFichierGammes(true);
}, "Aucune gamme n'a été modifiée par rapport à FF.")}
>
Gammes modifiées seulementLes produits dont la gamme a changé
lancer("complet", () => exporterFichierGammes(false), "Aucun produit à exporter.")}
>
Toutes les gammesTous les produits du fournisseur
Documents de travail lancer("tous", () => exporterTousProduits(nomFournisseur), "Aucun produit à exporter.")}
>
Tableau complet (Excel)Gamme avant / après et chiffres sur 12 mois
lancer("gammeA", exporterGammeA, "Aucun produit en gamme A.")}
>
Liste des produits en gamme A (Excel)Code-barres, référence, libellé, magasins
lancer("pdf", () => imprimerPdf(nomFournisseur), "Aucun produit à imprimer.")}
>
Tableau imprimable (PDF)Mise en page paysage
{ void handleSnapshot(); }}>
Garder une copie dans l'HistoriquePour reprendre cette session plus tard
{hasDrafts && (
)}
);
}
/**
* Mémoïsé : ses props sont stables, il n'a donc pas à se redessiner quand le
* parent change d'état (sélection, progression du chargement…).
*/
export const FloatingSummaryBar = React.memo(FloatingSummaryBarInner);