Sposto rosa, classifica e storico partite dagli array hardcoded al CMS Strapi

Nuovi moduli src/lib/strapi-client.ts + rosa-base.ts/classifica-csi.ts/
storico-match.ts (hook TanStack Query, cache lunga, nessun polling). L'id
stabile gN diventa il campo `codice` su Strapi, per non rompere le FK verso
pagelle/cacche/mvp/palloni/presenze/azioni scout già chiavate su quella
stringa. isAdmin ora prende il Giocatore invece di un id, così non dipende
più dal roster. Rifattorizzati di conseguenza tutti i punti che importavano
`giocatori`/`classifica`/`storicoMatch` come array sincroni (componenti,
hook, due route server) per usare gli hook o un parametro esplicito.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 15:39:40 +02:00
co-authored by Claude Sonnet 5
parent 3f61b32219
commit 953908388d
36 changed files with 678 additions and 364 deletions
+39 -18
View File
@@ -4,7 +4,8 @@ import { Cake, ChevronDown, Crown } from "lucide-react";
import { cn } from "@/lib/utils";
import { PageHeader, Section } from "@/components/crapp/ui-bits";
import { Avatar } from "@/components/crapp/Avatar";
import { formatData, storicoMatch } from "@/lib/crapp-data";
import { formatData } from "@/lib/crapp-data";
import { useStoricoMatch } from "@/lib/storico-match";
import { microcopyObiettivo, progressoObiettivo } from "@/lib/obiettivi";
import { useRosa, useObiettivi } from "@/lib/rosa";
import { usePresenzeUltimoMeseTutti } from "@/lib/presenze-mese";
@@ -43,7 +44,8 @@ export const Route = createFileRoute("/squadra")({
{ title: "Squadra CRAP Volley — CrAPP" },
{
name: "description",
content: "Rosa completa del CRAP Volley: giocatori, dati anagrafici, statistiche stagionali e badge sbloccati.",
content:
"Rosa completa del CRAP Volley: giocatori, dati anagrafici, statistiche stagionali e badge sbloccati.",
},
{ property: "og:title", content: "Squadra CRAP Volley — CrAPP" },
{
@@ -55,7 +57,6 @@ export const Route = createFileRoute("/squadra")({
component: Squadra,
});
const criteri = [
{ id: "presenze", label: "Presenze" },
{ id: "mediaVoto", label: "Media voto" },
@@ -75,6 +76,7 @@ function valore(
function Squadra() {
const rosa = useRosa();
const storicoMatch = useStoricoMatch();
const mese = usePresenzeUltimoMeseTutti();
const scoutMatches = useScoutMatches();
const votiMvp = useVotiMvp();
@@ -85,7 +87,9 @@ function Squadra() {
const obiettivi = useObiettivi();
const team = totaliSquadra(scoutMatches);
const mediaPresenze = rosa.length
? Math.round((rosa.reduce((s, g) => s + g.presenze / (g.totaliEventi || 1), 0) / rosa.length) * 100)
? Math.round(
(rosa.reduce((s, g) => s + g.presenze / (g.totaliEventi || 1), 0) / rosa.length) * 100,
)
: 0;
const ordinati = [...rosa].sort((a, b) => valore(b, criterio) - valore(a, criterio));
const max = ordinati[0] ? valore(ordinati[0], criterio) || 1 : 1;
@@ -116,7 +120,10 @@ function Squadra() {
<div className="space-y-2">
{rosa.map((g) => {
const stati = badgeGiocatore(g);
const sbloccati = [...stati.filter((b) => b.grado !== null), ...badgeSegretiSbloccati(g)];
const sbloccati = [
...stati.filter((b) => b.grado !== null),
...badgeSegretiSbloccati(g),
];
const isOpen = aperto === g.id;
return (
<article key={g.id} className="overflow-hidden rounded-3xl bg-card shadow-card">
@@ -126,7 +133,11 @@ function Squadra() {
className="flex w-full items-center gap-3 p-3 text-left active:scale-[0.99]"
aria-expanded={isOpen}
>
<Avatar id={g.id} fallback={g.numero ? String(g.numero) : g.iniziali} className="h-11 w-11 text-lg" />
<Avatar
id={g.id}
fallback={g.numero ? String(g.numero) : g.iniziali}
className="h-11 w-11 text-lg"
/>
<span className="min-w-0 flex-1">
<span className="block truncate text-sm font-bold leading-tight">{g.nome}</span>
<span className="mt-1 flex items-center gap-2">
@@ -160,19 +171,20 @@ function Squadra() {
<div className="border-t border-border px-4 pb-4 pt-3">
<div className="flex flex-wrap items-center gap-2">
<p className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
<Cake className="h-3.5 w-3.5" /> {formatData(g.nascita)} {g.nascita.slice(0, 4)}
<Cake className="h-3.5 w-3.5" /> {formatData(g.nascita)}{" "}
{g.nascita.slice(0, 4)}
</p>
</div>
<div className="mt-3 grid grid-cols-3 gap-2">
{[
{ l: "Presenze", v: `${g.presenze}/${g.totaliEventi}` },
{ l: "Presenze 30gg", v: `${mese[g.id]?.percentuale ?? 0}%` },
{ l: "Presenze di fila", v: g.streak },
{ l: "Media voto", v: g.mediaVoto || "—" },
{ l: "MVP", v: g.mvp },
{ l: "Cacche/partita 💩", v: g.cacchePartita || "—" },
].map((s) => (
{[
{ l: "Presenze", v: `${g.presenze}/${g.totaliEventi}` },
{ l: "Presenze 30gg", v: `${mese[g.id]?.percentuale ?? 0}%` },
{ l: "Presenze di fila", v: g.streak },
{ l: "Media voto", v: g.mediaVoto || "—" },
{ l: "MVP", v: g.mvp },
{ l: "Cacche/partita 💩", v: g.cacchePartita || "—" },
].map((s) => (
<div key={s.l} className="rounded-2xl bg-secondary p-2.5 text-center">
<p className="font-display text-xl leading-none">{s.v}</p>
<p className="mt-1 text-[10px] font-semibold uppercase text-muted-foreground">
@@ -201,9 +213,16 @@ function Squadra() {
<p className="mt-1 text-xs font-bold leading-tight">{b.def.nome}</p>
<p className="text-[10px] text-muted-foreground">
{b.valore} {b.def.unita}
{b.prossimaSoglia ? ` · ${b.prossimaSoglia} per ${gradoMeta[b.prossimo!].label.toLowerCase()}` : ""}
{b.prossimaSoglia
? ` · ${b.prossimaSoglia} per ${gradoMeta[b.prossimo!].label.toLowerCase()}`
: ""}
</p>
<p className={cn("mt-1.5 text-[10px] font-bold uppercase", meta.text)}>
<p
className={cn(
"mt-1.5 text-[10px] font-bold uppercase",
meta.text,
)}
>
{meta.label}
</p>
</div>
@@ -411,7 +430,9 @@ function Squadra() {
const meta = gradoMeta[grado];
const quanti = rosa.filter((g) => {
const raggiunto = gradoRaggiunto(b, b.valore(g));
return raggiunto ? gradiOrdine.indexOf(raggiunto) >= gradiOrdine.indexOf(grado) : false;
return raggiunto
? gradiOrdine.indexOf(raggiunto) >= gradiOrdine.indexOf(grado)
: false;
}).length;
return (
<div