Avviso certificati: il giocatore è avvisato 30 giorni prima (DD-041)

Il giocatore vede l'avviso giallo da 30 giorni prima della scadenza del
certificato medico; per lo staff resta 7 giorni, nero dopo la scadenza per
tutti. L'admin che è anche giocatore vede il proprio avviso personale oltre
a quello dello staff, da cui è escluso il suo nome.

La scelta degli avvisi per utente è in avvisiCertificatiUtente()
(profili-core) con test; documentati DD-041, DD-035, modulo e changelog.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-01 14:21:25 +02:00
1 parent 8f25143aa5
commit 4b5ffc6664
8 files changed
+285 -117

No files matched your search

+46 -45
View File
@@ -2,7 +2,7 @@ import { AlertCircle } from "lucide-react";
import { Link } from "@tanstack/react-router";
import { useProfili } from "@/lib/profili";
import {
avvisiCertificati,
avvisiCertificatiUtente,
formatDataBreve,
testoScadenza,
type AvvisoCertificato,
@@ -49,7 +49,10 @@ function Card({
);
}
/** Avviso certificati in scadenza/scaduti (DD-035): calcolato al volo, niente push. */
/**
* Avviso certificati in scadenza/scaduti (DD-035, DD-041): calcolato al volo, niente push.
* Il giocatore lo vede da 30 giorni prima della scadenza, lo staff da 7.
*/
export function AvvisoCertificati() {
const admin = useIsAdmin();
const ruoliPronti = useRuoliPronti();
@@ -61,50 +64,48 @@ export function AvvisoCertificati() {
const oggi = oggiISO();
if (admin) {
const { scaduti, inScadenza } = avvisiCertificati(rosa, profili, oggi);
if (scaduti.length === 0 && inScadenza.length === 0) return null;
return (
<>
{scaduti.length > 0 ? (
<Card
colore="primary"
titolo="Certificati scaduti"
righe={scaduti.map((a) => rigaStaff(a, `scaduto il ${formatDataBreve(a.scadenza)}`))}
/>
) : null}
{inScadenza.length > 0 ? (
<Card
colore="warning"
titolo="Certificati in scadenza"
righe={inScadenza.map((a) => rigaStaff(a, testoScadenza(a.giorni)))}
/>
) : null}
</>
);
}
// 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 cardPersonale = personale ? (
personale.giorni < 0 ? (
<Card
colore="primary"
titolo="Certificato scaduto"
cliccabile
righe={[
`Il tuo certificato medico è scaduto il ${formatDataBreve(personale.scadenza)}: caricane uno nuovo`,
]}
/>
) : (
<Card
colore="warning"
titolo="Certificato in scadenza"
cliccabile
righe={[`Il tuo certificato medico ${testoScadenza(personale.giorni)}`]}
/>
)
) : null;
if (!base) return null;
const { scaduti, inScadenza } = avvisiCertificati([base], profili, oggi);
const mio = scaduti[0] ?? inScadenza[0];
if (!mio) return null;
return mio.giorni < 0 ? (
<Card
colore="primary"
titolo="Certificato scaduto"
cliccabile
righe={[
`Il tuo certificato medico è scaduto il ${formatDataBreve(mio.scadenza)}: caricane uno nuovo`,
]}
/>
) : (
<Card
colore="warning"
titolo="Certificato in scadenza"
cliccabile
righe={[`Il tuo certificato medico ${testoScadenza(mio.giorni)}`]}
/>
if (!staff) return cardPersonale;
const { scaduti, inScadenza } = staff;
return (
<>
{cardPersonale}
{scaduti.length > 0 ? (
<Card
colore="primary"
titolo="Certificati scaduti"
righe={scaduti.map((a) => rigaStaff(a, `scaduto il ${formatDataBreve(a.scadenza)}`))}
/>
) : null}
{inScadenza.length > 0 ? (
<Card
colore="warning"
titolo="Certificati in scadenza"
righe={inScadenza.map((a) => rigaStaff(a, testoScadenza(a.giorni)))}
/>
) : null}
</>
);
}
+36 -2
View File
@@ -216,9 +216,12 @@ export function etichettaGiocatore(g: GiocatoreSquadra): string {
return `#${g.numero} ${nomeCompleto(g)}`;
}
/** Soglia dell'avviso certificati in Home (DD-035). */
/** Soglia dell'avviso certificati dello staff in Home (DD-035). */
export const GIORNI_AVVISO_CERTIFICATO = 7;
/** Soglia dell'avviso personale: il giocatore è avvisato un mese prima dello staff (DD-041). */
export const GIORNI_AVVISO_CERTIFICATO_GIOCATORE = 30;
/** Giorni di calendario tra due date `AAAA-MM-GG` (ora locale): negativo se `scadenza` è passata. */
export function giorniAllaScadenza(scadenza: string, oggi: string): number {
const a = new Date(`${scadenza}T00:00:00`);
@@ -258,11 +261,13 @@ function confrontaAvvisi(a: AvvisoCertificato, b: AvvisoCertificato): number {
* 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
* ancora un certificato caricato (è un problema diverso, non un avviso di scadenza).
* `soglia` sono i giorni di preavviso: 7 per lo staff, 30 per il giocatore (DD-041).
*/
export function avvisiCertificati(
rosa: GiocatoreSquadra[],
profili: Record<string, Profilo>,
oggi: string,
soglia: number = GIORNI_AVVISO_CERTIFICATO,
): { scaduti: AvvisoCertificato[]; inScadenza: AvvisoCertificato[] } {
const scaduti: AvvisoCertificato[] = [];
const inScadenza: AvvisoCertificato[] = [];
@@ -271,7 +276,7 @@ export function avvisiCertificati(
const p = profili[g.id];
if (!p?.certificatoScadenza || !p.certificatoPath) continue;
const giorni = giorniAllaScadenza(p.certificatoScadenza, oggi);
if (giorni > GIORNI_AVVISO_CERTIFICATO) continue;
if (giorni > soglia) continue;
const avviso: AvvisoCertificato = {
giocatoreId: g.id,
nome: g.nome,
@@ -285,3 +290,32 @@ export function avvisiCertificati(
inScadenza.sort(confrontaAvvisi);
return { scaduti, inScadenza };
}
/**
* 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
* altri della rosa (soglia 7 giorni), solo per l'admin, senza il suo nome perché già nel
* personale.
*/
export function avvisiCertificatiUtente(
rosa: GiocatoreSquadra[],
profili: Record<string, Profilo>,
oggi: string,
utente: { admin: boolean; base: GiocatoreSquadra | null },
): {
personale: AvvisoCertificato | undefined;
staff: { scaduti: AvvisoCertificato[]; inScadenza: AvvisoCertificato[] } | null;
} {
const mio = utente.base
? avvisiCertificati([utente.base], profili, oggi, GIORNI_AVVISO_CERTIFICATO_GIOCATORE)
: null;
const personale = mio ? (mio.scaduti[0] ?? mio.inScadenza[0]) : undefined;
const staff = utente.admin
? avvisiCertificati(
rosa.filter((g) => g.id !== utente.base?.id),
profili,
oggi,
)
: null;
return { personale, staff };
}