Files
CRAPP/src/lib/scout-store.ts
T

176 lines
4.3 KiB
TypeScript
Raw Normal View History

2026-08-06 08:36:47 +02:00
import { useSyncExternalStore } from "react";
import { giocatori, type Giocatore } from "./crapp-data";
2026-08-06 08:36:47 +02:00
export type AzioneTipo = "attacco" | "ace" | "muro" | "errore" | "punto_avv" | "errore_avv";
2026-08-06 08:36:47 +02:00
export const azioniMeta: Record<
AzioneTipo,
{ label: string; short: string; nostro: boolean; richiedeGiocatore: boolean; className: string }
> = {
attacco: {
label: "Punto attacco",
short: "Punto",
nostro: true,
richiedeGiocatore: true,
className: "bg-accent text-accent-foreground",
},
ace: {
label: "Ace",
short: "Ace",
nostro: true,
richiedeGiocatore: true,
className: "bg-success text-success-foreground",
},
muro: {
label: "Muro",
short: "Muro",
nostro: true,
richiedeGiocatore: true,
className: "bg-info text-info-foreground",
},
errore: {
label: "Errore nostro",
short: "Errore",
nostro: false,
richiedeGiocatore: true,
className: "bg-destructive text-destructive-foreground",
},
punto_avv: {
label: "Punto avversario",
short: "Punto avv.",
nostro: false,
richiedeGiocatore: false,
className: "bg-muted text-muted-foreground",
},
errore_avv: {
label: "Errore avversario",
short: "Err. avv.",
nostro: true,
richiedeGiocatore: false,
className: "bg-secondary text-foreground",
},
2026-08-06 08:36:47 +02:00
};
export type Azione = {
id: string;
tipo: AzioneTipo;
giocatoreId?: string;
set: number;
ts: number;
};
export type ScoutMatch = {
id: string;
data: string;
avversario: string;
casa: boolean;
setNostri: number;
setLoro: number;
parziali: Array<[number, number]>;
mvp: string;
azioni: Azione[];
};
const KEY = "crapp-scout-v1";
let cache: ScoutMatch[] | null = null;
const listeners = new Set<() => void>();
function read(): ScoutMatch[] {
if (cache) return cache;
if (typeof window === "undefined") return (cache = []);
try {
const raw = window.localStorage.getItem(KEY);
cache = raw ? (JSON.parse(raw) as ScoutMatch[]) : [];
} catch {
cache = [];
}
return cache;
}
function write(next: ScoutMatch[]) {
cache = next;
try {
window.localStorage.setItem(KEY, JSON.stringify(next));
} catch {
/* storage non disponibile */
}
listeners.forEach((l) => l());
}
export function salvaScoutMatch(m: ScoutMatch) {
write([m, ...read()]);
}
export function eliminaScoutMatch(id: string) {
write(read().filter((m) => m.id !== id));
}
export function useScoutMatches(): ScoutMatch[] {
return useSyncExternalStore(
(cb) => {
listeners.add(cb);
return () => listeners.delete(cb);
},
() => read(),
() => [],
);
}
/** Somma delle azioni di un match per giocatore. */
export function totaliPerGiocatore(azioni: Azione[]) {
const out = new Map<string, { punti: number; ace: number; muri: number; errori: number }>();
for (const a of azioni) {
if (!a.giocatoreId) continue;
const cur = out.get(a.giocatoreId) ?? { punti: 0, ace: 0, muri: 0, errori: 0 };
if (a.tipo === "attacco") cur.punti += 1;
if (a.tipo === "ace") {
cur.ace += 1;
cur.punti += 1;
}
if (a.tipo === "muro") {
cur.muri += 1;
cur.punti += 1;
}
if (a.tipo === "errore") cur.errori += 1;
out.set(a.giocatoreId, cur);
}
return out;
}
/** Totali di squadra ricavati dallo scout (dato tecnico, non personale). */
export function totaliSquadra(matches: ScoutMatch[]) {
const out = { punti: 0, ace: 0, muri: 0, errori: 0 };
for (const m of matches) {
for (const t of totaliPerGiocatore(m.azioni).values()) {
out.punti += t.punti;
out.ace += t.ace;
out.muri += t.muri;
out.errori += t.errori;
}
}
return out;
}
/** Presenze e MVP ricavati dalle partite scoutate: nessuna statistica individuale offensiva.
* `mvpPerMatch` mappa idPartita -> nome dell'MVP eletto dalla squadra. */
export function giocatoriConScout(
matches: ScoutMatch[],
mvpPerMatch: Record<string, string> = {},
): Giocatore[] {
return giocatori.map((g) => {
let presenze = 0;
let mvp = 0;
for (const m of matches) {
if (totaliPerGiocatore(m.azioni).has(g.id)) presenze += 1;
if (mvpPerMatch[m.id] === g.nome) mvp += 1;
}
return {
...g,
mvp: g.mvp + mvp,
presenze: g.presenze + presenze,
totaliEventi: g.totaliEventi + matches.length,
};
});
}