mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-20 05:28:11 +02:00
feat: implement hit-parade dashboard with interactive filtering, sorting, and data visualization components
This commit is contained in:
1 parent
2c02807493
commit
9469738ef0
3 files changed
+20
-6
No files matched your search
@@ -44,8 +44,10 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
|
|||||||
}, [pivotted]);
|
}, [pivotted]);
|
||||||
|
|
||||||
const nomenclatures = useMemo(() => {
|
const nomenclatures = useMemo(() => {
|
||||||
const set = new Set(pivotted.map(r => r.nomenclature));
|
const map = new Map<string, string>(); // code → libelle
|
||||||
return ["Tous", ...Array.from(set).sort((a, b) => a.localeCompare(b, "fr"))];
|
for (const r of pivotted) map.set(r.nomenclature_code, r.nomenclature);
|
||||||
|
const entries = Array.from(map.entries()).sort((a, b) => a[0].localeCompare(b[0], "fr"));
|
||||||
|
return [{ code: "Tous", libelle: "Tous" }, ...entries.map(([code, libelle]) => ({ code, libelle }))];
|
||||||
}, [pivotted]);
|
}, [pivotted]);
|
||||||
|
|
||||||
function handleSort(key: SortKey) {
|
function handleSort(key: SortKey) {
|
||||||
@@ -73,7 +75,7 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
|
|||||||
const filtered = useMemo(() =>
|
const filtered = useMemo(() =>
|
||||||
pivotted.filter(r =>
|
pivotted.filter(r =>
|
||||||
(filterFournisseur === "Tous" || r.fournisseur === filterFournisseur) &&
|
(filterFournisseur === "Tous" || r.fournisseur === filterFournisseur) &&
|
||||||
(filterNomenclature === "Tous" || r.nomenclature === filterNomenclature)
|
(filterNomenclature === "Tous" || r.nomenclature_code === filterNomenclature)
|
||||||
),
|
),
|
||||||
[pivotted, filterFournisseur, filterNomenclature]
|
[pivotted, filterFournisseur, filterNomenclature]
|
||||||
);
|
);
|
||||||
@@ -179,7 +181,9 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
|
|||||||
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 min-w-[200px]"
|
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 min-w-[200px]"
|
||||||
>
|
>
|
||||||
{nomenclatures.map(n => (
|
{nomenclatures.map(n => (
|
||||||
<option key={n} value={n}>{n}</option>
|
<option key={n.code} value={n.code}>
|
||||||
|
{n.code === "Tous" ? "Tous" : `${n.code} — ${n.libelle}`}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,7 +244,12 @@ export function HitParadeClient({ dateDebut, dateFin, pivotted }: Props) {
|
|||||||
<td className="bg-white px-4 py-2.5 font-mono text-xs text-gray-400 text-center">{row.codein}</td>
|
<td className="bg-white px-4 py-2.5 font-mono text-xs text-gray-400 text-center">{row.codein}</td>
|
||||||
<td className="bg-white px-4 py-2.5 text-gray-900 font-medium max-w-[260px] truncate" title={row.libelle.trim()}>{row.libelle.trim()}</td>
|
<td className="bg-white px-4 py-2.5 text-gray-900 font-medium max-w-[260px] truncate" title={row.libelle.trim()}>{row.libelle.trim()}</td>
|
||||||
<td className="bg-white px-4 py-2.5 text-xs text-gray-500 whitespace-nowrap text-center">{row.fournisseur}</td>
|
<td className="bg-white px-4 py-2.5 text-xs text-gray-500 whitespace-nowrap text-center">{row.fournisseur}</td>
|
||||||
<td className="bg-white px-4 py-2.5 text-xs text-gray-500 max-w-[180px] truncate" title={row.nomenclature}>{row.nomenclature}</td>
|
<td className="bg-white px-4 py-2.5 text-xs text-gray-500 whitespace-nowrap">
|
||||||
|
{row.nomenclature_code
|
||||||
|
? <><span className="font-mono font-semibold text-gray-700">{row.nomenclature_code}</span><span className="ml-1.5 text-gray-400">— {row.nomenclature}</span></>
|
||||||
|
: <span className="text-gray-300">—</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
<td className={`border-l-2 border-blue-200 px-4 py-2.5 text-center tabular-nums text-gray-700 ${sortKey === "qte292" ? "bg-blue-100 font-semibold" : "bg-blue-50/60"}`}>
|
<td className={`border-l-2 border-blue-200 px-4 py-2.5 text-center tabular-nums text-gray-700 ${sortKey === "qte292" ? "bg-blue-100 font-semibold" : "bg-blue-50/60"}`}>
|
||||||
{row.qte292 > 0 ? formatQte(row.qte292) : <span className="text-gray-300">—</span>}
|
{row.qte292 > 0 ? formatQte(row.qte292) : <span className="text-gray-300">—</span>}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface HitParadePivotRow {
|
|||||||
codein: string;
|
codein: string;
|
||||||
libelle: string;
|
libelle: string;
|
||||||
fournisseur: string;
|
fournisseur: string;
|
||||||
|
nomenclature_code: string;
|
||||||
nomenclature: string;
|
nomenclature: string;
|
||||||
qte292: number;
|
qte292: number;
|
||||||
ca292: number;
|
ca292: number;
|
||||||
@@ -29,6 +30,7 @@ function pivotHitParade(rows: HitParadeRow[]): HitParadePivotRow[] {
|
|||||||
codein: row.codein,
|
codein: row.codein,
|
||||||
libelle: row.libelle,
|
libelle: row.libelle,
|
||||||
fournisseur: row.fournisseur,
|
fournisseur: row.fournisseur,
|
||||||
|
nomenclature_code: row.nomenclature_code,
|
||||||
nomenclature: row.nomenclature,
|
nomenclature: row.nomenclature,
|
||||||
qte292: 0, ca292: 0, marge292: 0,
|
qte292: 0, ca292: 0, marge292: 0,
|
||||||
qte579: 0, ca579: 0, marge579: 0,
|
qte579: 0, ca579: 0, marge579: 0,
|
||||||
|
|||||||
@@ -717,6 +717,7 @@ export interface HitParadeRow {
|
|||||||
codein: string;
|
codein: string;
|
||||||
libelle: string;
|
libelle: string;
|
||||||
fournisseur: string;
|
fournisseur: string;
|
||||||
|
nomenclature_code: string;
|
||||||
nomenclature: string;
|
nomenclature: string;
|
||||||
site: string;
|
site: string;
|
||||||
qte_vendue: number;
|
qte_vendue: number;
|
||||||
@@ -739,6 +740,7 @@ export async function pgGetHitParade(dateDebut: string, dateFin: string): Promis
|
|||||||
a.codein::text AS codein,
|
a.codein::text AS codein,
|
||||||
a.libelle1::text AS libelle,
|
a.libelle1::text AS libelle,
|
||||||
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS fournisseur,
|
COALESCE(fi.nom, af.code, 'Sans fournisseur')::text AS fournisseur,
|
||||||
|
COALESCE(n.code, '')::text AS nomenclature_code,
|
||||||
COALESCE(n.libelle, 'Sans nomenclature')::text AS nomenclature,
|
COALESCE(n.libelle, 'Sans nomenclature')::text AS nomenclature,
|
||||||
m.site,
|
m.site,
|
||||||
SUM(ABS(m.qtemvt))::float AS qte_vendue,
|
SUM(ABS(m.qtemvt))::float AS qte_vendue,
|
||||||
@@ -752,7 +754,7 @@ export async function pgGetHitParade(dateDebut: string, dateFin: string): Promis
|
|||||||
WHERE m.datmvt BETWEEN ${dateDebut}::date AND ${dateFin}::date
|
WHERE m.datmvt BETWEEN ${dateDebut}::date AND ${dateFin}::date
|
||||||
AND m.site IN ('292', '579')
|
AND m.site IN ('292', '579')
|
||||||
AND m.genremvt = 3
|
AND m.genremvt = 3
|
||||||
GROUP BY a.no_id, a.codein, a.libelle1, fi.nom, af.code, n.libelle, m.site
|
GROUP BY a.no_id, a.codein, a.libelle1, fi.nom, af.code, n.code, n.libelle, m.site
|
||||||
),
|
),
|
||||||
stock_agg AS (
|
stock_agg AS (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -768,6 +770,7 @@ export async function pgGetHitParade(dateDebut: string, dateFin: string): Promis
|
|||||||
v.codein,
|
v.codein,
|
||||||
v.libelle,
|
v.libelle,
|
||||||
v.fournisseur,
|
v.fournisseur,
|
||||||
|
v.nomenclature_code,
|
||||||
v.nomenclature,
|
v.nomenclature,
|
||||||
v.site,
|
v.site,
|
||||||
v.qte_vendue,
|
v.qte_vendue,
|
||||||
|
|||||||
Reference in new issue
Block a user