Sposta la classifica interna in Statistiche e uniforma le tab Squadra.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
3b305b0f8e
commit
0eb2d60569
2 files changed
+92
-94
No files matched your search
@@ -68,6 +68,11 @@ Prima versione, pre-release.
|
|||||||
- Infrastruttura di sviluppo: migrazione da Lovable a sviluppo locale, repository GitHub
|
- Infrastruttura di sviluppo: migrazione da Lovable a sviluppo locale, repository GitHub
|
||||||
indipendente, deploy automatico su Vercel.
|
indipendente, deploy automatico su Vercel.
|
||||||
|
|
||||||
|
### Modificato
|
||||||
|
|
||||||
|
- In Squadra la classifica interna (filtro «Classifica per» + elenco) è sotto i 6 riquadri
|
||||||
|
di Statistiche; la tab «Classifica» è stata rimossa dalla barra delle sottosezioni.
|
||||||
|
|
||||||
### Sicurezza
|
### Sicurezza
|
||||||
|
|
||||||
- Migration `m4_solo_autenticati`: tolto al ruolo `anon` l'accesso alle tabelle dell'app
|
- Migration `m4_solo_autenticati`: tolto al ruolo `anon` l'accesso alle tabelle dell'app
|
||||||
|
|||||||
+87
-94
@@ -104,8 +104,7 @@ function Squadra() {
|
|||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
// Ogni tab è memoizzata: aprire/chiudere una card giocatore (stato `aperto`)
|
// Ogni tab è memoizzata: aprire/chiudere una card giocatore (stato `aperto`)
|
||||||
// non deve ricalcolare badge, classifica e obiettivi delle altre sezioni,
|
// non deve ricalcolare badge e obiettivi delle altre sezioni.
|
||||||
// che restano fuori schermo finché l'utente non cambia tab.
|
|
||||||
const contenutoRosa = useMemo(
|
const contenutoRosa = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -232,94 +231,93 @@ function Squadra() {
|
|||||||
|
|
||||||
const contenutoStats = useMemo(
|
const contenutoStats = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="space-y-4">
|
||||||
<StatTile valore={`${mediaPresenze}%`} label="Media presenze" />
|
<div className="grid grid-cols-3 gap-2">
|
||||||
<StatTile valore={matchGiocati} label="Match giocati" />
|
<StatTile valore={`${mediaPresenze}%`} label="Media presenze" />
|
||||||
<StatTile valore={mediaSquadra(pagelle) || "—"} label="Media pagelle" />
|
<StatTile valore={matchGiocati} label="Match giocati" />
|
||||||
<StatTile valore={team.punti} label="Punti squadra" />
|
<StatTile valore={mediaSquadra(pagelle) || "—"} label="Media pagelle" />
|
||||||
<StatTile valore={team.ace} label="Ace squadra" />
|
<StatTile valore={team.punti} label="Punti squadra" />
|
||||||
<StatTile valore={team.muri} label="Muri squadra" />
|
<StatTile valore={team.ace} label="Ace squadra" />
|
||||||
|
<StatTile valore={team.muri} label="Muri squadra" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setFiltroAperto(true)}
|
||||||
|
aria-haspopup="dialog"
|
||||||
|
className="flex w-full min-w-0 items-center gap-2 rounded-2xl bg-card px-3 py-2.5 text-left shadow-card"
|
||||||
|
>
|
||||||
|
<Trophy className="h-5 w-5 shrink-0 text-warning" aria-hidden />
|
||||||
|
<span className="shrink-0 text-[14px] text-foreground/80">Classifica per</span>
|
||||||
|
<span className="min-w-0 flex-1 truncate text-right text-[14px] font-bold">
|
||||||
|
{criteri.find((c) => c.id === criterio)?.label}
|
||||||
|
</span>
|
||||||
|
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Drawer open={filtroAperto} onOpenChange={setFiltroAperto}>
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Classifica per</DrawerTitle>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div className="flex flex-col gap-1 px-4 pb-6">
|
||||||
|
{criteri.map((c) => (
|
||||||
|
<DrawerClose key={c.id} asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setCriterio(c.id)}
|
||||||
|
className={cn(
|
||||||
|
"flex min-h-11 w-full items-center rounded-xl px-3 text-left text-sm font-bold",
|
||||||
|
criterio === c.id
|
||||||
|
? "bg-accent text-accent-foreground"
|
||||||
|
: "bg-secondary text-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{c.label}
|
||||||
|
</button>
|
||||||
|
</DrawerClose>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
|
<div className="mt-3 space-y-2">
|
||||||
|
{ordinati.map((g, i) => (
|
||||||
|
<div key={g.id} className="rounded-2xl bg-card p-3 shadow-card">
|
||||||
|
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3">
|
||||||
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
|
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-secondary font-display text-base">
|
||||||
|
{i + 1}
|
||||||
|
</span>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="truncate text-sm font-bold">
|
||||||
|
{g.nome}
|
||||||
|
{i === 0 ? (
|
||||||
|
<Crown className="ml-1 inline h-3.5 w-3.5 text-warning" />
|
||||||
|
) : null}
|
||||||
|
</p>
|
||||||
|
<p className="truncate text-xs text-muted-foreground">
|
||||||
|
#{g.numero} · {g.ruolo} · {g.streak} presenze consecutive
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="font-display text-xl tabular-nums">
|
||||||
|
{valore(g, criterio) || "—"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Barra
|
||||||
|
percentuale={Math.max(6, (valore(g, criterio) / max) * 100)}
|
||||||
|
altezza="h-1.5"
|
||||||
|
trackClassName="mt-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
[mediaPresenze, matchGiocati, pagelle, team],
|
[mediaPresenze, matchGiocati, pagelle, team, filtroAperto, criterio, ordinati, max],
|
||||||
);
|
|
||||||
|
|
||||||
const contenutoClassifica = useMemo(
|
|
||||||
() => (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setFiltroAperto(true)}
|
|
||||||
aria-haspopup="dialog"
|
|
||||||
className="flex w-full min-w-0 items-center gap-2 border-b border-border bg-card px-5 py-2.5 text-left"
|
|
||||||
>
|
|
||||||
<Trophy className="h-4 w-4 shrink-0 text-warning" aria-hidden />
|
|
||||||
<span className="shrink-0 text-sm text-foreground/80">Classifica per</span>
|
|
||||||
<span className="min-w-0 flex-1 truncate text-right text-sm font-bold">
|
|
||||||
{criteri.find((c) => c.id === criterio)?.label}
|
|
||||||
</span>
|
|
||||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Drawer open={filtroAperto} onOpenChange={setFiltroAperto}>
|
|
||||||
<DrawerContent>
|
|
||||||
<DrawerHeader>
|
|
||||||
<DrawerTitle>Classifica per</DrawerTitle>
|
|
||||||
</DrawerHeader>
|
|
||||||
<div className="flex flex-col gap-1 px-4 pb-6">
|
|
||||||
{criteri.map((c) => (
|
|
||||||
<DrawerClose key={c.id} asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setCriterio(c.id)}
|
|
||||||
className={cn(
|
|
||||||
"flex min-h-11 w-full items-center rounded-xl px-3 text-left text-sm font-bold",
|
|
||||||
criterio === c.id
|
|
||||||
? "bg-accent text-accent-foreground"
|
|
||||||
: "bg-secondary text-foreground",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{c.label}
|
|
||||||
</button>
|
|
||||||
</DrawerClose>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</DrawerContent>
|
|
||||||
</Drawer>
|
|
||||||
|
|
||||||
<div className="space-y-2 px-5 pt-3">
|
|
||||||
{ordinati.map((g, i) => (
|
|
||||||
<div key={g.id} className="rounded-2xl bg-card p-3 shadow-card">
|
|
||||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3">
|
|
||||||
<div className="flex min-w-0 items-center gap-3">
|
|
||||||
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-secondary font-display text-base">
|
|
||||||
{i + 1}
|
|
||||||
</span>
|
|
||||||
<div className="min-w-0">
|
|
||||||
<p className="truncate text-sm font-bold">
|
|
||||||
{g.nome}
|
|
||||||
{i === 0 ? <Crown className="ml-1 inline h-3.5 w-3.5 text-warning" /> : null}
|
|
||||||
</p>
|
|
||||||
<p className="truncate text-xs text-muted-foreground">
|
|
||||||
#{g.numero} · {g.ruolo} · {g.streak} presenze consecutive
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span className="font-display text-xl tabular-nums">
|
|
||||||
{valore(g, criterio) || "—"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Barra
|
|
||||||
percentuale={Math.max(6, (valore(g, criterio) / max) * 100)}
|
|
||||||
altezza="h-1.5"
|
|
||||||
trackClassName="mt-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
[filtroAperto, criterio, ordinati, max],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const contenutoObiettivi = useMemo(
|
const contenutoObiettivi = useMemo(
|
||||||
@@ -439,15 +437,10 @@ function Squadra() {
|
|||||||
<BarraSottosezioni
|
<BarraSottosezioni
|
||||||
defaultId="rosa"
|
defaultId="rosa"
|
||||||
variante="sottolineatura"
|
variante="sottolineatura"
|
||||||
|
riempiLarghezza
|
||||||
voci={[
|
voci={[
|
||||||
{ id: "rosa", label: "Rosa", contenuto: contenutoRosa },
|
{ id: "rosa", label: "Rosa", contenuto: contenutoRosa },
|
||||||
{ id: "stats", label: "Statistiche", contenuto: contenutoStats },
|
{ id: "stats", label: "Statistiche", contenuto: contenutoStats },
|
||||||
{
|
|
||||||
id: "classifica-giocatori",
|
|
||||||
label: "Classifica",
|
|
||||||
aTuttoLarghezza: true,
|
|
||||||
contenuto: contenutoClassifica,
|
|
||||||
},
|
|
||||||
{ id: "obiettivi", label: "Obiettivi", contenuto: contenutoObiettivi },
|
{ id: "obiettivi", label: "Obiettivi", contenuto: contenutoObiettivi },
|
||||||
{ id: "badge", label: "Badge", contenuto: contenutoBadge },
|
{ id: "badge", label: "Badge", contenuto: contenutoBadge },
|
||||||
]}
|
]}
|
||||||
|
|||||||
Reference in new issue
Block a user