From a6799b91d3daed31ffc488f22f1e1317877ca7da Mon Sep 17 00:00:00 2001 From: Ivan Cacciari Date: Mon, 7 Sep 2026 10:58:57 +0200 Subject: [PATCH] Ridisegna EventoCard e toglie infortunato dagli eventi extra-campo. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Card cliccabile con icone presenza compatte, senza barra percentuale; avatar home leggermente più grande. Co-authored-by: Cursor --- docs/modules/infortuni.md | 3 +- docs/modules/presenze.md | 8 +- src/components/crapp/EventoCard.tsx | 229 ++++++++++++++++++---------- src/components/crapp/ui-bits.tsx | 4 +- 4 files changed, 156 insertions(+), 88 deletions(-) diff --git a/docs/modules/infortuni.md b/docs/modules/infortuni.md index 6a494ed..3840ae6 100644 --- a/docs/modules/infortuni.md +++ b/docs/modules/infortuni.md @@ -38,7 +38,8 @@ profilo che mostri il numero di infortuni come statistica di superficie. - Nessuna durata o periodo tracciato: è solo un conteggio di eventi con quello stato, non un inizio/fine infortunio. - Il conteggio dipende dal fatto che qualcuno imposti correttamente lo stato "infortunato" - invece di "assente": nessuna validazione o promemoria lo garantisce. + invece di "assente": nessuna validazione o promemoria lo garantisce. Sulle card degli + eventi extra-campo (`tipo: "evento"`) l'opzione non è proposta in UI. - Poco visibile per valori bassi (1-2), perché emerge solo tramite un badge a soglia 3. - `conInfortuni()`, una funzione di merge alternativa nello stesso file, non risulta usata da nessuna parte del codice attuale — probabile residuo non collegato. diff --git a/docs/modules/presenze.md b/docs/modules/presenze.md index d056142..c982ced 100644 --- a/docs/modules/presenze.md +++ b/docs/modules/presenze.md @@ -2,7 +2,7 @@ **Stato:** implementato (v1.0) **File principali:** `src/lib/presenze.ts`, `src/lib/presenze-mese.ts`, `src/components/crapp/RosaPresenze.tsx`, -`src/routes/api/public/sollecita-presenze.ts` +`src/components/crapp/EventoCard.tsx`, `src/routes/api/public/sollecita-presenze.ts` --- @@ -24,7 +24,8 @@ attuale. Stati possibili (`Stato` in `src/lib/crapp-data.ts`): `presente`, `assente`, `forse`, `ritardo`, `infortunato`. Solo `presente` e `ritardo` contano come presenza effettiva nelle statistiche. L'assenza di una riga per `(evento, giocatore)` equivale a "non ha ancora -risposto". +risposto". Sulla card in home/calendario (`EventoCard`) lo stato `infortunato` è offerto solo +per partite e allenamenti; sugli eventi extra-campo non è selezionabile (non pertinente). --- @@ -43,6 +44,9 @@ RosaPresenze → src/components/crapp/RosaPresenze.tsx ↑ montato da (riepilogo, bottoni di risposta, gruppi per stato) allenamento.$id.tsx / partita.$id.tsx +EventoCard → src/components/crapp/EventoCard.tsx + ↑ home / calendario (riga compatta di stati; senza `infortunato` se tipo `evento`) + --- statistiche --- contaPresenzeGiocatore() / totaliEventiGiocatore() → src/lib/presenze.ts usePresenzeUltimoMese() → src/lib/presenze-mese.ts diff --git a/src/components/crapp/EventoCard.tsx b/src/components/crapp/EventoCard.tsx index c4c1813..7013e5d 100644 --- a/src/components/crapp/EventoCard.tsx +++ b/src/components/crapp/EventoCard.tsx @@ -1,10 +1,19 @@ -import { MapPin, Clock, Users, Cake, ArrowRight } from "lucide-react"; +import { + MapPin, + Clock, + Users, + Cake, + ChevronRight, + Check, + HelpCircle, + X, + Bandage, +} from "lucide-react"; import { Link } from "@tanstack/react-router"; import { cn } from "@/lib/utils"; import { Card } from "@/components/crapp/ui-bits"; 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"; @@ -17,18 +26,40 @@ const tipoMeta = { compleanno: { label: "Compleanno", className: "bg-success text-success-foreground" }, } as const; -const stati: Stato[] = ["presente", "forse", "ritardo", "assente", "infortunato"]; +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"]; + +/** Icone compatte per la riga unica dei controlli presenza (mockup). */ +const iconeStato: Record = { + presente: Check, + forse: HelpCircle, + ritardo: Clock, + assente: X, + /** Cerotto singolo: leggibile a 16px e allineato all'emoji 🩹. */ + infortunato: Bandage, +}; export function linkPerEvento(e: Evento) { if (e.tipo === "partita") { - return { to: "/partita/$id", params: { id: e.id }, label: "Dettagli" }; + return { to: "/partita/$id" as const, params: { id: e.id }, label: "Dettaglio partita" }; } if (e.tipo === "allenamento") { - return { to: "/allenamento/$id", params: { id: e.id }, label: "Dettagli" }; + return { + to: "/allenamento/$id" as const, + params: { id: e.id }, + label: "Dettaglio allenamento", + }; } return undefined; } +function etichettaData(iso: string) { + const giorno = iso.slice(8, 10); + const mese = formatData(iso).split(" ")[2]?.slice(0, 3)?.toUpperCase() ?? ""; + return `${giorno} ${mese}`; +} + export function EventoCard({ evento, linkTo, @@ -50,39 +81,17 @@ export function EventoCard({ ? { label: "Amichevole", className: "bg-accent/70 text-accent-foreground" } : tipoMeta[evento.tipo]; const totale = rosa.length; - const perc = totale ? Math.round((presentiVeri / totale) * 100) : 0; const isCompleanno = evento.tipo === "compleanno"; const passato = evento.data < dataOggi(); - - function ChipPresenza({ s }: { s: Stato }) { - const meta = statoMeta[s]; - const attivo = stato === s; - return ( - - ); - } + const note = evento.note.trim(); + const cliccabile = Boolean(linkTo); + 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; if (isCompleanno) { return ( @@ -94,20 +103,38 @@ export function EventoCard({

