Infortuni/ritardi: non contano più gli eventi futuri nei badge
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
aggiuntiva). Lo stesso meccanismo, con `contaRitardi()`, conta i ritardi. Il risultato
|
||||||
alimenta il campo `infortuni` del `Giocatore` in `useRosa()`.
|
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
|
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
|
dell'Infermeria" (sbloccato con almeno 3 infortuni): non esiste uno StatTile dedicato nel
|
||||||
profilo che mostri il numero di infortuni come statistica di superficie.
|
profilo che mostri il numero di infortuni come statistica di superficie.
|
||||||
|
|||||||
+45
-13
@@ -1,13 +1,31 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import type { Giocatore } from "./crapp-data";
|
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";
|
import { useRispostePresenze, type MappaPresenze } from "./presenze";
|
||||||
|
|
||||||
/** giocatoreId -> numero di eventi (allenamenti + partite) con stato "infortunato". */
|
/** giocatoreId -> numero di eventi (allenamenti + partite) con stato "infortunato". */
|
||||||
export type ContoInfortuni = Record<string, number>;
|
export type ContoInfortuni = Record<string, number>;
|
||||||
|
|
||||||
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 = {};
|
const out: ContoInfortuni = {};
|
||||||
for (const eventoId of Object.keys(presenze)) {
|
for (const eventoId of Object.keys(presenze)) {
|
||||||
|
const dataEvento = dataPerEvento.get(eventoId);
|
||||||
|
if (dataEvento === undefined || dataEvento >= oggi) continue;
|
||||||
const evento = presenze[eventoId] ?? {};
|
const evento = presenze[eventoId] ?? {};
|
||||||
for (const giocatoreId of Object.keys(evento)) {
|
for (const giocatoreId of Object.keys(evento)) {
|
||||||
if (evento[giocatoreId] === stato) out[giocatoreId] = (out[giocatoreId] ?? 0) + 1;
|
if (evento[giocatoreId] === stato) out[giocatoreId] = (out[giocatoreId] ?? 0) + 1;
|
||||||
@@ -16,14 +34,22 @@ function contaStato(presenze: MappaPresenze, stato: string): ContoInfortuni {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Conta gli infortuni dalla mappa presenze già in cache: ogni evento vale una volta sola. */
|
/** Conta gli infortuni dalla mappa presenze già in cache: ogni evento passato vale una volta sola. */
|
||||||
export function contaInfortuni(presenze: MappaPresenze): ContoInfortuni {
|
export function contaInfortuni(
|
||||||
return contaStato(presenze, "infortunato");
|
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. */
|
/** Conta i ritardi dalla stessa mappa presenze: ogni evento passato vale una volta sola. */
|
||||||
export function contaRitardi(presenze: MappaPresenze): ContoInfortuni {
|
export function contaRitardi(
|
||||||
return contaStato(presenze, "ritardo");
|
presenze: MappaPresenze,
|
||||||
|
eventi: Evento[],
|
||||||
|
oggi: string = dataOggi(),
|
||||||
|
): ContoInfortuni {
|
||||||
|
return contaStato(presenze, "ritardo", eventi, oggi);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function conInfortuni<T extends Giocatore>(
|
export function conInfortuni<T extends Giocatore>(
|
||||||
@@ -38,23 +64,29 @@ export function conInfortuni<T extends Giocatore>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Nessuna query aggiuntiva: riusa la cache delle risposte presenze. */
|
/** Nessuna query aggiuntiva: riusa le cache di risposte presenze ed eventi. */
|
||||||
export function useInfortuni(): ContoInfortuni {
|
export function useInfortuni(): ContoInfortuni {
|
||||||
const { presenze } = useRispostePresenze();
|
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 {
|
export function useRitardi(): ContoInfortuni {
|
||||||
const { presenze } = useRispostePresenze();
|
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. */
|
/** Un solo hook per entrambi i conteggi: evita hook extra nei componenti. */
|
||||||
export function useInfortuniERitardi(): { infortuni: ContoInfortuni; ritardi: ContoInfortuni } {
|
export function useInfortuniERitardi(): { infortuni: ContoInfortuni; ritardi: ContoInfortuni } {
|
||||||
const { presenze } = useRispostePresenze();
|
const { presenze } = useRispostePresenze();
|
||||||
|
const { eventi } = useEventi();
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({ infortuni: contaInfortuni(presenze), ritardi: contaRitardi(presenze) }),
|
() => ({
|
||||||
[presenze],
|
infortuni: contaInfortuni(presenze, eventi),
|
||||||
|
ritardi: contaRitardi(presenze, eventi),
|
||||||
|
}),
|
||||||
|
[presenze, eventi],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,27 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { conInfortuni, contaInfortuni, contaRitardi } from "@/lib/infortuni";
|
import { conInfortuni, contaInfortuni, contaRitardi } from "@/lib/infortuni";
|
||||||
import { giocatori } from "@/lib/crapp-data";
|
import { giocatori } from "@/lib/crapp-data";
|
||||||
|
import type { Evento } from "@/lib/eventi";
|
||||||
import type { MappaPresenze } from "@/lib/presenze";
|
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 = {
|
const presenze: MappaPresenze = {
|
||||||
e1: { g1: "infortunato", g2: "presente", g3: "ritardo" },
|
e1: { g1: "infortunato", g2: "presente", g3: "ritardo" },
|
||||||
e2: { g1: "infortunato", g2: "ritardo", g3: "assente" },
|
e2: { g1: "infortunato", g2: "ritardo", g3: "assente" },
|
||||||
@@ -11,14 +30,60 @@ const presenze: MappaPresenze = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- conteggi ----------------------------------------------------------------
|
// --- conteggi ----------------------------------------------------------------
|
||||||
assert.deepEqual(contaInfortuni(presenze), { g1: 2 }, "ogni evento vale una volta sola");
|
assert.deepEqual(
|
||||||
assert.deepEqual(contaRitardi(presenze), { g3: 1, g2: 1 });
|
contaInfortuni(presenze, eventi, OGGI),
|
||||||
assert.deepEqual(contaInfortuni({}), {});
|
{ g1: 2 },
|
||||||
assert.deepEqual(contaRitardi({ e1: {} }), {}, "evento senza risposte: nessun conteggio");
|
"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 ----
|
// --- conInfortuni: arricchisce il giocatore senza perdere gli altri campi ----
|
||||||
const base = giocatori.find((g) => g.id === "g1")!;
|
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.infortuni, 2);
|
||||||
assert.equal(arricchito.ritardi, 0, "g1 non ha ritardi");
|
assert.equal(arricchito.ritardi, 0, "g1 non ha ritardi");
|
||||||
assert.equal(arricchito.nome, base.nome, "il resto del giocatore resta intatto");
|
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");
|
assert.deepEqual([senzaDati.infortuni, senzaDati.ritardi], [0, 0], "assenza di dati = zero");
|
||||||
|
|
||||||
const g3 = giocatori.find((g) => g.id === "g3")!;
|
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");
|
console.log("infortuni: ok");
|
||||||
|
|||||||
Reference in New Issue
Block a user