1 Commits
Author SHA1 Message Date
davideandClaude Sonnet 5.5 f49351e536 Avviso certificati: mostra anche chi non l'ha caricato (DD-046), versione 1.2.3
Il giocatore senza certificato vede una card rossa cliccabile, l'admin
l'elenco dei mancanti sopra scaduti e in scadenza. Nessuna migration.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 14:15:17 +02:00
11 changed files with 149 additions and 36 deletions

No files matched your search

+3 -3
View File
@@ -9,9 +9,9 @@ schema in [docs/DATABASE.md](docs/DATABASE.md), le procedure in
## Stato generale ## Stato generale
- **Versione:** 1.2.2 su `develop` (su `main`, in produzione, resta la 1.2.1 fino al merge) — notifiche - **Versione:** 1.2.3 (in produzione su `main` la 1.2.2) — notifiche su tre canali, avvisi di palloni
su tre canali, avvisi di palloni (3 ore prima) e presenze (24 ore prima) una sola volta, niente (3 ore prima) e presenze (24 ore prima) una sola volta, niente promemoria evento, niente foto
promemoria evento, niente foto tessera, avviso certificato a 30 giorni per il giocatore. tessera, avviso certificato a 30 giorni per il giocatore, avviso rosso per chi non l'ha caricato.
- **Backend:** Supabase proprietario (`kfkcldwncxqaixetsjes`). Lovable Cloud e il vecchio - **Backend:** Supabase proprietario (`kfkcldwncxqaixetsjes`). Lovable Cloud e il vecchio
Project Ref `hetycilxgkdmccelwerq` non si usano più. Project Ref `hetycilxgkdmccelwerq` non si usano più.
- **Migration:** 41 file in `supabase/migrations/`, fino a `m30_sollecito_solo_24h`, tutte - **Migration:** 41 file in `supabase/migrations/`, fino a `m30_sollecito_solo_24h`, tutte
+9
View File
@@ -13,6 +13,15 @@ versioni `0.y.z` erano pre-release.
## [Non rilasciato] ## [Non rilasciato]
## [1.2.3] - 2026-10-04
Nella Home si vede anche chi non ha caricato il certificato medico. Nessuna migration.
### Modificato
- **Avviso certificati mancanti** — nella Home chi non ha caricato il certificato medico compare in una card rossa:
il giocatore vede il proprio avviso, l'admin l'elenco di chi manca, sopra scaduti e in scadenza (DD-046).
## [1.2.2] - 2026-10-01 ## [1.2.2] - 2026-10-01
Meno notifiche automatiche e un profilo più snello: gli avvisi dei palloni e delle presenze partono una sola volta, il Meno notifiche automatiche e un profilo più snello: gli avvisi dei palloni e delle presenze partono una sola volta, il
+3 -3
View File
@@ -38,9 +38,9 @@ Tutto quello che è rilasciato, fino alla versione 1.0.0 (vedi `CHANGELOG.md`).
calcolata dal CSI calcolata dal CSI
- [x] Ruolo allenatore — gestisce gli eventi e sollecita le presenze, profilo ridotto senza - [x] Ruolo allenatore — gestisce gli eventi e sollecita le presenze, profilo ridotto senza
stagione e badge ([specifica](modules/allenatore.md), DD-034) stagione e badge ([specifica](modules/allenatore.md), DD-034)
- [x] Avviso certificati in scadenza — in Home, giallo nei 7 giorni prima e nero dopo la - [x] Avviso certificati in scadenza — in Home, giallo nei 7 giorni prima, nero dopo la
scadenza: agli admin con i nomi dei giocatori, al giocatore solo per il proprio scadenza e rosso se il certificato manca (DD-046): agli admin con i nomi dei giocatori, al giocatore solo per il proprio
([specifica](modules/profilo-giocatore.md#avviso-certificati), DD-035) ([specifica](modules/profilo-giocatore.md#avviso-certificati), DD-035, DD-046)
- [x] Notifiche email — ogni notifica in-app inviata anche per email agli account registrati, - [x] Notifiche email — ogni notifica in-app inviata anche per email agli account registrati,
da un worker Docker sull'host di casa via Gmail, con interruttore «Email» in Profilo da un worker Docker sull'host di casa via Gmail, con interruttore «Email» in Profilo
([specifica](modules/notifiche-email.md), DD-036) ([specifica](modules/notifiche-email.md), DD-036)
+3 -2
View File
@@ -1,7 +1,7 @@
# DD-035 — Avviso certificati: calcolato in Home, visibile solo al titolare e agli admin # DD-035 — Avviso certificati: calcolato in Home, visibile solo al titolare e agli admin
**Data:** 24 settembre 2026 **Data:** 24 settembre 2026
**Stato:** Accettata (implementata, versione 1.1.0). La soglia del giocatore è stata alzata a 30 giorni da [DD-041](DD-041.md); quella dello staff resta 7. **Stato:** Accettata (implementata, versione 1.1.0). La soglia del giocatore è stata alzata a 30 giorni da [DD-041](DD-041.md); quella dello staff resta 7. [DD-046](DD-046.md) aggiunge l'avviso per chi non ha caricato il certificato.
**Contesto** **Contesto**
Il certificato medico ha una data di scadenza (`profili_giocatore.certificato_scadenza`), ma Il certificato medico ha una data di scadenza (`profili_giocatore.certificato_scadenza`), ma
@@ -40,7 +40,8 @@ certificato viene rinnovato. Specifica completa in
esiste per nessuna push (DD-025), e le push vanno riservate agli eventi importanti esiste per nessuna push (DD-025), e le push vanno riservate agli eventi importanti
([EFFICIENZA_CLOUD.md](../EFFICIENZA_CLOUD.md)). ([EFFICIENZA_CLOUD.md](../EFFICIENZA_CLOUD.md)).
- Includere i certificati mancanti → è un problema diverso (profilo incompleto), già coperto - Includere i certificati mancanti → è un problema diverso (profilo incompleto), già coperto
dal widget «Completa il tuo profilo» e dalla tab Profili. dal widget «Completa il tuo profilo» e dalla tab Profili. _Superata da [DD-046](DD-046.md):
anche i mancanti hanno un avviso, rosso._
**Conseguenze** **Conseguenze**
+33
View File
@@ -0,0 +1,33 @@
# DD-046 — Avviso certificati: anche chi non l'ha caricato
**Data:** 4 ottobre 2026
**Stato:** Accettata (modifica [DD-035](DD-035.md): il certificato mancante non è più escluso dall'avviso)
**Contesto**
L'avviso certificati di DD-035 guarda solo la scadenza e salta chi non ha mai caricato il
certificato («è un problema diverso»). Per l'admin significa che chi non ha caricato nulla non
compare in Home, pur essendo un caso più grave di uno scaduto.
**Decisione**
1. Un giocatore in rosa **senza data o senza file** del certificato è **mancante**.
2. L'**admin** vede la card rossa «Certificati mancanti» con l'elenco, **sopra** scaduti e in
scadenza; il suo nome non compare se è anche giocatore.
3. Il **giocatore** mancante vede la card rossa «Certificato mancante», cliccabile verso il tab
documenti. Vale anche per l'admin che è giocatore.
4. Nessuna soglia: l'avviso resta finché il certificato non è caricato. Il rosso è `destructive`,
già nel tema; giallo e nero degli altri avvisi non cambiano.
**Alternative scartate**
- Stesso nero degli scaduti → non distingue «scaduto» da «mai caricato», che è più grave.
- Solo elenco admin → l'admin che è anche giocatore sarebbe l'unico a non vedere il proprio buco.
**Conseguenze**
- `avvisiCertificati()` restituisce anche `mancanti`; `avvisiCertificatiUtente()` anche
`personaleMancante`.
- Un giocatore appena entrato in rosa compare tra i mancanti finché non carica il file.
**Riesame**
Se i giocatori nuovi riempiono l'elenco senza motivo, aggiungere un periodo di tolleranza.
+1
View File
@@ -86,6 +86,7 @@ Una decisione sostituita non si cancella: si cambia il suo **Stato** e si rimand
| [DD-038](DD-038.md) | Tre canali per ogni notifica | | [DD-038](DD-038.md) | Tre canali per ogni notifica |
| [DD-039](DD-039.md) | Niente promemoria all'allenatore | | [DD-039](DD-039.md) | Niente promemoria all'allenatore |
| [DD-040](DD-040.md) | Palloni e solleciti automatici | | [DD-040](DD-040.md) | Palloni e solleciti automatici |
| [DD-046](DD-046.md) | Avviso certificati mancanti |
| [DD-045](DD-045.md) | Sollecito presenze: un solo avviso a 24 ore | | [DD-045](DD-045.md) | Sollecito presenze: un solo avviso a 24 ore |
| [DD-044](DD-044.md) | Via la foto tessera dal profilo | | [DD-044](DD-044.md) | Via la foto tessera dal profilo |
| [DD-043](DD-043.md) | Niente promemoria evento a 24 e 3 ore | | [DD-043](DD-043.md) | Niente promemoria evento a 24 e 3 ore |
+9 -7
View File
@@ -118,11 +118,11 @@ staff.
| Admin | `GIORNI_AVVISO_CERTIFICATO = 7` | | Admin | `GIORNI_AVVISO_CERTIFICATO = 7` |
| Condizione | Stato | Avviso | | Condizione | Stato | Avviso |
| --------------------- | ----------- | ------------------ | | --------------------- | ----------- | ------------------------------ |
| `giorni > soglia` | valido | nessuno | | `giorni > soglia` | valido | nessuno |
| `0 ≤ giorni ≤ soglia` | in scadenza | giallo (`warning`) | | `0 ≤ giorni ≤ soglia` | in scadenza | giallo (`warning`) |
| `giorni < 0` | scaduto | nero (`primary`) | | `giorni < 0` | scaduto | nero (`primary`) |
| data o file mancanti | mancante | nessuno | | data o file mancanti | mancante | rosso (`destructive`) (DD-046) |
Esempio: scadenza il 5 ottobre 2026. Dal 5 settembre l'avviso giallo compare solo al Esempio: scadenza il 5 ottobre 2026. Dal 5 settembre l'avviso giallo compare solo al
giocatore; dal 28 settembre anche agli admin; dal 6 ottobre è nero per entrambi. giocatore; dal 28 settembre anche agli admin; dal 6 ottobre è nero per entrambi.
@@ -130,8 +130,10 @@ giocatore; dal 28 settembre anche agli admin; dal 6 ottobre è nero per entrambi
- Il giorno della scadenza il certificato vale ancora, coerente con `statoScadenza()`: il - 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. 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. - 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 - Il certificato **mancante** (nessuna data o nessun file) ha un avviso rosso, più grave dello
tab Profili di `/admin` e nel widget di completamento del giocatore. scaduto (DD-046): il giocatore vede «Certificato mancante» (cliccabile, porta ai documenti),
l'admin la card «Certificati mancanti» con l'elenco, in cima alle altre, senza il proprio
nome. Non ha soglie: resta finché il file non è caricato.
- Sono esclusi i giocatori disattivati e gli allenatori: conta solo la rosa (`inRosa()`, - Sono esclusi i giocatori disattivati e gli allenatori: conta solo la rosa (`inRosa()`,
cioè `attivo` e `tipo = 'giocatore'`). cioè `attivo` e `tipo = 'giocatore'`).
@@ -191,12 +193,12 @@ la Home non deve rompersi per un dato accessorio.
#### Implementazione #### Implementazione
| Pezzo | Ruolo | | Pezzo | Ruolo |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GIORNI_AVVISO_CERTIFICATO` (`profili-core.ts`) | la soglia dello staff, 7 | | `GIORNI_AVVISO_CERTIFICATO` (`profili-core.ts`) | la soglia dello staff, 7 |
| `GIORNI_AVVISO_CERTIFICATO_GIOCATORE` | la soglia del giocatore, 30 (DD-041) | | `GIORNI_AVVISO_CERTIFICATO_GIOCATORE` | la soglia del giocatore, 30 (DD-041) |
| `giorniAllaScadenza(scadenza, oggi)` | differenza in giorni di calendario tra due date `AAAA-MM-GG`, negativa se scaduto | | `giorniAllaScadenza(scadenza, oggi)` | differenza in giorni di calendario tra due date `AAAA-MM-GG`, negativa se scaduto |
| `avvisiCertificati(rosa, profili, oggi, soglia)` | funzione pura (`soglia` di default 7): restituisce `{ scaduti, inScadenza }`, ognuno una lista ordinata di `{ giocatoreId, nome, cognome, scadenza, giorni }` | | `avvisiCertificati(rosa, profili, oggi, soglia)` | funzione pura (`soglia` di default 7): restituisce `{ scaduti, inScadenza, mancanti }`; le prime due sono liste ordinate di `{ giocatoreId, nome, cognome, scadenza, giorni }`, `mancanti` di `{ giocatoreId, nome, cognome }` (DD-046) |
| `avvisiCertificatiUtente(rosa, profili, oggi, utente)` | funzione pura: `{ personale, staff }` per chi guarda; l'admin giocatore ha entrambi, senza il suo nome nello staff (DD-041) | | `avvisiCertificatiUtente(rosa, profili, oggi, utente)` | funzione pura: `{ personale, personaleMancante, staff }` per chi guarda; l'admin giocatore ha entrambi, senza il suo nome nello staff (DD-041) |
| `testoScadenza(giorni)` | «scade oggi» / «scade domani» / «scade tra N 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/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 `<AvvisoCertificati />` subito dopo `<PromemoriaPalloni />` | | `src/routes/index.tsx` | monta `<AvvisoCertificati />` subito dopo `<PromemoriaPalloni />` |
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "crapp", "name": "crapp",
"version": "1.2.2", "version": "1.2.3",
"private": true, "private": true,
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",
+24 -5
View File
@@ -18,7 +18,7 @@ function Card({
righe, righe,
cliccabile, cliccabile,
}: { }: {
colore: "warning" | "primary"; colore: "warning" | "primary" | "destructive";
titolo: string; titolo: string;
righe: string[]; righe: string[];
cliccabile?: boolean; cliccabile?: boolean;
@@ -26,6 +26,8 @@ function Card({
const classi = `mx-5 mt-4 block rounded-3xl p-4 shadow-pop ${ const classi = `mx-5 mt-4 block rounded-3xl p-4 shadow-pop ${
colore === "warning" colore === "warning"
? "bg-warning text-warning-foreground" ? "bg-warning text-warning-foreground"
: colore === "destructive"
? "bg-destructive text-destructive-foreground"
: "bg-primary text-primary-foreground" : "bg-primary text-primary-foreground"
} ${cliccabile ? "premi" : ""}`; } ${cliccabile ? "premi" : ""}`;
const contenuto = ( const contenuto = (
@@ -50,7 +52,7 @@ function Card({
} }
/** /**
* Avviso certificati in scadenza/scaduti (DD-035, DD-041): calcolato al volo, niente push. * Avviso certificati mancanti/scaduti/in scadenza (DD-035, DD-041, DD-046): calcolato al volo, niente push.
* Il giocatore lo vede da 30 giorni prima della scadenza, lo staff da 7. * Il giocatore lo vede da 30 giorni prima della scadenza, lo staff da 7.
*/ */
export function AvvisoCertificati() { export function AvvisoCertificati() {
@@ -65,8 +67,18 @@ export function AvvisoCertificati() {
const oggi = oggiISO(); const oggi = oggiISO();
// Admin che è anche giocatore: vede il proprio avviso a 30 giorni e quello dello staff (DD-041). // Admin che è anche giocatore: vede il proprio avviso a 30 giorni e quello dello staff (DD-041).
const { personale, staff } = avvisiCertificatiUtente(rosa, profili, oggi, { admin, base }); const { personale, personaleMancante, staff } = avvisiCertificatiUtente(rosa, profili, oggi, {
const cardPersonale = personale ? ( admin,
base,
});
const cardPersonale = personaleMancante ? (
<Card
colore="destructive"
titolo="Certificato mancante"
cliccabile
righe={["Non hai ancora caricato il certificato medico: caricalo nei documenti"]}
/>
) : personale ? (
personale.giorni < 0 ? ( personale.giorni < 0 ? (
<Card <Card
colore="primary" colore="primary"
@@ -87,10 +99,17 @@ export function AvvisoCertificati() {
) : null; ) : null;
if (!staff) return cardPersonale; if (!staff) return cardPersonale;
const { scaduti, inScadenza } = staff; const { scaduti, inScadenza, mancanti } = staff;
return ( return (
<> <>
{cardPersonale} {cardPersonale}
{mancanti.length > 0 ? (
<Card
colore="destructive"
titolo="Certificati mancanti"
righe={mancanti.map((m) => `${m.nome} ${m.cognome} — non caricato`)}
/>
) : null}
{scaduti.length > 0 ? ( {scaduti.length > 0 ? (
<Card <Card
colore="primary" colore="primary"
+23 -10
View File
@@ -247,14 +247,17 @@ export type AvvisoCertificato = {
giorni: number; giorni: number;
}; };
/** Giocatore in rosa senza certificato caricato (DD-046). */
export type CertificatoMancante = Pick<AvvisoCertificato, "giocatoreId" | "nome" | "cognome">;
function confrontaAvvisi(a: AvvisoCertificato, b: AvvisoCertificato): number { function confrontaAvvisi(a: AvvisoCertificato, b: AvvisoCertificato): number {
return a.giorni - b.giorni || a.cognome.localeCompare(b.cognome) || a.nome.localeCompare(b.nome); return a.giorni - b.giorni || a.cognome.localeCompare(b.cognome) || a.nome.localeCompare(b.nome);
} }
/** /**
* Certificati in scadenza o scaduti nella rosa (DD-035): calcolato al volo da * Certificati in scadenza o scaduti nella rosa (DD-035): calcolato al volo da
* `certificato_scadenza`, niente stato salvato. Esclude chi non è in rosa e chi non ha * `certificato_scadenza`, niente stato salvato. Esclude chi non è in rosa. Chi non ha
* ancora un certificato caricato (è un problema diverso, non un avviso di scadenza). * ancora un certificato caricato non ha una scadenza: finisce in `mancanti` (DD-046).
* `soglia` sono i giorni di preavviso: 7 per lo staff, 30 per il giocatore (DD-041). * `soglia` sono i giorni di preavviso: 7 per lo staff, 30 per il giocatore (DD-041).
*/ */
export function avvisiCertificati( export function avvisiCertificati(
@@ -262,13 +265,21 @@ export function avvisiCertificati(
profili: Record<string, Profilo>, profili: Record<string, Profilo>,
oggi: string, oggi: string,
soglia: number = GIORNI_AVVISO_CERTIFICATO, soglia: number = GIORNI_AVVISO_CERTIFICATO,
): { scaduti: AvvisoCertificato[]; inScadenza: AvvisoCertificato[] } { ): {
scaduti: AvvisoCertificato[];
inScadenza: AvvisoCertificato[];
mancanti: CertificatoMancante[];
} {
const scaduti: AvvisoCertificato[] = []; const scaduti: AvvisoCertificato[] = [];
const inScadenza: AvvisoCertificato[] = []; const inScadenza: AvvisoCertificato[] = [];
const mancanti: CertificatoMancante[] = [];
for (const g of rosa) { for (const g of rosa) {
if (!inRosa(g)) continue; if (!inRosa(g)) continue;
const p = profili[g.id]; const p = profili[g.id];
if (!p?.certificatoScadenza || !p.certificatoPath) continue; if (!p?.certificatoScadenza || !p.certificatoPath) {
mancanti.push({ giocatoreId: g.id, nome: g.nome, cognome: g.cognome });
continue;
}
const giorni = giorniAllaScadenza(p.certificatoScadenza, oggi); const giorni = giorniAllaScadenza(p.certificatoScadenza, oggi);
if (giorni > soglia) continue; if (giorni > soglia) continue;
const avviso: AvvisoCertificato = { const avviso: AvvisoCertificato = {
@@ -282,14 +293,15 @@ export function avvisiCertificati(
} }
scaduti.sort(confrontaAvvisi); scaduti.sort(confrontaAvvisi);
inScadenza.sort(confrontaAvvisi); inScadenza.sort(confrontaAvvisi);
return { scaduti, inScadenza }; mancanti.sort((a, b) => a.cognome.localeCompare(b.cognome) || a.nome.localeCompare(b.nome));
return { scaduti, inScadenza, mancanti };
} }
/** /**
* Avvisi certificati per chi guarda la Home (DD-035, DD-041): `personale` è il proprio * Avvisi certificati per chi guarda la Home (DD-035, DD-041): `personale` è il proprio
* certificato (soglia 30 giorni), per chiunque sia un giocatore in rosa; `staff` sono gli * certificato (soglia 30 giorni), per chiunque sia un giocatore in rosa, e `personaleMancante`
* altri della rosa (soglia 7 giorni), solo per l'admin, senza il suo nome perché già nel * dice che non l'ha ancora caricato (DD-046); `staff` sono gli altri della rosa (soglia 7
* personale. * giorni), solo per l'admin, senza il suo nome perché già nel personale.
*/ */
export function avvisiCertificatiUtente( export function avvisiCertificatiUtente(
rosa: GiocatoreSquadra[], rosa: GiocatoreSquadra[],
@@ -298,7 +310,8 @@ export function avvisiCertificatiUtente(
utente: { admin: boolean; base: GiocatoreSquadra | null }, utente: { admin: boolean; base: GiocatoreSquadra | null },
): { ): {
personale: AvvisoCertificato | undefined; personale: AvvisoCertificato | undefined;
staff: { scaduti: AvvisoCertificato[]; inScadenza: AvvisoCertificato[] } | null; personaleMancante: boolean;
staff: ReturnType<typeof avvisiCertificati> | null;
} { } {
const mio = utente.base const mio = utente.base
? avvisiCertificati([utente.base], profili, oggi, GIORNI_AVVISO_CERTIFICATO_GIOCATORE) ? avvisiCertificati([utente.base], profili, oggi, GIORNI_AVVISO_CERTIFICATO_GIOCATORE)
@@ -311,5 +324,5 @@ export function avvisiCertificatiUtente(
oggi, oggi,
) )
: null; : null;
return { personale, staff }; return { personale, personaleMancante: !!mio && mio.mancanti.length > 0, staff };
} }
+40 -5
View File
@@ -262,15 +262,31 @@ assert.deepEqual(
["g1"], ["g1"],
); );
assert.equal( const senzaCertificato = avvisiCertificati(
avvisiCertificati(
[{ ...squadra[0]!, id: "g6" }], [{ ...squadra[0]!, id: "g6" }],
{ g6: { ...vuoto, giocatoreId: "g6", certificatoScadenza: null, certificatoPath: null } }, { g6: { ...vuoto, giocatoreId: "g6", certificatoScadenza: null, certificatoPath: null } },
oggiTest, oggiTest,
).inScadenza.length,
0,
"certificato mancante: nessun avviso",
); );
assert.equal(senzaCertificato.inScadenza.length + senzaCertificato.scaduti.length, 0);
assert.deepEqual(
senzaCertificato.mancanti.map((m) => m.giocatoreId),
["g6"],
"certificato mancante (DD-046): in `mancanti`, non tra scadenze",
);
// DD-046: profilo assente e scadenza senza file valgono come mancante; fuori rosa e allenatori no
const mancantiRosa = avvisiCertificati(
[
...rosaCertificati,
{ ...squadra[0]!, id: "g7", nome: "Zeno", cognome: "Zeta", attivo: true, tipo: "giocatore" },
{ ...squadra[0]!, id: "g8", nome: "Aldo", cognome: "Alfa", attivo: true, tipo: "giocatore" },
],
{
...profiliCertificati,
g8: { ...vuoto, giocatoreId: "g8", certificatoScadenza: "2026-12-01", certificatoPath: null },
},
oggiTest,
).mancanti.map((m) => m.giocatoreId);
assert.deepEqual(mancantiRosa, ["g8", "g7"], "g7 senza profilo, g8 senza file; ordine per cognome");
const dueInScadenza: Record<string, Profilo> = { const dueInScadenza: Record<string, Profilo> = {
g1: { ...vuoto, giocatoreId: "g1", certificatoScadenza: "2026-09-27", certificatoPath: "p1" }, // 7 giorni, più lontano g1: { ...vuoto, giocatoreId: "g1", certificatoScadenza: "2026-09-27", certificatoPath: "p1" }, // 7 giorni, più lontano
@@ -412,5 +428,24 @@ const soloGiocatore = avvisiCertificatiUtente(rosaUtente, profiliUtente, oggiTes
}); });
assert.equal(soloGiocatore.personale?.giocatoreId, "g1"); assert.equal(soloGiocatore.personale?.giocatoreId, "g1");
assert.equal(soloGiocatore.staff, null, "il giocatore semplice non vede lo staff"); assert.equal(soloGiocatore.staff, null, "il giocatore semplice non vede lo staff");
assert.equal(soloGiocatore.personaleMancante, false);
// DD-046: chi non ha caricato il certificato lo vede nel personale; l'admin lo vede anche per gli altri
const adminSenzaFile = avvisiCertificatiUtente(rosaUtente, {}, oggiTest, {
admin: true,
base: rosaUtente[0]!,
});
assert.equal(adminSenzaFile.personaleMancante, true);
assert.equal(adminSenzaFile.personale, undefined);
assert.deepEqual(
adminSenzaFile.staff?.mancanti.map((m) => m.giocatoreId),
["g2"],
"lo staff non ripete il suo nome tra i mancanti",
);
assert.equal(
avvisiCertificatiUtente(rosaUtente, {}, oggiTest, { admin: true, base: null }).personaleMancante,
false,
"admin non giocatore: nessun avviso personale",
);
console.log("profili-core: ok"); console.log("profili-core: ok");