Sposta roster, eventi, presenze, voti, scout e palloni su PocketBase

Riscrive i moduli dati che parlavano con Supabase per usare l'SDK
PocketBase, mantenendo invariate le firme degli hook React Query così i
componenti a valle non cambiano: giocatori-squadra, eventi, presenze,
cacche, pagelle, mvp-voti, badge-social, scout (store/live/stato),
palloni.

Aggiunge due helper condivisi in src/integrations/pocketbase/:
- upsert.ts: PocketBase non ha upsert nativo, questi replicano il
  pattern onConflict di Supabase (per id significativo o per filtro su
  combinazione di campi), verificati contro un'istanza reale così da non
  duplicare record sulla stessa scrittura ripetuta;
- formato.ts: normalizza le date PocketBase ("AAAA-MM-GG HH:MM:SS.sssZ",
  spazio non "T") a ISO stretto, altrimenti Date.parse e i confronti
  testuali con altre date si comportano in modo incoerente.

Corregge anche due problemi trovati testando contro un'istanza reale:
- alcune API rule confrontavano un campo relazione con @request.auth.id
  senza richiedere l'autenticazione, lasciando passare richieste anonime
  quando entrambi i lati erano vuoti;
- gli hook non riconoscevano il superuser PocketBase (solo il ruolo
  admin applicativo), bloccando le operazioni fatte con pbAdmin.

Aggiunta la colonna "casa" mancante su eventi_app e i campi di sistema
created/updated (non generati automaticamente da PocketBase per le
collection create via migration, a differenza di quanto assunto
inizialmente).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 14:58:27 +02:00
co-authored by Claude Sonnet 5
parent 0f5a27ed5f
commit 689468141d
16 changed files with 430 additions and 275 deletions
+20 -10
View File
@@ -1,5 +1,6 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client";
import { pb } from "@/integrations/pocketbase/client";
import { upsertByFilter } from "@/integrations/pocketbase/upsert";
import { oggiISO } from "./palloni-core";
/** Ora di apertura del sondaggio, il giorno stesso della partita. */
@@ -48,6 +49,12 @@ export type RigaCacche = {
quantita: number;
};
type RigaCacchePocketBase = {
evento: string;
giocatore: string;
quantita: number;
};
export const CACCHE_KEY = ["cacche"] as const;
export function useCacche() {
@@ -55,11 +62,12 @@ export function useCacche() {
queryKey: CACCHE_KEY,
staleTime: 10 * 60_000,
queryFn: async (): Promise<RigaCacche[]> => {
const { data, error } = await supabase
.from("cacche_partita")
.select("evento_id, giocatore_id, quantita");
if (error) throw error;
return (data ?? []) as RigaCacche[];
const righe = await pb.collection("cacche_partita").getFullList<RigaCacchePocketBase>();
return righe.map((r) => ({
evento_id: r.evento,
giocatore_id: r.giocatore,
quantita: r.quantita,
}));
},
});
return { ...query, righe: query.data ?? [] };
@@ -69,10 +77,12 @@ export function useSalvaCacche() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (riga: RigaCacche) => {
const { error } = await supabase
.from("cacche_partita")
.upsert(riga, { onConflict: "evento_id,giocatore_id" });
if (error) throw error;
await upsertByFilter(
"cacche_partita",
"evento = {:evento} && giocatore = {:giocatore}",
{ evento: riga.evento_id, giocatore: riga.giocatore_id },
{ evento: riga.evento_id, giocatore: riga.giocatore_id, quantita: riga.quantita },
);
return riga;
},
onSuccess: (riga) => {