feat: Dashboard admin redesign to Quiet Luxury + client profitability widget

Replica il mock design-reference/pagina-dashboard: layout condensato a
schermata singola (4 KPI + griglia 2/3-1/3), token semantici per dual-theme.

- Nuova query getClientProfitability(year): valore orario reale per cliente
  (contrattualizzato / ore tracciate) con badge margine, target 100 €/h
- Nuovo widget ClientProfitability "Analisi Oraria & Redditività Clienti"
- Rewrite /admin: KPI strip + Cashflow + Redditività | Follow-up + Offerte
  Più Richieste. Rimossi chart mensile, card extra, barre ore/cliente
- Tokenizzati ForecastChart, OffersSoldChart, FollowUpWidget, YearSelector
  (ora pill interattiva); rimosso MonthlyChart e prop availableYears inutile
- DESIGN-SYSTEM.md: inventario + note dashboard (applied 2026-07-11)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 11:57:10 +02:00
parent e5fa07bba3
commit 08aadc1d97
9 changed files with 622 additions and 254 deletions
+22 -15
View File
@@ -15,22 +15,29 @@ export function ForecastChart({ data }: { data: ForecastMonth[] }) {
const nextMonth = data[1];
return (
<div className="bg-white rounded-xl border border-[#e5e7eb] p-6 space-y-5">
{nextMonth && (
<div className="flex items-baseline justify-between">
<div>
<p className="text-xs font-bold uppercase tracking-wider text-[#71717a]">
Previsto il mese prossimo
</p>
<p className="text-xs text-[#a1a1aa] mt-0.5 capitalize">{nextMonth.label}</p>
</div>
<p className="text-2xl font-bold tracking-tight text-[#1A463C]">
{fmtEur(nextMonth.total)}
<div className="bg-card rounded-xl border border-border shadow-card p-6 space-y-6">
<div className="flex justify-between items-start">
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
Previsione Flussi di Cassa (12 Mesi)
</h3>
<p className="text-[11px] text-muted-foreground mt-0.5">
Stima basata su retainer attivi e saldi dilazionati
</p>
</div>
)}
{nextMonth && (
<div className="text-right">
<p className="text-2xl font-bold tracking-tight text-primary font-mono">
{fmtEur(nextMonth.total)}
</p>
<p className="text-[11px] text-muted-foreground mt-0.5 capitalize">
Previsto {nextMonth.label}
</p>
</div>
)}
</div>
<div className="flex items-end gap-2 h-44 border-t border-[#f4f4f5] pt-4">
<div className="flex items-end gap-2 h-44 border-t border-border pt-4">
{data.map((m, i) => {
const pct = Math.round((m.total / max) * 100);
const isNext = i === 1;
@@ -39,7 +46,7 @@ export function ForecastChart({ data }: { data: ForecastMonth[] }) {
<div className="w-full flex flex-col justify-end" style={{ height: "120px" }}>
<div
className={`w-full rounded-t-md transition-all ${
isNext ? "bg-[#1A463C]" : "bg-[#1A463C]/35"
isNext ? "bg-primary" : "bg-primary/35"
}`}
style={{ height: `${pct}%`, minHeight: m.total > 0 ? "4px" : "0" }}
title={`${m.label}: ${m.total.toLocaleString("it-IT", { style: "currency", currency: "EUR", minimumFractionDigits: 2 })}`}
@@ -47,7 +54,7 @@ export function ForecastChart({ data }: { data: ForecastMonth[] }) {
</div>
<span
className={`text-[10px] capitalize ${
isNext ? "font-bold text-[#1A463C]" : "text-[#71717a]"
isNext ? "font-bold text-primary" : "text-muted-foreground"
}`}
>
{m.label.split(" ")[0]}