a20a9de2d7
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>
57 lines
2.0 KiB
TypeScript
57 lines
2.0 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import type { ForecastMonth } from "@/lib/forecast-queries";
|
|
|
|
export function ForecastChart({
|
|
data,
|
|
headerAction,
|
|
}: {
|
|
data: ForecastMonth[];
|
|
headerAction?: ReactNode;
|
|
}) {
|
|
const max = Math.max(...data.map((d) => d.total), 1);
|
|
// index 1 = mese prossimo (evidenziato)
|
|
|
|
return (
|
|
<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)
|
|
</h3>
|
|
<p className="text-[11px] text-muted-foreground mt-0.5">
|
|
Stima basata su retainer attivi e saldi dilazionati
|
|
</p>
|
|
</div>
|
|
{headerAction}
|
|
</div>
|
|
|
|
<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-2">
|
|
<div className="w-full flex flex-col justify-end" style={{ height: "150px" }}>
|
|
<div
|
|
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 })}`}
|
|
/>
|
|
</div>
|
|
<span
|
|
className={`text-[10px] capitalize ${
|
|
isNext ? "font-bold text-foreground" : "text-muted-foreground"
|
|
}`}
|
|
>
|
|
{m.label.split(" ")[0]}
|
|
</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|