"use client"; import { useMemo, useTransition } from "react"; import { useRouter } from "next/navigation"; import { History, Info, Megaphone, RotateCcw } from "lucide-react"; import type { Publicite, PubliciteHistorique } from "./page"; import { Badge, StoreBadge, type Ton } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { DataTable, type DataColumn, type DataFilter } from "@/components/ui/data-table"; import { Select, type SelectOption } from "@/components/ui/form-controls"; import { Pagination } from "@/components/ui/pagination"; import { ErrorState } from "@/components/ui/states"; import { Tabs, useUrlTab } from "@/components/ui/tabs"; import { Terme, InfoBulle } from "@/components/ui/tooltip"; import { fmtDecimal1, fmtEntier, fmtEur2 } from "@/lib/format"; import { nomMagasin } from "@/lib/magasins"; import { cn } from "@/lib/utils"; type Vue = "publicites" | "historique"; const STATUTS: Record = { en_cours: { libelle: "En cours", ton: "succes" }, passee: { libelle: "Passée", ton: "neutre" }, // Le filtre envoie `passees` à l'API : la même valeur peut revenir sur les lignes. passees: { libelle: "Passée", ton: "neutre" }, a_venir: { libelle: "À venir", ton: "accent" }, }; const OPTIONS_STATUT: SelectOption[] = [ { value: "toutes", label: "Toutes" }, { value: "en_cours", label: "En cours" }, { value: "passees", label: "Passées" }, { value: "a_venir", label: "À venir" }, ]; /** Pourquoi la liste est vide, selon le statut choisi. */ const VIDE_PAR_STATUT: Record = { toutes: "Aucune opération publicitaire n'est enregistrée pour le moment.", en_cours: "Aucune opération publicitaire n'est en cours en ce moment.", passees: "Aucune opération publicitaire passée n'a été trouvée.", a_venir: "Aucune opération publicitaire à venir n'est encore enregistrée.", }; const EXPLICATION_CA_PUB = "Chiffre d'affaires réalisé sur les produits de l'opération pendant sa durée (du premier au dernier jour de la publicité)."; function fmtDate(iso: string | null | undefined): string { if (!iso) return "—"; const d = new Date(iso); if (Number.isNaN(d.getTime())) return "—"; return d.toLocaleDateString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric" }); } /** Les montants de l'API arrivent parfois en texte : valeur absente ou illisible = 0. */ function nombre(v: number | string | null | undefined): number { const n = Number(v); return Number.isFinite(n) ? n : 0; } function somme(rows: readonly T[], valeur: (r: T) => number | string | null | undefined): number { return rows.reduce((s, r) => s + nombre(valeur(r)), 0); } const libelleStatut = (statut?: string) => STATUTS[statut ?? ""]?.libelle ?? statut ?? ""; /** `passee` et `passees` désignent le même statut. */ const cleStatut = (statut?: string) => (statut === "passees" ? "passee" : statut || null); /** Libellé suivi d'un « i » : explication libre au survol (hors glossaire). */ function StatutBadge({ statut }: { statut?: string }) { const s = STATUTS[statut ?? ""]; if (s) return {s.libelle}; return statut ? {statut} : —; } /** Intitulé d'abord, code de l'opération en petit dessous. */ function Operation({ intitule, code }: { intitule: string; code: string }) { return (
{intitule || "Opération sans intitulé"}
{code &&
Code {code}
}
); } /** Part du CA total : mise en avant à partir de 10 %, puis de 20 %. */ function PartCa({ pct }: { pct: number }) { return ( = 20 ? "font-semibold text-[var(--accent)]" : pct >= 10 ? "font-semibold" : "text-[var(--text-secondary)]", )} > {fmtDecimal1(pct)} % ); } /** Pied de la colonne « Part du CA total » : somme du CA pub / somme du CA total. */ function PartCaTotal({ rows }: { rows: readonly Publicite[] }) { const caPub = somme(rows, (r) => r.ca_pub_periode_pub); const caTotal = somme(rows, (r) => r.ca_total_periode_pub); if (caTotal <= 0) { return —; } return ( {fmtDecimal1((caPub / caTotal) * 100)} % ); } interface Props { vueInitiale: Vue; statut: string; page: number; pageSize: number; publicites: Publicite[]; total: number; pages: number; erreurPublicites?: string; historique: PubliciteHistorique[]; erreurHistorique?: string; } export function PublicitesClient({ vueInitiale, statut, page, pageSize, publicites, total, pages, erreurPublicites, historique, erreurHistorique, }: Props) { const router = useRouter(); const [chargement, startTransition] = useTransition(); const [vue, setVue] = useUrlTab("vue", vueInitiale); function naviguer(changements: { statut?: string; page?: number }) { const sp = new URLSearchParams({ vue, statut, page: String(page) }); if (changements.statut !== undefined) sp.set("statut", changements.statut); if (changements.page !== undefined) sp.set("page", String(changements.page)); startTransition(() => router.push(`/publicites?${sp}`)); } const reessayer = ( ); const validPublicites = useMemo(() => publicites.filter((p) => p.site !== "000"), [publicites]); const plusieursSites = useMemo(() => new Set(validPublicites.map((p) => p.site).filter(Boolean)).size > 1, [validPublicites]); const paginee = pages > 1; const filtresPublicites = useMemo[]>( () => plusieursSites ? [{ id: "site", label: "Magasin", valueOf: (p) => p.site || null, optionLabel: (code) => nomMagasin(code), allLabel: "Tous les magasins" }] : [], [plusieursSites], ); const colonnesPublicites = useMemo[]>( () => [ { id: "operation", header: "Opération", sortValue: (p) => p.intitule, cell: (p) => , footer: (rows) => `Total${paginee ? " de la page" : ""} · ${fmtEntier(rows.length)} publicité${rows.length > 1 ? "s" : ""}`, }, { id: "statut", header: "Statut", sortValue: (p) => libelleStatut(p.statut), cell: (p) => , }, { id: "site", header: "Magasin", sortValue: (p) => (p.site ? nomMagasin(p.site) : null), cell: (p) => (p.site ? : —), }, { id: "debut", header: "Début", sortValue: (p) => p.date_debut, cell: (p) => fmtDate(p.date_debut), className: "whitespace-nowrap tabular-nums", }, { id: "fin", header: "Fin", sortValue: (p) => p.date_fin, cell: (p) => fmtDate(p.date_fin), className: "whitespace-nowrap tabular-nums", }, { id: "caPub", header: CA pub période, align: "right", sortValue: (p) => nombre(p.ca_pub_periode_pub), cell: (p) => {fmtEur2(nombre(p.ca_pub_periode_pub))}, footer: (rows) => fmtEur2(somme(rows, (r) => r.ca_pub_periode_pub)), }, { id: "partCa", header: , align: "right", sortValue: (p) => nombre(p.pourc_capub_catotal), cell: (p) => , footer: (rows) => , }, { id: "quantite", header: "Quantité vendue", align: "right", sortValue: (p) => nombre(p.qte_vendue_pub), cell: (p) => fmtEntier(nombre(p.qte_vendue_pub)), footer: (rows) => fmtEntier(somme(rows, (r) => r.qte_vendue_pub)), }, { id: "clients", header: "Clients", align: "right", sortValue: (p) => nombre(p.client_pub_periode), cell: (p) => fmtEntier(nombre(p.client_pub_periode)), }, { id: "tauxSortie", header: , align: "right", sortValue: (p) => nombre(p.taux_sortie), cell: (p) => `${fmtDecimal1(nombre(p.taux_sortie))} %`, }, { id: "articles", header: "Nb articles", align: "right", sortValue: (p) => nombre(p.nb_articles), cell: (p) => fmtEntier(nombre(p.nb_articles)), }, ], [paginee], ); const filtresHistorique = useMemo[]>( () => [{ id: "statut", label: "Statut", valueOf: (h) => cleStatut(h.statut), optionLabel: (s) => libelleStatut(s), allLabel: "Tous" }], [], ); const colonnesHistorique = useMemo[]>( () => [ { id: "operation", header: "Opération", sortValue: (h) => h.intitule, cell: (h) => , footer: (rows) => `Total · ${fmtEntier(rows.length)} opération${rows.length > 1 ? "s" : ""}`, }, { id: "statut", header: "Statut", sortValue: (h) => libelleStatut(h.statut), cell: (h) => , }, { id: "debut", header: "Début", sortValue: (h) => h.date_debut, cell: (h) => fmtDate(h.date_debut), className: "whitespace-nowrap tabular-nums", }, { id: "fin", header: "Fin", sortValue: (h) => h.date_fin, cell: (h) => fmtDate(h.date_fin), className: "whitespace-nowrap tabular-nums", }, { id: "magasins", header: "Magasins", hint: "Nombre de magasins concernés par l'opération.", align: "right", sortValue: (h) => nombre(h.nb_sites), cell: (h) => fmtEntier(nombre(h.nb_sites)), }, { id: "ca", header: "CA total", hint: "Chiffre d'affaires de l'opération, tous magasins confondus.", align: "right", sortValue: (h) => nombre(h.ca_total), cell: (h) => {fmtEur2(nombre(h.ca_total))}, footer: (rows) => fmtEur2(somme(rows, (r) => r.ca_total)), }, { id: "quantite", header: "Quantité vendue", align: "right", sortValue: (h) => nombre(h.qte_totale), cell: (h) => fmtEntier(nombre(h.qte_totale)), footer: (rows) => fmtEntier(somme(rows, (r) => r.qte_totale)), }, ], [], ); const onglets = [ { value: "publicites", label: "Publicités", icon: Megaphone, count: erreurPublicites ? undefined : total }, { value: "historique", label: "Historique", icon: History, count: erreurHistorique ? undefined : historique.length }, ] as const; const selectStatut = (