From d63beae04c6b7c940bfb0607c47f42f122ce4bb4 Mon Sep 17 00:00:00 2001 From: Ivan Cacciari Date: Tue, 1 Sep 2026 22:34:07 +0200 Subject: [PATCH] Replace demo stats with real data Co-authored-by: Cursor --- src/lib/crapp-data.ts | 45 +++++++----------------------------- src/lib/mvp-voti.ts | 14 +++++++++++ src/lib/presenze.ts | 29 +++++++++++++++++++++++ src/lib/user-store.ts | 18 +++++++++------ src/routes/profilo.tsx | 4 +++- test/unit/crapp-data.test.ts | 7 ++++-- 6 files changed, 70 insertions(+), 47 deletions(-) diff --git a/src/lib/crapp-data.ts b/src/lib/crapp-data.ts index 829f00f..c464a76 100644 --- a/src/lib/crapp-data.ts +++ b/src/lib/crapp-data.ts @@ -82,40 +82,6 @@ function inizialiDa(nome: string) { .toUpperCase(); } -type StatsDemo = Pick; - -/** Statistiche demo: mix di badge bronzo / argento / oro già sbloccati. */ -const statsDemo: Record = { - "Ivan Cacciari": { presenze: 31, streak: 8, mvp: 5, mediaVoto: 8.7 }, - "Davide Grilli": { presenze: 27, streak: 5, mvp: 3, mediaVoto: 8.4 }, - "Nicola Pezzoli": { presenze: 24, streak: 4, mvp: 2, mediaVoto: 8.2 }, - "Laura Passabì": { presenze: 22, streak: 6, mvp: 3, mediaVoto: 8.1 }, - "Francesca Tucci": { presenze: 19, streak: 3, mvp: 1, mediaVoto: 7.9 }, - "Iacopo Ricci": { presenze: 21, streak: 2, mvp: 3, mediaVoto: 7.8 }, - "Alessandra Brunacci": { presenze: 14, streak: 3, mvp: 1, mediaVoto: 7.5 }, - "Mattias Bologna": { presenze: 12, streak: 2, mvp: 1, mediaVoto: 7.4 }, - "Giada Valbonesi": { presenze: 13, streak: 4, mvp: 1, mediaVoto: 7.6 }, - "Alessio Cocco": { presenze: 11, streak: 1, mvp: 0, mediaVoto: 7.2 }, - "Mattia Catalano": { presenze: 9, streak: 2, mvp: 0, mediaVoto: 7.0 }, - "Antonella Loverre": { presenze: 8, streak: 1, mvp: 0, mediaVoto: 6.9 }, - "Carlo Di Castelnuovo": { presenze: 7, streak: 1, mvp: 0, mediaVoto: 6.8 }, - "Camilla Esposito": { presenze: 6, streak: 2, mvp: 0, mediaVoto: 6.7 }, - "Salvador Battistella": { presenze: 20, streak: 5, mvp: 1, mediaVoto: 7.7 }, - "Silvia Chilese": { presenze: 16, streak: 3, mvp: 0, mediaVoto: 7.3 }, - "Cristina Titone": { presenze: 15, streak: 2, mvp: 0, mediaVoto: 7.1 }, -}; - -/** Serie demo derivate dalle statistiche: ogni tipo ha il suo contatore. */ -function serieDa(s?: StatsDemo) { - const streak = s?.streak ?? 0; - const presenze = s?.presenze ?? 0; - return { - serieAllenamenti: streak, - seriePartite: Math.ceil(streak / 2), - serieConferme: presenze >= 20 ? 12 : presenze >= 14 ? 8 : presenze >= 8 ? 4 : 1, - }; -} - export const giocatori: Giocatore[] = rosaCSI.map((r, i) => ({ id: `g${i + 1}`, nome: r.nome, @@ -123,14 +89,19 @@ export const giocatori: Giocatore[] = rosaCSI.map((r, i) => ({ ruolo: r.ruolo, nascita: r.nascita, iniziali: inizialiDa(r.nome), - totaliEventi: 32, + presenze: 0, + totaliEventi: 0, + streak: 0, + serieAllenamenti: 0, + seriePartite: 0, + serieConferme: 0, + mvp: 0, + mediaVoto: 0, infortuni: 0, ritardi: 0, palloni: 0, cacche: 0, cacchePartita: 0, - ...(statsDemo[r.nome] ?? { presenze: 0, streak: 0, mvp: 0, mediaVoto: 0 }), - ...serieDa(statsDemo[r.nome]), })); export type RigaClassifica = { diff --git a/src/lib/mvp-voti.ts b/src/lib/mvp-voti.ts index a41d857..acf5c45 100644 --- a/src/lib/mvp-voti.ts +++ b/src/lib/mvp-voti.ts @@ -84,3 +84,17 @@ export function vincitoriMvp(voti: VotoMvp[]): Record { export function mioVoto(voti: VotoMvp[], matchId: string, votanteId: string) { return voti.find((v) => v.match_id === matchId && v.votante_id === votanteId) ?? null; } + +/** MVP vinti per giocatore, contando una vittoria per partita votata. */ +export function mvpVintiPerGiocatore(voti: VotoMvp[]): Record { + const out: Record = {}; + const matchIds = new Set(voti.map((v) => v.match_id)); + for (const matchId of matchIds) { + const top = conteggioPartita(voti, matchId); + if (top.length > 0 && (top.length === 1 || top[0]!.voti > top[1]!.voti)) { + const id = top[0]!.id; + out[id] = (out[id] ?? 0) + 1; + } + } + return out; +} diff --git a/src/lib/presenze.ts b/src/lib/presenze.ts index 890d890..b89c470 100644 --- a/src/lib/presenze.ts +++ b/src/lib/presenze.ts @@ -1,12 +1,41 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { supabase } from "@/integrations/supabase/client"; import type { Stato } from "./crapp-data"; +import type { Evento } from "./eventi"; export const PRESENZE_KEY = ["risposte-presenze"] as const; /** eventoId -> giocatoreId -> stato */ export type MappaPresenze = Record>; +/** Allenamenti e partite CrAPP che contano per le statistiche di presenza. */ +function eventiContanoPresenze(eventi: Evento[], giocatoreId?: string) { + return eventi.filter( + (e) => + (e.tipo === "partita" || e.tipo === "allenamento") && + (giocatoreId === undefined || + e.convocati.length === 0 || + e.convocati.includes(giocatoreId)), + ); +} + +/** Presenze effettive (presente o in ritardo) su eventi CrAPP. */ +export function contaPresenzeGiocatore( + giocatoreId: string, + eventi: Evento[], + presenze: MappaPresenze, +): number { + return eventiContanoPresenze(eventi, giocatoreId).filter((e) => { + const stato = presenze[e.id]?.[giocatoreId]; + return stato === "presente" || stato === "ritardo"; + }).length; +} + +/** Eventi CrAPP rilevanti per il denominatore presenze di un giocatore. */ +export function totaliEventiGiocatore(giocatoreId: string, eventi: Evento[]): number { + return eventiContanoPresenze(eventi, giocatoreId).length; +} + async function fetchPresenze(): Promise { const { data, error } = await supabase .from("risposte_presenze") diff --git a/src/lib/user-store.ts b/src/lib/user-store.ts index 579afca..f88179d 100644 --- a/src/lib/user-store.ts +++ b/src/lib/user-store.ts @@ -1,6 +1,6 @@ import { useSyncExternalStore } from "react"; import { giocatori, type Giocatore } from "./crapp-data"; -import { conInfortuni, useInfortuniERitardi } from "./infortuni"; +import { useIo } from "./rosa"; const KEY = "crapp-user-v1"; const listeners = new Set<() => void>(); @@ -36,9 +36,9 @@ export function resetGiocatore() { write(null); } -/** Solo lettura locale: nessuna dipendenza da React Query (usabile fuori dal provider). */ -export function useGiocatoreBase(): Giocatore | null { - const id = useSyncExternalStore( +/** Id del giocatore collegato al dispositivo (localStorage). */ +export function useGiocatoreId(): string | null { + return useSyncExternalStore( (cb) => { listeners.add(cb); return () => listeners.delete(cb); @@ -46,11 +46,15 @@ export function useGiocatoreBase(): Giocatore | null { () => read(), () => null, ); +} + +/** Anagrafica base dal localStorage: le statistiche restano a zero finché non passa da `useIo`. */ +export function useGiocatoreBase(): Giocatore | null { + const id = useGiocatoreId(); return id ? (giocatori.find((x) => x.id === id) ?? null) : null; } +/** Giocatore corrente con statistiche calcolate da dati reali (presenze, MVP, badge, …). */ export function useGiocatoreCorrente(): Giocatore | null { - const g = useGiocatoreBase(); - const { infortuni, ritardi } = useInfortuniERitardi(); - return g ? conInfortuni(g, infortuni, ritardi) : null; + return useIo(); } diff --git a/src/routes/profilo.tsx b/src/routes/profilo.tsx index 1818376..e4aad5c 100644 --- a/src/routes/profilo.tsx +++ b/src/routes/profilo.tsx @@ -97,7 +97,9 @@ function Profilo() { } } - const percPresenze = Math.round((g.presenze / g.totaliEventi) * 100); + const percPresenze = g.totaliEventi + ? Math.round((g.presenze / g.totaliEventi) * 100) + : 0; async function onFile(e: React.ChangeEvent) { const file = e.target.files?.[0]; diff --git a/test/unit/crapp-data.test.ts b/test/unit/crapp-data.test.ts index 33bdbed..c54cdec 100644 --- a/test/unit/crapp-data.test.ts +++ b/test/unit/crapp-data.test.ts @@ -20,8 +20,11 @@ for (const g of giocatori) { assert.ok(g.numero > 0, `${g.id}: numero di maglia positivo`); assert.equal(g.iniziali.length, 2, `${g.id}: due iniziali`); assert.equal(g.iniziali, g.iniziali.toUpperCase(), `${g.id}: iniziali maiuscole`); - assert.ok(g.mediaVoto >= 0 && g.mediaVoto <= 10, `${g.id}: media voto nel range 1-10`); - assert.ok(g.presenze <= g.totaliEventi, `${g.id}: presenze mai oltre gli eventi totali`); + assert.equal(g.mediaVoto, 0, `${g.id}: media voto iniziale a zero`); + assert.equal(g.presenze, 0, `${g.id}: presenze iniziali a zero`); + assert.equal(g.totaliEventi, 0, `${g.id}: totaliEventi iniziale a zero`); + assert.equal(g.mvp, 0, `${g.id}: mvp iniziale a zero`); + assert.equal(g.streak, 0, `${g.id}: streak iniziale a zero`); } // --- formatData --------------------------------------------------------------