"use client"; import { useRouter } from "next/navigation"; import { useState, useMemo } from "react"; import type { HitParadePivotRow } from "./page"; type SortKey = "caTotal" | "ca292" | "ca579" | "qteTotal" | "qte292" | "qte579" | "stockTotal" | "stock292" | "stock579"; type SortDir = "desc" | "asc"; const DEFAULT_SORT: SortKey = "caTotal"; const DEFAULT_DIR: SortDir = "desc"; function formatCA(v: number) { return new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR", maximumFractionDigits: 0 }).format(v); } function formatQte(v: number) { return new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 0 }).format(v); } function formatPct(ca: number, marge: number) { if (ca === 0) return "—"; return ((marge / ca) * 100).toFixed(1) + " %"; } interface Props { dateDebut: string; dateFin: string; pivotted: HitParadePivotRow[]; } export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) { const router = useRouter(); const [sortKey, setSortKey] = useState(DEFAULT_SORT); const [sortDir, setSortDir] = useState(DEFAULT_DIR); const [filterFournisseur, setFilterFournisseur] = useState("Tous"); // Dates locales pour les inputs (évite rechargement à chaque frappe) const [localDebut, setLocalDebut] = useState(dateDebut); const [localFin, setLocalFin] = useState(dateFin); const fournisseurs = useMemo(() => { const set = new Set(pivotted.map(r => r.fournisseur)); return ["Tous", ...Array.from(set).sort((a, b) => a.localeCompare(b, "fr"))]; }, [pivotted]); function handleSort(key: SortKey) { if (sortKey === key) { setSortDir(d => d === "desc" ? "asc" : "desc"); } else { setSortKey(key); setSortDir("desc"); } } function resetSort() { setSortKey(DEFAULT_SORT); setSortDir(DEFAULT_DIR); } function applyDates() { if (localDebut && localFin) { router.push(`/hit-parade?debut=${localDebut}&fin=${localFin}`); } } const isSorted = sortKey !== DEFAULT_SORT || sortDir !== DEFAULT_DIR; const filtered = useMemo(() => filterFournisseur === "Tous" ? pivotted : pivotted.filter(r => r.fournisseur === filterFournisseur), [pivotted, filterFournisseur] ); const sorted = useMemo(() => [...filtered].sort((a, b) => { const diff = a[sortKey] - b[sortKey]; return sortDir === "desc" ? -diff : diff; }), [filtered, sortKey, sortDir] ); const totals = useMemo(() => sorted.reduce( (acc, r) => ({ qte292: acc.qte292 + r.qte292, ca292: acc.ca292 + r.ca292, marge292: acc.marge292 + r.marge292, qte579: acc.qte579 + r.qte579, ca579: acc.ca579 + r.ca579, marge579: acc.marge579 + r.marge579, qteTotal: acc.qteTotal + r.qteTotal, caTotal: acc.caTotal + r.caTotal, margeTotal: acc.margeTotal + r.margeTotal, stock292: acc.stock292 + r.stock292, stock579: acc.stock579 + r.stock579, stockTotal: acc.stockTotal + r.stockTotal, }), { qte292: 0, ca292: 0, marge292: 0, qte579: 0, ca579: 0, marge579: 0, qteTotal: 0, caTotal: 0, margeTotal: 0, stock292: 0, stock579: 0, stockTotal: 0 } ), [sorted]); function ColHeader({ label, sortable, k }: { label: string; sortable?: SortKey; k: string }) { const isActive = sortable && sortKey === sortable; return ( handleSort(sortable) : undefined} className={[ "px-4 py-2 text-right text-xs font-semibold whitespace-nowrap select-none", sortable ? "cursor-pointer hover:bg-blue-50" : "", isActive ? "bg-blue-50 text-blue-700" : "text-gray-600", ].join(" ")} > {label} {sortable && ( {isActive ? (sortDir === "desc" ? "↓" : "↑") : ↕} )} ); } return (
{/* Barre de contrôles */}
{/* Période */}
setLocalDebut(e.target.value)} className="rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400" />
setLocalFin(e.target.value)} className="rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400" />
{/* Filtre fournisseur */}
{sorted.length} produits {isSorted && ( )}
{/* Tableau */}
{sorted.map((row, idx) => ( ))}
Code Désignation Fournisseur Frouard / Nancy — 292 Houdemont — 579 Total Réseau
% Marge % Marge % Marge
{row.codein} {row.libelle.trim()} {row.fournisseur} {row.qte292 > 0 ? formatQte(row.qte292) : —} {row.ca292 > 0 ? formatCA(row.ca292) : —} {row.ca292 > 0 ? formatPct(row.ca292, row.marge292) : ""} {row.stock292 > 0 ? formatQte(row.stock292) : —} {row.qte579 > 0 ? formatQte(row.qte579) : —} {row.ca579 > 0 ? formatCA(row.ca579) : —} {row.ca579 > 0 ? formatPct(row.ca579, row.marge579) : ""} {row.stock579 > 0 ? formatQte(row.stock579) : —} {row.qteTotal > 0 ? formatQte(row.qteTotal) : —} {row.caTotal > 0 ? formatCA(row.caTotal) : —} {row.caTotal > 0 ? formatPct(row.caTotal, row.margeTotal) : ""} {row.stockTotal > 0 ? formatQte(row.stockTotal) : 0}
TOTAL — {sorted.length} articles {formatQte(totals.qte292)} {formatCA(totals.ca292)} {formatPct(totals.ca292, totals.marge292)} {formatQte(totals.stock292)} {formatQte(totals.qte579)} {formatCA(totals.ca579)} {formatPct(totals.ca579, totals.marge579)} {formatQte(totals.stock579)} {formatQte(totals.qteTotal)} {formatCA(totals.caTotal)} {formatPct(totals.caTotal, totals.margeTotal)} {formatQte(totals.stockTotal)}
); }