Scheda di dettaglio per gli eventi extra-campo, senza infortunato tra le risposte

Gli eventi generici (es. cena di squadra) avevano solo il conteggio presenze in
card, senza modo di vedere chi ha risposto. Aggiunta la route /evento/$id sul
modello di allenamento/partita, con RosaPresenze per l'elenco nominativo.

Lo stato "infortunato" non è una risposta pertinente per questi eventi: escluso
da badge, gruppi e bottoni di risposta sia in RosaPresenze che in EventoCard,
tramite la funzione condivisa includeInfortunato() in presenze.ts.

Documentazione dei moduli (calendario, presenze, palloni, badge) allineata.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5 committed 2026-09-28 11:01:46 +02:00
1 parent a72eb087d1
commit 39918f2eb7
13 files changed
+220 -41

No files matched your search

+9 -12
View File
@@ -15,7 +15,7 @@ import { Card } from "@/components/crapp/ui-bits";
import { formatData, statoMeta, type Stato } from "@/lib/crapp-data";
import type { Evento } from "@/lib/eventi";
import { inRosa, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import { usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze";
import { includeInfortunato, usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze";
import { useGiocatoreInCampo } from "@/lib/user-store";
import { dataOggi } from "@/lib/scout-live";
@@ -26,9 +26,7 @@ const tipoMeta = {
compleanno: { label: "Compleanno", className: "bg-success text-success-foreground" },
} as const;
const statiSportivi: Stato[] = ["presente", "forse", "ritardo", "assente", "infortunato"];
/** Eventi extra-campo (pizze, uscite…): l'infortunio non è una risposta pertinente. */
const statiEventoExtra: Stato[] = ["presente", "forse", "ritardo", "assente"];
const statiBase: Stato[] = ["presente", "forse", "ritardo", "assente"];
/** Icone compatte per la riga unica dei controlli presenza (mockup). */
const iconeStato: Record<Stato, typeof Check> = {
@@ -51,6 +49,9 @@ export function linkPerEvento(e: Evento) {
label: "Dettaglio allenamento",
};
}
if (e.tipo === "evento") {
return { to: "/evento/$id" as const, params: { id: e.id }, label: "Dettaglio evento" };
}
return undefined;
}
@@ -87,15 +88,11 @@ export function EventoCard({
const isCompleanno = evento.tipo === "compleanno";
const passato = evento.data < dataOggi();
const cliccabile = Boolean(linkTo);
/** Solo gli eventi extra-campo non hanno scheda dedicata: le note restano sulla card. */
/** Anteprima delle note anche in card per gli eventi extra-campo (oltre al dettaglio). */
const noteCard = evento.tipo === "evento" ? evento.note.trim() : "";
const stati =
evento.tipo === "evento"
? // Se resta un vecchio "infortunato", mostra il bottone solo per poterlo togliere.
stato === "infortunato"
? [...statiEventoExtra, "infortunato" as const]
: statiEventoExtra
: statiSportivi;
const stati = includeInfortunato(evento.tipo, stato)
? [...statiBase, "infortunato" as const]
: statiBase;
if (isCompleanno) {
return (
+15 -4
View File
@@ -6,6 +6,7 @@ import { Card } from "@/components/crapp/ui-bits";
import { Avatar } from "@/components/crapp/Avatar";
import { Barra } from "@/components/motion/Barra";
import { statoMeta, type Giocatore, type Stato } from "@/lib/crapp-data";
import type { EventoTipo } from "@/lib/eventi";
import { usePresenzeEvento, useSalvaPresenza } from "@/lib/presenze";
import { useAnagraficaRosa } from "@/lib/rosa";
import { useGiocatoreBase, useGiocatoreInCampo } from "@/lib/user-store";
@@ -15,7 +16,16 @@ import { dataOggi } from "@/lib/scout-live";
const ordine: Stato[] = ["presente", "ritardo", "forse", "infortunato", "assente"];
export function RosaPresenze({ eventoId, data }: { eventoId: string; data: string }) {
export function RosaPresenze({
eventoId,
data,
tipo = "allenamento",
}: {
eventoId: string;
data: string;
/** Negli eventi extra-campo "infortunato" non è pertinente: niente badge né opzione. */
tipo?: EventoTipo;
}) {
const { risposte, isPending } = usePresenzeEvento(eventoId);
const salva = useSalvaPresenza();
// Solo `.id`/`.nome` servono qui: `useGiocatoreBase`/`useAnagraficaRosa` bastano,
@@ -32,6 +42,7 @@ export function RosaPresenze({ eventoId, data }: { eventoId: string; data: strin
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;
const ordineVisibile = tipo === "evento" ? ordine.filter((s) => s !== "infortunato") : ordine;
async function sollecita() {
setSollecito(true);
@@ -67,7 +78,7 @@ export function RosaPresenze({ eventoId, data }: { eventoId: string; data: strin
<Barra percentuale={perc} altezza="h-1.5" trackClassName="mt-2" />
<div className="mt-3 flex flex-wrap gap-1.5">
{ordine.map((s) => {
{ordineVisibile.map((s) => {
const n = rosa.filter((g) => risposte[g.id] === s).length;
return (
<span
@@ -92,7 +103,7 @@ export function RosaPresenze({ eventoId, data }: { eventoId: string; data: strin
La tua risposta
</p>
<div className="mt-2 flex flex-wrap gap-1.5">
{ordine.map((s) => {
{ordineVisibile.map((s) => {
const attivo = risposte[io.id] === s;
return (
<button
@@ -140,7 +151,7 @@ export function RosaPresenze({ eventoId, data }: { eventoId: string; data: strin
</p>
) : null}
{ordine.map((s) => {
{ordineVisibile.map((s) => {
const lista = rosa.filter((g) => risposte[g.id] === s);
if (lista.length === 0) return null;
return (