From 9cc63b7854a23048f99c881badbb83f8225ef8d1 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 7 Sep 2026 13:58:54 +0200 Subject: [PATCH] =?UTF-8?q?Infortuni/ritardi:=20non=20contano=20pi=C3=B9?= =?UTF-8?q?=20gli=20eventi=20futuri=20nei=20badge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit contaInfortuni()/contaRitardi() leggevano tutta la mappa presenze senza guardare la data dell'evento: un giocatore può segnarsi infortunato o in ritardo anche su un evento non ancora avvenuto (l'UI lo permette finché l'evento non è passato), e quel conteggio arrivava subito ai badge segreti "Infermeria" e "Ritardi". Ora filtrano su data < oggi, come già fa eventiContanoPresenze() per le statistiche di presenza: con l'avanzare della data reale l'evento prima futuro entra da solo nel conteggio. Co-Authored-By: Claude Sonnet 5 --- docs/modules/infortuni.md | 6 +++ src/lib/infortuni.ts | 58 ++++++++++++++++++++------ test/unit/infortuni.test.ts | 81 ++++++++++++++++++++++++++++++++++--- 3 files changed, 126 insertions(+), 19 deletions(-) diff --git a/docs/modules/infortuni.md b/docs/modules/infortuni.md index 3840ae6..5062042 100644 --- a/docs/modules/infortuni.md +++ b/docs/modules/infortuni.md @@ -27,6 +27,12 @@ volte compare lo stato `infortunato` nella mappa presenze già in cache (nessuna aggiuntiva). Lo stesso meccanismo, con `contaRitardi()`, conta i ritardi. Il risultato alimenta il campo `infortuni` del `Giocatore` in `useRosa()`. +Un evento con data futura o odierna non viene contato, anche se la risposta è già registrata +(l'UI permette di segnarsi infortunato o in ritardo su un evento non ancora passato): il +conteggio filtra su `data < oggi`, come già fa `eventiContanoPresenze()` in +[presenze.md](presenze.md), e cresce da solo con l'avanzare della data reale senza bisogno di +altro codice. + Visibile in UI solo indirettamente, tramite il [badge](badge.md) segreto "Cliente VIP dell'Infermeria" (sbloccato con almeno 3 infortuni): non esiste uno StatTile dedicato nel profilo che mostri il numero di infortuni come statistica di superficie. diff --git a/src/lib/infortuni.ts b/src/lib/infortuni.ts index 3eca0dc..3a82c86 100644 --- a/src/lib/infortuni.ts +++ b/src/lib/infortuni.ts @@ -1,13 +1,31 @@ import { useMemo } from "react"; import type { Giocatore } from "./crapp-data"; +import type { Evento } from "./eventi"; +import { useEventi } from "./eventi"; +import { dataOggi } from "./scout-live"; import { useRispostePresenze, type MappaPresenze } from "./presenze"; /** giocatoreId -> numero di eventi (allenamenti + partite) con stato "infortunato". */ export type ContoInfortuni = Record; -function contaStato(presenze: MappaPresenze, stato: string): ContoInfortuni { +/** + * Un giocatore può segnarsi infortunato o in ritardo anche su un evento futuro + * (l'UI lo permette finché l'evento non è passato): finché quell'evento non è + * avvenuto davvero non deve contare per i badge, altrimenti si sbloccherebbero + * in anticipo. Un evento non più in `eventi` (es. cancellato) non viene contato: + * non potendo verificarne la data, si esclude per prudenza. + */ +function contaStato( + presenze: MappaPresenze, + stato: string, + eventi: Evento[], + oggi: string = dataOggi(), +): ContoInfortuni { + const dataPerEvento = new Map(eventi.map((e) => [e.id, e.data])); const out: ContoInfortuni = {}; for (const eventoId of Object.keys(presenze)) { + const dataEvento = dataPerEvento.get(eventoId); + if (dataEvento === undefined || dataEvento >= oggi) continue; const evento = presenze[eventoId] ?? {}; for (const giocatoreId of Object.keys(evento)) { if (evento[giocatoreId] === stato) out[giocatoreId] = (out[giocatoreId] ?? 0) + 1; @@ -16,14 +34,22 @@ function contaStato(presenze: MappaPresenze, stato: string): ContoInfortuni { return out; } -/** Conta gli infortuni dalla mappa presenze già in cache: ogni evento vale una volta sola. */ -export function contaInfortuni(presenze: MappaPresenze): ContoInfortuni { - return contaStato(presenze, "infortunato"); +/** Conta gli infortuni dalla mappa presenze già in cache: ogni evento passato vale una volta sola. */ +export function contaInfortuni( + presenze: MappaPresenze, + eventi: Evento[], + oggi: string = dataOggi(), +): ContoInfortuni { + return contaStato(presenze, "infortunato", eventi, oggi); } -/** Conta i ritardi dalla stessa mappa presenze: ogni evento vale una volta sola. */ -export function contaRitardi(presenze: MappaPresenze): ContoInfortuni { - return contaStato(presenze, "ritardo"); +/** Conta i ritardi dalla stessa mappa presenze: ogni evento passato vale una volta sola. */ +export function contaRitardi( + presenze: MappaPresenze, + eventi: Evento[], + oggi: string = dataOggi(), +): ContoInfortuni { + return contaStato(presenze, "ritardo", eventi, oggi); } export function conInfortuni( @@ -38,23 +64,29 @@ export function conInfortuni( }; } -/** Nessuna query aggiuntiva: riusa la cache delle risposte presenze. */ +/** Nessuna query aggiuntiva: riusa le cache di risposte presenze ed eventi. */ export function useInfortuni(): ContoInfortuni { const { presenze } = useRispostePresenze(); - return useMemo(() => contaInfortuni(presenze), [presenze]); + const { eventi } = useEventi(); + return useMemo(() => contaInfortuni(presenze, eventi), [presenze, eventi]); } -/** Nessuna query aggiuntiva: riusa la cache delle risposte presenze. */ +/** Nessuna query aggiuntiva: riusa le cache di risposte presenze ed eventi. */ export function useRitardi(): ContoInfortuni { const { presenze } = useRispostePresenze(); - return useMemo(() => contaRitardi(presenze), [presenze]); + const { eventi } = useEventi(); + return useMemo(() => contaRitardi(presenze, eventi), [presenze, eventi]); } /** Un solo hook per entrambi i conteggi: evita hook extra nei componenti. */ export function useInfortuniERitardi(): { infortuni: ContoInfortuni; ritardi: ContoInfortuni } { const { presenze } = useRispostePresenze(); + const { eventi } = useEventi(); return useMemo( - () => ({ infortuni: contaInfortuni(presenze), ritardi: contaRitardi(presenze) }), - [presenze], + () => ({ + infortuni: contaInfortuni(presenze, eventi), + ritardi: contaRitardi(presenze, eventi), + }), + [presenze, eventi], ); } diff --git a/test/unit/infortuni.test.ts b/test/unit/infortuni.test.ts index f7f6a4a..58c0613 100644 --- a/test/unit/infortuni.test.ts +++ b/test/unit/infortuni.test.ts @@ -2,8 +2,27 @@ import assert from "node:assert/strict"; import { conInfortuni, contaInfortuni, contaRitardi } from "@/lib/infortuni"; import { giocatori } from "@/lib/crapp-data"; +import type { Evento } from "@/lib/eventi"; import type { MappaPresenze } from "@/lib/presenze"; +const ev = (id: string, data: string): Evento => ({ + id, + tipo: "allenamento", + titolo: id, + luogo: "", + data, + ora: "20:00", + note: "", + convocati: [], + campionato: false, + casa: true, + pagelleChiuse: false, +}); + +const OGGI = "2026-09-04"; + +const eventi: Evento[] = [ev("e1", "2026-08-20"), ev("e2", "2026-08-27"), ev("e3", "2026-09-03")]; + const presenze: MappaPresenze = { e1: { g1: "infortunato", g2: "presente", g3: "ritardo" }, e2: { g1: "infortunato", g2: "ritardo", g3: "assente" }, @@ -11,14 +30,60 @@ const presenze: MappaPresenze = { }; // --- conteggi ---------------------------------------------------------------- -assert.deepEqual(contaInfortuni(presenze), { g1: 2 }, "ogni evento vale una volta sola"); -assert.deepEqual(contaRitardi(presenze), { g3: 1, g2: 1 }); -assert.deepEqual(contaInfortuni({}), {}); -assert.deepEqual(contaRitardi({ e1: {} }), {}, "evento senza risposte: nessun conteggio"); +assert.deepEqual( + contaInfortuni(presenze, eventi, OGGI), + { g1: 2 }, + "ogni evento passato vale una volta sola", +); +assert.deepEqual(contaRitardi(presenze, eventi, OGGI), { g3: 1, g2: 1 }); +assert.deepEqual(contaInfortuni({}, eventi, OGGI), {}); +assert.deepEqual( + contaRitardi({ e1: {} }, eventi, OGGI), + {}, + "evento senza risposte: nessun conteggio", +); + +// --- eventi futuri: non contano finché non sono passati ----------------------- +const eventiConFuturo: Evento[] = [...eventi, ev("f1", "2026-09-10")]; +const presenzeConFuturo: MappaPresenze = { + ...presenze, + f1: { g1: "infortunato", g3: "ritardo" }, +}; +assert.deepEqual( + contaInfortuni(presenzeConFuturo, eventiConFuturo, OGGI), + { g1: 2 }, + "l'infortunio su un evento futuro non conta ancora", +); +assert.deepEqual( + contaRitardi(presenzeConFuturo, eventiConFuturo, OGGI), + { g3: 1, g2: 1 }, + "il ritardo su un evento futuro non conta ancora", +); +// Con l'avanzare della data, l'evento prima futuro entra nel conteggio. +assert.deepEqual( + contaInfortuni(presenzeConFuturo, eventiConFuturo, "2026-09-11"), + { g1: 3 }, + "una volta passata la data dell'evento, l'infortunio viene conteggiato", +); +assert.deepEqual( + contaRitardi(presenzeConFuturo, eventiConFuturo, "2026-09-11"), + { g3: 2, g2: 1 }, + "una volta passata la data dell'evento, il ritardo viene conteggiato", +); +// Evento non più presente in `eventi` (es. cancellato): escluso per prudenza. +assert.deepEqual( + contaInfortuni({ ...presenze, sconosciuto: { g1: "infortunato" } }, eventi, OGGI), + { g1: 2 }, + "un evento senza data nota non viene contato", +); // --- conInfortuni: arricchisce il giocatore senza perdere gli altri campi ---- const base = giocatori.find((g) => g.id === "g1")!; -const arricchito = conInfortuni(base, contaInfortuni(presenze), contaRitardi(presenze)); +const arricchito = conInfortuni( + base, + contaInfortuni(presenze, eventi, OGGI), + contaRitardi(presenze, eventi, OGGI), +); assert.equal(arricchito.infortuni, 2); assert.equal(arricchito.ritardi, 0, "g1 non ha ritardi"); assert.equal(arricchito.nome, base.nome, "il resto del giocatore resta intatto"); @@ -28,6 +93,10 @@ const senzaDati = conInfortuni(base, {}); assert.deepEqual([senzaDati.infortuni, senzaDati.ritardi], [0, 0], "assenza di dati = zero"); const g3 = giocatori.find((g) => g.id === "g3")!; -assert.equal(conInfortuni(g3, contaInfortuni(presenze), contaRitardi(presenze)).ritardi, 1); +assert.equal( + conInfortuni(g3, contaInfortuni(presenze, eventi, OGGI), contaRitardi(presenze, eventi, OGGI)) + .ritardi, + 1, +); console.log("infortuni: ok");