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>
62 lines
2.2 KiB
TypeScript
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>
|
|
);
|
|
}
|