From 480cf5c46075b6af19ebe9f7014fa881df856bba Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Tue, 7 Apr 2026 13:00:58 +0200 Subject: [PATCH] feat: add PublicitesClient component with filtering, sorting, and data visualization for advertising performance --- src/app/(dashboard)/publicites/client.tsx | 118 +++++++++++++++++++--- 1 file changed, 104 insertions(+), 14 deletions(-) diff --git a/src/app/(dashboard)/publicites/client.tsx b/src/app/(dashboard)/publicites/client.tsx index 8948280..2442824 100644 --- a/src/app/(dashboard)/publicites/client.tsx +++ b/src/app/(dashboard)/publicites/client.tsx @@ -1,6 +1,8 @@ "use client"; import { useRouter } from "next/navigation"; +import { useState, useMemo } from "react"; +import { ChevronUp, ChevronDown, ChevronsUpDown } from "lucide-react"; import type { Publicite } from "./page"; const FIRST_YEAR = 2020; @@ -18,6 +20,16 @@ function fmtPct(v: number | string) { return Number(v).toFixed(2).replace(".", ",") + " %"; } +type SortKey = "intitule" | "site" | "ca_pub_periode_pub" | "pourc_capub_catotal" | "qte_vendue_pub" | "taux_sortie"; +type SortDir = "asc" | "desc"; + +function SortIcon({ col, sortKey, sortDir }: { col: SortKey; sortKey: SortKey; sortDir: SortDir }) { + if (col !== sortKey) return ; + return sortDir === "asc" + ? + : ; +} + interface Props { year: number; publicites: Publicite[]; @@ -28,14 +40,58 @@ export function PublicitesClient({ year, publicites }: Props) { const currentYear = new Date().getFullYear(); const years = Array.from({ length: currentYear - FIRST_YEAR + 1 }, (_, i) => currentYear - i); + const [filterSite, setFilterSite] = useState("all"); + const [sortKey, setSortKey] = useState("intitule"); + const [sortDir, setSortDir] = useState("asc"); + + const sites = useMemo(() => { + const s = new Set(publicites.map(p => p.site).filter(Boolean)); + return Array.from(s).sort(); + }, [publicites]); + function handleYearChange(e: React.ChangeEvent) { router.push(`/publicites?year=${e.target.value}`); } + function handleSort(key: SortKey) { + if (sortKey === key) { + setSortDir(d => d === "asc" ? "desc" : "asc"); + } else { + setSortKey(key); + setSortDir("asc"); + } + } + + const filtered = useMemo(() => { + let rows = filterSite === "all" ? publicites : publicites.filter(p => p.site === filterSite); + rows = [...rows].sort((a, b) => { + let va: string | number; + let vb: string | number; + if (sortKey === "intitule" || sortKey === "site") { + va = String(a[sortKey] ?? "").toLowerCase(); + vb = String(b[sortKey] ?? "").toLowerCase(); + } else { + va = Number(a[sortKey]); + vb = Number(b[sortKey]); + } + if (va < vb) return sortDir === "asc" ? -1 : 1; + if (va > vb) return sortDir === "asc" ? 1 : -1; + return 0; + }); + return rows; + }, [publicites, filterSite, sortKey, sortDir]); + + function thProps(key: SortKey, className: string) { + return { + className: `${className} cursor-pointer select-none hover:bg-gray-100 transition-colors`, + onClick: () => handleSort(key), + }; + } + return (
{/* Barre de contrôles */} -
+
setFilterSite(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-orange-400 min-w-[160px]" + > + + {sites.map(s => ( + + ))} + +
+ )} +
- {publicites.length} publicité{publicites.length !== 1 ? "s" : ""} + {filtered.length} publicité{filtered.length !== 1 ? "s" : ""}
{/* Tableau */}
- {publicites.length === 0 ? ( + {filtered.length === 0 ? (
Aucune publicité trouvée pour {year}.
@@ -67,25 +139,43 @@ export function PublicitesClient({ year, publicites }: Props) { N° - Libellé + + Opération + + + Magasin + Date début Date fin - CA pub période - % CA total - Qté vendue + + CA pub période + + + % CA total + + + Qté vendue + Clients - Taux sortie + + Taux sortie + Nb articles - {publicites.map((pub, idx) => ( + {filtered.map((pub, idx) => ( {pub.tcr_code} {pub.intitule} + + + {pub.site || "—"} + + {fmtDate(pub.date_debut)} @@ -123,17 +213,17 @@ export function PublicitesClient({ year, publicites }: Props) { - - TOTAL — {publicites.length} publicités + + TOTAL — {filtered.length} publicités - {fmtCA(publicites.reduce((s, p) => s + Number(p.ca_pub_periode_pub), 0))} + {fmtCA(filtered.reduce((s, p) => s + Number(p.ca_pub_periode_pub), 0))} — - {publicites.reduce((s, p) => s + Number(p.qte_vendue_pub), 0).toLocaleString("fr-FR")} + {filtered.reduce((s, p) => s + Number(p.qte_vendue_pub), 0).toLocaleString("fr-FR")}