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>
69 lines
2.5 KiB
TypeScript
69 lines
2.5 KiB
TypeScript
import type { ClientProfitabilityRow } from "@/lib/analytics-queries";
|
|
|
|
function fmtRate(n: number) {
|
|
return n.toLocaleString("it-IT", {
|
|
style: "currency",
|
|
currency: "EUR",
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
});
|
|
}
|
|
|
|
const MARGIN_LABEL: Record<ClientProfitabilityRow["margin"], string> = {
|
|
ottimo: "Margine Ottimo",
|
|
in_linea: "In Linea",
|
|
sotto: "Sotto Target",
|
|
};
|
|
|
|
export function ClientProfitability({ data }: { data: ClientProfitabilityRow[] }) {
|
|
return (
|
|
<div className="bg-card rounded-xl border border-border-light shadow-card p-6">
|
|
<h3 className="text-xs font-bold uppercase tracking-wider text-muted-foreground mb-4">
|
|
Analisi Oraria & Redditività Clienti
|
|
</h3>
|
|
|
|
{data.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground italic text-center py-4">
|
|
Nessuna sessione tracciata per calcolare la redditività.
|
|
</p>
|
|
) : (
|
|
<div className="space-y-4">
|
|
{data.map((row) => {
|
|
const isOttimo = row.margin === "ottimo";
|
|
return (
|
|
<div
|
|
key={row.clientId}
|
|
className="flex items-center justify-between gap-4 p-3 rounded-lg border border-border-light bg-muted/30"
|
|
>
|
|
<div className="min-w-0">
|
|
<h4 className="text-xs font-semibold text-foreground truncate">
|
|
{row.clientName}
|
|
</h4>
|
|
<p className="text-[10px] text-muted-foreground">
|
|
Tracciate:{" "}
|
|
<span className="font-mono">{row.hours.toLocaleString("it-IT")}h</span> — Valore
|
|
Orario Target:{" "}
|
|
<span className="font-mono">{row.target}€/h</span>
|
|
</p>
|
|
</div>
|
|
<div className="text-right shrink-0">
|
|
<span
|
|
className={`text-xs font-bold font-mono px-2 py-0.5 rounded border ${
|
|
isOttimo
|
|
? "text-emerald-700 bg-emerald-50 border-emerald-100 dark:text-emerald-400 dark:bg-emerald-500/10 dark:border-emerald-500/20"
|
|
: "text-foreground bg-muted border-border"
|
|
}`}
|
|
>
|
|
{fmtRate(row.realRate)} /h Reali
|
|
</span>
|
|
<p className="text-[9px] text-muted-foreground mt-1">{MARGIN_LABEL[row.margin]}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|