Dashboard amministratore e profilo per il tesseramento

Implementa la voce "Dashboard amministratore" della roadmap v1.1, come descritta
in docs/modules/profilo-giocatore.md, insieme alla parte di profilo che la
alimenta.

- /admin: stato dei profili della squadra, download di documento, certificato e
  foto tessera, export CSV con i 12 campi del tesseramento CSI. Un certificato
  scaduto non conta come valido.
- Profilo giocatore: dati personali, documento (fronte e retro), certificato e
  foto tessera, con widget di completamento in Home che sparisce al 100%.
- I permessi di amministrazione arrivano da user_roles (DD-011) e non più dalla
  lista di nomi in crapp-data.ts, che resta come ponte finché
  VITE_AUTH_OBBLIGATORIA non viene acceso in produzione.
- Login Google via Supabase Auth: al primo accesso l'account si collega a uno
  slot libero di giocatori_squadra, e il vincolo lo fa rispettare il trigger di
  M1 (DD-016 regola 2).

Migration additive: M2 crea profili_giocatore, M3 il bucket privato
profili-giocatore. Nessuna tabella v1.0 viene toccata, quindi si possono
applicare senza cambiare il comportamento attuale dell'app.

supabase/config.toml e seed.sql configurano lo stack locale: serve perché il
progetto Supabase è uno solo, condiviso tra sviluppo e produzione.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 17:57:59 +02:00
co-authored by Claude Opus 5
parent ea29f053e3
commit da51517ffc
23 changed files with 1635 additions and 63 deletions
+4 -2
View File
@@ -4,7 +4,7 @@ import { ArrowLeft, CalendarPlus, Loader2, Pencil, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { PageHeader, Section } from "@/components/crapp/ui-bits";
import { formatData, giocatori, isAdmin } from "@/lib/crapp-data";
import { formatData, giocatori } from "@/lib/crapp-data";
import {
categoriaEvento,
daCategoria,
@@ -16,6 +16,7 @@ import {
type Evento,
} from "@/lib/eventi";
import { useGiocatoreCorrente } from "@/lib/user-store";
import { useIsAdmin } from "@/lib/ruoli";
export const Route = createFileRoute("/eventi")({
head: () => ({
@@ -47,12 +48,13 @@ const tipi: Array<{ id: CategoriaEvento; label: string }> = [
function GestioneEventi() {
const io = useGiocatoreCorrente();
const admin = useIsAdmin();
const { eventi, isPending } = useEventi();
const salva = useSalvaEvento();
const elimina = useEliminaEvento();
const [bozza, setBozza] = useState<Evento | null>(null);
if (!io || !isAdmin(io.id)) {
if (!io || !admin) {
return (
<>
<PageHeader titolo="Gestione eventi" sottotitolo="Area riservata" />