From 5ca081a6af33e180bd6f31bf9ecc83a2104822cf Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 19 Oct 2026 08:12:02 +0200 Subject: [PATCH 1/4] =?UTF-8?q?Eventi=20visibili=20solo=20ai=20convocati;?= =?UTF-8?q?=20=C2=ABRosa=20e=20presenze=C2=BB=20filtrata=20sui=20convocati?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - «Rosa e presenze» (allenamento, partita, evento) elenca, conta e solleciti solo i convocati invece di tutta la rosa; il riquadro «La tua risposta» sparisce per i non convocati. - Calendario, Home e elenco eventi mostrano un evento con convocati solo a chi è convocato; admin e allenatori lo vedono sempre, `convocati` vuoto = tutti (eventoVisibile). - Filtro solo lato interfaccia: la RLS di eventi_app non cambia. Co-Authored-By: Claude Sonnet 5.5 --- docs/CHANGELOG.md | 13 +++++++++++++ src/components/crapp/RosaPresenze.tsx | 9 +++++++-- src/lib/eventi-visibili.ts | 14 ++++++++++++++ src/lib/eventi.ts | 13 +++++++++++++ src/routes/allenamento.$id.tsx | 7 ++++++- src/routes/calendario.tsx | 5 +++-- src/routes/eventi.tsx | 4 ++-- src/routes/evento.$id.tsx | 7 ++++++- src/routes/index.tsx | 5 +++-- src/routes/partita.$id.tsx | 7 ++++++- test/unit/eventi.test.ts | 8 ++++++++ 11 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 src/lib/eventi-visibili.ts diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3641570..08d4d66 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,19 @@ versioni `0.y.z` erano pre-release. ## [Non rilasciato] +### Modificato + +- Un evento con dei convocati compare nel Calendario, in Home e nell'elenco eventi solo ai + convocati. Admin e allenatori lo vedono sempre; `convocati` vuoto = visibile a tutti. È un + filtro dell'interfaccia: i dati restano leggibili dal database (RLS di `eventi_app` invariata). + +### Corretto + +- «Rosa e presenze» di allenamenti, partite ed eventi mostra solo i convocati (prima elencava + sempre tutta la rosa, anche se ne era stato convocato uno solo). Il conteggio «Hanno risposto», + il pulsante «Sollecita» e il riquadro «La tua risposta» seguono la stessa regola; `convocati` + vuoto resta «tutta la rosa». + ## [1.4.0] - 2026-10-09 Gli admin e gli allenatori vengono avvisati quando una presenza cambia a ridosso di una partita o di un allenamento. diff --git a/src/components/crapp/RosaPresenze.tsx b/src/components/crapp/RosaPresenze.tsx index ad9c9cf..e4a5595 100644 --- a/src/components/crapp/RosaPresenze.tsx +++ b/src/components/crapp/RosaPresenze.tsx @@ -20,9 +20,12 @@ export function RosaPresenze({ eventoId, data, tipo = "allenamento", + convocati = [], }: { eventoId: string; data: string; + /** Id dei convocati: vuoto = tutta la rosa (come `convocatiEvento()`). */ + convocati?: string[]; /** Negli eventi extra-campo "infortunato" non è pertinente: niente badge né opzione. */ tipo?: EventoTipo; }) { @@ -34,7 +37,9 @@ export function RosaPresenze({ // Il nome di chi sollecita: admin o allenatore, che non ha una risposta propria (DD-034). const mittente = useGiocatoreBase(); const puoSollecitare = usePuoGestireEventi(); - const rosa = useAnagraficaRosa(); + const anagrafica = useAnagraficaRosa(); + const rosa = + convocati.length === 0 ? anagrafica : anagrafica.filter((g) => convocati.includes(g.id)); const [sollecito, setSollecito] = useState(false); const passato = data < dataOggi(); @@ -97,7 +102,7 @@ export function RosaPresenze({ - {io ? ( + {io && rosa.some((g) => g.id === io.id) ? (

