Avvisi in Home: componente condiviso, più leggibili; turno palloni ridisegnato

Certificati e turno palloni usano lo stesso componente Avviso: icona propria per
ogni caso (il colore non è l'unico segnale), testo più grande, elenchi di persone
in righe con dettaglio a destra e conteggio, freccia sulle card cliccabili,
comparsa graduale. Il turno palloni ha la pillola «Oggi»/«In arrivo» e i
messaggi in riquadri. Solo aspetto: testi e regole di comparsa invariati.
Test del componente, CHANGELOG, ARCHITECTURE e test/README aggiornati.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-06 11:44:58 +02:00
1 parent e7ae2c1884
commit 519f9aaff4
7 files changed
+258 -74

No files matched your search

+1 -1
View File
@@ -26,7 +26,7 @@ la consegna effettiva a schermo bloccato richiede un telefono e il servizio push
| Cartella | Cosa verifica | Serve rete? |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi (calcolo e card di dettaglio), notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route, lettura del `.env` ed elenco dei bucket dello script di backup, logica del worker in `mailer/` (email e push dei promemoria), preferenza «Email» e avviso del sondaggio) | No |
| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi (calcolo e card di dettaglio), avvisi in Home, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route, lettura del `.env` ed elenco dei bucket dello script di backup, logica del worker in `mailer/` (email e push dei promemoria), preferenza «Email» e avviso del sondaggio) | No |
| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`), le letture lato server delle route push (`lettori-server`) e la pulizia a cascata dei dati collegati alla cancellazione di un evento (`pulizia-evento`, M14) la funzione di bonifica delle righe orfane (`bonifica-evento`, M16), il centro notifiche in-app (`notifiche-utente`, M17), la coda delle email e le sue funzioni RPC (`notifiche-email`, M22), i promemoria evento che non ricompaiono se eliminati (`promemoria-eventi`, M23), la coda delle push dei promemoria (`push-promemoria`, M24), i job automatici di turno palloni e sollecito presenze con revoca, finestre e testi identici al codice (`notifiche-automatiche`, M26-M28 e M30, DD-040, DD-042, DD-043 e DD-045: palloni un solo avviso a 3 ore, sollecito uno solo a 24 ore, nessun promemoria evento) la colonna `foto_path` eliminata (`foto-tessera-rimossa`, M29, DD-044) e il sondaggio in-app/email (`sondaggio-notifiche`, M24), i destinatari delle notifiche per ruolo (`destinatari-notifiche`: promemoria, messaggio dello staff, sondaggio, sollecito e turno palloni con allenatore e giocatore), gli avvisi di compleanno con ora, più compleanni lo stesso giorno, registro e 29 febbraio (`notifiche-compleanno`, M31, DD-047), la nascita pubblica sincronizzata dal profilo (`nascita-pubblica`, M18) e il suo backfill per i profili preesistenti (`backfill-nascita-esistenti`, M19 — unico test che esegue SQL diretto via `docker exec` nel container Postgres, perché PostgREST non espone quell'update multi-tabella come RPC) sul database locale | Sì |
| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì |
| `helpers/` | Avvio del server di test e mini-harness condiviso | — |
+84
View File
@@ -0,0 +1,84 @@
/** Check del componente degli avvisi in Home: `bun test/unit/avviso.test.ts`. */
import assert from "node:assert/strict";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { CalendarClock, FileX } from "lucide-react";
import { CorpoAvviso, type TonoAvviso } from "@/components/crapp/Avviso";
const html = (p: Parameters<typeof CorpoAvviso>[0]) =>
renderToStaticMarkup(createElement(CorpoAvviso, p));
// --- ogni tono ha il suo colore, e l'icona è decorativa (il titolo dice già tutto) ---
const classi: Record<TonoAvviso, string> = {
critico: "bg-destructive",
attenzione: "bg-warning",
scaduto: "bg-primary",
azione: "bg-accent-grad",
};
for (const [tono, classe] of Object.entries(classi) as [TonoAvviso, string][]) {
const h = html({ tono, icona: FileX, titolo: "Titolo" });
assert.ok(h.includes(classe), `${tono}: classe colore ${classe}`);
assert.ok(h.includes("Titolo"), `${tono}: titolo`);
assert.ok(h.includes('aria-hidden="true"'), `${tono}: icona nascosta ai lettori di schermo`);
}
// --- testi: un paragrafo per frase ---------------------------------------------------
const conTesti = html({
tono: "attenzione",
icona: CalendarClock,
titolo: "Certificato in scadenza",
testi: ["Prima frase.", "Seconda frase."],
});
assert.equal((conTesti.match(/<p /g) ?? []).length, 3, "titolo + due paragrafi");
assert.ok(!conTesti.includes("<ul"), "senza elenco niente lista");
// --- elenco: righe chi/cosa, conteggio solo con più di una riga ----------------------
const una = html({
tono: "critico",
icona: FileX,
titolo: "Certificati mancanti",
elenco: [{ chi: "Mario Rossi", cosa: "non caricato" }],
});
assert.ok(una.includes("Mario Rossi") && una.includes("non caricato"), "riga con chi e cosa");
assert.ok(!una.includes("persone"), "una sola riga: nessun conteggio");
const tre = html({
tono: "critico",
icona: FileX,
titolo: "Certificati mancanti",
elenco: [
{ chi: "Mario Rossi", cosa: "non caricato" },
{ chi: "Luca Bianchi", cosa: "non caricato" },
{ chi: "Anna Verdi", cosa: "non caricato" },
],
});
assert.equal((tre.match(/<li /g) ?? []).length, 3, "una riga per persona");
assert.ok(tre.includes('aria-label="3 persone"'), "conteggio accessibile");
// --- freccia solo se la card porta altrove -------------------------------------------
const base = { tono: "scaduto", icona: FileX, titolo: "t" } as const;
assert.ok(!html(base).includes("lucide-chevron-right"), "senza link niente freccia");
assert.ok(
html({ ...base, cliccabile: true }).includes("lucide-chevron-right"),
"con link la freccia",
);
// --- etichetta e riquadri (turno palloni) --------------------------------------------
const palloni = html({
tono: "azione",
icona: FileX,
titolo: "Turno palloni",
etichetta: "Oggi",
testi: ["Porta i palloni."],
riquadri: true,
});
assert.ok(palloni.includes("Oggi"), "etichetta mostrata");
assert.ok(palloni.includes("bg-black/15"), "il messaggio è in un riquadro");
assert.ok(palloni.includes("bg-black/25"), "etichetta in pillola");
assert.ok(!html({ ...base }).includes("bg-black/25"), "senza etichetta niente pillola");
assert.ok(
!html({ ...base, testi: ["x"] }).includes("bg-black/15"),
"senza riquadri testo semplice",
);
console.log("avviso: ok");