Implementa l'avviso certificati in scadenza in Home (DD-035)
Calcolato al volo da certificato_scadenza in profili-core.ts: nessuna tabella, nessuna migration, nessuna push. Lo vedono solo gli admin (nomi di tutta la rosa, non cliccabile) e il giocatore interessato (solo il proprio, apre Documenti). Montato in Home subito dopo il promemoria palloni. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
49d4d9de31
commit
a51cdb43e4
7 files changed
+323
-9
No files matched your search
+5
-4
@@ -178,10 +178,11 @@ collega uno slot lo occupa anche in produzione, e va liberato da un admin.
|
|||||||
|
|
||||||
## Prossimo sviluppo
|
## Prossimo sviluppo
|
||||||
|
|
||||||
**Avviso certificati in scadenza** (24/09/2026, branch `develop`): specifica scritta in
|
**Avviso certificati in scadenza** (27/09/2026, branch `develop`): codice scritto secondo la
|
||||||
[docs/modules/profilo-giocatore.md](docs/modules/profilo-giocatore.md#avviso-certificati),
|
specifica 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
|
(decisione DD-035), non ancora rilasciato in una versione. Solo client: nessuna migration e
|
||||||
push. Le altre voci aperte stanno in [docs/ROADMAP.md](docs/ROADMAP.md), sotto «Prossimo».
|
nessuna push. Le altre voci aperte stanno in [docs/ROADMAP.md](docs/ROADMAP.md), sotto
|
||||||
|
«Prossimo».
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ sulla tab Stagione.
|
|||||||
|
|
||||||
### Avviso certificati
|
### Avviso certificati
|
||||||
|
|
||||||
**Stato:** da implementare (DD-035).
|
**Stato:** implementato (DD-035).
|
||||||
|
|
||||||
Un avviso in Home segnala i certificati medici in scadenza o scaduti, così nessuno se ne
|
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
|
accorge quando il giocatore è già fuori regola. Si calcola al volo dalla data di scadenza
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { AlertCircle } from "lucide-react";
|
||||||
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import { useProfili } from "@/lib/profili";
|
||||||
|
import {
|
||||||
|
avvisiCertificati,
|
||||||
|
formatDataBreve,
|
||||||
|
testoScadenza,
|
||||||
|
type AvvisoCertificato,
|
||||||
|
} from "@/lib/profili-core";
|
||||||
|
import { useGiocatoriSquadra } from "@/lib/giocatori-squadra";
|
||||||
|
import { oggiISO } from "@/lib/palloni-core";
|
||||||
|
import { useIsAdmin, useRuoliPronti } from "@/lib/ruoli";
|
||||||
|
import { useGiocatoreBase } from "@/lib/user-store";
|
||||||
|
|
||||||
|
function Card({
|
||||||
|
colore,
|
||||||
|
titolo,
|
||||||
|
righe,
|
||||||
|
cliccabile,
|
||||||
|
}: {
|
||||||
|
colore: "warning" | "primary";
|
||||||
|
titolo: string;
|
||||||
|
righe: string[];
|
||||||
|
cliccabile?: boolean;
|
||||||
|
}) {
|
||||||
|
const classi = `mx-5 mt-4 block rounded-3xl p-4 shadow-pop ${
|
||||||
|
colore === "warning"
|
||||||
|
? "bg-warning text-warning-foreground"
|
||||||
|
: "bg-primary text-primary-foreground"
|
||||||
|
} ${cliccabile ? "premi" : ""}`;
|
||||||
|
const contenuto = (
|
||||||
|
<>
|
||||||
|
<p className="flex items-center gap-2 font-display text-lg uppercase leading-none">
|
||||||
|
<AlertCircle className="h-5 w-5" /> {titolo}
|
||||||
|
</p>
|
||||||
|
{righe.map((r) => (
|
||||||
|
<p key={r} className="mt-2 text-xs leading-snug opacity-90">
|
||||||
|
{r}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return cliccabile ? (
|
||||||
|
<Link to="/profilo" search={{ tab: "documenti" }} className={classi}>
|
||||||
|
{contenuto}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div className={classi}>{contenuto}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Avviso certificati in scadenza/scaduti (DD-035): calcolato al volo, niente push. */
|
||||||
|
export function AvvisoCertificati() {
|
||||||
|
const admin = useIsAdmin();
|
||||||
|
const ruoliPronti = useRuoliPronti();
|
||||||
|
const base = useGiocatoreBase();
|
||||||
|
const { righe: rosa } = useGiocatoriSquadra();
|
||||||
|
const { profili, isPending } = useProfili();
|
||||||
|
|
||||||
|
if (!ruoliPronti || isPending) return null;
|
||||||
|
|
||||||
|
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}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)}`]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rigaStaff(a: AvvisoCertificato, dettaglio: string): string {
|
||||||
|
return `${a.nome} ${a.cognome} — ${dettaglio}`;
|
||||||
|
}
|
||||||
+71
-1
@@ -1,5 +1,5 @@
|
|||||||
import { rigaCsv } from "./scout-export";
|
import { rigaCsv } from "./scout-export";
|
||||||
import { nomeCompleto, type GiocatoreSquadra } from "./giocatori-squadra";
|
import { inRosa, nomeCompleto, type GiocatoreSquadra } from "./giocatori-squadra";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profilo amministrativo di un giocatore (DD-016). I file veri stanno nel bucket privato
|
* Profilo amministrativo di un giocatore (DD-016). I file veri stanno nel bucket privato
|
||||||
@@ -215,3 +215,73 @@ export function csvTesseramento(
|
|||||||
export function etichettaGiocatore(g: GiocatoreSquadra): string {
|
export function etichettaGiocatore(g: GiocatoreSquadra): string {
|
||||||
return `#${g.numero} ${nomeCompleto(g)}`;
|
return `#${g.numero} ${nomeCompleto(g)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Soglia dell'avviso certificati in Home (DD-035). */
|
||||||
|
export const GIORNI_AVVISO_CERTIFICATO = 7;
|
||||||
|
|
||||||
|
/** 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`);
|
||||||
|
const b = new Date(`${oggi}T00:00:00`);
|
||||||
|
return Math.round((a.getTime() - b.getTime()) / 86_400_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Data in formato `GG/MM/AAAA`, per i testi dell'avviso certificati. */
|
||||||
|
export function formatDataBreve(iso: string): string {
|
||||||
|
return new Date(`${iso}T00:00:00`).toLocaleDateString("it-IT", {
|
||||||
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Riga dell'avviso certificati per un giocatore (DD-035). */
|
||||||
|
export function testoScadenza(giorni: number): string {
|
||||||
|
if (giorni === 0) return "scade oggi";
|
||||||
|
if (giorni === 1) return "scade domani";
|
||||||
|
return `scade tra ${giorni} giorni`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AvvisoCertificato = {
|
||||||
|
giocatoreId: string;
|
||||||
|
nome: string;
|
||||||
|
cognome: string;
|
||||||
|
scadenza: string;
|
||||||
|
giorni: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function confrontaAvvisi(a: AvvisoCertificato, b: AvvisoCertificato): number {
|
||||||
|
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
|
||||||
|
* `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).
|
||||||
|
*/
|
||||||
|
export function avvisiCertificati(
|
||||||
|
rosa: GiocatoreSquadra[],
|
||||||
|
profili: Record<string, Profilo>,
|
||||||
|
oggi: string,
|
||||||
|
): { scaduti: AvvisoCertificato[]; inScadenza: AvvisoCertificato[] } {
|
||||||
|
const scaduti: AvvisoCertificato[] = [];
|
||||||
|
const inScadenza: AvvisoCertificato[] = [];
|
||||||
|
for (const g of rosa) {
|
||||||
|
if (!inRosa(g)) continue;
|
||||||
|
const p = profili[g.id];
|
||||||
|
if (!p?.certificatoScadenza || !p.certificatoPath) continue;
|
||||||
|
const giorni = giorniAllaScadenza(p.certificatoScadenza, oggi);
|
||||||
|
if (giorni > GIORNI_AVVISO_CERTIFICATO) continue;
|
||||||
|
const avviso: AvvisoCertificato = {
|
||||||
|
giocatoreId: g.id,
|
||||||
|
nome: g.nome,
|
||||||
|
cognome: g.cognome,
|
||||||
|
scadenza: p.certificatoScadenza,
|
||||||
|
giorni,
|
||||||
|
};
|
||||||
|
(giorni < 0 ? scaduti : inScadenza).push(avviso);
|
||||||
|
}
|
||||||
|
scaduti.sort(confrontaAvvisi);
|
||||||
|
inScadenza.sort(confrontaAvvisi);
|
||||||
|
return { scaduti, inScadenza };
|
||||||
|
}
|
||||||
+11
-3
@@ -24,15 +24,23 @@ async function fetchRuoli(utenteId: string | null): Promise<Ruolo[] | null> {
|
|||||||
return (data ?? []).map((r) => r.role as Ruolo);
|
return (data ?? []).map((r) => r.role as Ruolo);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useRuoli(): Ruolo[] {
|
function useRuoliQuery() {
|
||||||
const { utenteId } = useSessione();
|
const { utenteId } = useSessione();
|
||||||
// Il ruolo cambia solo quando un admin lo assegna: una lettura per sessione basta.
|
// Il ruolo cambia solo quando un admin lo assegna: una lettura per sessione basta.
|
||||||
const query = useQuery({
|
return useQuery({
|
||||||
queryKey: [...RUOLI_KEY, utenteId],
|
queryKey: [...RUOLI_KEY, utenteId],
|
||||||
queryFn: () => fetchRuoli(utenteId),
|
queryFn: () => fetchRuoli(utenteId),
|
||||||
staleTime: 30 * 60_000,
|
staleTime: 30 * 60_000,
|
||||||
});
|
});
|
||||||
return query.data ?? [];
|
}
|
||||||
|
|
||||||
|
function useRuoli(): Ruolo[] {
|
||||||
|
return useRuoliQuery().data ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** L'avviso certificati (DD-035) aspetta i ruoli: niente flash dell'avviso sbagliato. */
|
||||||
|
export function useRuoliPronti(): boolean {
|
||||||
|
return !useRuoliQuery().isPending;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useIsAdmin(): boolean {
|
export function useIsAdmin(): boolean {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { Flame, ChevronRight } from "lucide-react";
|
import { Flame, ChevronRight } from "lucide-react";
|
||||||
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
||||||
import { PromemoriaPalloni } from "@/components/crapp/PromemoriaPalloni";
|
import { PromemoriaPalloni } from "@/components/crapp/PromemoriaPalloni";
|
||||||
|
import { AvvisoCertificati } from "@/components/crapp/AvvisoCertificati";
|
||||||
import { Card, LinkProfilo, Section, StatTile, TeamLogo } from "@/components/crapp/ui-bits";
|
import { Card, LinkProfilo, Section, StatTile, TeamLogo } from "@/components/crapp/ui-bits";
|
||||||
import { CompletaProfilo } from "@/components/crapp/ProfiloAmministrativo";
|
import { CompletaProfilo } from "@/components/crapp/ProfiloAmministrativo";
|
||||||
import { Reveal } from "@/components/motion/Reveal";
|
import { Reveal } from "@/components/motion/Reveal";
|
||||||
@@ -120,6 +121,8 @@ function Index() {
|
|||||||
|
|
||||||
<PromemoriaPalloni />
|
<PromemoriaPalloni />
|
||||||
|
|
||||||
|
<AvvisoCertificati />
|
||||||
|
|
||||||
<CompletaProfilo giocatoreId={base.id} allenatore={allenatore} indice={1} />
|
<CompletaProfilo giocatoreId={base.id} allenatore={allenatore} indice={1} />
|
||||||
|
|
||||||
<Section
|
<Section
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import {
|
import {
|
||||||
aRigaProfilo,
|
aRigaProfilo,
|
||||||
|
avvisiCertificati,
|
||||||
completamento,
|
completamento,
|
||||||
completamentoAllenatore,
|
completamentoAllenatore,
|
||||||
csvTesseramento,
|
csvTesseramento,
|
||||||
daRigaProfilo,
|
daRigaProfilo,
|
||||||
|
formatDataBreve,
|
||||||
|
giorniAllaScadenza,
|
||||||
sezioniComplete,
|
sezioniComplete,
|
||||||
statoScadenza,
|
statoScadenza,
|
||||||
|
testoScadenza,
|
||||||
type Profilo,
|
type Profilo,
|
||||||
} from "@/lib/profili-core";
|
} from "@/lib/profili-core";
|
||||||
import {
|
import {
|
||||||
@@ -214,4 +218,119 @@ assert.equal(
|
|||||||
);
|
);
|
||||||
assert.equal(completamentoAllenatore({ ...vuoto, telefono: " " }), 0, "spazi soli non contano");
|
assert.equal(completamentoAllenatore({ ...vuoto, telefono: " " }), 0, "spazi soli non contano");
|
||||||
|
|
||||||
|
// --- avviso certificati (DD-035) ------------------------------------------------
|
||||||
|
assert.equal(giorniAllaScadenza("2026-09-27", "2026-09-20"), 7);
|
||||||
|
assert.equal(giorniAllaScadenza("2026-09-19", "2026-09-20"), -1, "scaduto ieri");
|
||||||
|
assert.equal(giorniAllaScadenza("2026-09-20", "2026-09-20"), 0, "scade oggi");
|
||||||
|
assert.equal(giorniAllaScadenza("2026-10-01", "2026-09-30"), 1, "cambio di mese");
|
||||||
|
assert.equal(giorniAllaScadenza("2027-01-01", "2026-12-31"), 1, "cambio di anno");
|
||||||
|
|
||||||
|
assert.equal(formatDataBreve("2026-09-12"), "12/09/2026");
|
||||||
|
|
||||||
|
assert.equal(testoScadenza(0), "scade oggi");
|
||||||
|
assert.equal(testoScadenza(1), "scade domani");
|
||||||
|
assert.equal(testoScadenza(5), "scade tra 5 giorni");
|
||||||
|
|
||||||
|
const rosaCertificati: GiocatoreSquadra[] = [
|
||||||
|
{ ...squadra[0]!, id: "g1", nome: "Ivan", cognome: "Cacciari", attivo: true, tipo: "giocatore" },
|
||||||
|
{ ...squadra[0]!, id: "g2", nome: "Anna", cognome: "Bruni", attivo: true, tipo: "giocatore" },
|
||||||
|
{ ...squadra[0]!, id: "g3", nome: "Marco", cognome: "Verdi", attivo: true, tipo: "giocatore" },
|
||||||
|
{ ...squadra[0]!, id: "g4", nome: "Sara", cognome: "Neri", attivo: false, tipo: "giocatore" },
|
||||||
|
{ ...squadra[0]!, id: "g5", nome: "Luca", cognome: "Bianchi", attivo: true, tipo: "allenatore" },
|
||||||
|
];
|
||||||
|
const oggiTest = "2026-09-20";
|
||||||
|
const profiliCertificati: Record<string, Profilo> = {
|
||||||
|
g1: { ...vuoto, giocatoreId: "g1", certificatoScadenza: "2026-09-25", certificatoPath: "p1" }, // in scadenza, 5 giorni
|
||||||
|
g2: { ...vuoto, giocatoreId: "g2", certificatoScadenza: "2026-09-10", certificatoPath: "p2" }, // scaduto da 10 giorni
|
||||||
|
g3: { ...vuoto, giocatoreId: "g3", certificatoScadenza: "2026-10-31", certificatoPath: "p3" }, // valido, fuori soglia
|
||||||
|
g4: { ...vuoto, giocatoreId: "g4", certificatoScadenza: "2026-09-21", certificatoPath: "p4" }, // disattivato: escluso
|
||||||
|
g5: { ...vuoto, giocatoreId: "g5", certificatoScadenza: "2026-09-21", certificatoPath: "p5" }, // allenatore: escluso
|
||||||
|
};
|
||||||
|
const avvisi = avvisiCertificati(rosaCertificati, profiliCertificati, oggiTest);
|
||||||
|
assert.deepEqual(
|
||||||
|
avvisi.scaduti.map((a) => a.giocatoreId),
|
||||||
|
["g2"],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
avvisi.inScadenza.map((a) => a.giocatoreId),
|
||||||
|
["g1"],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
avvisiCertificati(
|
||||||
|
[{ ...squadra[0]!, id: "g6" }],
|
||||||
|
{ g6: { ...vuoto, giocatoreId: "g6", certificatoScadenza: null, certificatoPath: null } },
|
||||||
|
oggiTest,
|
||||||
|
).inScadenza.length,
|
||||||
|
0,
|
||||||
|
"certificato mancante: nessun avviso",
|
||||||
|
);
|
||||||
|
|
||||||
|
const dueInScadenza: Record<string, Profilo> = {
|
||||||
|
g1: { ...vuoto, giocatoreId: "g1", certificatoScadenza: "2026-09-27", certificatoPath: "p1" }, // 7 giorni, più lontano
|
||||||
|
g2: { ...vuoto, giocatoreId: "g2", certificatoScadenza: "2026-09-21", certificatoPath: "p2" }, // 1 giorno, più vicino
|
||||||
|
};
|
||||||
|
const ordinati = avvisiCertificati(
|
||||||
|
[
|
||||||
|
{ ...squadra[0]!, id: "g1", nome: "Ivan", cognome: "Cacciari" },
|
||||||
|
{ ...squadra[0]!, id: "g2", nome: "Anna", cognome: "Bruni" },
|
||||||
|
],
|
||||||
|
dueInScadenza,
|
||||||
|
oggiTest,
|
||||||
|
).inScadenza.map((a) => a.giocatoreId);
|
||||||
|
assert.deepEqual(ordinati, ["g2", "g1"], "la scadenza più vicina viene prima");
|
||||||
|
|
||||||
|
const pariGiorni: Record<string, Profilo> = {
|
||||||
|
g1: { ...vuoto, giocatoreId: "g1", certificatoScadenza: "2026-09-25", certificatoPath: "p1" },
|
||||||
|
g2: { ...vuoto, giocatoreId: "g2", certificatoScadenza: "2026-09-25", certificatoPath: "p2" },
|
||||||
|
};
|
||||||
|
const alfabetico = avvisiCertificati(
|
||||||
|
[
|
||||||
|
{ ...squadra[0]!, id: "g1", nome: "Ivan", cognome: "Zeta" },
|
||||||
|
{ ...squadra[0]!, id: "g2", nome: "Anna", cognome: "Alfa" },
|
||||||
|
],
|
||||||
|
pariGiorni,
|
||||||
|
oggiTest,
|
||||||
|
).inScadenza.map((a) => a.giocatoreId);
|
||||||
|
assert.deepEqual(alfabetico, ["g2", "g1"], "a parità di data, ordine alfabetico per cognome");
|
||||||
|
|
||||||
|
// soglia esatta (DD-035): 8 giorni fuori, 7 e 0 dentro
|
||||||
|
const rosaSoglia: GiocatoreSquadra[] = [
|
||||||
|
{ ...squadra[0]!, id: "g1", nome: "Ivan", cognome: "Cacciari" },
|
||||||
|
{ ...squadra[0]!, id: "g2", nome: "Anna", cognome: "Bruni" },
|
||||||
|
{ ...squadra[0]!, id: "g3", nome: "Marco", cognome: "Verdi" },
|
||||||
|
];
|
||||||
|
const profiliSoglia: Record<string, Profilo> = {
|
||||||
|
g1: { ...vuoto, giocatoreId: "g1", certificatoScadenza: "2026-09-28", certificatoPath: "p1" }, // 8 giorni: fuori
|
||||||
|
g2: { ...vuoto, giocatoreId: "g2", certificatoScadenza: "2026-09-27", certificatoPath: "p2" }, // 7 giorni: dentro
|
||||||
|
g3: { ...vuoto, giocatoreId: "g3", certificatoScadenza: "2026-09-20", certificatoPath: "p3" }, // 0 giorni: dentro
|
||||||
|
};
|
||||||
|
const soglia = avvisiCertificati(rosaSoglia, profiliSoglia, oggiTest);
|
||||||
|
assert.deepEqual(
|
||||||
|
soglia.inScadenza.map((a) => a.giocatoreId),
|
||||||
|
["g3", "g2"],
|
||||||
|
"8 giorni resta valido, 7 e 0 generano avviso",
|
||||||
|
);
|
||||||
|
assert.equal(soglia.scaduti.length, 0);
|
||||||
|
|
||||||
|
// ordinamento tra scaduti: il più vecchio prima, poi alfabetico a parità di data
|
||||||
|
const rosaScaduti: GiocatoreSquadra[] = [
|
||||||
|
{ ...squadra[0]!, id: "g1", nome: "Ivan", cognome: "Zeta" },
|
||||||
|
{ ...squadra[0]!, id: "g2", nome: "Anna", cognome: "Alfa" },
|
||||||
|
{ ...squadra[0]!, id: "g3", nome: "Marco", cognome: "Verdi" },
|
||||||
|
];
|
||||||
|
const profiliScaduti: Record<string, Profilo> = {
|
||||||
|
g1: { ...vuoto, giocatoreId: "g1", certificatoScadenza: "2026-09-01", certificatoPath: "p1" }, // scaduto da 19 giorni
|
||||||
|
g2: { ...vuoto, giocatoreId: "g2", certificatoScadenza: "2026-09-01", certificatoPath: "p2" }, // pari data di g1
|
||||||
|
g3: { ...vuoto, giocatoreId: "g3", certificatoScadenza: "2026-09-19", certificatoPath: "p3" }, // scaduto da 1 giorno
|
||||||
|
};
|
||||||
|
const scadutiOrdinati = avvisiCertificati(rosaScaduti, profiliScaduti, oggiTest).scaduti.map(
|
||||||
|
(a) => a.giocatoreId,
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
scadutiOrdinati,
|
||||||
|
["g2", "g1", "g3"],
|
||||||
|
"il più vecchio prima, poi alfabetico a parità di data (Alfa prima di Zeta)",
|
||||||
|
);
|
||||||
|
|
||||||
console.log("profili-core: ok");
|
console.log("profili-core: ok");
|
||||||
Reference in new issue
Block a user