import type { Metadata } from "next"; import Link from "next/link"; import { unstable_cache } from "next/cache"; import { BarChart3, Euro, House, Receipt, ShoppingBasket, ShoppingCart, Store } from "lucide-react"; import { pgGetDashboardData, type DashboardSiteStats, type DashboardSiteTop10, type DashboardTopItem } from "@/lib/pg-ff-client"; import { PageHeader } from "@/components/ui/page-header"; import { StatCard } from "@/components/ui/stat-card"; import { Card, CardHeader } from "@/components/ui/card"; import { Badge, DeltaBadge } from "@/components/ui/badge"; import { EmptyState, ErrorState } from "@/components/ui/states"; import { Terme } from "@/components/ui/tooltip"; import { LIBELLE_TOUS_MAGASINS, nomMagasin } from "@/lib/magasins"; import { fmtDecimal1, fmtEntier, fmtEur2 } from "@/lib/format"; import { couleurMarge } from "@/lib/marge"; export const metadata: Metadata = { title: "Accueil" }; const getCachedDashboardData = unstable_cache( pgGetDashboardData, ["dashboard-data"], // 30 minutes : ce sont les ventes de la veille, elles ne changent pas dans la journée. { revalidate: 1800 } ); // --------------------------------------------------------------------------- // Calculs // --------------------------------------------------------------------------- /** Évolution en % ; `null` quand il n'y a pas de base de comparaison. */ function evolution(actuel: number, precedent: number): number | null { if (!precedent) return null; return ((actuel - precedent) / Math.abs(precedent)) * 100; } function panierMoyen(ca: number, tickets: number): number | null { return tickets > 0 ? ca / tickets : null; } /** « 2026-10-02 » → « vendredi 2 octobre 2026 ». */ function dateLongue(iso: string): string { const d = new Date(`${iso}T12:00:00`); if (Number.isNaN(d.getTime())) return iso; return d.toLocaleDateString("fr-FR", { weekday: "long", day: "numeric", month: "long", year: "numeric" }); } // --------------------------------------------------------------------------- // Blocs // --------------------------------------------------------------------------- function Indicateur({ label, valeur, valeurN1, delta }: { label: string; valeur: string; valeurN1: string | null; delta: number | null; }) { return (
{label}
{valeur}
{valeurN1 && N-1 : {valeurN1}}
); } function CarteMagasin({ site }: { site: DashboardSiteStats }) { const panier = panierMoyen(site.ca_hier, site.tickets_hier); const panierN1 = panierMoyen(site.ca_n1, site.tickets_n1); return ( {nomMagasin(site.site)}} />
0 ? fmtEur2(site.ca_n1) : null} delta={evolution(site.ca_hier, site.ca_n1)} /> 0 ? fmtEntier(site.tickets_n1) : null} delta={evolution(site.tickets_hier, site.tickets_n1)} />
); } type Critere = "ca" | "qte" | "marge"; const CRITERES: Record = { ca: { titre: "Meilleur chiffre d'affaires", icone: Euro }, qte: { titre: "Plus grandes quantités", icone: ShoppingCart }, marge: { titre: "Meilleure marge", icone: BarChart3 }, }; function valeurPrincipale(item: DashboardTopItem, critere: Critere): string { if (critere === "qte") return `${fmtEntier(item.qte)} vendus`; if (critere === "marge") return fmtEur2(item.marge); return fmtEur2(item.ca); } function ListeTop10({ items, critere }: { items: DashboardTopItem[]; critere: Critere }) { const { titre, icone: Icone } = CRITERES[critere]; return (

{titre}

{items.length === 0 ? (

Aucune vente enregistrée.

) : (
    {items.map((item, i) => { const taux = item.ca > 0 ? (item.marge / item.ca) * 100 : null; return (
  1. {i + 1}
    {item.libelle1 ?? item.codein} {valeurPrincipale(item, critere)}
    {item.fournisseur} {critere !== "ca" && CA {fmtEur2(item.ca)}} {critere !== "qte" && {fmtEntier(item.qte)} vendus} Stock {fmtEntier(item.stock)} {taux != null && ( Marge {fmtDecimal1(taux)} % )}
  2. ); })}
)}
); } function Top10Magasin({ siteData }: { siteData: DashboardSiteTop10 }) { return (

{nomMagasin(siteData.site)}

); } function TitreSection({ titre, description }: { titre: string; description?: React.ReactNode }) { return (

{titre}

{description &&

{description}

}
); } // --------------------------------------------------------------------------- // Page // --------------------------------------------------------------------------- export default async function DashboardPage() { let data; try { data = await getCachedDashboardData(); } catch (e) { console.error("[Dashboard] pgGetDashboardData failed:", e); return (
); } const { dateHier, dateN1, sites, top10BySite } = data; const totalCa = sites.reduce((s, r) => s + r.ca_hier, 0); const totalCaN1 = sites.reduce((s, r) => s + r.ca_n1, 0); const totalTickets = sites.reduce((s, r) => s + r.tickets_hier, 0); const totalTicketsN1 = sites.reduce((s, r) => s + r.tickets_n1, 0); const panier = panierMoyen(totalCa, totalTickets); const panierN1 = panierMoyen(totalCaN1, totalTicketsN1); return (
Les ventes de {dateLongue(dateHier)} {dateN1 && <>, comparées au même jour de la semaine l'an dernier ({dateLongue(dateN1)})}. } actions={Données mises à jour chaque nuit} /> {sites.length === 0 ? ( ) : (
Total de nos magasins, évolution par rapport à .} />
0 ? `N-1 : ${fmtEur2(totalCaN1)}` : undefined} icon={Euro} /> 0 ? `N-1 : ${fmtEntier(totalTicketsN1)}` : undefined} icon={Receipt} />
{sites.map((s) => )}
{top10BySite.length > 0 && (
{top10BySite.map((s) => )}
)}
)}
); }