From 0c136f8f53507a551ce8936d65f49bb24cd90112 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Sun, 6 Sep 2026 00:24:39 +0200 Subject: [PATCH] Apre lo Scout Live dalla pagina partita e a tutta la squadra. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tolto dalla home e poi da Squadra, `ScoutEntry` era rimasto orfano: `/scout` si raggiungeva solo scrivendo l'URL a mano. Ora la card sta nella sezione «Scout live» di `/partita/$id`. La prop `eventoId` la accende solo se la partita aperta è quella di oggi: senza, da una partita futura o passata si sarebbe finiti sullo scout di un'altra. Cade anche la riserva agli admin, in `ScoutEntry` e nella route: può scoutare chiunque sia autenticato, uno per volta grazie al lock di sessione. È anche l'unico controllo che c'era, visto che le policy RLS sono sempre state aperte a tutti gli autenticati. Co-Authored-By: Claude Opus 5 --- docs/CHANGELOG.md | 9 ++++++ docs/modules/scout-live.md | 23 +++++++++++---- src/components/crapp/ScoutEntry.tsx | 43 ++++++++++++++--------------- src/routes/partita.$id.tsx | 5 ++++ src/routes/scout.tsx | 12 -------- 5 files changed, 51 insertions(+), 41 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 296226e..b1d5295 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,15 @@ qui: sta in [ROADMAP.md](ROADMAP.md). ## Versione attuale — agosto 2026 +### Lo Scout Live si apre dalla pagina della partita + +- Tolto dalla home, era rimasto senza nessun link: `/scout` si raggiungeva solo scrivendo + l'URL. Ora la card `ScoutEntry` sta in `/partita/$id`, sezione «Scout live». +- Si accende solo se la partita aperta è quella di oggi (nuova prop `eventoId`), altrimenti + resta grigia con «Si attiva il giorno della partita». Lock di sessione invariato. +- Non è più riservato agli admin: può scoutare chiunque sia autenticato, uno per volta grazie + al lock. Sparisce il messaggio «Scout riservato». + ### Il sondaggio pre-partita apre alle 8:00 del giorno della partita - Prima era sempre votabile, anche settimane prima: ora la card resta chiusa con l'avviso di diff --git a/docs/modules/scout-live.md b/docs/modules/scout-live.md index a77eea0..40ed2a4 100644 --- a/docs/modules/scout-live.md +++ b/docs/modules/scout-live.md @@ -30,10 +30,21 @@ aggiornato da qualunque dispositivo. ## Chi può usarlo -Solo gli admin lato UI: `ScoutEntry.tsx` e `scout.tsx` bloccano i non-admin con il messaggio -"Scout riservato". **Il controllo non è imposto a livello database**: le policy RLS di -`scout_sessioni`/`scout_live`/`scout_partite` sono aperte a qualunque utente autenticato, non -solo agli admin — la migration M4 toglie l'accesso solo al ruolo `anon`. +Chiunque sia autenticato: non è più riservato agli admin. A tenere l'ordine basta il lock di +sessione — scoutizza uno per volta, gli altri vedono "In uso da …". Questo allinea l'interfaccia +alle policy RLS di `scout_sessioni`/`scout_live`/`scout_partite`, che sono sempre state aperte a +qualunque utente autenticato (la migration M4 toglie l'accesso solo al ruolo `anon`). + +--- + +## Da dove ci si arriva + +`ScoutEntry.tsx` è l'unico accesso a `/scout`: sta nella pagina della partita +(`partita.$id.tsx`, sezione «Scout live»), visibile a tutta la squadra. Si accende solo se +**quella** partita è quella di oggi — la prop `eventoId` confronta l'evento aperto con +`partitaDiOggi()` — e se nessun altro ha il lock; negli altri casi resta una card grigia non +cliccabile («Si attiva il giorno della partita» / «In uso da …»). Dalla home è stato tolto +perché occupava spazio 6 giorni su 7. --- @@ -104,7 +115,8 @@ calcolati a runtime). ## Limiti noti -- Controllo "solo admin" non imposto dal database (vedi sopra). +- Scout aperto a tutta la squadra: nessun filtro su chi può registrare le azioni, l'unica + garanzia è il lock di sessione (vedi sopra). - Possibile, per quanto improbabile, doppio "successo" applicativo nel prendere il lock: lettura e upsert non sono atomici. - `scout_partite` si inserisce ma non si corregge dall'interfaccia: solo eliminazione totale. @@ -116,4 +128,3 @@ calcolati a runtime). ## Evoluzioni possibili - Realtime (Supabase Realtime) per aggiornare la sessione condivisa senza refresh manuale. -- Restringere le policy RLS al solo ruolo admin. diff --git a/src/components/crapp/ScoutEntry.tsx b/src/components/crapp/ScoutEntry.tsx index 8becdd1..c892bdb 100644 --- a/src/components/crapp/ScoutEntry.tsx +++ b/src/components/crapp/ScoutEntry.tsx @@ -3,36 +3,33 @@ 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(); +/** + * Accesso allo scout live: attivo solo il giorno della partita e se nessun altro lo sta usando. + * Con `eventoId` si accende solo se quella partita è proprio quella di oggi. + */ +export function ScoutEntry({ + variante = "grande", + eventoId, +}: { + variante?: "grande" | "compatto"; + eventoId?: string; +}) { + const { pronto, partita: diOggi } = usePartitaDiOggi(); + const partita = eventoId && diOggi?.id !== eventoId ? null : diOggi; 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 disponibile = 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 titolo = !partita ? "Scout live non attivo" : occupato ? "Scout occupato" : "Scout live"; + const sottotitolo = !partita + ? "Si attiva il giorno della partita" + : occupato + ? `In uso da ${attiva!.giocatore_nome}` + : "Segna punti, ace e muri in tempo reale"; const contenuto = ( <> diff --git a/src/routes/partita.$id.tsx b/src/routes/partita.$id.tsx index 47468b8..9105fc1 100644 --- a/src/routes/partita.$id.tsx +++ b/src/routes/partita.$id.tsx @@ -9,6 +9,7 @@ import { useCsi } from "@/lib/csi"; import { matchDaPartitaCsi, partiteGiocate } from "@/lib/csi-core"; import { Pagelle } from "@/components/crapp/Pagelle"; import { SondaggioCacche } from "@/components/crapp/SondaggioCacche"; +import { ScoutEntry } from "@/components/crapp/ScoutEntry"; import { useScoutMatches, totaliPerGiocatore, totaliSquadra } from "@/lib/scout-store"; import { csvScoutMatch, scaricaCsv } from "@/lib/scout-export"; import { useGiocatoreCorrente } from "@/lib/user-store"; @@ -204,6 +205,10 @@ function PartitaDetail() { )} +
+ +
+
diff --git a/src/routes/scout.tsx b/src/routes/scout.tsx index f65a3f4..994bb06 100644 --- a/src/routes/scout.tsx +++ b/src/routes/scout.tsx @@ -8,7 +8,6 @@ import { formatData } from "@/lib/crapp-data"; import type { Evento } from "@/lib/eventi"; import { useRosa } from "@/lib/rosa"; import { useGiocatoreCorrente } from "@/lib/user-store"; -import { useIsAdmin } from "@/lib/ruoli"; import { usePresenzeEvento } from "@/lib/presenze"; import { statoIniziale, @@ -83,7 +82,6 @@ function Blocco({ function Scout() { const { pronto, partita } = usePartitaDiOggi(); const io = useGiocatoreCorrente(); - const admin = useIsAdmin(); const sessione = useSessioneScout(partita?.id ?? null); const statoSalvato = useStatoScout(partita?.id ?? null); const apri = useApriSessioneScout(); @@ -102,16 +100,6 @@ function Scout() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [controllo, partita?.id, io?.id]); - if (io && !admin) { - return ( - } - titolo="Scout riservato" - testo="Lo scout live è uno strumento tecnico per allenatori e referenti della squadra." - /> - ); - } - if (!pronto || sessione.isLoading || statoSalvato.isLoading) { return ( } titolo="Scout live" testo="Caricamento…" />