mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-11 17:26:32 +02:00
147 lines
6.3 KiB
TypeScript
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'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>
|
|
);
|
|
}
|