Aggiunge il tracciamento del tesseramento CSI in giocatori_squadra
Numero e data della tessera arrivano dal comitato dopo l'iscrizione, quindi li scrive solo un admin (come numero/ruolo): estende il trigger di M1/M5, aggiunge il pannello dedicato in /admin con badge e conteggio in dashboard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+65
-1
@@ -1,6 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import {
|
||||
BadgeCheck,
|
||||
ChevronDown,
|
||||
Download,
|
||||
FileText,
|
||||
@@ -25,9 +26,11 @@ import {
|
||||
useGiocatoriSquadra,
|
||||
useImpostaAttivo,
|
||||
useSalvaDatiSquadra,
|
||||
useSalvaTesseramento,
|
||||
useScollegaAccount,
|
||||
validaDatiSquadra,
|
||||
type DatiSquadra,
|
||||
type DatiTesseramento,
|
||||
type GiocatoreSquadra,
|
||||
} from "@/lib/giocatori-squadra";
|
||||
import { scaricaFile, useProfili, useSalvaProfilo } from "@/lib/profili";
|
||||
@@ -102,11 +105,18 @@ function ModificaGiocatore({ g, profilo }: { g: GiocatoreSquadra; profilo: Profi
|
||||
const { righe } = useGiocatoriSquadra();
|
||||
const salvaSquadra = useSalvaDatiSquadra();
|
||||
const salvaProfilo = useSalvaProfilo();
|
||||
const salvaTesseramento = useSalvaTesseramento();
|
||||
const scollega = useScollegaAccount();
|
||||
const impostaAttivo = useImpostaAttivo();
|
||||
|
||||
const [datiSquadra, setDatiSquadra] = useState<DatiSquadra | null>(null);
|
||||
const [bozza, setBozza] = useState<Profilo | null>(null);
|
||||
const [tesseramento, setTesseramento] = useState<DatiTesseramento | null>(null);
|
||||
|
||||
const tesseramentoCorrente: DatiTesseramento = tesseramento ?? {
|
||||
numeroTessera: g.numeroTessera,
|
||||
dataTessera: g.dataTessera,
|
||||
};
|
||||
|
||||
const squadraCorrente: DatiSquadra = datiSquadra ?? {
|
||||
nome: g.nome,
|
||||
@@ -148,6 +158,16 @@ function ModificaGiocatore({ g, profilo }: { g: GiocatoreSquadra; profilo: Profi
|
||||
}
|
||||
}
|
||||
|
||||
async function confermaTesseramento() {
|
||||
try {
|
||||
await salvaTesseramento.mutateAsync({ giocatoreId: g.id, dati: tesseramentoCorrente });
|
||||
setTesseramento(null);
|
||||
toast.success("Tesseramento aggiornato");
|
||||
} catch (e) {
|
||||
toast.error(messaggioErrore(e, "Salvataggio non riuscito"));
|
||||
}
|
||||
}
|
||||
|
||||
async function confermaProfilo() {
|
||||
try {
|
||||
await salvaProfilo.mutateAsync(profiloCorrente);
|
||||
@@ -229,6 +249,39 @@ function ModificaGiocatore({ g, profilo }: { g: GiocatoreSquadra; profilo: Profi
|
||||
Salva dati squadra
|
||||
</button>
|
||||
|
||||
<h3 className="font-display text-sm uppercase tracking-wide">Tesseramento CSI</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Campo label="Numero tessera">
|
||||
<input
|
||||
value={tesseramentoCorrente.numeroTessera ?? ""}
|
||||
maxLength={40}
|
||||
placeholder="Non ancora tesserato"
|
||||
onChange={(e) =>
|
||||
setTesseramento({ ...tesseramentoCorrente, numeroTessera: e.target.value || null })
|
||||
}
|
||||
className={classiInput}
|
||||
/>
|
||||
</Campo>
|
||||
<Campo label="Data tessera">
|
||||
<input
|
||||
type="date"
|
||||
value={tesseramentoCorrente.dataTessera ?? ""}
|
||||
onChange={(e) =>
|
||||
setTesseramento({ ...tesseramentoCorrente, dataTessera: e.target.value || null })
|
||||
}
|
||||
className={classiInput}
|
||||
/>
|
||||
</Campo>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={confermaTesseramento}
|
||||
disabled={!tesseramento || salvaTesseramento.isPending}
|
||||
className="premi w-full rounded-2xl bg-primary py-2.5 text-xs font-bold uppercase text-primary-foreground disabled:opacity-50"
|
||||
>
|
||||
Salva tesseramento
|
||||
</button>
|
||||
|
||||
<CampiProfilo
|
||||
corrente={profiloCorrente}
|
||||
aggiorna={(patch) => setBozza({ ...profiloCorrente, ...patch })}
|
||||
@@ -401,6 +454,15 @@ function SchedaGiocatore({
|
||||
stato={sezioni.foto ? "presente" : "assente"}
|
||||
path={profilo?.fotoPath ?? null}
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-bold uppercase",
|
||||
statoClasse[g.numeroTessera ? "presente" : "assente"],
|
||||
)}
|
||||
>
|
||||
<BadgeCheck className="h-3.5 w-3.5" />
|
||||
{g.numeroTessera ? "Tesserato" : "Da tesserare"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{aperta ? <ModificaGiocatore g={g} profilo={profilo} /> : null}
|
||||
@@ -581,13 +643,14 @@ function Dashboard() {
|
||||
statoScadenza(profili[g.id]?.certificatoScadenza, profili[g.id]?.certificatoPath, oggi) ===
|
||||
"valido",
|
||||
).length;
|
||||
const tesserati = attivi.filter((g) => g.numeroTessera).length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader titolo="Dashboard" sottotitolo="Profili e tesseramento" />
|
||||
|
||||
<Section titolo="Squadra">
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<StatTile valore={attivi.length} label="Giocatori" />
|
||||
<StatTile valore={`${completi}/${attivi.length}`} label="Profili completi" />
|
||||
<StatTile
|
||||
@@ -595,6 +658,7 @@ function Dashboard() {
|
||||
label="Certificati validi"
|
||||
hint="non scaduti"
|
||||
/>
|
||||
<StatTile valore={`${tesserati}/${attivi.length}`} label="Tesserati" hint="CSI" />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user