{evento.titolo}

{evento.luogo}

-
-

{evento.data.slice(8, 10)}

-

- {formatData(evento.data).split(" ")[2]?.slice(0, 3)} -

+
+ {etichettaData(evento.data)}
); } + const sottotitoloPartita = + evento.tipo === "partita" + ? `${evento.casa ? "Casa" : "Trasferta"}${evento.campionato ? " · Campionato" : " · Amichevole"}` + : null; + return ( - -
-
+ + {/* Link a tutta card: i controlli sopra (z-10) restano indipendenti. */} + {linkTo ? ( + + ) : null} + +
+
{tipo.label} -

{evento.titolo}

+ + {etichettaData(evento.data)} + {linkTo ? : null} +
-
-

{evento.data.slice(8, 10)}

-

- {formatData(evento.data).split(" ")[2]?.slice(0, 3)} -

+ +

{evento.titolo}

+ {sottotitoloPartita ? ( +

{sottotitoloPartita}

+ ) : null} + +
+ + + {evento.ora} + + + + {evento.luogo} + + + + + {presentiVeri}/{totale} + +
-
-
- - {evento.ora} - - - - {evento.luogo} - - - {presentiVeri}/{totale} - -
- - - - {io || linkTo ? ( -
- {io ? stati.slice(0, -1).map((s) => ) : null} - {/* Ultima riga: ultimo chip + Apri insieme, così non nasce una terza riga. */} -
- {io ? : null} - {linkTo ? ( - - {linkTo.label} - - ) : null} + {io ? ( +
+ {stati.map((s) => { + const meta = statoMeta[s]; + const Icon = iconeStato[s]; + const attivo = stato === s; + const mostraTesto = s === "presente" && attivo; + return ( + + ); + })}
-
- ) : null} + ) : null} + + {note ? ( +

📝 {note}

+ ) : null} +
); } diff --git a/src/components/crapp/ui-bits.tsx b/src/components/crapp/ui-bits.tsx index fda8b50..4d2120f 100644 --- a/src/components/crapp/ui-bits.tsx +++ b/src/components/crapp/ui-bits.tsx @@ -53,14 +53,14 @@ export function Card({ */ export function LinkProfilo() { const g = useIo(); - if (!g) return ; + if (!g) return ; return ( - + ); }