Obiettivi cliccabili con card di dettaglio; presenze del mese sui convocati

Ogni obiettivo di squadra apre un drawer che spiega come si calcola, cosa conta,
cosa è una presenza persa, cosa resta fuori, periodo, fonte dei dati ed esempio.
«90% di presenze del mese» usa ora i convocati dell'evento come posti (tutta la
rosa se non ce ne sono), come le presenze collettive: chi non è convocato non
abbassa più la percentuale. Test unit, card e integrazione; docs aggiornate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-06 11:34:58 +02:00
1 parent 6b8cc33512
commit c2c3b249f2
10 files changed
+779 -73

No files matched your search

+5
View File
@@ -15,6 +15,9 @@ versioni `0.y.z` erano pre-release.
### Aggiunto
- **Obiettivi cliccabili** — toccando un obiettivo (in Squadra o nella card della Home) si apre una card che
spiega tutto: come si calcola, cosa conta e cosa no, periodo di validità, fonte dei dati, un esempio. Nessuna
migration.
- **Notifica di compleanno** — ogni giorno, dalle 8:00, il festeggiato riceve un messaggio di auguri e tutti gli altri membri
attivi, allenatori compresi, il solo avviso «Oggi è il compleanno di …»; con più compleanni lo stesso giorno un solo
messaggio a testa. Push, centro notifiche ed email. Il 29 febbraio non è gestito (DD-047, issue #11). Migration
@@ -22,6 +25,8 @@ versioni `0.y.z` erano pre-release.
### Modificato
- **Obiettivo «90% di presenze del mese»** — i posti sono i convocati di ogni partita e allenamento (tutta la rosa se
non ce ne sono), come nelle presenze collettive: chi non è convocato non abbassa più la percentuale. Nessuna migration.
- **Obiettivo «1 evento di squadra al mese»** — si completa quando arriva l'ora dell'evento, non appena viene
creato. Un evento futuro non conta finché la sua data e ora non sono passate. Nessuna migration.
- **Obiettivo «Presenze collettive»** — sostituisce «250 presenze complessive»: è la percentuale di presenze di tutta la
+25 -11
View File
@@ -39,7 +39,7 @@ corrente dinamico (vedi sotto).
| id | Obiettivo | Calcolo | Target | Fonte |
| ----- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ---------------------------------- |
| `o1` | 90% presenze del mese | risposte presente/ritardo su partite+allenamenti del mese corrente (dinamico) | 90% | `risposte_presenze` |
| `o1` | 90% presenze del mese | presente/ritardo su partite+allenamenti del mese corrente; posti = convocati dell'evento, tutta la rosa se vuoto (dinamico) | 90% | `risposte_presenze` |
| `o2` | Tutti rispondono alle convocazioni | risposte totali / eventi possibili (esclusi i compleanni) | 90% | `risposte_presenze` |
| `o7` | Presenze collettive | % di presenze (presente/ritardo) su tutti gli allenamenti e le partite definiti, passati e futuri; posti = convocati dell'evento (tutta la rosa se vuoto) | 90 | `eventi_app` + `risposte_presenze` |
| `o12` | Media pagelle da 7.5 | media di tutti i voti, arrotondata a una cifra decimale | 7.5 | `pagelle_voti` |
@@ -51,7 +51,12 @@ corrente dinamico (vedi sotto).
| `o6` | 1 evento di squadra al mese | eventi di tipo "evento" nel mese corrente la cui data e ora sono già passate (dinamico) | 1 | `eventi_app` |
Mostrati in `squadra.tsx` (elenco completo con barra di progresso) e in `index.tsx` (home: il
primo obiettivo non completato). Un obiettivo che supera il 90% genera anche una notifica
primo obiettivo non completato).
Ogni obiettivo è **cliccabile**: apre la card di dettaglio (`ObiettivoDrawer`, stesso drawer dei
badge) con stato attuale, come si calcola, cosa conta e cosa no, quando vale, da dove arrivano i
dati e un esempio con numeri. Per le due percentuali di presenze (`o1`, `o7`) la card distingue «Presenza persa» (assente, forse, infortunato, nessuna risposta: il posto resta vuoto) da «Non entra nel calcolo» (cene, eventi di squadra, compleanni). I testi stanno nel campo `dettaglio` di ogni obiettivo in
`obiettivi.ts`: chi cambia il calcolo o il target di un obiettivo aggiorna anche il suo
`dettaglio`, altrimenti la card mente. Un obiettivo che supera il 90% genera anche una notifica
smart (`notifiche-smart.ts`). I target fissi (200 pagelle, 7.5 di media, 1/5/10
vittorie) sono scelte editoriali da rivedere a mano a ogni stagione — nessuna configurazione o
UI per farlo, si cambia il numero in `obiettivi.ts`. Fa eccezione "Continuità di squadra"
@@ -75,8 +80,9 @@ passaggio a completato compare al refresh successivo.
`o7` ("Presenze collettive") invece **non** si azzera: copre tutta la stagione, cioè tutti gli
allenamenti e le partite in calendario, passati e futuri (le risposte sugli eventi futuri sono
conferme). Differisce da `o1` per periodo (stagione, non mese) e perché i posti sono i convocati
di ciascun evento, non sempre l'intera rosa. Non c'è un confine di stagione esplicito: conta ciò
conferme). Differisce da `o1` solo per il periodo (stagione, non mese): anche in `o1` i posti sono i
convocati di ciascun evento, così chi non è convocato non pesa; con la stessa funzione
`percentualePresenze()`. Non c'è un confine di stagione esplicito: conta ciò
che c'è in `eventi_app`.
`o2` ("Tutti rispondono alle convocazioni") non si azzera — aggrega su tutti gli eventi in
@@ -156,14 +162,22 @@ Dettagli completi (endpoint, identificativi di stagione, altri limiti del colleg
Tutti e 10 gli obiettivi hanno unit test **e** integration test end-to-end (dati scritti/letti
da un backend reale, non solo funzione pura con contesto costruito a mano).
| Obiettivi | Unit test | Integration test |
| ---------- | ----------------------------- | -------------------------------------------------------- |
| o1, o2, o6 | `test/unit/obiettivi.test.ts` | `test/integration/obiettivi.test.ts` (Supabase locale) |
| o7 | `test/unit/obiettivi.test.ts` | `test/integration/obiettivi.test.ts` (Supabase locale) |
| o11 | `test/unit/obiettivi.test.ts` | `test/integration/obiettivi.test.ts` (Supabase locale) |
| o12, o13 | `test/unit/obiettivi.test.ts` | `test/integration/obiettivi.test.ts` (Supabase locale) |
| o3, o4, o5 | `test/unit/obiettivi.test.ts` | `test/integration/api.test.ts` (CSI reale in produzione) |
| Obiettivi | Unit test | Integration test |
| ---------- | ----------------------------- | -------------------------------------------------------------------------------- |
| o1, o2, o6 | `test/unit/obiettivi.test.ts` | `test/integration/obiettivi.test.ts` (Supabase locale, o1 anche con i convocati) |
| o7 | `test/unit/obiettivi.test.ts` | `test/integration/obiettivi.test.ts` (Supabase locale) |
| o11 | `test/unit/obiettivi.test.ts` | `test/integration/obiettivi.test.ts` (Supabase locale) |
| o12, o13 | `test/unit/obiettivi.test.ts` | `test/integration/obiettivi.test.ts` (Supabase locale) |
| o3, o4, o5 | `test/unit/obiettivi.test.ts` | `test/integration/api.test.ts` (CSI reale in produzione) |
- **Presenze (o1, o7), casi limite** (`test/unit/obiettivi.test.ts`): solo partite e allenamenti
contano, assente/forse/infortunato/nessuna risposta sono posti vuoti, il non convocato non pesa,
confine di mese nel fuso Europe/Rome, convocato fuori rosa, rosa vuota, arrotondamento e soglia
del 90%. Verificato anche con mutazioni (posti = tutta la rosa; eventi di squadra contati): il test
diventa rosso.
- **Card di dettaglio** (`test/unit/obiettivi-dettaglio.test.ts`): ogni obiettivo ha tutti i testi,
«Presenza persa» solo su o1/o7, la card renderizzata mostra tutte le sezioni e lo stato
in corso/completato.
- **o1/o2/o6** (Supabase locale): scrive eventi e risposte veri su `eventi_app`/
`risposte_presenze`, li rilegge con `leggiEventi()` (la stessa funzione server dell'app) e una
query REST equivalente a `fetchPresenze()`. Copre: contesto vuoto, aggregazione su più eventi,