2 Commits
Author SHA1 Message Date
davideandClaude Sonnet 5 d52ceeb0f9 Bump versione a 0.9.1 e aggiorna il changelog
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 07:51:03 +02:00
Ivan CacciariandCursor a9f146d77e Migliora le schede dello storico partite: loghi, casa-ospite e chevron.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 21:01:49 +02:00
4 changed files with 114 additions and 31 deletions
+8
View File
@@ -12,6 +12,14 @@ all'indietro.
## [Non rilasciato] ## [Non rilasciato]
## [0.9.1] - 2026-09-10
### Modificato
- **Storico partite** — ogni scheda mostra il logo accanto al nome di entrambe le squadre
(CRAP e avversario), risultato e parziali in ordine casaospite (verde/rosso restano
vittoria/sconfitta CRAP) e un chevron a destra per chiarire che la riga apre il dettaglio.
## [0.9.0] - 2026-09-09 ## [0.9.0] - 2026-09-09
Prima versione pre-release: lo sviluppo precedente non era versionato a parte, quindi Prima versione pre-release: lo sviluppo precedente non era versionato a parte, quindi
+2 -1
View File
@@ -154,7 +154,8 @@ CSI (portale)
useCsi() → src/lib/csi.ts (React Query, staleTime 6h) useCsi() → src/lib/csi.ts (React Query, staleTime 6h)
/classifica → src/routes/classifica.tsx (tab "Classifica": Coppa sopra, Girone /classifica → src/routes/classifica.tsx (tab "Classifica": Coppa sopra, Girone
sotto; tab "Storico partite": ogni gara cliccabile) sotto; tab "Storico partite": ogni squadra col proprio logo,
chevron di dettaglio sulle gare cliccabili)
CSI (portale, 3 endpoint) CSI (portale, 3 endpoint)
↓ fetch server-side on-demand, cache per-partita 6 ore ↓ fetch server-side on-demand, cache per-partita 6 ore
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "crapp", "name": "crapp",
"version": "0.9.0", "version": "0.9.1",
"private": true, "private": true,
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",
+103 -29
View File
@@ -3,7 +3,7 @@ import { createFileRoute, Link } from "@tanstack/react-router";
import { AlertTriangle, ChevronRight, RefreshCw } from "lucide-react"; import { AlertTriangle, ChevronRight, RefreshCw } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { formatData, type RigaClassifica } from "@/lib/crapp-data"; import { formatData, type RigaClassifica } from "@/lib/crapp-data";
import { PageHeader } from "@/components/crapp/ui-bits"; import { PageHeader, TeamLogo } from "@/components/crapp/ui-bits";
import { BarraSottosezioni } from "@/components/crapp/BarraSottosezioni"; import { BarraSottosezioni } from "@/components/crapp/BarraSottosezioni";
import { useScoutMatches } from "@/lib/scout-store"; import { useScoutMatches } from "@/lib/scout-store";
import { useCsi } from "@/lib/csi"; import { useCsi } from "@/lib/csi";
@@ -12,6 +12,43 @@ import { useVotiMvp, vincitoriMvp } from "@/lib/mvp-voti";
import { useEventi } from "@/lib/eventi"; import { useEventi } from "@/lib/eventi";
import { LogoSquadra } from "@/components/crapp/DettaglioCsi"; import { LogoSquadra } from "@/components/crapp/DettaglioCsi";
const LOGO_NOI = "/logo-nerorosso.svg";
const NOME_NOI = "CRAP Volley";
/** Logo CRAP o avversario; cerchio con iniziali se il CSI non ha l'immagine. */
function LogoPartita({
nostro,
logoAvversario,
avversario,
}: {
nostro: boolean;
logoAvversario: string;
avversario: string;
}) {
if (nostro) {
return (
<TeamLogo src={LOGO_NOI} className="h-8 w-8 rounded-lg shadow-none" />
);
}
if (logoAvversario) {
return <LogoSquadra src={logoAvversario} alt={avversario} className="h-8 w-8" />;
}
const iniziali = avversario
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((p) => p[0]?.toUpperCase() ?? "")
.join("");
return (
<span
className="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-secondary text-[10px] font-bold text-muted-foreground"
aria-hidden
>
{iniziali || "?"}
</span>
);
}
const TAB_CLASSIFICA = ["classifica", "storico"] as const; const TAB_CLASSIFICA = ["classifica", "storico"] as const;
type TabClassifica = (typeof TAB_CLASSIFICA)[number]; type TabClassifica = (typeof TAB_CLASSIFICA)[number];
@@ -169,60 +206,96 @@ function Classifica() {
{tuttiMatch.map((m) => { {tuttiMatch.map((m) => {
const vinta = m.setNostri > m.setLoro; const vinta = m.setNostri > m.setLoro;
const eventoId = eventoIdPerData.get(m.data); const eventoId = eventoIdPerData.get(m.data);
const cliccabile = Boolean(eventoId) || !m.scout;
const casa = {
nome: m.casa ? NOME_NOI : m.avversario,
nostro: m.casa,
};
const trasferta = {
nome: m.casa ? m.avversario : NOME_NOI,
nostro: !m.casa,
};
// Badge e parziali in ordine casaospite; il colore resta sulla vittoria CRAP.
const setCasa = m.casa ? m.setNostri : m.setLoro;
const setOspite = m.casa ? m.setLoro : m.setNostri;
const contenuto = ( const contenuto = (
<> <>
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3"> <div className="flex items-center gap-2">
<div className="flex min-w-0 items-center gap-2"> <div className="min-w-0 flex-1 space-y-1.5">
<LogoSquadra src={m.logoAvversario} alt={m.avversario} className="h-8 w-8" /> <div className="flex min-w-0 items-center gap-2">
<div className="min-w-0"> <LogoPartita
<p className="truncate text-sm font-bold"> nostro={casa.nostro}
{m.casa ? "CRAP Volley" : m.avversario} vs{" "} logoAvversario={m.logoAvversario}
{m.casa ? m.avversario : "CRAP Volley"} avversario={m.avversario}
</p> />
<p className="text-xs text-muted-foreground"> <p className="truncate text-sm font-bold">{casa.nome}</p>
{formatData(m.data)} · MVP {m.mvp || "da votare"}
{m.scout ? " · scoutata" : ""}
</p>
</div> </div>
<div className="flex min-w-0 items-center gap-2">
<LogoPartita
nostro={trasferta.nostro}
logoAvversario={m.logoAvversario}
avversario={m.avversario}
/>
<p className="truncate text-sm font-bold">{trasferta.nome}</p>
</div>
<p className="text-xs text-muted-foreground">
{formatData(m.data)} · MVP {m.mvp || "da votare"}
{m.scout ? " · scoutata" : ""}
</p>
</div> </div>
<span <span
className={cn( className={cn(
"rounded-xl px-2.5 py-1 font-display text-lg", "shrink-0 rounded-xl px-2.5 py-1 font-display text-xl",
vinta vinta
? "bg-success text-success-foreground" ? "bg-success text-success-foreground"
: "bg-destructive text-destructive-foreground", : "bg-destructive text-destructive-foreground",
)} )}
> >
{m.setNostri}-{m.setLoro} {setCasa}-{setOspite}
</span> </span>
{cliccabile ? (
<ChevronRight
className="h-5 w-5 shrink-0 text-muted-foreground"
aria-hidden
/>
) : null}
</div> </div>
<div className="mt-3 flex flex-wrap items-center gap-1.5"> <div className="mt-3 flex flex-wrap items-center gap-1.5">
{m.parziali.map((p, i) => ( {m.parziali.map((p, i) => {
<span const puntiCasa = m.casa ? p[0] : p[1];
key={i} const puntiOspite = m.casa ? p[1] : p[0];
className={cn( const setVintoDaNoi = p[0] > p[1];
"rounded-lg px-2 py-1 text-xs font-semibold tabular-nums", return (
p[0] > p[1] ? "bg-secondary" : "bg-muted text-muted-foreground", <span
)} key={i}
> className={cn(
{p[0]}-{p[1]} "rounded-lg px-2 py-1 text-xs font-semibold tabular-nums",
</span> setVintoDaNoi
))} ? "bg-secondary"
: "bg-muted text-muted-foreground",
)}
>
{puntiCasa}-{puntiOspite}
</span>
);
})}
{eventoId && !m.mvp ? ( {eventoId && !m.mvp ? (
<span className="ml-auto inline-flex items-center gap-0.5 text-xs font-bold uppercase text-accent"> <span className="ml-auto inline-flex items-center gap-0.5 text-xs font-bold uppercase text-accent">
Vota MVP <ChevronRight className="h-3.5 w-3.5" /> Vota MVP <ChevronRight className="h-3.5 w-3.5" aria-hidden />
</span> </span>
) : null} ) : null}
</div> </div>
</> </>
); );
const aria = `${casa.nome} vs ${trasferta.nome}: dettaglio partita`;
if (eventoId) { if (eventoId) {
return ( return (
<Link <Link
key={m.id} key={m.id}
to="/partita/$id" to="/partita/$id"
params={{ id: eventoId }} params={{ id: eventoId }}
className="premi block rounded-3xl bg-card p-4 shadow-card active:scale-[0.99]" aria-label={aria}
className="premi block rounded-3xl bg-card p-4 shadow-card ring-1 ring-transparent transition-[box-shadow,transform] active:scale-[0.99] hover:ring-accent/30"
> >
{contenuto} {contenuto}
</Link> </Link>
@@ -240,7 +313,8 @@ function Classifica() {
key={m.id} key={m.id}
to="/partita-csi/$id" to="/partita-csi/$id"
params={{ id: m.id }} params={{ id: m.id }}
className="premi block rounded-3xl bg-card p-4 shadow-card active:scale-[0.99]" aria-label={aria}
className="premi block rounded-3xl bg-card p-4 shadow-card ring-1 ring-transparent transition-[box-shadow,transform] active:scale-[0.99] hover:ring-accent/30"
> >
{contenuto} {contenuto}
</Link> </Link>