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:
@@ -1,9 +1,10 @@
|
|||||||
import { MapPin, Clock, Users, Cake, ArrowRight } from "lucide-react";
|
import { MapPin, Clock, Users, Cake, ArrowRight } from "lucide-react";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { cn } from "@/lib/utils";
|
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 type { Evento } from "@/lib/eventi";
|
||||||
import { Barra } from "@/components/motion/Barra";
|
import { Barra } from "@/components/motion/Barra";
|
||||||
|
import { useGiocatoriSquadra } from "@/lib/giocatori-squadra";
|
||||||
import { usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze";
|
import { usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze";
|
||||||
import { useGiocatoreCorrente } from "@/lib/user-store";
|
import { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
|
|
||||||
@@ -36,16 +37,18 @@ export function EventoCard({
|
|||||||
const { risposte } = usePresenzeEvento(evento.id);
|
const { risposte } = usePresenzeEvento(evento.id);
|
||||||
const salva = useSalvaPresenza();
|
const salva = useSalvaPresenza();
|
||||||
const io = useGiocatoreCorrente();
|
const io = useGiocatoreCorrente();
|
||||||
|
const { righe: squadra } = useGiocatoriSquadra();
|
||||||
|
const rosa = squadra.filter((g) => g.attivo);
|
||||||
const stato = io ? risposte[io.id] : undefined;
|
const stato = io ? risposte[io.id] : undefined;
|
||||||
const presentiVeri = giocatori.filter(
|
const presentiVeri = rosa.filter(
|
||||||
(g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo",
|
(g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo",
|
||||||
).length;
|
).length;
|
||||||
const tipo =
|
const tipo =
|
||||||
evento.tipo === "partita" && !evento.campionato
|
evento.tipo === "partita" && !evento.campionato
|
||||||
? { label: "Amichevole", className: "bg-accent/70 text-accent-foreground" }
|
? { label: "Amichevole", className: "bg-accent/70 text-accent-foreground" }
|
||||||
: tipoMeta[evento.tipo];
|
: tipoMeta[evento.tipo];
|
||||||
const totale = giocatori.length;
|
const totale = rosa.length;
|
||||||
const perc = Math.round((presentiVeri / totale) * 100);
|
const perc = totale ? Math.round((presentiVeri / totale) * 100) : 0;
|
||||||
const isCompleanno = evento.tipo === "compleanno";
|
const isCompleanno = evento.tipo === "compleanno";
|
||||||
|
|
||||||
if (isCompleanno) {
|
if (isCompleanno) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ClipboardCheck, Lock } from "lucide-react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Avatar } from "@/components/crapp/Avatar";
|
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 { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
import { mieiVoti, pagellePartita, usePagelle, useVotaPagella } from "@/lib/pagelle";
|
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. */
|
/** Pagelle di fine partita: voto anonimo 1-10 a ciascun compagno. */
|
||||||
export function Pagelle({
|
export function Pagelle({
|
||||||
matchId,
|
matchId,
|
||||||
convocati = giocatori,
|
convocati,
|
||||||
chiuse = false,
|
chiuse = false,
|
||||||
}: {
|
}: {
|
||||||
matchId: string;
|
matchId: string;
|
||||||
convocati?: typeof giocatori;
|
convocati: Giocatore[];
|
||||||
chiuse?: boolean;
|
chiuse?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const io = useGiocatoreCorrente();
|
const io = useGiocatoreCorrente();
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import { toast } from "sonner";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Avatar } from "@/components/crapp/Avatar";
|
import { Avatar } from "@/components/crapp/Avatar";
|
||||||
import { Barra } from "@/components/motion/Barra";
|
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 { usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze";
|
||||||
|
import { useRosa } from "@/lib/rosa";
|
||||||
import { useGiocatoreCorrente } from "@/lib/user-store";
|
import { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
import { useIsAdmin } from "@/lib/ruoli";
|
import { useIsAdmin } from "@/lib/ruoli";
|
||||||
|
|
||||||
@@ -16,13 +17,13 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
|
|||||||
const salva = useSalvaPresenza();
|
const salva = useSalvaPresenza();
|
||||||
const io = useGiocatoreCorrente();
|
const io = useGiocatoreCorrente();
|
||||||
const admin = useIsAdmin();
|
const admin = useIsAdmin();
|
||||||
|
const rosa = useRosa();
|
||||||
const [sollecito, setSollecito] = useState(false);
|
const [sollecito, setSollecito] = useState(false);
|
||||||
|
|
||||||
const mancanti = giocatori.filter((g) => !risposte[g.id]);
|
const mancanti = rosa.filter((g) => !risposte[g.id]);
|
||||||
const risposteN = giocatori.length - mancanti.length;
|
const risposteN = rosa.length - mancanti.length;
|
||||||
const perc = Math.round((risposteN / giocatori.length) * 100);
|
const perc = rosa.length ? Math.round((risposteN / rosa.length) * 100) : 0;
|
||||||
const daSollecitare =
|
const daSollecitare = mancanti.length + rosa.filter((g) => risposte[g.id] === "forse").length;
|
||||||
mancanti.length + giocatori.filter((g) => risposte[g.id] === "forse").length;
|
|
||||||
|
|
||||||
async function sollecita() {
|
async function sollecita() {
|
||||||
setSollecito(true);
|
setSollecito(true);
|
||||||
@@ -51,7 +52,7 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
|
|||||||
<div className="rounded-3xl bg-card p-4 shadow-card">
|
<div className="rounded-3xl bg-card p-4 shadow-card">
|
||||||
<div className="flex items-baseline justify-between">
|
<div className="flex items-baseline justify-between">
|
||||||
<p className="text-sm font-bold">
|
<p className="text-sm font-bold">
|
||||||
Hanno risposto {risposteN}/{giocatori.length}
|
Hanno risposto {risposteN}/{rosa.length}
|
||||||
</p>
|
</p>
|
||||||
<span className="font-display text-xl leading-none">{perc}%</span>
|
<span className="font-display text-xl leading-none">{perc}%</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,7 +60,7 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
|
|||||||
|
|
||||||
<div className="mt-3 flex flex-wrap gap-1.5">
|
<div className="mt-3 flex flex-wrap gap-1.5">
|
||||||
{ordine.map((s) => {
|
{ordine.map((s) => {
|
||||||
const n = giocatori.filter((g) => risposte[g.id] === s).length;
|
const n = rosa.filter((g) => risposte[g.id] === s).length;
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
key={s}
|
key={s}
|
||||||
@@ -130,7 +131,7 @@ export function RosaPresenze({ eventoId }: { eventoId: string }) {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{ordine.map((s) => {
|
{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;
|
if (lista.length === 0) return null;
|
||||||
return (
|
return (
|
||||||
<Gruppo
|
<Gruppo
|
||||||
@@ -162,7 +163,7 @@ function Gruppo({
|
|||||||
}: {
|
}: {
|
||||||
titolo: string;
|
titolo: string;
|
||||||
n: number;
|
n: number;
|
||||||
lista: typeof giocatori;
|
lista: Giocatore[];
|
||||||
attenzione?: boolean;
|
attenzione?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
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 { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
import { mediaPartita, useCacche, useSalvaCacche } from "@/lib/cacche";
|
import { mediaPartita, useCacche, useSalvaCacche } from "@/lib/cacche";
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ export function SondaggioCacche({ eventoId }: { eventoId: string }) {
|
|||||||
const io = useGiocatoreCorrente();
|
const io = useGiocatoreCorrente();
|
||||||
const { righe } = useCacche();
|
const { righe } = useCacche();
|
||||||
const salva = useSalvaCacche();
|
const salva = useSalvaCacche();
|
||||||
|
const { righe: squadra } = useGiocatoriSquadra();
|
||||||
|
|
||||||
const dellaPartita = righe.filter((r) => r.evento_id === eventoId);
|
const dellaPartita = righe.filter((r) => r.evento_id === eventoId);
|
||||||
const mia = dellaPartita.find((r) => r.giocatore_id === io?.id);
|
const mia = dellaPartita.find((r) => r.giocatore_id === io?.id);
|
||||||
@@ -18,7 +19,10 @@ export function SondaggioCacche({ eventoId }: { eventoId: string }) {
|
|||||||
const classifica = [...dellaPartita]
|
const classifica = [...dellaPartita]
|
||||||
.sort((a, b) => b.quantita - a.quantita)
|
.sort((a, b) => b.quantita - a.quantita)
|
||||||
.slice(0, 3)
|
.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) {
|
async function rispondi(quantita: number) {
|
||||||
if (!io) return;
|
if (!io) return;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Check, CircleDot, Loader2, Pencil } from "lucide-react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Avatar } from "@/components/crapp/Avatar";
|
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 { useAssegnaTurno, useTurniPalloni } from "@/lib/palloni";
|
||||||
import { useGiocatoreCorrente } from "@/lib/user-store";
|
import { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
|
|
||||||
@@ -12,10 +12,12 @@ export function TurnoPalloni({ eventoId }: { eventoId: string }) {
|
|||||||
const { salvati, turni, isPending } = useTurniPalloni();
|
const { salvati, turni, isPending } = useTurniPalloni();
|
||||||
const assegna = useAssegnaTurno();
|
const assegna = useAssegnaTurno();
|
||||||
const io = useGiocatoreCorrente();
|
const io = useGiocatoreCorrente();
|
||||||
|
const { righe: squadra } = useGiocatoriSquadra();
|
||||||
|
const rosa = squadra.filter((g) => g.attivo);
|
||||||
|
|
||||||
const id = turni[eventoId];
|
const id = turni[eventoId];
|
||||||
const proposto = !salvati[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) {
|
function scegli(giocatoreId: string) {
|
||||||
setAperto(false);
|
setAperto(false);
|
||||||
@@ -46,7 +48,7 @@ export function TurnoPalloni({ eventoId }: { eventoId: string }) {
|
|||||||
{isPending || assegna.isPending ? (
|
{isPending || assegna.isPending ? (
|
||||||
<Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
|
<Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
|
||||||
) : null}
|
) : null}
|
||||||
<span className="truncate">{giocatore ? giocatore.nome : "Da assegnare"}</span>
|
<span className="truncate">{giocatore ? nomeCompleto(giocatore) : "Da assegnare"}</span>
|
||||||
{proposto && giocatore ? (
|
{proposto && giocatore ? (
|
||||||
<span className="shrink-0 rounded-full bg-card px-1.5 py-0.5 text-[9px] font-bold uppercase text-muted-foreground">
|
<span className="shrink-0 rounded-full bg-card px-1.5 py-0.5 text-[9px] font-bold uppercase text-muted-foreground">
|
||||||
proposto
|
proposto
|
||||||
@@ -59,7 +61,7 @@ export function TurnoPalloni({ eventoId }: { eventoId: string }) {
|
|||||||
|
|
||||||
{aperto ? (
|
{aperto ? (
|
||||||
<div className="mt-2.5 max-h-56 space-y-1 overflow-y-auto rounded-xl bg-card p-1.5">
|
<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
|
<button
|
||||||
key={g.id}
|
key={g.id}
|
||||||
type="button"
|
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" />
|
<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}
|
{g.id === id ? <Check className="h-4 w-4 shrink-0 text-accent" /> : null}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
|||||||
import { Crown, Vote } from "lucide-react";
|
import { Crown, Vote } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
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 { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
import { conteggioPartita, mioVoto, useVotaMvp, useVotiMvp, type VotoMvp } from "@/lib/mvp-voti";
|
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 io = useGiocatoreCorrente();
|
||||||
const voti = useVotiMvp();
|
const voti = useVotiMvp();
|
||||||
const vota = useVotaMvp();
|
const vota = useVotaMvp();
|
||||||
|
const { righe: squadra } = useGiocatoriSquadra();
|
||||||
|
const rosa = squadra.filter((g) => g.attivo);
|
||||||
const [aperto, setAperto] = useState(false);
|
const [aperto, setAperto] = useState(false);
|
||||||
|
|
||||||
const tutti: VotoMvp[] = voti.data ?? [];
|
const tutti: VotoMvp[] = voti.data ?? [];
|
||||||
@@ -69,12 +71,12 @@ export function VotazioneMvp({ matchId }: { matchId: string }) {
|
|||||||
|
|
||||||
{aperto ? (
|
{aperto ? (
|
||||||
<div className="mt-3 grid max-h-60 grid-cols-2 gap-1.5 overflow-y-auto">
|
<div className="mt-3 grid max-h-60 grid-cols-2 gap-1.5 overflow-y-auto">
|
||||||
{giocatori.map((g) => (
|
{rosa.map((g) => (
|
||||||
<button
|
<button
|
||||||
key={g.id}
|
key={g.id}
|
||||||
type="button"
|
type="button"
|
||||||
disabled={vota.isPending}
|
disabled={vota.isPending}
|
||||||
onClick={() => invia(g.id, g.nome)}
|
onClick={() => invia(g.id, nomeCompleto(g))}
|
||||||
className={cn(
|
className={cn(
|
||||||
"truncate rounded-xl px-2.5 py-2 text-left text-xs font-semibold transition-colors",
|
"truncate rounded-xl px-2.5 py-2 text-left text-xs font-semibold transition-colors",
|
||||||
mio?.votato_id === g.id
|
mio?.votato_id === g.id
|
||||||
@@ -82,7 +84,7 @@ export function VotazioneMvp({ matchId }: { matchId: string }) {
|
|||||||
: "bg-card text-foreground",
|
: "bg-card text-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{g.nome}
|
{nomeCompleto(g)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
|||||||
import { Check, Crown, Sparkles } from "lucide-react";
|
import { Check, Crown, Sparkles } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
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 { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
import {
|
import {
|
||||||
categorieSocial,
|
categorieSocial,
|
||||||
@@ -18,6 +18,8 @@ export function VotoSocial({ matchId }: { matchId: string }) {
|
|||||||
const io = useGiocatoreCorrente();
|
const io = useGiocatoreCorrente();
|
||||||
const voti = useVotiSocial();
|
const voti = useVotiSocial();
|
||||||
const vota = useVotaSocial();
|
const vota = useVotaSocial();
|
||||||
|
const { righe: squadra } = useGiocatoriSquadra();
|
||||||
|
const rosa = squadra.filter((g) => g.attivo);
|
||||||
const [aperta, setAperta] = useState<string | null>(null);
|
const [aperta, setAperta] = useState<string | null>(null);
|
||||||
|
|
||||||
const tutti = voti.data ?? [];
|
const tutti = voti.data ?? [];
|
||||||
@@ -93,14 +95,14 @@ export function VotoSocial({ matchId }: { matchId: string }) {
|
|||||||
|
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<div className="grid max-h-56 grid-cols-2 gap-1.5 overflow-y-auto border-t border-border p-3">
|
<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)
|
.filter((g) => g.id !== io?.id)
|
||||||
.map((g) => (
|
.map((g) => (
|
||||||
<button
|
<button
|
||||||
key={g.id}
|
key={g.id}
|
||||||
type="button"
|
type="button"
|
||||||
disabled={vota.isPending}
|
disabled={vota.isPending}
|
||||||
onClick={() => invia(cat.id, g.id, g.nome)}
|
onClick={() => invia(cat.id, g.id, nomeCompleto(g))}
|
||||||
className={cn(
|
className={cn(
|
||||||
"truncate rounded-xl px-2.5 py-2 text-left text-xs font-semibold transition-colors",
|
"truncate rounded-xl px-2.5 py-2 text-left text-xs font-semibold transition-colors",
|
||||||
mio?.votato_id === g.id
|
mio?.votato_id === g.id
|
||||||
@@ -108,7 +110,7 @@ export function VotoSocial({ matchId }: { matchId: string }) {
|
|||||||
: "bg-secondary text-foreground",
|
: "bg-secondary text-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{g.nome}
|
{nomeCompleto(g)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+7
-6
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { supabase } from "@/integrations/supabase/client";
|
import { supabase } from "@/integrations/supabase/client";
|
||||||
import { giocatori } from "./crapp-data";
|
import type { Giocatore } from "./crapp-data";
|
||||||
|
|
||||||
export type EventoTipo = "partita" | "allenamento" | "evento" | "compleanno";
|
export type EventoTipo = "partita" | "allenamento" | "evento" | "compleanno";
|
||||||
|
|
||||||
@@ -158,8 +158,9 @@ export function useEliminaEvento() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Compleanni della rosa, come eventi di calendario dell'anno indicato. */
|
/** Compleanni della rosa, come eventi di calendario dell'anno indicato. */
|
||||||
export function compleanniEventi(anno = new Date().getFullYear()): Evento[] {
|
export function compleanniEventi(rosa: Giocatore[], anno = new Date().getFullYear()): Evento[] {
|
||||||
return giocatori
|
return rosa
|
||||||
|
.filter((g) => g.nascita)
|
||||||
.map((g) => {
|
.map((g) => {
|
||||||
const md = g.nascita.slice(5);
|
const md = g.nascita.slice(5);
|
||||||
const eta = anno - Number(g.nascita.slice(0, 4));
|
const eta = anno - Number(g.nascita.slice(0, 4));
|
||||||
@@ -181,7 +182,7 @@ export function compleanniEventi(anno = new Date().getFullYear()): Evento[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Rosa convocata per un evento: se non specificata vale tutta la rosa. */
|
/** Rosa convocata per un evento: se non specificata vale tutta la rosa. */
|
||||||
export function convocatiEvento(evento: Evento | null) {
|
export function convocatiEvento(evento: Evento | null, rosa: Giocatore[]) {
|
||||||
if (!evento || evento.convocati.length === 0) return giocatori;
|
if (!evento || evento.convocati.length === 0) return rosa;
|
||||||
return giocatori.filter((g) => evento.convocati.includes(g.id));
|
return rosa.filter((g) => evento.convocati.includes(g.id));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { giocatori } from "./crapp-data";
|
import type { Giocatore } from "./crapp-data";
|
||||||
import { azioniMeta, totaliPerGiocatore, type ScoutMatch } from "./scout-store";
|
import { azioniMeta, totaliPerGiocatore, type ScoutMatch } from "./scout-store";
|
||||||
|
|
||||||
/** Riga CSV con separatore ";" (Excel IT), riusata anche dall'export tesseramento. */
|
/** Riga CSV con separatore ";" (Excel IT), riusata anche dall'export tesseramento. */
|
||||||
@@ -12,7 +12,7 @@ export function rigaCsv(campi: Array<string | number>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Esporta la scoutizzazione di una partita in CSV (separatore ";" per Excel IT). */
|
/** Esporta la scoutizzazione di una partita in CSV (separatore ";" per Excel IT). */
|
||||||
export function csvScoutMatch(match: ScoutMatch): string {
|
export function csvScoutMatch(match: ScoutMatch, rosa: Giocatore[]): string {
|
||||||
const righe: string[] = [];
|
const righe: string[] = [];
|
||||||
righe.push(
|
righe.push(
|
||||||
rigaCsv([
|
rigaCsv([
|
||||||
@@ -29,7 +29,7 @@ export function csvScoutMatch(match: ScoutMatch): string {
|
|||||||
righe.push("");
|
righe.push("");
|
||||||
righe.push(rigaCsv(["Numero", "Giocatore", "Ruolo", "Punti", "Ace", "Muri", "Errori"]));
|
righe.push(rigaCsv(["Numero", "Giocatore", "Ruolo", "Punti", "Ace", "Muri", "Errori"]));
|
||||||
const totali = totaliPerGiocatore(match.azioni);
|
const totali = totaliPerGiocatore(match.azioni);
|
||||||
for (const g of giocatori) {
|
for (const g of rosa) {
|
||||||
const t = totali.get(g.id);
|
const t = totali.get(g.id);
|
||||||
if (!t) continue;
|
if (!t) continue;
|
||||||
righe.push(rigaCsv([g.numero, g.nome, g.ruolo, t.punti, t.ace, t.muri, t.errori]));
|
righe.push(rigaCsv([g.numero, g.nome, g.ruolo, t.punti, t.ace, t.muri, t.errori]));
|
||||||
@@ -37,7 +37,7 @@ export function csvScoutMatch(match: ScoutMatch): string {
|
|||||||
righe.push("");
|
righe.push("");
|
||||||
righe.push(rigaCsv(["Set", "Giocatore", "Azione"]));
|
righe.push(rigaCsv(["Set", "Giocatore", "Azione"]));
|
||||||
for (const a of match.azioni) {
|
for (const a of match.azioni) {
|
||||||
const g = giocatori.find((x) => x.id === a.giocatoreId);
|
const g = rosa.find((x) => x.id === a.giocatoreId);
|
||||||
righe.push(rigaCsv([a.set, g?.nome ?? "—", azioniMeta[a.tipo].label]));
|
righe.push(rigaCsv([a.set, g?.nome ?? "—", azioniMeta[a.tipo].label]));
|
||||||
}
|
}
|
||||||
return righe.join("\n");
|
return righe.join("\n");
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
import { ArrowLeft, MapPin, Clock, Users, Dumbbell, CalendarDays } from "lucide-react";
|
import { ArrowLeft, MapPin, Clock, Users, Dumbbell, CalendarDays } from "lucide-react";
|
||||||
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
||||||
import { formatData, giocatori } from "@/lib/crapp-data";
|
import { formatData } from "@/lib/crapp-data";
|
||||||
import { convocatiEvento, useEvento } from "@/lib/eventi";
|
import { convocatiEvento, useEvento } from "@/lib/eventi";
|
||||||
|
import { useRosa } from "@/lib/rosa";
|
||||||
import { TurnoPalloni } from "@/components/crapp/TurnoPalloni";
|
import { TurnoPalloni } from "@/components/crapp/TurnoPalloni";
|
||||||
import { RosaPresenze } from "@/components/crapp/RosaPresenze";
|
import { RosaPresenze } from "@/components/crapp/RosaPresenze";
|
||||||
import { usePresenzeEvento } from "@/lib/presenze";
|
import { usePresenzeEvento } from "@/lib/presenze";
|
||||||
@@ -34,7 +35,8 @@ function AllenamentoDetail() {
|
|||||||
const { id } = Route.useParams();
|
const { id } = Route.useParams();
|
||||||
const { evento } = useEvento(id);
|
const { evento } = useEvento(id);
|
||||||
const { risposte } = usePresenzeEvento(id);
|
const { risposte } = usePresenzeEvento(id);
|
||||||
const presentiVeri = giocatori.filter(
|
const rosa = useRosa();
|
||||||
|
const presentiVeri = rosa.filter(
|
||||||
(g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo",
|
(g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo",
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ function AllenamentoDetail() {
|
|||||||
|
|
||||||
<div className="mt-4 inline-flex items-center gap-2 rounded-full bg-secondary px-3 py-1.5 text-xs font-semibold">
|
<div className="mt-4 inline-flex items-center gap-2 rounded-full bg-secondary px-3 py-1.5 text-xs font-semibold">
|
||||||
<Users className="h-4 w-4" />
|
<Users className="h-4 w-4" />
|
||||||
Conferme: {presentiVeri}/{convocatiEvento(evento).length}
|
Conferme: {presentiVeri}/{convocatiEvento(evento, rosa).length}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TurnoPalloni eventoId={evento.id} />
|
<TurnoPalloni eventoId={evento.id} />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
||||||
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
||||||
import { compleanniEventi, useEventi, type Evento } from "@/lib/eventi";
|
import { compleanniEventi, useEventi, type Evento } from "@/lib/eventi";
|
||||||
|
import { useRosa } from "@/lib/rosa";
|
||||||
import { useGiocatoreCorrente } from "@/lib/user-store";
|
import { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
import { useIsAdmin } from "@/lib/ruoli";
|
import { useIsAdmin } from "@/lib/ruoli";
|
||||||
import {
|
import {
|
||||||
@@ -100,11 +101,12 @@ function Calendario() {
|
|||||||
const io = useGiocatoreCorrente();
|
const io = useGiocatoreCorrente();
|
||||||
const admin = useIsAdmin();
|
const admin = useIsAdmin();
|
||||||
const { eventi } = useEventi();
|
const { eventi } = useEventi();
|
||||||
|
const rosa = useRosa();
|
||||||
const { anno, mese, precedente, successivo } = useMeseNav();
|
const { anno, mese, precedente, successivo } = useMeseNav();
|
||||||
const { giorni, offsetLunedi } = giorniDelMese(anno, mese);
|
const { giorni, offsetLunedi } = giorniDelMese(anno, mese);
|
||||||
const mesePrefix = `${anno}-${pad2(mese + 1)}`;
|
const mesePrefix = `${anno}-${pad2(mese + 1)}`;
|
||||||
|
|
||||||
const compleanni = compleanniEventi(anno);
|
const compleanni = compleanniEventi(rosa, anno);
|
||||||
const compleanniMese = compleanni.filter((c) => c.data.startsWith(mesePrefix));
|
const compleanniMese = compleanni.filter((c) => c.data.startsWith(mesePrefix));
|
||||||
const eventiMese = eventi.filter((e) => e.data.startsWith(mesePrefix));
|
const eventiMese = eventi.filter((e) => e.data.startsWith(mesePrefix));
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { ArrowLeft, CalendarPlus, Loader2, Pencil, Trash2 } from "lucide-react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
||||||
import { formatData, giocatori } from "@/lib/crapp-data";
|
import { formatData } from "@/lib/crapp-data";
|
||||||
|
import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
|
||||||
import {
|
import {
|
||||||
categoriaEvento,
|
categoriaEvento,
|
||||||
daCategoria,
|
daCategoria,
|
||||||
@@ -50,9 +51,11 @@ function GestioneEventi() {
|
|||||||
const io = useGiocatoreCorrente();
|
const io = useGiocatoreCorrente();
|
||||||
const admin = useIsAdmin();
|
const admin = useIsAdmin();
|
||||||
const { eventi, isPending } = useEventi();
|
const { eventi, isPending } = useEventi();
|
||||||
|
const { righe: squadra } = useGiocatoriSquadra();
|
||||||
const salva = useSalvaEvento();
|
const salva = useSalvaEvento();
|
||||||
const elimina = useEliminaEvento();
|
const elimina = useEliminaEvento();
|
||||||
const [bozza, setBozza] = useState<Evento | null>(null);
|
const [bozza, setBozza] = useState<Evento | null>(null);
|
||||||
|
const rosa = squadra.filter((g) => g.attivo);
|
||||||
|
|
||||||
if (!io || !admin) {
|
if (!io || !admin) {
|
||||||
return (
|
return (
|
||||||
@@ -228,7 +231,7 @@ function GestioneEventi() {
|
|||||||
label={`Convocati (${bozza.convocati.length === 0 ? "tutta la rosa" : bozza.convocati.length})`}
|
label={`Convocati (${bozza.convocati.length === 0 ? "tutta la rosa" : bozza.convocati.length})`}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-2 gap-1.5">
|
<div className="grid grid-cols-2 gap-1.5">
|
||||||
{giocatori.map((g) => {
|
{rosa.map((g) => {
|
||||||
const scelto = bozza.convocati.includes(g.id);
|
const scelto = bozza.convocati.includes(g.id);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -248,7 +251,7 @@ function GestioneEventi() {
|
|||||||
: "bg-secondary text-muted-foreground",
|
: "bg-secondary text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{g.nome}
|
{nomeCompleto(g)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { createFileRoute, Link } from "@tanstack/react-router";
|
|||||||
import { ArrowLeft, MapPin, Clock, Users, Trophy, Swords, Download } from "lucide-react";
|
import { ArrowLeft, MapPin, Clock, Users, Trophy, Swords, Download } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
||||||
import { formatData, giocatori } from "@/lib/crapp-data";
|
import { formatData } from "@/lib/crapp-data";
|
||||||
import { convocatiEvento, useEvento } from "@/lib/eventi";
|
import { convocatiEvento, useEvento } from "@/lib/eventi";
|
||||||
|
import { useRosa } from "@/lib/rosa";
|
||||||
import { useCsi } from "@/lib/csi";
|
import { useCsi } from "@/lib/csi";
|
||||||
import { matchDaPartitaCsi, partiteGiocate } from "@/lib/csi-core";
|
import { matchDaPartitaCsi, partiteGiocate } from "@/lib/csi-core";
|
||||||
import { Pagelle } from "@/components/crapp/Pagelle";
|
import { Pagelle } from "@/components/crapp/Pagelle";
|
||||||
@@ -49,7 +50,8 @@ function PartitaDetail() {
|
|||||||
const scoutMatches = useScoutMatches();
|
const scoutMatches = useScoutMatches();
|
||||||
const { data: csi } = useCsi();
|
const { data: csi } = useCsi();
|
||||||
const { risposte } = usePresenzeEvento(id);
|
const { risposte } = usePresenzeEvento(id);
|
||||||
const presentiVeri = giocatori.filter(
|
const rosa = useRosa();
|
||||||
|
const presentiVeri = rosa.filter(
|
||||||
(g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo",
|
(g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo",
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ function PartitaDetail() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const convocati = convocatiEvento(evento);
|
const convocati = convocatiEvento(evento, rosa);
|
||||||
const scout = scoutMatches.find((m) => m.id === evento.id || m.data === evento.data) ?? null;
|
const scout = scoutMatches.find((m) => m.id === evento.id || m.data === evento.data) ?? null;
|
||||||
const csiMatch = csi
|
const csiMatch = csi
|
||||||
? partiteGiocate(csi.partite).find((p) => p.data === evento.data)
|
? partiteGiocate(csi.partite).find((p) => p.data === evento.data)
|
||||||
@@ -229,7 +231,7 @@ function PartitaDetail() {
|
|||||||
</p>
|
</p>
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
{[...totaliPerGiocatore(scout.azioni).entries()].map(([gid, t]) => {
|
{[...totaliPerGiocatore(scout.azioni).entries()].map(([gid, t]) => {
|
||||||
const g = giocatori.find((x) => x.id === gid);
|
const g = rosa.find((x) => x.id === gid);
|
||||||
if (!g) return null;
|
if (!g) return null;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -250,7 +252,10 @@ function PartitaDetail() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
scaricaCsv(`scout-${scout.data}-${scout.avversario}.csv`, csvScoutMatch(scout))
|
scaricaCsv(
|
||||||
|
`scout-${scout.data}-${scout.avversario}.csv`,
|
||||||
|
csvScoutMatch(scout, rosa),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
className="premi mt-4 flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-grad py-3 text-sm font-bold uppercase text-accent-foreground shadow-pop"
|
className="premi mt-4 flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-grad py-3 text-sm font-bold uppercase text-accent-foreground shadow-pop"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|||||||
import { Undo2, Save, CheckCircle2, Radio, Lock, CalendarX2, LogOut } from "lucide-react";
|
import { Undo2, Save, CheckCircle2, Radio, Lock, CalendarX2, LogOut } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { giocatori, formatData } from "@/lib/crapp-data";
|
import { formatData } from "@/lib/crapp-data";
|
||||||
import type { Evento } from "@/lib/eventi";
|
import type { Evento } from "@/lib/eventi";
|
||||||
|
import { useRosa } from "@/lib/rosa";
|
||||||
import { useGiocatoreCorrente } from "@/lib/user-store";
|
import { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
import { useIsAdmin } from "@/lib/ruoli";
|
import { useIsAdmin } from "@/lib/ruoli";
|
||||||
import { usePresenzeEvento } from "@/lib/presenze";
|
import { usePresenzeEvento } from "@/lib/presenze";
|
||||||
@@ -214,15 +215,16 @@ function ScoutBoard({
|
|||||||
const cancella = useCancellaStatoScout();
|
const cancella = useCancellaStatoScout();
|
||||||
const salvaMatch = useSalvaScoutMatch();
|
const salvaMatch = useSalvaScoutMatch();
|
||||||
const { risposte } = usePresenzeEvento(partita.id);
|
const { risposte } = usePresenzeEvento(partita.id);
|
||||||
|
const rosa = useRosa();
|
||||||
const finito = useRef(false);
|
const finito = useRef(false);
|
||||||
|
|
||||||
/** In campo solo chi ha confermato la presenza (anche in ritardo). */
|
/** In campo solo chi ha confermato la presenza (anche in ritardo). */
|
||||||
const convocati = useMemo(() => {
|
const convocati = useMemo(() => {
|
||||||
const presenti = giocatori.filter(
|
const presenti = rosa.filter(
|
||||||
(g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo",
|
(g) => risposte[g.id] === "presente" || risposte[g.id] === "ritardo",
|
||||||
);
|
);
|
||||||
return presenti.length > 0 ? presenti : giocatori;
|
return presenti.length > 0 ? presenti : rosa;
|
||||||
}, [risposte]);
|
}, [risposte, rosa]);
|
||||||
|
|
||||||
// Salvataggio condiviso: se chi scouta si disconnette, il prossimo riprende da qui.
|
// Salvataggio condiviso: se chi scouta si disconnette, il prossimo riprende da qui.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -453,7 +455,7 @@ function ScoutBoard({
|
|||||||
) : (
|
) : (
|
||||||
<ul className="mt-2 space-y-1.5">
|
<ul className="mt-2 space-y-1.5">
|
||||||
{ultime.map((a, i) => {
|
{ultime.map((a, i) => {
|
||||||
const g = giocatori.find((x) => x.id === a.giocatoreId);
|
const g = rosa.find((x) => x.id === a.giocatoreId);
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={a.id}
|
key={a.id}
|
||||||
|
|||||||
Reference in New Issue
Block a user