feat: chat globale revisioni + pagina statistiche admin

- Chat revisioni: rimuovi commenti inline da timeline/kanban, aggiungi
  ChatSection con feed cronologico + selector task opzionale (Invio per inviare)
  Bolle stile chat: Tu (destra, giallo) / iamcavalli (sinistra, verde)
  Tag task su ogni messaggio quando il messaggio non è generale
- API /api/client/comment: supporto entity_type "general" (entity_id = clientId)
- Pagina /admin/analytics: year selector ←→, 4 metric card (contrattualizzato,
  incassato, da incassare, clienti acquisiti), bar chart mensile incassato via CSS
- NavBar: link "Statistiche"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Simone Cavalli
2026-05-16 12:52:25 +02:00
parent 457656a2a9
commit d322162c0a
3 changed files with 171 additions and 4 deletions
+4 -4
View File
@@ -9,12 +9,12 @@ export function NavBar() {
<nav className="bg-[#1A463C] px-6 py-3 flex items-center justify-between">
<div className="flex items-center gap-6">
<span className="font-bold text-white tracking-tight">iamcavalli</span>
<Link
href="/admin"
className="text-sm text-white/70 hover:text-white transition-colors"
>
<Link href="/admin" className="text-sm text-white/70 hover:text-white transition-colors">
Clienti
</Link>
<Link href="/admin/analytics" className="text-sm text-white/70 hover:text-white transition-colors">
Statistiche
</Link>
</div>
<Button
variant="ghost"
+70
View File
@@ -0,0 +1,70 @@
"use client";
import { useRouter } from "next/navigation";
const MONTHS = ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"];
export function YearSelector({
currentYear,
availableYears,
}: {
currentYear: number;
availableYears: number[];
}) {
const router = useRouter();
const thisYear = new Date().getFullYear();
function go(y: number) {
router.push(`/admin/analytics?year=${y}`);
}
return (
<div className="flex items-center gap-3">
<button
onClick={() => go(currentYear - 1)}
className="w-8 h-8 rounded-lg border border-[#e5e7eb] flex items-center justify-center text-[#71717a] hover:border-[#1A463C] hover:text-[#1A463C] transition-colors"
aria-label="Anno precedente"
>
</button>
<span className="text-lg font-bold text-[#1a1a1a] tabular-nums w-14 text-center">
{currentYear}
</span>
<button
onClick={() => go(currentYear + 1)}
disabled={currentYear >= thisYear}
className="w-8 h-8 rounded-lg border border-[#e5e7eb] flex items-center justify-center text-[#71717a] hover:border-[#1A463C] hover:text-[#1A463C] transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
aria-label="Anno successivo"
>
</button>
</div>
);
}
export function MonthlyChart({ data, year }: { data: number[]; year: number }) {
const max = Math.max(...data, 1);
return (
<div className="bg-white rounded-xl border border-[#e5e7eb] p-6">
<h3 className="text-sm font-bold text-[#1a1a1a] mb-6">Incassato mese per mese {year}</h3>
<div className="flex items-end gap-2 h-40">
{data.map((val, i) => {
const pct = Math.round((val / max) * 100);
return (
<div key={i} className="flex-1 flex flex-col items-center gap-1">
<div className="w-full flex flex-col justify-end" style={{ height: "120px" }}>
<div
className="w-full rounded-t-md bg-[#1A463C] transition-all"
style={{ height: `${pct}%`, minHeight: val > 0 ? "4px" : "0" }}
title={`${val.toLocaleString("it-IT", { minimumFractionDigits: 2 })}`}
/>
</div>
<span className="text-[10px] text-[#71717a]">{MONTHS[i]}</span>
</div>
);
})}
</div>
</div>
);
}