diff --git a/src/app/(dashboard)/hit-parade/client.tsx b/src/app/(dashboard)/hit-parade/client.tsx index 6159892..b533e9d 100644 --- a/src/app/(dashboard)/hit-parade/client.tsx +++ b/src/app/(dashboard)/hit-parade/client.tsx @@ -22,24 +22,25 @@ function formatPct(ca: number, marge: number) { } interface Props { - mois: string; - currentMois: string; + dateDebut: string; + dateFin: string; pivotted: HitParadePivotRow[]; } -export function HitParadeClient({ mois, pivotted }: Props) { +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"); - const months: { value: string; label: string }[] = []; - const now = new Date(); - for (let i = 0; i < 24; i++) { - const d = new Date(now.getFullYear(), now.getMonth() - i); - const value = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`; - const label = new Intl.DateTimeFormat("fr-FR", { year: "numeric", month: "long" }).format(d); - months.push({ value, label: label.charAt(0).toUpperCase() + label.slice(1) }); - } + // 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) { @@ -55,25 +56,37 @@ export function HitParadeClient({ mois, pivotted }: Props) { 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 sorted = useMemo(() => { - return [...pivotted].sort((a, b) => { + 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; - }); - }, [pivotted, sortKey, sortDir]); + }), + [filtered, sortKey, sortDir] + ); - const totals = useMemo(() => pivotted.reduce( + 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, }), { qte292: 0, ca292: 0, marge292: 0, qte579: 0, ca579: 0, marge579: 0, qteTotal: 0, caTotal: 0, margeTotal: 0 } - ), [pivotted]); + ), [sorted]); - function ColHeader({ label, sortable, k }: { label: string; sortable?: SortKey; k?: string }) { + function ColHeader({ label, sortable, k }: { label: string; sortable?: SortKey; k: string }) { const isActive = sortable && sortKey === sortable; return ( {/* Barre de contrôles */} -
-
- Mois - 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 && ( )} - - {!isSorted && ( - Trié par CA Total ↓ — cliquez sur une colonne pour trier - )}
{/* Tableau */}
- {/* Ligne 1 — groupes */} @@ -151,7 +190,6 @@ export function HitParadeClient({ mois, pivotted }: Props) { Total Réseau - {/* Ligne 2 — sous-colonnes */} @@ -165,46 +203,40 @@ export function HitParadeClient({ mois, pivotted }: Props) { - {sorted.map((row, idx) => { - const highlight = sortKey.includes("292") ? "bg-blue-50/30" : sortKey.includes("579") ? "bg-violet-50/30" : ""; - return ( - - - - - {/* 292 */} - - - - {/* 579 */} - - - - {/* Total */} - - - - - ); - })} + {sorted.map((row, idx) => ( + + + + + + + + + + + + + + + ))} diff --git a/src/app/(dashboard)/hit-parade/page.tsx b/src/app/(dashboard)/hit-parade/page.tsx index b5673c5..0faf7e5 100644 --- a/src/app/(dashboard)/hit-parade/page.tsx +++ b/src/app/(dashboard)/hit-parade/page.tsx @@ -53,15 +53,22 @@ function pivotHitParade( return Array.from(map.values()); } +function defaultDates() { + const today = new Date(); + const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + const fmt = (d: Date) => d.toISOString().slice(0, 10); + return { debut: fmt(firstOfMonth), fin: fmt(today) }; +} + export default async function HitParadePage(props: { searchParams: Promise>; }) { const searchParams = await props.searchParams; - const today = new Date(); - const currentMois = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}`; - const mois = (searchParams.mois as string) || currentMois; + const defaults = defaultDates(); + const dateDebut = (searchParams.debut as string) || defaults.debut; + const dateFin = (searchParams.fin as string) || defaults.fin; - const rows = await pgGetHitParade(mois); + const rows = await pgGetHitParade(dateDebut, dateFin); const pivotted = pivotHitParade(rows); return ( @@ -69,8 +76,8 @@ export default async function HitParadePage(props: {

Hit Parade

diff --git a/src/features/admin/components/user-management.tsx b/src/features/admin/components/user-management.tsx index 594e2c7..bfe641d 100644 --- a/src/features/admin/components/user-management.tsx +++ b/src/features/admin/components/user-management.tsx @@ -111,43 +111,45 @@ export function UserManagement() { -
-
- - setNewUsername(e.target.value)} - className="apple-input" - placeholder="ex: jean.dupont" - /> -
-
- - setNewPassword(e.target.value)} - className="apple-input" - placeholder="••••••••" - /> -
-
- - + +
+
+ + setNewUsername(e.target.value)} + className="apple-input" + placeholder="ex: jean.dupont" + /> +
+
+ + setNewPassword(e.target.value)} + className="apple-input" + placeholder="••••••••" + /> +
+
+ + +
Code Désignation
{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.qte579 > 0 ? formatQte(row.qte579) : —} - - {row.ca579 > 0 ? formatCA(row.ca579) : —} - - {row.ca579 > 0 ? formatPct(row.ca579, row.marge579) : ""} - - {row.qteTotal > 0 ? formatQte(row.qteTotal) : —} - - {row.caTotal > 0 ? formatCA(row.caTotal) : —} - - {row.caTotal > 0 ? formatPct(row.caTotal, row.margeTotal) : ""} -
{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.qte579 > 0 ? formatQte(row.qte579) : —} + + {row.ca579 > 0 ? formatCA(row.ca579) : —} + + {row.ca579 > 0 ? formatPct(row.ca579, row.marge579) : ""} + + {row.qteTotal > 0 ? formatQte(row.qteTotal) : —} + + {row.caTotal > 0 ? formatCA(row.caTotal) : —} + + {row.caTotal > 0 ? formatPct(row.caTotal, row.margeTotal) : ""} +