Files
CollectFlow/src/features/grid/components/grid-client.tsx
T

147 lines
6.3 KiB
TypeScript

"use client";
import { useEffect, useState, useTransition } from "react";
import { HeatmapGrid } from "@/features/grid/components/heatmap-grid";
import { FloatingSummaryBar } from "@/features/grid/components/floating-summary-bar";
import { BulkActionToolbar } from "@/features/grid/components/bulk-action-toolbar";
import { GridFilterBar } from "@/features/grid/components/grid-filter-bar";
import { ExportDropdown } from "@/features/grid/components/export-dropdown";
import { useGridStore } from "@/features/grid/store/use-grid-store";
import { useSaveDrafts } from "@/features/grid/hooks/use-save-drafts";
import type { ProductRow } from "@/types/grid";
import { CheckCircle, AlertCircle, Loader2 } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { computeProductScores } from "@/lib/score-engine";
interface GridClientProps {
initialRows: ProductRow[];
codeFournisseur: string;
nomFournisseur: string;
fournisseurs: { code: string; nom: string }[];
magasins: { code: string; nom: string }[];
magasin: string;
}
export function GridClient({ initialRows, codeFournisseur, nomFournisseur, fournisseurs, magasins, magasin }: GridClientProps) {
const setRows = useGridStore((s) => s.setRows);
const setActiveGridQuery = useGridStore((s) => s.setActiveGridQuery);
const setFilter = useGridStore((s) => s.setFilter);
const searchParams = useSearchParams();
const [selectedCodeins, setSelectedCodeins] = useState<string[]>([]);
const [isPending, startTransition] = useTransition();
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
const visibleCodeins = initialRows.map(r => r.codein);
const { save, hasDrafts, count } = useSaveDrafts(magasin, visibleCodeins);
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
// Sync active search params to the store
useEffect(() => {
if (!isMounted) return;
const currentQueryString = searchParams.toString();
// Set the active query, ensuring it starts with ? if not empty
setActiveGridQuery(currentQueryString ? `?${currentQueryString}` : "");
}, [searchParams, setActiveGridQuery, isMounted]);
useEffect(() => {
if (!isMounted) return;
// Réinitialiser les filtres gamme et code3 lors du changement de fournisseur.
// Ces filtres sont persistés en localStorage (Zustand persist) et s'appliquent
// au nouveau fournisseur, cachant toutes les lignes si le filtre ne correspond pas.
setFilter("codeGamme", null);
setFilter("code3", null);
const scoredRows = computeProductScores([...initialRows]);
setRows(scoredRows);
}, [codeFournisseur, initialRows, setRows, setFilter, isMounted]);
const handleSave = () => {
startTransition(async () => {
const result = await save();
setSaveStatus(result.success ? "success" : "error");
setTimeout(() => setSaveStatus("idle"), 3000);
});
};
if (!isMounted) {
return <div className="p-8 text-center animate-pulse text-muted italic">Initialisation de la grille...</div>;
}
const activeStoreNom = magasins.find(m => m.code === magasin)?.nom || "National (Total)";
return (
<div className="flex flex-col h-full space-y-3 min-h-0 pb-12">
{/* Header */}
<div className="flex items-end justify-between shrink-0">
<div>
<h1 className="text-2xl font-bold tracking-tight">Révision d&apos;Assortiment</h1>
<p className="text-[13px] mt-0.5" style={{ color: "var(--text-secondary)" }}>
<span className="font-semibold" style={{ color: "var(--text-primary)" }}>{nomFournisseur}</span>
{" "}• <span className="font-medium" style={{ color: "var(--text-muted)" }}>{activeStoreNom}</span>
{" "}• {initialRows.length} références
</p>
</div>
<div className="flex items-center gap-3">
<div id="grid-toolbar-actions"></div>
<ExportDropdown nomFournisseur={nomFournisseur} />
{hasDrafts && (
<button
onClick={handleSave}
disabled={isPending}
className="flex items-center gap-2 px-4 py-2 bg-emerald-700 hover:bg-emerald-600 text-white text-sm font-semibold rounded-lg disabled:opacity-60 transition-colors"
>
{isPending ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : saveStatus === "success" ? (
<CheckCircle className="w-4 h-4" />
) : saveStatus === "error" ? (
<AlertCircle className="w-4 h-4" />
) : null}
{isPending
? "Sauvegarde..."
: saveStatus === "success"
? "Sauvegardé !"
: saveStatus === "error"
? "Erreur"
: `Valider ${count} changement${count > 1 ? "s" : ""}`}
</button>
)}
</div>
</div>
{/* Filters */}
<div className="shrink-0 print:hidden">
<GridFilterBar fournisseurs={fournisseurs} magasins={magasins} />
</div>
{/* Bulk toolbar (contextual) */}
<div className="shrink-0 print:hidden">
<BulkActionToolbar
selectedCodeins={selectedCodeins}
onClearSelection={() => setSelectedCodeins([])}
/>
</div>
{/* Main grid */}
<div className="flex-1 min-h-0 min-w-0">
<HeatmapGrid
onSelectionChange={setSelectedCodeins}
/>
</div>
{/* Summary bar */}
<div className="print:hidden">
<FloatingSummaryBar />
</div>
</div>
);
}