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>
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
import { db } from "@/db";
|
|
import { clients, projects, payments } from "@/db/schema";
|
|
import { eq, sum, count, sql } from "drizzle-orm";
|
|
|
|
export type KpiStats = {
|
|
clientiAttivi: number;
|
|
clientiNuoviMese: number; // clients created in the current calendar month
|
|
revenueTotale: string; // sum of projects.accepted_total (non-archived)
|
|
pagamentiInSospeso: string; // sum of payments.amount where status = 'da_saldare' or 'inviata'
|
|
progettiInCorso: number; // projects not archived
|
|
};
|
|
|
|
export type DashboardStats = {
|
|
kpi: KpiStats;
|
|
};
|
|
|
|
export async function getDashboardStats(): Promise<DashboardStats> {
|
|
// ── KPIs ─────────────────────────────────────────────────────────────────────
|
|
const [clientiAttiviRow] = await db
|
|
.select({ count: count() })
|
|
.from(clients)
|
|
.where(eq(clients.archived, false));
|
|
|
|
const [clientiNuoviMeseRow] = await db
|
|
.select({ count: count() })
|
|
.from(clients)
|
|
.where(
|
|
sql`date_trunc('month', ${clients.created_at}) = date_trunc('month', now())`
|
|
);
|
|
|
|
const [revenueRow] = await db
|
|
.select({ total: sum(projects.accepted_total) })
|
|
.from(projects)
|
|
.where(eq(projects.archived, false));
|
|
|
|
const [pagamentiDaSaldareRow] = await db
|
|
.select({ total: sum(payments.amount) })
|
|
.from(payments)
|
|
.where(eq(payments.status, "da_saldare"));
|
|
|
|
const [pagamentiInviataRow] = await db
|
|
.select({ total: sum(payments.amount) })
|
|
.from(payments)
|
|
.where(eq(payments.status, "inviata"));
|
|
|
|
const [progettiRow] = await db
|
|
.select({ count: count() })
|
|
.from(projects)
|
|
.where(eq(projects.archived, false));
|
|
|
|
const pagamentiSospeso =
|
|
(parseFloat(pagamentiDaSaldareRow?.total ?? "0") || 0) +
|
|
(parseFloat(pagamentiInviataRow?.total ?? "0") || 0);
|
|
|
|
const kpi: KpiStats = {
|
|
clientiAttivi: clientiAttiviRow?.count ?? 0,
|
|
clientiNuoviMese: clientiNuoviMeseRow?.count ?? 0,
|
|
revenueTotale: revenueRow?.total ?? "0",
|
|
pagamentiInSospeso: pagamentiSospeso.toFixed(2),
|
|
progettiInCorso: progettiRow?.count ?? 0,
|
|
};
|
|
|
|
return { kpi };
|
|
}
|