1115bb2265
Il widget dei messaggi esisteva gia', ma era il terzo riquadro della colonna stretta: sotto la piega, cioe' invisibile. Un messaggio non letto e' la cosa piu' urgente della giornata e ora sta in testa alla dashboard, a piena larghezza, due colonne. Due dati erano gia' in ConversationSummary e non venivano mostrati: da quanto aspetta il messaggio e su cosa e' stato scritto (fase, task, deliverable o generale). Sono esattamente i due che dicono con che fretta rispondere. Il tempo relativo si ferma alla settimana e poi passa alla data: oltre, "23 giorni fa" non aiuta piu' nessuno. La fascia sparisce del tutto quando non c'e' niente da leggere, invece di lasciare a video un riquadro vuoto che si impara a saltare — e con lui si imparerebbe a saltare anche quello pieno. relativeTime va in src/lib/dates.ts perche' serve anche altrove. FollowUpWidget ha ancora la sua copia locale con il prefisso "Contattato": la si unifica quando la si tocca, non oggi. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
91 lines
3.2 KiB
TypeScript
91 lines
3.2 KiB
TypeScript
import { Suspense } from "react";
|
|
import { getDashboardStats } from "@/lib/dashboard-queries";
|
|
import { FollowUpWidget } from "@/components/admin/dashboard/FollowUpWidget";
|
|
import { InboxBand } from "@/components/admin/dashboard/InboxBand";
|
|
import {
|
|
getAnalyticsByYear,
|
|
getTotalTrackedHours,
|
|
getClientProfitability,
|
|
} from "@/lib/analytics-queries";
|
|
import { getRevenueForecast12Months, getOffersSoldBreakdown } from "@/lib/forecast-queries";
|
|
import { YearSelector } from "@/components/admin/YearSelector";
|
|
import { ForecastChart } from "@/components/admin/ForecastChart";
|
|
import { OffersSoldChart } from "@/components/admin/OffersSoldChart";
|
|
import { ClientProfitability } from "@/components/admin/dashboard/ClientProfitability";
|
|
import { MetricCard, fmtEur0 } from "@/components/admin/MetricCard";
|
|
|
|
export const revalidate = 0;
|
|
|
|
export default async function AdminDashboard({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ year?: string }>;
|
|
}) {
|
|
const { year: yearParam } = await searchParams;
|
|
const year = parseInt(yearParam ?? "") || new Date().getFullYear();
|
|
|
|
const { kpi } = await getDashboardStats();
|
|
|
|
const [data, totalHours, profitability, forecast, offersSold] = await Promise.all([
|
|
getAnalyticsByYear(year),
|
|
getTotalTrackedHours(year),
|
|
getClientProfitability(year),
|
|
getRevenueForecast12Months(),
|
|
getOffersSoldBreakdown(),
|
|
]);
|
|
|
|
const collectedPct =
|
|
data.contracted > 0 ? Math.round((data.collected / data.contracted) * 100) : 0;
|
|
|
|
return (
|
|
<div className="flex flex-col gap-8">
|
|
{/* Messaggi in attesa — in cima perché è la cosa più urgente della
|
|
giornata. Non renderizza nulla quando non c'è niente da leggere. */}
|
|
<Suspense fallback={null}>
|
|
<InboxBand />
|
|
</Suspense>
|
|
|
|
{/* KPI strip */}
|
|
<section className="grid grid-cols-2 lg:grid-cols-4 gap-6">
|
|
<MetricCard
|
|
label="Clienti Attivi"
|
|
value={String(kpi.clientiAttivi)}
|
|
delta={kpi.clientiNuoviMese > 0 ? `+${kpi.clientiNuoviMese} questo mese` : undefined}
|
|
sub="Totale corrente"
|
|
/>
|
|
<MetricCard
|
|
label="Contrattualizzato"
|
|
value={fmtEur0(data.contracted)}
|
|
sub={`Totale anno ${year}`}
|
|
/>
|
|
<MetricCard
|
|
label="Incassato Reale"
|
|
value={fmtEur0(data.collected)}
|
|
valueAccent={data.collected > 0}
|
|
sub={`${collectedPct}% del totale`}
|
|
/>
|
|
<MetricCard label="Ore Tracciate" value={`${totalHours}h`} sub="Su progetti attivi" />
|
|
</section>
|
|
|
|
{/* Griglia contenuti: 2/3 + 1/3 */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
{/* Colonna 2/3 */}
|
|
<div className="lg:col-span-2 flex flex-col gap-8">
|
|
<ForecastChart data={forecast} headerAction={<YearSelector currentYear={year} />} />
|
|
<ClientProfitability data={profitability} />
|
|
</div>
|
|
|
|
{/* Colonna 1/3 */}
|
|
<div className="lg:col-span-1 flex flex-col gap-8">
|
|
<Suspense
|
|
fallback={<div className="animate-pulse h-40 bg-muted rounded-xl" />}
|
|
>
|
|
<FollowUpWidget />
|
|
</Suspense>
|
|
<OffersSoldChart data={offersSold} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|