La tua risposta diff --git a/src/lib/eventi-visibili.ts b/src/lib/eventi-visibili.ts new file mode 100644 index 0000000..dccdaa5 --- /dev/null +++ b/src/lib/eventi-visibili.ts @@ -0,0 +1,14 @@ +import { eventoVisibile, useEventi } from "./eventi"; +import { usePuoGestireEventi } from "./ruoli"; +import { useGiocatoreId } from "./user-store"; + +/** Gli eventi che l'utente corrente può vedere (calendario, home, elenco). */ +export function useEventiVisibili() { + const { eventi, ...resto } = useEventi(); + const giocatoreId = useGiocatoreId(); + const vedeTutto = usePuoGestireEventi(); + return { + ...resto, + eventi: eventi.filter((e) => eventoVisibile(e, giocatoreId, vedeTutto)), + }; +} diff --git a/src/lib/eventi.ts b/src/lib/eventi.ts index a68d851..2cf690d 100644 --- a/src/lib/eventi.ts +++ b/src/lib/eventi.ts @@ -90,6 +90,19 @@ export function useEventi() { return { ...query, eventi: query.data ?? [] }; } +/** + * Chi vede un evento: i convocati; se `convocati` è vuoto, tutta la rosa. Chi gestisce gli + * eventi (admin e allenatori) li vede sempre, convocato o no. + */ +export function eventoVisibile( + evento: Pick, + giocatoreId: string | null, + vedeTutto: boolean, +): boolean { + if (vedeTutto || evento.convocati.length === 0) return true; + return giocatoreId !== null && evento.convocati.includes(giocatoreId); +} + export function useEvento(id: string) { const { eventi, ...resto } = useEventi(); return { ...resto, evento: eventi.find((e) => e.id === id) ?? null }; diff --git a/src/routes/allenamento.$id.tsx b/src/routes/allenamento.$id.tsx index 2a6e031..f58adc6 100644 --- a/src/routes/allenamento.$id.tsx +++ b/src/routes/allenamento.$id.tsx @@ -93,7 +93,12 @@ function AllenamentoDetail() {

- +
); diff --git a/src/routes/calendario.tsx b/src/routes/calendario.tsx index 80b730b..31f046e 100644 --- a/src/routes/calendario.tsx +++ b/src/routes/calendario.tsx @@ -8,7 +8,8 @@ import { molla, proietta } from "@/lib/molla"; import { useMotoRidotto } from "@/lib/motion"; import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard"; import { Card, PageHeader, Section } from "@/components/crapp/ui-bits"; -import { compleanniEventi, useEventi, type Evento } from "@/lib/eventi"; +import { compleanniEventi, type Evento } from "@/lib/eventi"; +import { useEventiVisibili } from "@/lib/eventi-visibili"; import { useAnagraficaRosa } from "@/lib/rosa"; import { usePuoGestireEventi } from "@/lib/ruoli"; import { giorniDelMese, giorniIT, mesiIT, pad2, useMeseNav } from "@/lib/calendario"; @@ -56,7 +57,7 @@ function Calendario() { const [giornoSelezionato, setGiornoSelezionato] = useState(null); const [drawerAperto, setDrawerAperto] = useState(false); const puoGestire = usePuoGestireEventi(); - const { eventi } = useEventi(); + const { eventi } = useEventiVisibili(); const rosa = useAnagraficaRosa({ conAllenatori: true }); // `useMotoRidotto` copre anche i device deboli (RAM bassa), non solo // `prefers-reduced-motion`: disattiva anche lo swipe orizzontale tra mesi. diff --git a/src/routes/eventi.tsx b/src/routes/eventi.tsx index 0bfd065..bc65a71 100644 --- a/src/routes/eventi.tsx +++ b/src/routes/eventi.tsx @@ -1,3 +1,4 @@ +import { useEventiVisibili } from "@/lib/eventi-visibili"; import { useEffect, useState } from "react"; import { createFileRoute } from "@tanstack/react-router"; import { CalendarPlus, ChevronLeft, ChevronRight, Loader2, Pencil, Trash2, X } from "lucide-react"; @@ -22,7 +23,6 @@ import { daCategoria, eventoVuoto, useEliminaEvento, - useEventi, useSalvaEvento, type CategoriaEvento, type Evento, @@ -65,7 +65,7 @@ function GestioneEventi() { const io = useGiocatoreBase(); // Admin o allenatore (DD-034): la RLS di `eventi_app` (M21) dice lo stesso. const puoGestire = usePuoGestireEventi(); - const { eventi, isPending, isError, error, refetch } = useEventi(); + const { eventi, isPending, isError, error, refetch } = useEventiVisibili(); const { righe: squadra } = useGiocatoriSquadra(); const salva = useSalvaEvento(); const elimina = useEliminaEvento(); diff --git a/src/routes/evento.$id.tsx b/src/routes/evento.$id.tsx index a3bf16a..9a5b4d3 100644 --- a/src/routes/evento.$id.tsx +++ b/src/routes/evento.$id.tsx @@ -88,7 +88,12 @@ function EventoDetail() {
- +
); diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 83cde7c..0413975 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -11,7 +11,8 @@ import { Barra } from "@/components/motion/Barra"; import { Numero } from "@/components/motion/Numero"; import { ObiettivoDrawer } from "@/components/crapp/ObiettivoDrawer"; import { microcopyObiettivo, progressoObiettivo } from "@/lib/obiettivi"; -import { useEventi, type Evento } from "@/lib/eventi"; +import { type Evento } from "@/lib/eventi"; +import { useEventiVisibili } from "@/lib/eventi-visibili"; import { useRispostePresenze } from "@/lib/presenze"; import { useIo, useObiettivi } from "@/lib/rosa"; import { isAllenatore, nomeCompleto } from "@/lib/giocatori-squadra"; @@ -47,7 +48,7 @@ function Index() { const base = useGiocatoreBase(); const giocatore = useIo(); const allenatore = isAllenatore(base); - const { eventi } = useEventi(); + const { eventi } = useEventiVisibili(); const { presenze } = useRispostePresenze(); const oggi = new Date().toISOString().slice(0, 10); const prossimi: Evento[] = eventi.filter((e) => e.data >= oggi).slice(0, 3); diff --git a/src/routes/partita.$id.tsx b/src/routes/partita.$id.tsx index b79972d..8f6c5e2 100644 --- a/src/routes/partita.$id.tsx +++ b/src/routes/partita.$id.tsx @@ -297,7 +297,12 @@ function PartitaDetail() { ) : null}
- +
); diff --git a/test/unit/eventi.test.ts b/test/unit/eventi.test.ts index 46b12b2..162247e 100644 --- a/test/unit/eventi.test.ts +++ b/test/unit/eventi.test.ts @@ -7,6 +7,7 @@ import { convocatiEvento, daCategoria, daRiga, + eventoVisibile, eventoVuoto, type RigaEvento, } from "@/lib/eventi"; @@ -123,3 +124,10 @@ assert.match(bozza.id, /^e[a-z0-9]+$/, "id generato dal client"); assert.notEqual(bozza.id, "", "ogni bozza ha un id"); console.log("eventi: ok"); + +// --- eventoVisibile: i non convocati non vedono l'evento, chi gestisce sì ----- +assert.equal(eventoVisibile({ convocati: ["g1"] }, "g1", false), true, "il convocato lo vede"); +assert.equal(eventoVisibile({ convocati: ["g1"] }, "g2", false), false, "il non convocato no"); +assert.equal(eventoVisibile({ convocati: ["g1"] }, null, false), false, "senza slot no"); +assert.equal(eventoVisibile({ convocati: ["g1"] }, "g2", true), true, "admin/allenatore sempre"); +assert.equal(eventoVisibile({ convocati: [] }, "g2", false), true, "vuoto = tutta la rosa"); From 8def97731ce594de5ae6ed8d064d0cb87b80c5d5 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 19 Oct 2026 08:14:17 +0200 Subject: [PATCH 2/4] Porta la versione a 1.4.1: eventi visibili solo ai convocati Sezione 1.4.1 del CHANGELOG, versione in package.json e package-lock.json, PROJECT_STATE aggiornato. Nessuna migration. Co-Authored-By: Claude Sonnet 5.5 --- PROJECT_STATE.md | 3 ++- docs/CHANGELOG.md | 5 +++++ package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 8cfe70f..d20b2f4 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -9,7 +9,8 @@ schema in [docs/DATABASE.md](docs/DATABASE.md), le procedure in ## Stato generale -- **Versione:** 1.4.0 (in produzione su `main` la 1.3.0, tag `v1.3.0`) — avviso «Presenza modificata» ad admin e +- **Versione:** 1.4.1 (in produzione su `main` la 1.3.0, tag `v1.3.0`) — gli eventi con convocati si vedono + solo ai convocati (admin e allenatori sempre) e «Rosa e presenze» elenca solo loro. La 1.4.0: avviso «Presenza modificata» ad admin e allenatori (DD-050, `m32`-`m33`). La 1.3.1: il Calendario mostra solo i prossimi compleanni del mese; tolta la serie «Conferme 24h» con i badge «Risposta lampo» e «Mai un forfait». La 1.3.0 portava la notifica di compleanno, gli obiettivi cliccabili, le presenze sui convocati, gli avvisi in Home diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 08d4d66..6a05990 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,11 @@ versioni `0.y.z` erano pre-release. ## [Non rilasciato] +## [1.4.1] - 2026-10-19 + +Gli eventi con dei convocati si vedono solo a loro, e «Rosa e presenze» elenca solo i convocati. +Nessuna migration. + ### Modificato - Un evento con dei convocati compare nel Calendario, in Home e nell'elenco eventi solo ai diff --git a/package-lock.json b/package-lock.json index e98bbb3..d41345f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "crapp", - "version": "1.4.0", + "version": "1.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "crapp", - "version": "1.4.0", + "version": "1.4.1", "dependencies": { "@supabase/supabase-js": "^2.111.0", "@tailwindcss/vite": "^4.2.1", diff --git a/package.json b/package.json index 9053f63..5c18122 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "crapp", - "version": "1.4.0", + "version": "1.4.1", "private": true, "sideEffects": false, "type": "module", From 5ee148b88b65850cb235e7cf91c79be82b1a1961 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 19 Oct 2026 08:29:36 +0200 Subject: [PATCH 3/4] Card evento filtrata sui convocati + copertura test per numero di convocati MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - La card (Calendario e Home) conta «presenti/convocati» e mostra i pulsanti di presenza solo ai convocati; «Da confermare» in Home esclude gli eventi a cui non si è convocati. - Nuove eConvocato() e rosaConvocata(): il filtro, prima ripetuto in tre punti, è una funzione pura usata da card, «Rosa e presenze» e convocatiEvento. - Test per 0, 1, 2, 3, tutti e id sconosciuti su visibilità, convocazione e solleciti. Co-Authored-By: Claude Sonnet 5.5 --- docs/CHANGELOG.md | 7 +++++ src/components/crapp/EventoCard.tsx | 7 +++-- src/components/crapp/RosaPresenze.tsx | 5 ++-- src/lib/eventi.ts | 19 ++++++++++--- src/routes/index.tsx | 4 +-- test/unit/eventi.test.ts | 41 +++++++++++++++++++++++++++ test/unit/presenze.test.ts | 32 +++++++++++++++++++++ 7 files changed, 103 insertions(+), 12 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6a05990..4b5e28f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,13 @@ versioni `0.y.z` erano pre-release. ## [Non rilasciato] +### Corretto + +- La card dell'evento (Calendario e Home) conta «presenti/convocati» invece di dividere per tutta la + rosa, e i pulsanti di presenza compaiono solo ai convocati: un admin che è anche giocatore ma non + è convocato vede l'evento senza poter rispondere. In «Da confermare» (Home) non finiscono più gli + eventi a cui non sei convocato. + ## [1.4.1] - 2026-10-19 Gli eventi con dei convocati si vedono solo a loro, e «Rosa e presenze» elenca solo i convocati. diff --git a/src/components/crapp/EventoCard.tsx b/src/components/crapp/EventoCard.tsx index 36be015..478e37b 100644 --- a/src/components/crapp/EventoCard.tsx +++ b/src/components/crapp/EventoCard.tsx @@ -13,7 +13,7 @@ import { Link } from "@tanstack/react-router"; import { cn } from "@/lib/utils"; import { Card } from "@/components/crapp/ui-bits"; import { formatData, statoMeta, type Stato } from "@/lib/crapp-data"; -import type { Evento } from "@/lib/eventi"; +import { eConvocato, rosaConvocata, type Evento } from "@/lib/eventi"; import { inRosa, useGiocatoriSquadra } from "@/lib/giocatori-squadra"; import { includeInfortunato, usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze"; import { useGiocatoreInCampo } from "@/lib/user-store"; @@ -75,7 +75,8 @@ export function EventoCard({ // Rilevante perché ogni card monta questo hook: il Calendario ne rende diverse insieme. const io = useGiocatoreInCampo(); const { righe: squadra } = useGiocatoriSquadra(); - const rosa = squadra.filter(inRosa); + // Solo i convocati contano e rispondono (vuoto = tutta la rosa). + const rosa = rosaConvocata(squadra.filter(inRosa), evento.convocati); const stato = io ? risposte[io.id] : undefined; const presentiVeri = rosa.filter( (g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo", @@ -187,7 +188,7 @@ export function EventoCard({

) : null} - {io ? ( + {io && eConvocato(evento, io.id) ? (
convocati.includes(g.id)); + const rosa = rosaConvocata(anagrafica, convocati); const [sollecito, setSollecito] = useState(false); const passato = data < dataOggi(); diff --git a/src/lib/eventi.ts b/src/lib/eventi.ts index 2cf690d..a0f1477 100644 --- a/src/lib/eventi.ts +++ b/src/lib/eventi.ts @@ -90,6 +90,11 @@ export function useEventi() { return { ...query, eventi: query.data ?? [] }; } +/** Il giocatore è tra i convocati (elenco vuoto = tutta la rosa). */ +export function eConvocato(evento: Pick, giocatoreId: string): boolean { + return evento.convocati.length === 0 || evento.convocati.includes(giocatoreId); +} + /** * Chi vede un evento: i convocati; se `convocati` è vuoto, tutta la rosa. Chi gestisce gli * eventi (admin e allenatori) li vede sempre, convocato o no. @@ -99,8 +104,9 @@ export function eventoVisibile( giocatoreId: string | null, vedeTutto: boolean, ): boolean { - if (vedeTutto || evento.convocati.length === 0) return true; - return giocatoreId !== null && evento.convocati.includes(giocatoreId); + if (vedeTutto) return true; + if (evento.convocati.length === 0) return true; + return giocatoreId !== null && eConvocato(evento, giocatoreId); } export function useEvento(id: string) { @@ -211,6 +217,11 @@ export function compleanniEventi( /** Rosa convocata per un evento: se non specificata vale tutta la rosa. */ export function convocatiEvento(evento: Evento | null, rosa: Giocatore[]) { - if (!evento || evento.convocati.length === 0) return rosa; - return rosa.filter((g) => evento.convocati.includes(g.id)); + return rosaConvocata(rosa, evento?.convocati ?? []); +} + +/** Restringe una rosa ai convocati indicati: elenco vuoto = tutta la rosa. */ +export function rosaConvocata(rosa: T[], convocati: string[]): T[] { + if (convocati.length === 0) return rosa; + return rosa.filter((g) => convocati.includes(g.id)); } diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 0413975..1838ff0 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -11,7 +11,7 @@ import { Barra } from "@/components/motion/Barra"; import { Numero } from "@/components/motion/Numero"; import { ObiettivoDrawer } from "@/components/crapp/ObiettivoDrawer"; import { microcopyObiettivo, progressoObiettivo } from "@/lib/obiettivi"; -import { type Evento } from "@/lib/eventi"; +import { eConvocato, type Evento } from "@/lib/eventi"; import { useEventiVisibili } from "@/lib/eventi-visibili"; import { useRispostePresenze } from "@/lib/presenze"; import { useIo, useObiettivi } from "@/lib/rosa"; @@ -55,7 +55,7 @@ function Index() { const prossimo = prossimi[0] ?? null; const daConfermare = prossimi .slice(1) - .filter((e) => !giocatore || !presenze[e.id]?.[giocatore.id]); + .filter((e) => !giocatore || (eConvocato(e, giocatore.id) && !presenze[e.id]?.[giocatore.id])); const linkProssimo = prossimo ? linkPerEvento(prossimo) : null; const { data: csi } = useCsi(); const noi = csi?.classifica.find((r) => isNostraSquadra(r.squadra)); diff --git a/test/unit/eventi.test.ts b/test/unit/eventi.test.ts index 162247e..20bad8d 100644 --- a/test/unit/eventi.test.ts +++ b/test/unit/eventi.test.ts @@ -7,7 +7,9 @@ import { convocatiEvento, daCategoria, daRiga, + eConvocato, eventoVisibile, + rosaConvocata, eventoVuoto, type RigaEvento, } from "@/lib/eventi"; @@ -131,3 +133,42 @@ assert.equal(eventoVisibile({ convocati: ["g1"] }, "g2", false), false, "il non assert.equal(eventoVisibile({ convocati: ["g1"] }, null, false), false, "senza slot no"); assert.equal(eventoVisibile({ convocati: ["g1"] }, "g2", true), true, "admin/allenatore sempre"); assert.equal(eventoVisibile({ convocati: [] }, "g2", false), true, "vuoto = tutta la rosa"); + +// --- eConvocato: elenco vuoto = tutti ---------------------------------------- +assert.equal(eConvocato({ convocati: ["g1"] }, "g1"), true, "convocato"); +assert.equal(eConvocato({ convocati: ["g1"] }, "g2"), false, "non convocato"); +assert.equal(eConvocato({ convocati: [] }, "g2"), true, "vuoto = tutti"); + +// --- Copertura per numero di convocati: 1, 2, 3, tutti, nessuno, id sconosciuti - +const squadra = ["g1", "g2", "g3", "g4"].map((id) => ({ id })); +const ids = (l: Array<{ id: string }>) => l.map((g) => g.id); +const casi: Array<{ nome: string; convocati: string[]; attesi: string[] }> = [ + { nome: "nessuno indicato = tutta la rosa", convocati: [], attesi: ["g1", "g2", "g3", "g4"] }, + { nome: "solo 1", convocati: ["g2"], attesi: ["g2"] }, + { nome: "2", convocati: ["g1", "g4"], attesi: ["g1", "g4"] }, + { nome: "3", convocati: ["g3", "g1", "g2"], attesi: ["g1", "g2", "g3"] }, + { nome: "tutti indicati", convocati: ["g1", "g2", "g3", "g4"], attesi: ["g1", "g2", "g3", "g4"] }, + { nome: "id sconosciuto ignorato", convocati: ["g2", "zz"], attesi: ["g2"] }, + { nome: "solo ids sconosciuti: nessuno", convocati: ["zz"], attesi: [] }, +]; +for (const { nome, convocati, attesi } of casi) { + assert.deepEqual(ids(rosaConvocata(squadra, convocati)), attesi, `rosaConvocata: ${nome}`); + const conv = { convocati }; + for (const { id } of squadra) { + const dentro = attesi.includes(id) || convocati.length === 0; + assert.equal( + eConvocato(conv, id), + convocati.length === 0 || convocati.includes(id), + `eConvocato ${id}: ${nome}`, + ); + assert.equal(eventoVisibile(conv, id, false), dentro, `visibile a ${id}: ${nome}`); + assert.equal( + eventoVisibile(conv, id, true), + true, + `admin/allenatore vede sempre (${id}): ${nome}`, + ); + } + assert.equal(eventoVisibile(conv, null, false), convocati.length === 0, `senza slot: ${nome}`); + assert.equal(eventoVisibile(conv, null, true), true, `admin senza slot: ${nome}`); +} +assert.equal(rosaConvocata([], ["g1"]).length, 0, "rosa vuota resta vuota"); diff --git a/test/unit/presenze.test.ts b/test/unit/presenze.test.ts index 0ba147b..a6a1172 100644 --- a/test/unit/presenze.test.ts +++ b/test/unit/presenze.test.ts @@ -294,3 +294,35 @@ assert.equal( ); console.log("presenze: ok"); + +// --- Solleciti per numero di convocati: 1, 2, 3, tutti ------------------------ +{ + const sq = (["g1", "g2", "g3", "g4"] as const).map((id) => ({ + id, + attivo: true, + tipo: "giocatore" as const, + })); + const nessuna: Array<{ giocatore_id: string; stato: string }> = []; + assert.deepEqual(destinatariSollecito(sq, nessuna, ["g2"]), ["g2"], "solo 1 convocato"); + assert.deepEqual(destinatariSollecito(sq, nessuna, ["g1", "g3"]), ["g1", "g3"], "2 convocati"); + assert.deepEqual( + destinatariSollecito(sq, nessuna, ["g1", "g2", "g4"]), + ["g1", "g2", "g4"], + "3 convocati", + ); + assert.deepEqual( + destinatariSollecito(sq, [{ giocatore_id: "g1", stato: "presente" }], ["g1", "g3"]), + ["g3"], + "il convocato che ha già risposto non va sollecitato", + ); + assert.deepEqual( + destinatariSollecito(sq, [{ giocatore_id: "g4", stato: "presente" }], ["g1"]), + ["g1"], + "chi ha risposto ma non è convocato non cambia i destinatari", + ); + assert.deepEqual( + destinatariSollecito(sq, nessuna, []), + ["g1", "g2", "g3", "g4"], + "nessuno = tutti", + ); +} From 7996dd050e3f60370ce3af5c2688af218af46e51 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 19 Oct 2026 08:31:10 +0200 Subject: [PATCH 4/4] =?UTF-8?q?CHANGELOG:=20la=20correzione=20della=20card?= =?UTF-8?q?=20evento=20=C3=A8=20parte=20della=201.4.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sposta la voce da «Non rilasciato» alla sezione 1.4.1. Co-Authored-By: Claude Sonnet 5.5 --- docs/CHANGELOG.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4b5e28f..24b2bab 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,13 +13,6 @@ versioni `0.y.z` erano pre-release. ## [Non rilasciato] -### Corretto - -- La card dell'evento (Calendario e Home) conta «presenti/convocati» invece di dividere per tutta la - rosa, e i pulsanti di presenza compaiono solo ai convocati: un admin che è anche giocatore ma non - è convocato vede l'evento senza poter rispondere. In «Da confermare» (Home) non finiscono più gli - eventi a cui non sei convocato. - ## [1.4.1] - 2026-10-19 Gli eventi con dei convocati si vedono solo a loro, e «Rosa e presenze» elenca solo i convocati. @@ -37,6 +30,10 @@ Nessuna migration. sempre tutta la rosa, anche se ne era stato convocato uno solo). Il conteggio «Hanno risposto», il pulsante «Sollecita» e il riquadro «La tua risposta» seguono la stessa regola; `convocati` vuoto resta «tutta la rosa». +- La card dell'evento (Calendario e Home) conta «presenti/convocati» invece di dividere per tutta la + rosa, e i pulsanti di presenza compaiono solo ai convocati: un admin che è anche giocatore ma non + è convocato vede l'evento senza poter rispondere. In «Da confermare» (Home) non finiscono più gli + eventi a cui non sei convocato. ## [1.4.0] - 2026-10-09