Sincronizza lo Scout Live tra dispositivi (blocco e archivio partite)

Due bug architetturali, entrambi con la stessa causa: dati che vivevano
solo in localStorage, quindi visibili a un solo dispositivo.

- Il blocco "chi sta scoutando" (scout-live.ts) non funzionava mai tra
  telefoni diversi: due persone potevano prendere il controllo insieme
  da dispositivi diversi, sovrascrivendosi a vicenda le azioni nel
  salvataggio condiviso. Ora usa scout_sessioni, tabella già presente
  nello schema ma mai collegata al codice.

- La partita scoutata finita (scout-store.ts) veniva salvata solo in
  localStorage: "Punti/Ace/Muri squadra" e le presenze derivate dallo
  scout esistevano solo sul telefono di chi aveva chiuso la partita.
  Nuova tabella scout_partite archivia la partita completa (azioni
  incluse) per tutta la squadra.

useScoutMatches() mantiene la stessa firma di prima (ScoutMatch[]), ora
alimentata da una query invece che da localStorage: nessuna modifica
necessaria nei punti che la leggono (squadra, classifica, home,
dettaglio partita, rosa). Rigenerati i tipi Supabase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 16:08:59 +02:00
co-authored by Claude Sonnet 5
parent d2d62b6799
commit c314a08f6d
10 changed files with 362 additions and 144 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ function Index() {
const ultima = csiGiocate[0]
? { ...matchDaPartitaCsi(csiGiocate[0]), mvp: mvpPerMatch[csiGiocate[0].id] ?? "" }
: scoutMatches[0]
? { ...scoutMatches[0], mvp: mvpPerMatch[scoutMatches[0].id] ?? scoutMatches[0].mvp }
? { ...scoutMatches[0], mvp: mvpPerMatch[scoutMatches[0].id] ?? "" }
: null;
const obiettivi = useObiettivi();
const obiettivo = obiettivi.find((o) => progressoObiettivo(o) < 100) ?? obiettivi[0] ?? null;
+23 -14
View File
@@ -26,7 +26,7 @@ import {
} from "@/lib/scout-live";
import {
azioniMeta,
salvaScoutMatch,
useSalvaScoutMatch,
totaliPerGiocatore,
type Azione,
type AzioneTipo,
@@ -212,6 +212,7 @@ function ScoutBoard({
const [selezionato, setSelezionato] = useState<string | null>(null);
const salva = useSalvaStatoScout();
const cancella = useCancellaStatoScout();
const salvaMatch = useSalvaScoutMatch();
const { risposte } = usePresenzeEvento(partita.id);
const finito = useRef(false);
@@ -280,7 +281,7 @@ function ScoutBoard({
toast.success(`Set ${setCorrente} chiuso ${puntiNoi}-${puntiLoro}`);
}
function finePartita() {
async function finePartita() {
const parziali: Array<[number, number]> =
puntiNoi + puntiLoro > 0 ? [...setChiusi, [puntiNoi, puntiLoro]] : setChiusi;
if (parziali.length === 0) {
@@ -288,17 +289,24 @@ function ScoutBoard({
return;
}
const vinti = parziali.filter(([n, l]) => n > l).length;
salvaScoutMatch({
id: `s${Date.now()}`,
data: new Date().toISOString().slice(0, 10),
avversario: avversario.trim() || "Avversario",
casa,
setNostri: vinti,
setLoro: parziali.length - vinti,
parziali,
mvp: "",
azioni,
});
try {
await salvaMatch.mutateAsync({
eventoId: partita.id,
match: {
id: `s${Date.now()}`,
data: new Date().toISOString().slice(0, 10),
avversario: avversario.trim() || "Avversario",
casa,
setNostri: vinti,
setLoro: parziali.length - vinti,
parziali,
azioni,
},
});
} catch {
toast.error("Salvataggio non riuscito, riprova");
return;
}
toast.success("Partita salvata: ora la squadra può votare l'MVP");
finito.current = true;
void cancella.mutateAsync(partita.id);
@@ -511,7 +519,8 @@ function ScoutBoard({
<button
type="button"
onClick={finePartita}
className="inline-flex items-center justify-center gap-2 rounded-2xl bg-accent-grad py-3 text-sm font-bold uppercase text-accent-foreground shadow-pop"
disabled={salvaMatch.isPending}
className="inline-flex items-center justify-center gap-2 rounded-2xl bg-accent-grad py-3 text-sm font-bold uppercase text-accent-foreground shadow-pop disabled:opacity-50"
>
<Save className="h-4 w-4" /> Fine partita
</button>