Implementa il ruolo allenatore nell'app

- Modello: tipo sugli slot, inRosa() al posto del filtro su attivo in tutte le
  letture della rosa di gioco (presenze, convocati, voti, palloni, classifiche,
  badge, sollecito); gli allenatori restano solo nei compleanni e in Squadra.
- Permessi: user_roles letto una volta per tutti i ruoli; usePuoGestireEventi()
  apre /eventi e il sollecito presenze anche all'allenatore, lato server con
  richiediGestoreEventi(). Palloni, sondaggio e messaggi restano all'admin.
- Identità: useGiocatoreInCampo() è null per l'allenatore, così non risponde alle
  presenze e non vota; useSonoAllenatore() decide cosa si vede.
- Profilo: per l'allenatore solo Docs (nome, cognome, nascita, telefono, email) e
  Opzioni; card avatar e tab Opzioni estratte per riusarle.
- Home, Squadra e partita: niente statistiche personali, badge, cacche e
  obiettivi per l'allenatore; in Rosa compare con la dicitura «Allenatore».
- Admin: tipo in «Aggiungi», allenatori a parte in Profili, fuori da conteggi ed
  export CSI.

Test unit su rosa di gioco e completamento, route test sull'allenatore.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
davideandClaude Opus 5.5 committed 2026-09-24 14:54:35 +02:00
1 parent 15a010855e
commit 2ec72fd08f
35 files changed
+1215 -577

No files matched your search

+7 -6
View File
@@ -16,7 +16,7 @@ import {
} from "@/components/ui/drawer";
import { formatData } from "@/lib/crapp-data";
import { giorniDelMese, giorniIT, mesiIT, pad2, useMeseNav } from "@/lib/calendario";
import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import { inRosa, nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import {
categoriaEvento,
daCategoria,
@@ -28,7 +28,7 @@ import {
type Evento,
} from "@/lib/eventi";
import { useGiocatoreBase } from "@/lib/user-store";
import { useIsAdmin } from "@/lib/ruoli";
import { usePuoGestireEventi } from "@/lib/ruoli";
import { molla, proietta } from "@/lib/molla";
import { useMotoRidotto } from "@/lib/motion";
@@ -61,9 +61,10 @@ const tipi: Array<{ id: CategoriaEvento; label: string }> = [
];
function GestioneEventi() {
// Solo verità (`!io`, gate admin): `useGiocatoreBase` basta, niente statistiche.
// Solo verità (`!io`, gate admin o allenatore): `useGiocatoreBase` basta, niente statistiche.
const io = useGiocatoreBase();
const admin = useIsAdmin();
// Admin o allenatore (DD-034): la RLS di `eventi_app` (M21) dice lo stesso.
const puoGestire = usePuoGestireEventi();
const { eventi, isPending, isError, error, refetch } = useEventi();
const { righe: squadra } = useGiocatoriSquadra();
const salva = useSalvaEvento();
@@ -72,7 +73,7 @@ function GestioneEventi() {
const [daEliminare, setDaEliminare] = useState<Evento | null>(null);
const [confermaModifica, setConfermaModifica] = useState(false);
const [cambiaGiorno, setCambiaGiorno] = useState(false);
const rosa = squadra.filter((g) => g.attivo);
const rosa = squadra.filter(inRosa);
// SSR-safe: la data di oggi arriva solo dopo il mount.
const [oggi, setOggi] = useState<{ anno: number; mese: number; giorno: number } | null>(null);
@@ -85,7 +86,7 @@ function GestioneEventi() {
const { anno, mese, direzione, precedente, successivo } = useMeseNav();
const { giorni, offsetLunedi } = giorniDelMese(anno, mese);
if (!io || !admin) {
if (!io || !puoGestire) {
return (
<>
<PageHeader titolo="Gestione eventi" sottotitolo="Area riservata" />