Files
CRAPP/src/components/crapp/ScoutEntry.tsx
T
davideandClaude Opus 5 da51517ffc 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>
2026-08-30 17:57:59 +02:00

62 lines
2.2 KiB
TypeScript

import { Link } from "@tanstack/react-router";
import { ChevronRight, Lock, Radio } from "lucide-react";
import { cn } from "@/lib/utils";
import { sessioneScaduta, usePartitaDiOggi, useSessioneScout } from "@/lib/scout-live";
import { useGiocatoreCorrente } from "@/lib/user-store";
import { useIsAdmin } from "@/lib/ruoli";
/** Accesso allo scout live: attivo solo il giorno della partita e se nessun altro lo sta usando. */
export function ScoutEntry({ variante = "grande" }: { variante?: "grande" | "compatto" }) {
const { pronto, partita } = usePartitaDiOggi();
const io = useGiocatoreCorrente();
const admin = useIsAdmin();
const { data: sessione } = useSessioneScout(partita?.id ?? null);
// Strumento tecnico: solo i referenti/allenatori scoutizzano la partita.
const abilitato = admin;
const attiva = sessione && !sessioneScaduta(sessione) ? sessione : null;
const occupato = !!attiva && attiva.giocatore_id !== io?.id;
const disponibile = abilitato && pronto && !!partita && !occupato;
const titolo = !abilitato ? "Scout live" : !partita ? "Scout live non attivo" : occupato ? "Scout occupato" : "Scout live";
const sottotitolo = !abilitato ? "Riservato ad allenatori e referenti" : !partita
? "Si attiva il giorno della partita"
: occupato
? `In uso da ${attiva!.giocatore_nome}`
: "Segna punti, ace e muri in tempo reale";
const contenuto = (
<>
{occupato ? <Lock className="h-5 w-5 shrink-0" /> : <Radio className="h-5 w-5 shrink-0" />}
<span className="min-w-0 flex-1">
<span className="block font-display text-lg uppercase leading-none">{titolo}</span>
<span className="block text-[11px] opacity-80">{sottotitolo}</span>
</span>
{disponibile ? <ChevronRight className="h-5 w-5 shrink-0" /> : null}
</>
);
const classi = cn(
"flex items-center gap-3 rounded-3xl p-4 shadow-card",
variante === "compatto" && "gap-2 rounded-2xl p-3",
disponibile
? "bg-accent-grad text-accent-foreground shadow-pop"
: "bg-card text-muted-foreground",
);
if (!disponibile) {
return (
<div className={classi} aria-disabled>
{contenuto}
</div>
);
}
return (
<Link to="/scout" className={classi}>
{contenuto}
</Link>
);
}