diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 1a3ce87..1e95fbb 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -178,9 +178,10 @@ collega uno slot lo occupa anche in produzione, e va liberato da un admin. ## Prossimo sviluppo -Niente di assegnato: tutto quello che era in lavorazione è chiuso, tesseramento CSI incluso -(numero e data di tessera registrabili da `/admin`, migration `m8_tesseramento_csi`). Le voci -ancora aperte stanno in [docs/ROADMAP.md](docs/ROADMAP.md), sotto «Prossimo». +**Avviso certificati in scadenza** (24/09/2026, branch `develop`): specifica scritta in +[docs/modules/profilo-giocatore.md](docs/modules/profilo-giocatore.md#avviso-certificati), +decisione in DD-035, codice non ancora iniziato. Solo client: nessuna migration e nessuna +push. Le altre voci aperte stanno in [docs/ROADMAP.md](docs/ROADMAP.md), sotto «Prossimo». --- diff --git a/docs/DESIGN_DECISIONS.md b/docs/DESIGN_DECISIONS.md index 2b1778b..fb829fd 100644 --- a/docs/DESIGN_DECISIONS.md +++ b/docs/DESIGN_DECISIONS.md @@ -50,6 +50,7 @@ Serve a rispondere a domande del tipo: | [DD-032](#dd-032--la-stagione-csi-si-ricava-dalle-date-delle-gare) | Stagione CSI dalle date delle gare | | [DD-033](#dd-033--gestione-eventi-solo-calendario-giorno-fissato-dal-tocco) | Gestione eventi solo da calendario | | [DD-034](#dd-034--lallenatore-è-uno-slot-della-squadra-con-tipo-diverso-non-un-giocatore) | Ruolo allenatore | +| [DD-035](#dd-035--avviso-certificati-calcolato-in-home-visibile-solo-al-titolare-e-agli-admin) | Avviso certificati in Home | **In valutazione** @@ -1451,3 +1452,61 @@ collega uno slot di tipo allenatore: è quindi l'admin a concederlo, registrando **Riesame** Se arriva un secondo tipo di staff (dirigente, segreteria) o se i filtri sulla rosa diventano fonte di bug, passare alla tabella `staff` separata. + +### DD-035 — Avviso certificati: calcolato in Home, visibile solo al titolare e agli admin + +**Data:** 24 settembre 2026 +**Stato:** Accettata (da implementare) + +**Contesto** +Il certificato medico ha una data di scadenza (`profili_giocatore.certificato_scadenza`), ma +oggi ci si accorge che è scaduto solo aprendo la tab Profili di `/admin`: spesso quando il +giocatore è già fuori regola. La richiesta è un avviso in Home, giallo nei 7 giorni prima +della scadenza e nero dopo, con i nomi dei giocatori, che sparisca da solo quando il +certificato viene rinnovato. Specifica completa in +[modules/profilo-giocatore.md](modules/profilo-giocatore.md#avviso-certificati). + +**Decisione** + +1. L'avviso si **calcola al volo** dalla data già salvata, con una funzione pura in + `profili-core.ts`: nessuna tabella, nessuno stato «avvisato», nessuna migration. +2. Lo vedono **solo gli admin**, con i nomi di tutta la rosa, e **il giocatore interessato**, + riferito solo a sé. L'allenatore e i compagni non lo vedono. +3. L'avviso dello staff **non è cliccabile**; quello personale apre + `/profilo?tab=documenti`. +4. Soglia fissa di **7 giorni** in una costante; il giorno della scadenza il certificato vale + ancora (come `statoScadenza()`). +5. **Niente push**: l'avviso vive solo in Home. + +**Alternative scartate** + +- Mostrarlo anche all'allenatore → il certificato è un dato sanitario che oggi l'allenatore + non vede (DD-034); servirebbe una funzione `SECURITY DEFINER` apposta per un'informazione + che all'allenatore non serve ad agire. +- Salvare gli avvisi in una tabella o nel centro notifiche (DD-030) → uno stato da tenere + allineato alla data, che si disallinea appena il giocatore corregge la scadenza; calcolarlo + è gratis e sempre giusto. +- Avviso dello staff cliccabile verso `/admin?tab=profili` → l'admin non può caricare il file + al posto del giocatore (DD-017), quindi il tocco non gli farebbe fare nulla in più, e + richiederebbe di aggiungere a `/admin` la lettura della tab dall'URL. +- Mostrare al massimo alcuni nomi e un «e altri N» → a inizio stagione ne scadono molti + insieme ed è proprio lì che l'elenco completo serve. +- Push quotidiana a chi ha il certificato in scadenza → servirebbe un cron, che oggi non + esiste per nessuna push (DD-025), e le push vanno riservate agli eventi importanti + ([EFFICIENZA_CLOUD.md](EFFICIENZA_CLOUD.md)). +- Includere i certificati mancanti → è un problema diverso (profilo incompleto), già coperto + dal widget «Completa il tuo profilo» e dalla tab Profili. + +**Conseguenze** + +- Nessuna query nuova: si riusa `useProfili()`, già in cache per Profilo e `/admin`, e le RLS + esistenti decidono quanti profili arrivano (tutti all'admin, il proprio al giocatore). +- Il rinnovo non richiede nessuna azione di chiusura: cambiata la data, l'avviso si aggiorna + da solo. +- Chi non apre l'app non viene avvisato: l'avviso raggiunge solo chi entra in Home. +- Un admin che è anche giocatore vede solo l'avviso dello staff, dove compare il suo nome. + +**Riesame** +Se gli avvisi in Home non bastano (certificati che scadono comunque senza essere rinnovati), +valutare una push, legata a un cron o a un invio manuale dell'admin come per i palloni. Se 7 +giorni si rivelano pochi per prenotare una visita, alzare la costante o renderla regolabile. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index a2fccee..38be512 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -41,6 +41,10 @@ Tutto quello che è rilasciato, fino alla versione 1.0.0 (vedi `CHANGELOG.md`). ## Prossimo +- [ ] Avviso certificati in scadenza — in Home, giallo nei 7 giorni prima e nero dopo la + scadenza: agli admin con i nomi dei giocatori, al giocatore solo per il proprio + ([specifica](modules/profilo-giocatore.md#avviso-certificati)) + - [ ] Calendario ufficiale — i dati delle gare future arrivano già dal feed CSI, la pagina Campionato usa solo quelle giocate - [ ] Database esercizi diff --git a/docs/modules/allenatore.md b/docs/modules/allenatore.md index c876881..608c154 100644 --- a/docs/modules/allenatore.md +++ b/docs/modules/allenatore.md @@ -69,7 +69,8 @@ quando quei campi sono compilati. La Home dell'allenatore tiene ciò che riguarda la squadra e toglie ciò che riguarda il giocatore: restano classifica e bilancio, prossimo impegno e ultima partita; spariscono lo streak personale, il promemoria palloni, «Da confermare», l'obiettivo di squadra e il -«Colpo d'occhio» con le statistiche personali. +«Colpo d'occhio» con le statistiche personali. Non compare nemmeno l'avviso certificati: lo +vedono solo gli admin e il giocatore interessato (DD-035). ## Squadra @@ -148,19 +149,20 @@ giocatore). ## Permessi (riepilogo) -| Azione | Giocatore | Allenatore | Admin | -| ----------------------------------------------- | --------- | ------------------ | ------------------ | -| Modificare nome e cognome | no | sì, i propri | tutti | -| Dati personali e foto profilo | i propri | i propri (ridotti) | tutti (non i file) | -| Documento, certificato, foto tessera | i propri | — | lettura/download | -| Creare/modificare/eliminare eventi, convocati | no | sì | sì | -| Sollecitare le presenze | no | sì | sì | -| Promemoria palloni, notifica personalizzata | no | no | sì | -| Rispondere alle presenze, votare, essere votato | sì | no | sì (se giocatore) | -| Tab Stagione e Badge del profilo | sì | no | sì | -| Tab Obiettivi e Badge di Squadra | sì | no | sì | -| Badge e classifiche/dati sulle cacche | sì | no | sì | -| Dashboard `/admin` | no | no | sì | +| Azione | Giocatore | Allenatore | Admin | +| ----------------------------------------------- | ---------- | ------------------ | ------------------ | +| Modificare nome e cognome | no | sì, i propri | tutti | +| Dati personali e foto profilo | i propri | i propri (ridotti) | tutti (non i file) | +| Documento, certificato, foto tessera | i propri | — | lettura/download | +| Avviso certificati in Home | il proprio | no | tutta la rosa | +| Creare/modificare/eliminare eventi, convocati | no | sì | sì | +| Sollecitare le presenze | no | sì | sì | +| Promemoria palloni, notifica personalizzata | no | no | sì | +| Rispondere alle presenze, votare, essere votato | sì | no | sì (se giocatore) | +| Tab Stagione e Badge del profilo | sì | no | sì | +| Tab Obiettivi e Badge di Squadra | sì | no | sì | +| Badge e classifiche/dati sulle cacche | sì | no | sì | +| Dashboard `/admin` | no | no | sì | ## Implementazione diff --git a/docs/modules/notifiche.md b/docs/modules/notifiche.md index d404825..9778260 100644 --- a/docs/modules/notifiche.md +++ b/docs/modules/notifiche.md @@ -18,6 +18,11 @@ indipendenti: - **Notifiche smart** — notifiche locali mostrate solo ad app aperta, generate da badge, serie e obiettivi appena raggiunti; non è un canale push separato. +Gli avvisi fissi in Home (turno palloni, certificati in scadenza) non passano da nessuno dei +due: sono card calcolate dai dati, che spariscono da sole quando la condizione finisce. In +particolare il certificato in scadenza **non** genera push né voci nel centro notifiche +(DD-035, specifica in [profilo-giocatore.md](profilo-giocatore.md#avviso-certificati)). + --- ## Dati diff --git a/docs/modules/profilo-giocatore.md b/docs/modules/profilo-giocatore.md index 0b100cc..f2ff735 100644 --- a/docs/modules/profilo-giocatore.md +++ b/docs/modules/profilo-giocatore.md @@ -64,7 +64,7 @@ Se il profilo non è completo compare automaticamente un widget di completamento ## Home -Il giocatore visualizza un widget dedicato. +Il giocatore visualizza un widget dedicato e, quando serve, l'avviso sul certificato medico. ### Completa il tuo profilo @@ -80,6 +80,128 @@ Quando tutte le sezioni sono complete il widget scompare automaticamente. Il tap apre Profilo sulla sottosezione **Documenti** (`/profilo?tab=documenti`), non sulla tab Stagione. +### Avviso certificati + +**Stato:** da implementare (DD-035). + +Un avviso in Home segnala i certificati medici in scadenza o scaduti, così nessuno se ne +accorge quando il giocatore è già fuori regola. Si calcola al volo dalla data di scadenza +già salvata: niente tabella, niente migration, niente push. + +#### Chi lo vede + +| Utente | Cosa vede | Al tocco | +| --------------------------- | --------------------------------------------------------------- | ----------------------------- | +| Admin | l'avviso **dello staff**: i nomi di tutti i giocatori coinvolti | niente, non è cliccabile | +| Giocatore interessato | l'avviso **personale**: solo il proprio certificato | apre `/profilo?tab=documenti` | +| Admin che è anche giocatore | solo l'avviso dello staff, dove compare già il suo nome | niente | +| Altri giocatori | niente | — | +| Allenatore | niente: non vede i certificati altrui e non ne ha uno (DD-034) | — | + +L'avviso dello staff non è cliccabile, come quello dei palloni: l'admin non può caricare il +certificato al posto del giocatore (DD-017), quindi può solo sollecitarlo, e il giocatore +riceve già il proprio avviso. Quello personale porta invece dove si carica il certificato +nuovo, come il widget «Completa il tuo profilo». + +I certificati sono dati sanitari: fuori da `/admin` li legge solo il titolare (DD-016). +Nessuna policy cambia, perché `useProfili()` restituisce già tutti i profili a un admin e il +solo proprio a un giocatore. + +#### Quando compare + +`giorni` è la differenza in giorni di calendario tra la scadenza e oggi (ora locale), soglia +`GIORNI_AVVISO_CERTIFICATO = 7`. + +| Condizione | Stato | Avviso | +| -------------------- | ----------- | ------------------ | +| `giorni > 7` | valido | nessuno | +| `0 ≤ giorni ≤ 7` | in scadenza | giallo (`warning`) | +| `giorni < 0` | scaduto | nero (`primary`) | +| data o file mancanti | mancante | nessuno | + +- Il giorno della scadenza il certificato vale ancora, coerente con `statoScadenza()`: il + giocatore compare nel giallo con «scade oggi» e passa al nero dal giorno dopo. +- L'avviso nero resta finché il giocatore non aggiorna la data: non c'è un limite di tempo. +- Il certificato **mancante** non genera avviso: è un problema diverso, già visibile nella + tab Profili di `/admin` e nel widget di completamento del giocatore. +- Sono esclusi i giocatori disattivati e gli allenatori: conta solo la rosa (`inRosa()`, + cioè `attivo` e `tipo = 'giocatore'`). + +#### Testi + +| Avviso | Titolo (staff / personale) | Riga | +| ----------------- | -------------------------- | ------------------------------------------------------------------------ | +| Giallo, staff | «Certificati in scadenza» | «Mario Rossi — scade tra 5 giorni», «… — scade domani», «… — scade oggi» | +| Nero, staff | «Certificati scaduti» | «Mario Rossi — scaduto il 12/09/2026» | +| Giallo, personale | «Certificato in scadenza» | «Il tuo certificato medico scade tra 5 giorni» / «domani» / «oggi» | +| Nero, personale | «Certificato scaduto» | «Il tuo certificato medico è scaduto il 12/09/2026: caricane uno nuovo» | + +Il numero di giorni scende da solo ogni giorno (7, 6, 5… domani, oggi): l'avviso non +conserva niente, rilegge la data a ogni apertura della Home. + +#### Più giocatori + +- Più giocatori nello stesso stato stanno in un **unico avviso**, una riga ciascuno, e + compaiono **tutti**: nessun limite al numero di nomi, anche a inizio stagione quando ne + scadono molti insieme. +- Se ci sono sia certificati in scadenza sia scaduti compaiono **due avvisi**: prima quello + nero, poi quello giallo. +- Ordine delle righe: dalla scadenza più vicina nel giallo, dal certificato scaduto da più + tempo nel nero; a parità di data, alfabetico per cognome e poi per nome. L'ordine è + stabile, quindi l'elenco non si rimescola tra un'apertura e l'altra. + +#### Aspetto e posizione + +- Stessa forma del banner palloni (`PromemoriaPalloni`): card arrotondata a tutta larghezza + con icona e titolo in `font-display` maiuscolo, righe in testo piccolo sotto. +- Giallo: sfondo `bg-warning`, testo `text-warning-foreground`. Nero: sfondo `bg-primary`, + testo `text-primary-foreground`. L'app è solo chiara (DD-022), quindi il nero non rischia + di confondersi con lo sfondo. +- In Home sta subito **sotto il banner palloni** e sopra «Completa il tuo profilo»: prima + ciò che scade oggi, poi ciò che manca. + +#### Rinnovo + +Il giocatore rinnova dal proprio profilo, sottosezione Documenti: carica il file nuovo e +aggiorna la data. Il salvataggio aggiorna la cache dei profili (`setQueryData`), quindi il +suo avviso personale sparisce subito. Nell'avviso dello staff il nome sparisce alla lettura +successiva dei profili da parte dell'admin (cache di 30 minuti, `useProfili()`), senza +nessuna azione da parte sua. Quando non resta nessuno, l'avviso non compare più. + +Basta aggiornare la data: l'avviso guarda solo quella (e la presenza di un file). Che il +file caricato sia davvero quello nuovo resta un controllo dell'admin, come oggi. Anche l'admin +può correggere la data dalla scheda del giocatore (DD-017, non il file): l'avviso segue la +data, chiunque l'abbia scritta. + +#### Caricamento + +Finché ruoli e profili non sono arrivati, l'avviso non compare: così un admin che è anche +giocatore non vede per un attimo l'avviso personale prima di quello dello staff. Se la +lettura dei profili fallisce l'avviso semplicemente non compare, senza messaggio d'errore: +la Home non deve rompersi per un dato accessorio. + +#### Implementazione + +| Pezzo | Ruolo | +| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `GIORNI_AVVISO_CERTIFICATO` (`profili-core.ts`) | la soglia, 7 | +| `giorniAllaScadenza(scadenza, oggi)` | differenza in giorni di calendario tra due date `AAAA-MM-GG`, negativa se scaduto | +| `avvisiCertificati(rosa, profili, oggi)` | funzione pura: restituisce `{ scaduti, inScadenza }`, ognuno una lista ordinata di `{ giocatoreId, nome, cognome, scadenza, giorni }` | +| `testoScadenza(giorni)` | «scade oggi» / «scade domani» / «scade tra N giorni» | +| `src/components/crapp/AvvisoCertificati.tsx` | legge `useGiocatoriSquadra()`, `useProfili()`, `useIsAdmin()` e l'utente corrente; sceglie avviso dello staff o personale e disegna le card | +| `src/routes/index.tsx` | monta `` subito dopo `` | + +- "Oggi" viene da `oggiISO()` (`palloni-core.ts`), in ora locale. Non da + `new Date().toISOString()`, che è in UTC e tra mezzanotte e le 2 darebbe il giorno prima. +- Nessuna query nuova: `useProfili()` ha la stessa chiave di cache usata da Profilo e + `/admin`, quindi al massimo una lettura di `profili_giocatore` per sessione (vedi + [EFFICIENZA_CLOUD.md](../EFFICIENZA_CLOUD.md)). Per l'allenatore il componente non legge + i profili. +- Test in `test/unit/profili-core.test.ts`: soglia (8 giorni no, 7 sì, 0 sì, −1 scaduto), + certificato senza file o senza data escluso, giocatore disattivato e allenatore esclusi, + ordinamento (per data, poi alfabetico), testi al singolare e al plurale, cambio di mese e + di anno nel calcolo dei giorni. + ## Profilo Il profilo viene suddiviso in sette aree. @@ -132,6 +254,10 @@ Upload. Il giocatore può aggiornare liberamente sia la data sia il file. +Quando mancano 7 giorni o meno alla scadenza, o il certificato è scaduto, il giocatore +riceve un avviso nella propria Home e compare in quello degli admin (vedi +[Avviso certificati](#avviso-certificati)). + Lo storico non viene mantenuto nella prima versione. ### Foto tessera @@ -255,6 +381,9 @@ Quando tutte le sezioni risultano complete il profilo raggiunge il 100%. dati, modificare dati squadra e dati personali di chiunque e scollegare un account (DD-017). Non carica file al posto di altri. +**Avviso certificati** — il giocatore vede solo il proprio, l'admin i nomi di tutta la rosa, +nessun altro ne vede (vedi [Avviso certificati](#avviso-certificati)). + ## Versione 1 - Profilo giocatore @@ -269,6 +398,7 @@ Non carica file al posto di altri. ## Versioni future - Storico certificati medici +- Avviso certificati anche via push, o con una soglia regolabile dall'admin (DD-035, Riesame) - Gestione documenti aggiuntivi - Consensi privacy - Firma digitale