Aggancia Squadra, Presenze, Pagelle, Scout e calendario alla rosa reale

convocatiEvento() e compleanniEventi() prendono ora la rosa come parametro
invece di leggere la lista statica; csvScoutMatch() idem. Le schermate di
gestione eventi, dettaglio allenamento/partita, calendario e scout live,
più i widget di presenze/pagelle/voto MVP/voto social/sondaggio
cacche/turno palloni, passano tutte la rosa letta da useRosa() o
useGiocatoriSquadra(): un giocatore aggiunto o disattivato dalla dashboard
admin ora si riflette ovunque.
This commit is contained in:
2026-09-03 10:17:03 +02:00
parent b6c0f40f66
commit 4c0ea66126
14 changed files with 88 additions and 59 deletions
+7 -4
View File
@@ -1,9 +1,10 @@
import { MapPin, Clock, Users, Cake, ArrowRight } from "lucide-react";
import { Link } from "@tanstack/react-router";
import { cn } from "@/lib/utils";
import { formatData, giocatori, statoMeta, type Stato } from "@/lib/crapp-data";
import { formatData, statoMeta, type Stato } from "@/lib/crapp-data";
import type { Evento } from "@/lib/eventi";
import { Barra } from "@/components/motion/Barra";
import { useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import { usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze";
import { useGiocatoreCorrente } from "@/lib/user-store";
@@ -36,16 +37,18 @@ export function EventoCard({
const { risposte } = usePresenzeEvento(evento.id);
const salva = useSalvaPresenza();
const io = useGiocatoreCorrente();
const { righe: squadra } = useGiocatoriSquadra();
const rosa = squadra.filter((g) => g.attivo);
const stato = io ? risposte[io.id] : undefined;
const presentiVeri = giocatori.filter(
const presentiVeri = rosa.filter(
(g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo",
).length;
const tipo =
evento.tipo === "partita" && !evento.campionato
? { label: "Amichevole", className: "bg-accent/70 text-accent-foreground" }
: tipoMeta[evento.tipo];
const totale = giocatori.length;
const perc = Math.round((presentiVeri / totale) * 100);
const totale = rosa.length;
const perc = totale ? Math.round((presentiVeri / totale) * 100) : 0;
const isCompleanno = evento.tipo === "compleanno";
if (isCompleanno) {
+3 -3
View File
@@ -3,7 +3,7 @@ import { ClipboardCheck, Lock } from "lucide-react";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { Avatar } from "@/components/crapp/Avatar";
import { giocatori } from "@/lib/crapp-data";
import type { Giocatore } from "@/lib/crapp-data";
import { useGiocatoreCorrente } from "@/lib/user-store";
import { mieiVoti, pagellePartita, usePagelle, useVotaPagella } from "@/lib/pagelle";
@@ -12,11 +12,11 @@ const voti = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
/** Pagelle di fine partita: voto anonimo 1-10 a ciascun compagno. */
export function Pagelle({
matchId,
convocati = giocatori,
convocati,
chiuse = false,
}: {
matchId: string;
convocati?: typeof giocatori;
convocati: Giocatore[];
chiuse?: boolean;
}) {
const io = useGiocatoreCorrente();
+11 -10
View File
@@ -4,8 +4,9 @@ import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { Avatar } from "@/components/crapp/Avatar";
import { Barra } from "@/components/motion/Barra";
import { giocatori, statoMeta, type Stato } from "@/lib/crapp-data";
import { statoMeta, type Giocatore, type Stato } from "@/lib/crapp-data";
import { usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze";
import { useRosa } from "@/lib/rosa";
import { useGiocatoreCorrente } from "@/lib/user-store";
import { useIsAdmin } from "@/lib/ruoli";
@@ -16,13 +17,13 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
const salva = useSalvaPresenza();
const io = useGiocatoreCorrente();
const admin = useIsAdmin();
const rosa = useRosa();
const [sollecito, setSollecito] = useState(false);
const mancanti = giocatori.filter((g) => !risposte[g.id]);
const risposteN = giocatori.length - mancanti.length;
const perc = Math.round((risposteN / giocatori.length) * 100);
const daSollecitare =
mancanti.length + giocatori.filter((g) => risposte[g.id] === "forse").length;
const mancanti = rosa.filter((g) => !risposte[g.id]);
const risposteN = rosa.length - mancanti.length;
const perc = rosa.length ? Math.round((risposteN / rosa.length) * 100) : 0;
const daSollecitare = mancanti.length + rosa.filter((g) => risposte[g.id] === "forse").length;
async function sollecita() {
setSollecito(true);
@@ -51,7 +52,7 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
<div className="rounded-3xl bg-card p-4 shadow-card">
<div className="flex items-baseline justify-between">
<p className="text-sm font-bold">
Hanno risposto {risposteN}/{giocatori.length}
Hanno risposto {risposteN}/{rosa.length}
</p>
<span className="font-display text-xl leading-none">{perc}%</span>
</div>
@@ -59,7 +60,7 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
<div className="mt-3 flex flex-wrap gap-1.5">
{ordine.map((s) => {
const n = giocatori.filter((g) => risposte[g.id] === s).length;
const n = rosa.filter((g) => risposte[g.id] === s).length;
return (
<span
key={s}
@@ -130,7 +131,7 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
) : null}
{ordine.map((s) => {
const lista = giocatori.filter((g) => risposte[g.id] === s);
const lista = rosa.filter((g) => risposte[g.id] === s);
if (lista.length === 0) return null;
return (
<Gruppo
@@ -162,7 +163,7 @@ function Gruppo({
}: {
titolo: string;
n: number;
lista: typeof giocatori;
lista: Giocatore[];
attenzione?: boolean;
}) {
return (
+6 -2
View File
@@ -1,6 +1,6 @@
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { giocatori } from "@/lib/crapp-data";
import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import { useGiocatoreCorrente } from "@/lib/user-store";
import { mediaPartita, useCacche, useSalvaCacche } from "@/lib/cacche";
@@ -11,6 +11,7 @@ export function SondaggioCacche({ eventoId }: { eventoId: string }) {
const io = useGiocatoreCorrente();
const { righe } = useCacche();
const salva = useSalvaCacche();
const { righe: squadra } = useGiocatoriSquadra();
const dellaPartita = righe.filter((r) => r.evento_id === eventoId);
const mia = dellaPartita.find((r) => r.giocatore_id === io?.id);
@@ -18,7 +19,10 @@ export function SondaggioCacche({ eventoId }: { eventoId: string }) {
const classifica = [...dellaPartita]
.sort((a, b) => b.quantita - a.quantita)
.slice(0, 3)
.map((r) => ({ ...r, nome: giocatori.find((g) => g.id === r.giocatore_id)?.nome ?? "—" }));
.map((r) => {
const g = squadra.find((g) => g.id === r.giocatore_id);
return { ...r, nome: g ? nomeCompleto(g) : "—" };
});
async function rispondi(quantita: number) {
if (!io) return;
+7 -5
View File
@@ -3,7 +3,7 @@ import { Check, CircleDot, Loader2, Pencil } from "lucide-react";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { Avatar } from "@/components/crapp/Avatar";
import { giocatori } from "@/lib/crapp-data";
import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import { useAssegnaTurno, useTurniPalloni } from "@/lib/palloni";
import { useGiocatoreCorrente } from "@/lib/user-store";
@@ -12,10 +12,12 @@ export function TurnoPalloni({ eventoId }: { eventoId: string }) {
const { salvati, turni, isPending } = useTurniPalloni();
const assegna = useAssegnaTurno();
const io = useGiocatoreCorrente();
const { righe: squadra } = useGiocatoriSquadra();
const rosa = squadra.filter((g) => g.attivo);
const id = turni[eventoId];
const proposto = !salvati[eventoId];
const giocatore = giocatori.find((g) => g.id === id);
const giocatore = rosa.find((g) => g.id === id);
function scegli(giocatoreId: string) {
setAperto(false);
@@ -46,7 +48,7 @@ export function TurnoPalloni({ eventoId }: { eventoId: string }) {
{isPending || assegna.isPending ? (
<Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
) : null}
<span className="truncate">{giocatore ? giocatore.nome : "Da assegnare"}</span>
<span className="truncate">{giocatore ? nomeCompleto(giocatore) : "Da assegnare"}</span>
{proposto && giocatore ? (
<span className="shrink-0 rounded-full bg-card px-1.5 py-0.5 text-[9px] font-bold uppercase text-muted-foreground">
proposto
@@ -59,7 +61,7 @@ export function TurnoPalloni({ eventoId }: { eventoId: string }) {
{aperto ? (
<div className="mt-2.5 max-h-56 space-y-1 overflow-y-auto rounded-xl bg-card p-1.5">
{giocatori.map((g) => (
{rosa.map((g) => (
<button
key={g.id}
type="button"
@@ -70,7 +72,7 @@ export function TurnoPalloni({ eventoId }: { eventoId: string }) {
)}
>
<Avatar id={g.id} fallback={String(g.numero)} className="h-7 w-7 text-xs" />
<span className="min-w-0 flex-1 truncate">{g.nome}</span>
<span className="min-w-0 flex-1 truncate">{nomeCompleto(g)}</span>
{g.id === id ? <Check className="h-4 w-4 shrink-0 text-accent" /> : null}
</button>
))}
+6 -4
View File
@@ -2,7 +2,7 @@ import { useState } from "react";
import { Crown, Vote } from "lucide-react";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { giocatori } from "@/lib/crapp-data";
import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import { useGiocatoreCorrente } from "@/lib/user-store";
import { conteggioPartita, mioVoto, useVotaMvp, useVotiMvp, type VotoMvp } from "@/lib/mvp-voti";
@@ -11,6 +11,8 @@ export function VotazioneMvp({ matchId }: { matchId: string }) {
const io = useGiocatoreCorrente();
const voti = useVotiMvp();
const vota = useVotaMvp();
const { righe: squadra } = useGiocatoriSquadra();
const rosa = squadra.filter((g) => g.attivo);
const [aperto, setAperto] = useState(false);
const tutti: VotoMvp[] = voti.data ?? [];
@@ -69,12 +71,12 @@ export function VotazioneMvp({ matchId }: { matchId: string }) {
{aperto ? (
<div className="mt-3 grid max-h-60 grid-cols-2 gap-1.5 overflow-y-auto">
{giocatori.map((g) => (
{rosa.map((g) => (
<button
key={g.id}
type="button"
disabled={vota.isPending}
onClick={() => invia(g.id, g.nome)}
onClick={() => invia(g.id, nomeCompleto(g))}
className={cn(
"truncate rounded-xl px-2.5 py-2 text-left text-xs font-semibold transition-colors",
mio?.votato_id === g.id
@@ -82,7 +84,7 @@ export function VotazioneMvp({ matchId }: { matchId: string }) {
: "bg-card text-foreground",
)}
>
{g.nome}
{nomeCompleto(g)}
</button>
))}
</div>
+6 -4
View File
@@ -2,7 +2,7 @@ import { useState } from "react";
import { Check, Crown, Sparkles } from "lucide-react";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { giocatori } from "@/lib/crapp-data";
import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import { useGiocatoreCorrente } from "@/lib/user-store";
import {
categorieSocial,
@@ -18,6 +18,8 @@ export function VotoSocial({ matchId }: { matchId: string }) {
const io = useGiocatoreCorrente();
const voti = useVotiSocial();
const vota = useVotaSocial();
const { righe: squadra } = useGiocatoriSquadra();
const rosa = squadra.filter((g) => g.attivo);
const [aperta, setAperta] = useState<string | null>(null);
const tutti = voti.data ?? [];
@@ -93,14 +95,14 @@ export function VotoSocial({ matchId }: { matchId: string }) {
{isOpen ? (
<div className="grid max-h-56 grid-cols-2 gap-1.5 overflow-y-auto border-t border-border p-3">
{giocatori
{rosa
.filter((g) => g.id !== io?.id)
.map((g) => (
<button
key={g.id}
type="button"
disabled={vota.isPending}
onClick={() => invia(cat.id, g.id, g.nome)}
onClick={() => invia(cat.id, g.id, nomeCompleto(g))}
className={cn(
"truncate rounded-xl px-2.5 py-2 text-left text-xs font-semibold transition-colors",
mio?.votato_id === g.id
@@ -108,7 +110,7 @@ export function VotoSocial({ matchId }: { matchId: string }) {
: "bg-secondary text-foreground",
)}
>
{g.nome}
{nomeCompleto(g)}
</button>
))}
</div>