From 2eb196861b1279a54142c28a7371927a441c7312 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 22 Mar 2026 10:41:32 +0100 Subject: [PATCH] feat: Implement initial dashboard page displaying network-level KPIs and individual store statistics. --- src/app/(dashboard)/dashboard/page.tsx | 404 ++++++++++++++++++------- 1 file changed, 287 insertions(+), 117 deletions(-) diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 7d10558..aebbad4 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -356,136 +356,237 @@ function Top10Table({ title, items, sortKey, accent }: { sortKey: "ca" | "qte" | "marge"; accent: typeof tableAccents.ca; }) { - const maxVal = Math.max(items[0]?.[sortKey] ?? 1, 1); - return (
- {/* Table header */} + {/* ── Header strip ── */}
- {sortKey === "ca" && } - {sortKey === "qte" && } - {sortKey === "marge" && } + {sortKey === "ca" && } + {sortKey === "qte" && } + {sortKey === "marge" && }
- + {title} + {/* column labels */} +
+ {sortKey === "ca" && ( + <> + QTÉ + CA + + )} + {sortKey === "qte" && ( + QTÉ + )} + {sortKey === "marge" && ( + MARGE + )} +
- {/* Rows */} -
- {items.length === 0 ? ( -
- Aucune donnée -
- ) : ( - items.map((item, i) => { - const val = item[sortKey]; - const barPct = maxVal > 0 ? Math.max((val / maxVal) * 100, 2) : 0; - const txMarge = item.ca > 0 ? (item.marge / item.ca) * 100 : 0; - const isTopThree = i < 3; + {/* ── Rows ── */} + {items.length === 0 ? ( +
+ Aucune donnée +
+ ) : ( + items.map((item, i) => { + const txMarge = item.ca > 0 ? (item.marge / item.ca) * 100 : 0; + const margeColor = txMarge > 25 ? "#22c55e" : txMarge > 15 ? "#f59e0b" : "var(--text-muted)"; + const margeBg = txMarge > 25 ? "rgba(34,197,94,0.1)" : txMarge > 15 ? "rgba(245,158,11,0.1)" : "rgba(100,116,139,0.1)"; + const margeBorder = txMarge > 25 ? "rgba(34,197,94,0.25)" : txMarge > 15 ? "rgba(245,158,11,0.25)" : "rgba(100,116,139,0.18)"; + const isEven = i % 2 === 0; + const isTopThree = i < 3; - return ( -
- {/* Main row */} -
- - - {item.libelle1 ?? item.codein} - - - {sortKey === "qte" ? fmtQte(val) : fmtEur(val)} - -
+ return ( +
+ {/* ── Primary row: rank + name + main value ── */} +
+ - {/* Progress bar + meta */} -
-
-
-
- - {sortKey === "ca" && `${fmtQte(item.qte)} pcs · `} - {sortKey !== "ca" && `CA ${fmtEur(item.ca)} · `} - 25 ? "#22c55e" : txMarge > 15 ? "#f59e0b" : "var(--text-muted)" }}> - {fmtPct(txMarge, false)} mg - - -
+ textOverflow: "ellipsis", + whiteSpace: "nowrap", + minWidth: 0, + lineHeight: 1.3, + }} + > + {item.libelle1 ?? item.codein} + + + {/* Main sort-key value */} + + {sortKey === "qte" + ? `${fmtQte(item.qte)} pcs` + : sortKey === "marge" + ? fmtEur(item.marge) + : fmtEur(item.ca)} +
- ); - }) - )} -
+ + {/* ── Secondary info row ── */} +
+ {/* For CA table: show qte + marge */} + {sortKey === "ca" && ( + <> + + {fmtQte(item.qte)} pcs + + │ + + mg {fmtEur(item.marge)} + + + )} + + {/* For QTE table: show CA + marge */} + {sortKey === "qte" && ( + <> + + CA {fmtEur(item.ca)} + + │ + + mg {fmtEur(item.marge)} + + + )} + + {/* For Marge table: show CA + qte */} + {sortKey === "marge" && ( + <> + + CA {fmtEur(item.ca)} + + │ + + {fmtQte(item.qte)} pcs + + + )} + + {/* Taux de marge pill — always shown */} + + {fmtPct(txMarge, false)} + +
+
+ ); + }) + )}
); } @@ -494,32 +595,101 @@ function SiteHitParade({ siteData, dateHier }: { siteData: DashboardSiteTop10; d const name = SITE_NAMES[siteData.site] ?? siteData.site; return ( -
- {/* Section title */} -
+
+ {/* ── Section header ── */} +
+ {/* subtle glow behind icon */} +
+
- +
-
-

- Hit Parade — {name} -

-

- Données du {dateLabel(dateHier)} · site {siteData.site} + +

+
+

+ Hit Parade +

+ + {name} + +
+

+ Données du {dateLabel(dateHier)} · Site {siteData.site} +  · Top 10 produits par CA, Quantité et Marge

+ + {/* Store badge */} +
+ + {name} +
- {/* Three tables */} -
+ {/* ── Three tables side by side ── */} +