diff --git a/src/app/(dashboard)/hit-parade/client.tsx b/src/app/(dashboard)/hit-parade/client.tsx index 8e3dc65..ec4b18a 100644 --- a/src/app/(dashboard)/hit-parade/client.tsx +++ b/src/app/(dashboard)/hit-parade/client.tsx @@ -28,6 +28,43 @@ interface Props { pivotted: HitParadePivotRow[]; } +function ColHeader({ + label, + sortable, + group, + sortKey, + sortDir, + onSort, +}: { + label: string; + sortable?: SortKey; + group?: "292" | "579" | "total"; + sortKey: SortKey; + sortDir: SortDir; + onSort: (key: SortKey) => void; +}) { + const isActive = sortable && sortKey === sortable; + const groupBg = group === "292" ? "bg-blue-50" : group === "579" ? "bg-violet-50" : group === "total" ? "bg-emerald-50" : ""; + const activeBg = group === "292" ? "bg-blue-100 text-blue-700" : group === "579" ? "bg-violet-100 text-violet-700" : group === "total" ? "bg-emerald-100 text-emerald-700" : "bg-blue-50 text-blue-700"; + return ( + onSort(sortable) : undefined} + className={[ + "px-2 py-2 text-center text-xs font-semibold whitespace-nowrap select-none", + sortable ? "cursor-pointer" : "", + isActive ? activeBg : `${groupBg} text-gray-600`, + ].join(" ")} + > + {label} + {sortable && ( + + {isActive ? (sortDir === "desc" ? "↓" : "↑") : ↕} + + )} + + ); +} + export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) { const router = useRouter(); const [sortKey, setSortKey] = useState(DEFAULT_SORT); @@ -73,6 +110,32 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) { const isSorted = sortKey !== DEFAULT_SORT || sortDir !== DEFAULT_DIR; + const filtered = useMemo(() => + pivotted.filter(r => + (filterFournisseur === "Tous" || r.fournisseur === filterFournisseur) && + (filterNomenclature === "Tous" || r.nomenclature_code === filterNomenclature) + ), + [pivotted, filterFournisseur, filterNomenclature] + ); + + 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 exportToExcel() { const headers = [ "Code", "Désignation", "Fournisseur", "Nomenclature", @@ -115,56 +178,6 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) { XLSX.writeFile(wb, `hit-parade_${dateDebut}_${dateFin}.xlsx`); } - const filtered = useMemo(() => - pivotted.filter(r => - (filterFournisseur === "Tous" || r.fournisseur === filterFournisseur) && - (filterNomenclature === "Tous" || r.nomenclature_code === filterNomenclature) - ), - [pivotted, filterFournisseur, filterNomenclature] - ); - - 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, group }: { label: string; sortable?: SortKey; k: string; group?: "292" | "579" | "total" }) { - const isActive = sortable && sortKey === sortable; - const groupBg = group === "292" ? "bg-blue-50" : group === "579" ? "bg-violet-50" : group === "total" ? "bg-emerald-50" : ""; - const activeBg = group === "292" ? "bg-blue-100 text-blue-700" : group === "579" ? "bg-violet-100 text-violet-700" : group === "total" ? "bg-emerald-100 text-emerald-700" : "bg-blue-50 text-blue-700"; - return ( - handleSort(sortable) : undefined} - className={[ - "px-2 py-2 text-center text-xs font-semibold whitespace-nowrap select-none", - sortable ? "cursor-pointer" : "", - isActive ? activeBg : `${groupBg} text-gray-600`, - ].join(" ")} - > - {label} - {sortable && ( - - {isActive ? (sortDir === "desc" ? "↓" : "↑") : ↕} - - )} - - ); - } - return (
{/* Barre de contrôles */} @@ -277,22 +290,22 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) { - - + + % Marge - - - + + + % Marge - - - + + + % Marge - + - {sorted.map((row, idx) => ( + {sorted.map((row) => ( {row.codein} {row.libelle.trim()} diff --git a/src/features/grid/api/get-product-rows.ts b/src/features/grid/api/get-product-rows.ts index 102bb76..725257f 100644 --- a/src/features/grid/api/get-product-rows.ts +++ b/src/features/grid/api/get-product-rows.ts @@ -391,8 +391,10 @@ async function reconcileSelectedStoreFromMensuelApi( for (const entry of entries) { const period = entry.mois.replace("-", ""); if (!sortedPeriods.includes(period)) continue; - const qty = Math.abs(Number(entry.ventes?.qte_vendue ?? 0) || 0); - const ca = Math.abs(Number(entry.ventes?.ca_ht ?? 0) || 0); + // qte_vendue / ca_ht sont NÉGATIFS côté API (ventes nettes) : on + // les nie au lieu de Math.abs pour que les retours restent déduits. + const qty = -(Number(entry.ventes?.qte_vendue ?? 0) || 0); + const ca = -(Number(entry.ventes?.ca_ht ?? 0) || 0); const marge = Number(entry.ventes?.marge ?? 0) || 0; const stock = Number(entry.stock_fin_mois ?? 0) || 0; const receptions = Number(entry.receptions?.qte_recue ?? 0) || 0;