diff --git a/src/components/crapp/EventoCard.tsx b/src/components/crapp/EventoCard.tsx index e014d02..b63c3aa 100644 --- a/src/components/crapp/EventoCard.tsx +++ b/src/components/crapp/EventoCard.tsx @@ -16,7 +16,7 @@ import { formatData, statoMeta, type Stato } from "@/lib/crapp-data"; import type { Evento } from "@/lib/eventi"; import { useGiocatoriSquadra } from "@/lib/giocatori-squadra"; import { usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useGiocatoreBase } from "@/lib/user-store"; import { dataOggi } from "@/lib/scout-live"; const tipoMeta = { @@ -69,7 +69,10 @@ export function EventoCard({ }) { const { risposte } = usePresenzeEvento(evento.id); const salva = useSalvaPresenza(); - const io = useGiocatoreCorrente(); + // Solo `io.id` serve qui (per leggere/scrivere la propria risposta): `useGiocatoreBase` + // legge la sola anagrafica, non le statistiche di tutta la rosa di `useGiocatoreCorrente`. + // Rilevante perché ogni card monta questo hook: il Calendario ne rende diverse insieme. + const io = useGiocatoreBase(); const { righe: squadra } = useGiocatoriSquadra(); const rosa = squadra.filter((g) => g.attivo); const stato = io ? risposte[io.id] : undefined; diff --git a/src/components/crapp/Pagelle.tsx b/src/components/crapp/Pagelle.tsx index f36c266..03309ba 100644 --- a/src/components/crapp/Pagelle.tsx +++ b/src/components/crapp/Pagelle.tsx @@ -5,7 +5,7 @@ import { cn } from "@/lib/utils"; import { Card } from "@/components/crapp/ui-bits"; import { Avatar } from "@/components/crapp/Avatar"; import type { Giocatore } from "@/lib/crapp-data"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useGiocatoreBase } from "@/lib/user-store"; import { mieiVoti, pagellePartita, usePagelle, useVotaPagella } from "@/lib/pagelle"; const voti = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; @@ -20,7 +20,8 @@ export function Pagelle({ convocati: Giocatore[]; chiuse?: boolean; }) { - const io = useGiocatoreCorrente(); + // Solo `.id` serve qui: `useGiocatoreBase` basta, niente statistiche. + const io = useGiocatoreBase(); const { voti: tutti, isPending } = usePagelle(); const vota = useVotaPagella(); const [apertoPer, setApertoPer] = useState(null); diff --git a/src/components/crapp/PromemoriaPalloni.tsx b/src/components/crapp/PromemoriaPalloni.tsx index 1819daa..f5067a8 100644 --- a/src/components/crapp/PromemoriaPalloni.tsx +++ b/src/components/crapp/PromemoriaPalloni.tsx @@ -3,11 +3,12 @@ import { formatData } from "@/lib/crapp-data"; import { eventiPalloni, eventoPrecedente, eventoSuccessivo, oggiISO } from "@/lib/palloni-core"; import { useTurniPalloni } from "@/lib/palloni"; import { useEventi } from "@/lib/eventi"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useGiocatoreBase } from "@/lib/user-store"; /** Avvisi per chi è di turno: prendere i palloni oggi, o riportarli oggi. */ export function PromemoriaPalloni() { - const io = useGiocatoreCorrente(); + // Solo `.id` serve qui: `useGiocatoreBase` basta, niente statistiche. Montato in Home. + const io = useGiocatoreBase(); const { turni } = useTurniPalloni(); const { eventi } = useEventi(); if (!io) return null; diff --git a/src/components/crapp/RosaPresenze.tsx b/src/components/crapp/RosaPresenze.tsx index 0cd1ad5..974c17d 100644 --- a/src/components/crapp/RosaPresenze.tsx +++ b/src/components/crapp/RosaPresenze.tsx @@ -7,8 +7,8 @@ import { Avatar } from "@/components/crapp/Avatar"; import { Barra } from "@/components/motion/Barra"; import { statoMeta, type Giocatore, type Stato } from "@/lib/crapp-data"; import { usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze"; -import { useRosa } from "@/lib/rosa"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useAnagraficaRosa } from "@/lib/rosa"; +import { useGiocatoreBase } from "@/lib/user-store"; import { intestazioniAutenticate } from "@/lib/auth"; import { useIsAdmin } from "@/lib/ruoli"; import { dataOggi } from "@/lib/scout-live"; @@ -18,9 +18,11 @@ const ordine: Stato[] = ["presente", "ritardo", "forse", "infortunato", "assente export function RosaPresenze({ eventoId, data }: { eventoId: string; data: string }) { const { risposte, isPending } = usePresenzeEvento(eventoId); const salva = useSalvaPresenza(); - const io = useGiocatoreCorrente(); + // Solo `.id`/`.nome` servono qui: `useGiocatoreBase`/`useAnagraficaRosa` bastano, + // niente statistiche di squadra. + const io = useGiocatoreBase(); const admin = useIsAdmin(); - const rosa = useRosa(); + const rosa = useAnagraficaRosa(); const [sollecito, setSollecito] = useState(false); const passato = data < dataOggi(); @@ -169,7 +171,7 @@ function Gruppo({ }: { titolo: string; n: number; - lista: Giocatore[]; + lista: Array>; attenzione?: boolean; }) { return ( diff --git a/src/components/crapp/ScoutEntry.tsx b/src/components/crapp/ScoutEntry.tsx index c892bdb..6caa845 100644 --- a/src/components/crapp/ScoutEntry.tsx +++ b/src/components/crapp/ScoutEntry.tsx @@ -2,7 +2,7 @@ import { Link } from "@tanstack/react-router"; import { ChevronRight, Lock, Radio } from "lucide-react"; import { cn } from "@/lib/utils"; import { sessioneScaduta, usePartitaDiOggi, useSessioneScout } from "@/lib/scout-live"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useGiocatoreBase } from "@/lib/user-store"; /** * Accesso allo scout live: attivo solo il giorno della partita e se nessun altro lo sta usando. @@ -17,7 +17,8 @@ export function ScoutEntry({ }) { const { pronto, partita: diOggi } = usePartitaDiOggi(); const partita = eventoId && diOggi?.id !== eventoId ? null : diOggi; - const io = useGiocatoreCorrente(); + // Solo `.id` serve qui: `useGiocatoreBase` basta, niente statistiche. + const io = useGiocatoreBase(); const { data: sessione } = useSessioneScout(partita?.id ?? null); const attiva = sessione && !sessioneScaduta(sessione) ? sessione : null; diff --git a/src/components/crapp/SondaggioCacche.tsx b/src/components/crapp/SondaggioCacche.tsx index 96689e3..6f5dd4e 100644 --- a/src/components/crapp/SondaggioCacche.tsx +++ b/src/components/crapp/SondaggioCacche.tsx @@ -4,7 +4,7 @@ import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { Card } from "@/components/crapp/ui-bits"; import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useGiocatoreBase } from "@/lib/user-store"; import { intestazioniAutenticate } from "@/lib/auth"; import { useIsAdmin } from "@/lib/ruoli"; import { @@ -27,7 +27,8 @@ export function SondaggioCacche({ dataEvento: string; oraEvento: string; }) { - const io = useGiocatoreCorrente(); + // Solo `.id` serve qui: `useGiocatoreBase` basta, niente statistiche. + const io = useGiocatoreBase(); const { righe } = useCacche(); const salva = useSalvaCacche(); const { righe: squadra } = useGiocatoriSquadra(); diff --git a/src/components/crapp/TurnoPalloni.tsx b/src/components/crapp/TurnoPalloni.tsx index 42d78a5..066d082 100644 --- a/src/components/crapp/TurnoPalloni.tsx +++ b/src/components/crapp/TurnoPalloni.tsx @@ -7,14 +7,15 @@ import { intestazioniAutenticate } from "@/lib/auth"; import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra"; import { useAssegnaTurno, useTurniPalloni } from "@/lib/palloni"; import { useIsAdmin } from "@/lib/ruoli"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useGiocatoreBase } from "@/lib/user-store"; export function TurnoPalloni({ eventoId }: { eventoId: string }) { const [aperto, setAperto] = useState(false); const [avviso, setAvviso] = useState(false); const { salvati, turni, isPending } = useTurniPalloni(); const assegna = useAssegnaTurno(); - const io = useGiocatoreCorrente(); + // Solo `.nome` serve qui: `useGiocatoreBase` basta, niente statistiche. + const io = useGiocatoreBase(); const admin = useIsAdmin(); const { righe: squadra } = useGiocatoriSquadra(); const rosa = squadra.filter((g) => g.attivo); diff --git a/src/components/crapp/VotazioneMvp.tsx b/src/components/crapp/VotazioneMvp.tsx index a19753c..55f79e6 100644 --- a/src/components/crapp/VotazioneMvp.tsx +++ b/src/components/crapp/VotazioneMvp.tsx @@ -3,7 +3,7 @@ import { Crown, Vote } from "lucide-react"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useGiocatoreBase } from "@/lib/user-store"; import { usePresenzeEvento } from "@/lib/presenze"; import type { Evento } from "@/lib/eventi"; import { @@ -25,7 +25,8 @@ import { */ export function VotazioneMvp({ evento }: { evento: Evento }) { const matchId = evento.id; - const io = useGiocatoreCorrente(); + // Solo `.id` serve qui: `useGiocatoreBase` basta, niente statistiche. + const io = useGiocatoreBase(); const voti = useVotiMvp(); const vota = useVotaMvp(); const { righe: squadra } = useGiocatoriSquadra(); diff --git a/src/components/crapp/VotoSocial.tsx b/src/components/crapp/VotoSocial.tsx index 1431dee..ccb21a8 100644 --- a/src/components/crapp/VotoSocial.tsx +++ b/src/components/crapp/VotoSocial.tsx @@ -3,7 +3,7 @@ import { Check, Crown, Sparkles } from "lucide-react"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useGiocatoreBase } from "@/lib/user-store"; import { categorieSocial, conteggioCategoria, @@ -15,7 +15,8 @@ import { /** Voto social post-partita: un compagno per categoria, veloce da mobile. */ export function VotoSocial({ matchId }: { matchId: string }) { - const io = useGiocatoreCorrente(); + // Solo `.id` serve qui: `useGiocatoreBase` basta, niente statistiche. + const io = useGiocatoreBase(); const voti = useVotiSocial(); const vota = useVotaSocial(); const { righe: squadra } = useGiocatoriSquadra(); diff --git a/src/lib/rosa.ts b/src/lib/rosa.ts index e8ceff3..2f5b3ea 100644 --- a/src/lib/rosa.ts +++ b/src/lib/rosa.ts @@ -25,18 +25,26 @@ function iniziali(nome: string, cognome: string): string { } /** - * Solo anagrafica (id, nome, data di nascita) dei giocatori attivi — es. per i - * compleanni nel Calendario. A differenza di `useRosa` non legge MVP, pagelle, - * cacche, palloni né infortuni: evita di montare quei cinque hook e il relativo - * `useMemo` solo per una data di nascita. + * Solo anagrafica (id, nome, ruolo, numero, data di nascita) dei giocatori attivi — es. + * per i compleanni nel Calendario o le liste presenze. A differenza di `useRosa` non + * legge MVP, pagelle, cacche, palloni né infortuni: evita di montare quei cinque hook e + * il relativo `useMemo` solo per l'anagrafica. */ -export function useAnagraficaRosa(): Array> { +export function useAnagraficaRosa(): Array< + Pick +> { const { righe: squadra } = useGiocatoriSquadra(); return useMemo( () => squadra .filter((g) => g.attivo) - .map((g) => ({ id: g.id, nome: nomeCompleto(g), nascita: nascitaPerId[g.id] ?? "" })), + .map((g) => ({ + id: g.id, + nome: nomeCompleto(g), + ruolo: g.ruolo, + numero: g.numero, + nascita: nascitaPerId[g.id] ?? "", + })), [squadra], ); } diff --git a/src/routes/benvenuto.tsx b/src/routes/benvenuto.tsx index 1b42ac2..c20cda0 100644 --- a/src/routes/benvenuto.tsx +++ b/src/routes/benvenuto.tsx @@ -10,7 +10,7 @@ import { useCollegaGiocatore, useGiocatoriSquadra, } from "@/lib/giocatori-squadra"; -import { impostaGiocatore, resetGiocatore, useGiocatoreCorrente } from "@/lib/user-store"; +import { impostaGiocatore, resetGiocatore, useGiocatoreBase } from "@/lib/user-store"; export const Route = createFileRoute("/benvenuto")({ validateSearch: (search: Record): { next?: string } => { @@ -38,7 +38,8 @@ export const Route = createFileRoute("/benvenuto")({ function Benvenuto() { const navigate = useNavigate(); const { next } = Route.useSearch(); - const giocatore = useGiocatoreCorrente(); + // Solo verità (`!!giocatore`): `useGiocatoreBase` (anagrafica) basta, niente statistiche. + const giocatore = useGiocatoreBase(); const { pronta, utenteId, emailUtente } = useSessione(); const { righe, daDatabase } = useGiocatoriSquadra(); const collega = useCollegaGiocatore(); diff --git a/src/routes/eventi.tsx b/src/routes/eventi.tsx index be29a12..c3b9477 100644 --- a/src/routes/eventi.tsx +++ b/src/routes/eventi.tsx @@ -25,7 +25,7 @@ import { type CategoriaEvento, type Evento, } from "@/lib/eventi"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useGiocatoreBase } from "@/lib/user-store"; import { useIsAdmin } from "@/lib/ruoli"; export const Route = createFileRoute("/eventi")({ @@ -57,7 +57,8 @@ const tipi: Array<{ id: CategoriaEvento; label: string }> = [ ]; function GestioneEventi() { - const io = useGiocatoreCorrente(); + // Solo verità (`!io`, gate admin): `useGiocatoreBase` basta, niente statistiche. + const io = useGiocatoreBase(); const admin = useIsAdmin(); const { eventi, isPending, isError, error, refetch } = useEventi(); const { righe: squadra } = useGiocatoriSquadra(); diff --git a/src/routes/partita.$id.tsx b/src/routes/partita.$id.tsx index d162f4d..e058097 100644 --- a/src/routes/partita.$id.tsx +++ b/src/routes/partita.$id.tsx @@ -12,7 +12,6 @@ import { SondaggioCacche } from "@/components/crapp/SondaggioCacche"; import { ScoutEntry } from "@/components/crapp/ScoutEntry"; import { useScoutMatches, totaliPerGiocatore, totaliSquadra } from "@/lib/scout-store"; import { csvScoutMatch, scaricaCsv } from "@/lib/scout-export"; -import { useGiocatoreCorrente } from "@/lib/user-store"; import { useIsAdmin } from "@/lib/ruoli"; import { VotazioneMvp } from "@/components/crapp/VotazioneMvp"; import { VotoSocial } from "@/components/crapp/VotoSocial"; @@ -46,7 +45,6 @@ export const Route = createFileRoute("/partita/$id")({ function PartitaDetail() { const { id } = Route.useParams(); const { evento } = useEvento(id); - const io = useGiocatoreCorrente(); const admin = useIsAdmin(); const scoutMatches = useScoutMatches(); const { data: csi } = useCsi(); diff --git a/src/routes/scout.tsx b/src/routes/scout.tsx index 994bb06..7cc4122 100644 --- a/src/routes/scout.tsx +++ b/src/routes/scout.tsx @@ -6,8 +6,8 @@ import { cn } from "@/lib/utils"; import { Card } from "@/components/crapp/ui-bits"; import { formatData } from "@/lib/crapp-data"; import type { Evento } from "@/lib/eventi"; -import { useRosa } from "@/lib/rosa"; -import { useGiocatoreCorrente } from "@/lib/user-store"; +import { useAnagraficaRosa } from "@/lib/rosa"; +import { useGiocatoreBase } from "@/lib/user-store"; import { usePresenzeEvento } from "@/lib/presenze"; import { statoIniziale, @@ -81,7 +81,8 @@ function Blocco({ function Scout() { const { pronto, partita } = usePartitaDiOggi(); - const io = useGiocatoreCorrente(); + // Solo `.id`/`.nome` servono qui: `useGiocatoreBase` basta, niente statistiche. + const io = useGiocatoreBase(); const sessione = useSessioneScout(partita?.id ?? null); const statoSalvato = useStatoScout(partita?.id ?? null); const apri = useApriSessioneScout(); @@ -204,7 +205,8 @@ function ScoutBoard({ const cancella = useCancellaStatoScout(); const salvaMatch = useSalvaScoutMatch(); const { risposte } = usePresenzeEvento(partita.id); - const rosa = useRosa(); + // Solo id/nome/numero servono (selezione e display): niente statistiche di squadra. + const rosa = useAnagraficaRosa(); const finito = useRef(false); /** In campo solo chi ha confermato la presenza (anche in ritardo). */ diff --git a/todo.md b/todo.md index d43eae7..34cee8d 100644 --- a/todo.md +++ b/todo.md @@ -137,17 +137,64 @@ Fix: nuovo hook `useAnagraficaRosa` in `rosa.ts` — solo `useGiocatoriSquadra` accetta ora `Pick[]` invece dell'intero `Giocatore[]`, riflettendo che è tutto ciò che usa. +## 11. [x] `EventoCard` ricalcolava la rosa intera una volta per card — APPLICATA + +Causa più probabile del lag "ancora presente" su Calendario dopo i punti 9-10: `EventoCard` +([EventoCard.tsx:72](src/components/crapp/EventoCard.tsx#L72)) usava `useGiocatoreCorrente` +(= `useIo` = `useRosa`, le 6 statistiche del punto 9) solo per leggere `io.id` — mai una +statistica. Il problema si moltiplica perché **ogni** `EventoCard` monta il proprio hook: il +Calendario ne renderizza diverse insieme (fino a 3 in "Prossimi eventi", altre in "Compleanni", +altre ancora nel drawer del giorno), quindi apriva Calendario = N ricalcoli indipendenti +dell'intera rosa con tutte le statistiche, non uno solo. Home ha lo stesso pattern (usa +`EventoCard` per "Prossimo impegno" e "Da confermare"). + +Fix: `EventoCard` usa ora `useGiocatoreBase` (sola anagrafica) invece di `useGiocatoreCorrente`. + +Nota: `useGiocatoreCorrente` è usato in altri 12 file (`PromemoriaPalloni`, `TurnoPalloni`, +`Pagelle`, `RosaPresenze`, `VotoSocial`, `VotazioneMvp`, `ScoutEntry`, `SondaggioCacche`, +`CelebrazioneBadge`, `partita.$id.tsx`, `eventi.tsx`, `scout.tsx`, `benvenuto.tsx`) — non +verificati singolarmente in questo giro. Se il lag emergesse altrove, controllare prima se +quell'uso legge davvero un campo statistico (allora serve `useIo`) o solo l'identità (allora +`useGiocatoreBase` basta), stesso ragionamento dei punti 9-11. + +## 12. [x] Stesso bug in altri 12 file — APPLICATA + +Audit di tutti gli altri usi di `useGiocatoreCorrente` (oltre `EventoCard`, punto 11): 12 su 13 +leggevano solo `.id`/`.nome`/verità, mai una statistica — stesso identico bug, ognuno però +montato una volta sola (non moltiplicato come in `EventoCard`). + +Corretti (→ `useGiocatoreBase`): `benvenuto.tsx`, `VotazioneMvp.tsx`, `SondaggioCacche.tsx`, +`VotoSocial.tsx`, `eventi.tsx`, `PromemoriaPalloni.tsx` (Home — impatto più alto del gruppo), +`TurnoPalloni.tsx`, `ScoutEntry.tsx`, `Pagelle.tsx`. + +`partita.$id.tsx:49`: `io` era dichiarato e mai più usato — rimosso del tutto (variabile morta, +nessun downgrade necessario). + +Trovati due bonus con lo stesso pattern ma sull'hook `useRosa` (non `useGiocatoreCorrente`), +sistemati nello stesso giro: +- `scout.tsx`: sia `io` (→ `useGiocatoreBase`) sia `rosa` (→ `useAnagraficaRosa`, usava solo + id/nome/numero per la selezione live). +- `RosaPresenze.tsx` (montata su partita **e** allenamento): stesso doppio fix. `useAnagraficaRosa` + esteso con `ruolo` e `numero` (oltre a id/nome/nascita) per coprire anche questo caso. + +L'unica eccezione confermata è `CelebrazioneBadge.tsx`, che usa realmente le statistiche +complete tramite `useNotificheSmart` — montato globalmente in `__root.tsx`, quindi resta il +costo di base più alto rimasto in giro, ma non è downgradabile: le servono davvero. + ## Note - `useMotoRidotto` (`lib/motion.ts`) è ora l'heuristic condiviso di "device debole" usato in `BottomNav.tsx`, `BarraSottosezioni.tsx` e `calendario.tsx` (punti 2, 3, 6). Se si riprende il punto 4 (coriandoli), conviene usare lo stesso hook invece di un check separato. -- Applicati: 1, 2, 3, 6, 7, 8, 9, 10. Restano da discutere/prioritizzare: 4 (coriandoli su - device medi), 5 (virtualizzazione liste lunghe). -- Pattern ricorrente (punti 9, 10): `useRosa()` è comodo ma calcola *tutte* le statistiche della - squadra; usarlo solo per identità/anagrafica (id, nome, nascita, iniziali) costa 5-6 hook e un - `useMemo` su tutta la rosa inutilmente. Se in futuro emerge un altro caso simile, riusare - `useAnagraficaRosa` (o `useGiocatoreBase` per il singolo giocatore) invece di `useRosa`/`useIo`. +- Applicati: 1, 2, 3, 6, 7, 8, 9, 10, 11, 12. Restano da discutere/prioritizzare: 4 (coriandoli + su device medi), 5 (virtualizzazione liste lunghe), e `CelebrazioneBadge.tsx` (usa + legittimamente le statistiche complete ma è montato su ogni pagina — non downgradabile, + eventualmente da rivedere con un intervento diverso, es. memoizzazione più aggressiva). +- Pattern ricorrente (punti 9, 10, 11): `useRosa()`/`useIo()`/`useGiocatoreCorrente()` sono + comodi ma calcolano *tutte* le statistiche della squadra; usarli solo per identità/anagrafica + (id, nome, nascita, iniziali) costa 5-6 hook e un `useMemo` su tutta la rosa inutilmente, e il + costo si moltiplica per ogni componente che lo monta (punto 11). Per la sola identità: + `useGiocatoreBase` (singolo giocatore) o `useAnagraficaRosa` (tutta la rosa). - Se il lag persistesse ancora dopo questi fix, il prossimo passo è profilare un device Android reale (Chrome DevTools remoto o `chrome://inspect`) invece di continuare a ipotizzare: a questo punto le cause "ovvie" lette dal codice sono coperte, e senza un trace reale si rischia