style: rifinitura Quiet Luxury dashboard — bordi soft, badge, colori

Allinea la dashboard alla rifinitura del mock (finezza bordi/colori/badge):

- Card su border-border-light (slate-100) per il bordo sottile
- KPI: valore verde su Incassato Reale, delta verde "+N questo mese" su
  Clienti Attivi (nuova metrica getDashboardStats.clientiNuoviMese)
- Redditività: badge "Ottimo" con tint emerald soft (non brand primary)
- Follow-up: righe come box bordati (no avatar), link "Apri →" primary
- Forecast: selettore anno (pill interattiva) spostato nell'header della card,
  barre non-picco grigie (bg-border), rimosso il numero "mese prossimo"
- Offerte Più Richieste: rimossa la pill totale nell'header

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 12:18:10 +02:00
parent 08aadc1d97
commit a20a9de2d7
7 changed files with 95 additions and 129 deletions
+18 -30
View File
@@ -1,22 +1,19 @@
import type { ReactNode } from "react";
import type { ForecastMonth } from "@/lib/forecast-queries";
function fmtEur(n: number) {
return n.toLocaleString("it-IT", {
style: "currency",
currency: "EUR",
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
}
export function ForecastChart({ data }: { data: ForecastMonth[] }) {
export function ForecastChart({
data,
headerAction,
}: {
data: ForecastMonth[];
headerAction?: ReactNode;
}) {
const max = Math.max(...data.map((d) => d.total), 1);
// index 0 = mese corrente, index 1 = mese prossimo (evidenziato)
const nextMonth = data[1];
// index 1 = mese prossimo (evidenziato)
return (
<div className="bg-card rounded-xl border border-border shadow-card p-6 space-y-6">
<div className="flex justify-between items-start">
<div className="bg-card rounded-xl border border-border-light shadow-card p-6">
<div className="flex justify-between items-center mb-6">
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
Previsione Flussi di Cassa (12 Mesi)
@@ -25,28 +22,19 @@ export function ForecastChart({ data }: { data: ForecastMonth[] }) {
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>
)}
{headerAction}
</div>
<div className="flex items-end gap-2 h-44 border-t border-border pt-4">
<div className="flex items-end gap-2 h-48 border-b border-border-light pt-4">
{data.map((m, i) => {
const pct = Math.round((m.total / max) * 100);
const isNext = i === 1;
return (
<div key={`${m.year}-${m.month}`} className="flex-1 flex flex-col items-center gap-1">
<div className="w-full flex flex-col justify-end" style={{ height: "120px" }}>
<div key={`${m.year}-${m.month}`} className="flex-1 flex flex-col items-center gap-2">
<div className="w-full flex flex-col justify-end" style={{ height: "150px" }}>
<div
className={`w-full rounded-t-md transition-all ${
isNext ? "bg-primary" : "bg-primary/35"
className={`w-full rounded-t transition-all ${
isNext ? "bg-primary" : "bg-border hover:bg-primary/50"
}`}
style={{ height: `${pct}%`, minHeight: m.total > 0 ? "4px" : "0" }}
title={`${m.label}: ${m.total.toLocaleString("it-IT", { style: "currency", currency: "EUR", minimumFractionDigits: 2 })}`}
@@ -54,7 +42,7 @@ export function ForecastChart({ data }: { data: ForecastMonth[] }) {
</div>
<span
className={`text-[10px] capitalize ${
isNext ? "font-bold text-primary" : "text-muted-foreground"
isNext ? "font-bold text-foreground" : "text-muted-foreground"
}`}
>
{m.label.split(" ")[0]}