Aggiunge sottosezioni a swipe nel profilo, con stagione e serie unite.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -265,9 +265,12 @@ export function CampiProfilo({
|
|||||||
export function ProfiloAmministrativo({
|
export function ProfiloAmministrativo({
|
||||||
giocatoreId,
|
giocatoreId,
|
||||||
indice = 0,
|
indice = 0,
|
||||||
|
/** Se false, mostra solo il contenuto (es. dentro `BarraSottosezioni`). */
|
||||||
|
conTendina = true,
|
||||||
}: {
|
}: {
|
||||||
giocatoreId: string;
|
giocatoreId: string;
|
||||||
indice?: number;
|
indice?: number;
|
||||||
|
conTendina?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { profili } = useProfili();
|
const { profili } = useProfili();
|
||||||
const salva = useSalvaProfilo();
|
const salva = useSalvaProfilo();
|
||||||
@@ -301,79 +304,90 @@ export function ProfiloAmministrativo({
|
|||||||
const caricato = (campo: keyof Profilo) => async (path: string) =>
|
const caricato = (campo: keyof Profilo) => async (path: string) =>
|
||||||
scrivi({ ...corrente, [campo]: path });
|
scrivi({ ...corrente, [campo]: path });
|
||||||
|
|
||||||
|
const corpo = (
|
||||||
|
<div className="space-y-3 rounded-3xl bg-card p-4 shadow-card">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<div className="h-1.5 min-w-0 flex-1 overflow-hidden rounded-full bg-secondary">
|
||||||
|
<div
|
||||||
|
className="h-full rounded-full bg-accent-grad transition-all"
|
||||||
|
style={{ width: `${perc}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="shrink-0 text-xs font-bold tabular-nums text-muted-foreground">
|
||||||
|
{perc}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Servono agli amministratori per il tesseramento CSI. Li vedi solo tu e loro.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<CampiProfilo
|
||||||
|
corrente={corrente}
|
||||||
|
aggiorna={aggiorna}
|
||||||
|
sezioni={sezioni}
|
||||||
|
fileDocumento={
|
||||||
|
<div className="divide-y divide-border">
|
||||||
|
<CampoFile
|
||||||
|
label="Foto fronte"
|
||||||
|
path={corrente.documentoFrontePath}
|
||||||
|
sezione="documento-fronte"
|
||||||
|
giocatoreId={giocatoreId}
|
||||||
|
onCaricato={caricato("documentoFrontePath")}
|
||||||
|
/>
|
||||||
|
<CampoFile
|
||||||
|
label="Foto retro"
|
||||||
|
path={corrente.documentoRetroPath}
|
||||||
|
sezione="documento-retro"
|
||||||
|
giocatoreId={giocatoreId}
|
||||||
|
onCaricato={caricato("documentoRetroPath")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
fileCertificato={
|
||||||
|
<CampoFile
|
||||||
|
label="Certificato medico"
|
||||||
|
path={corrente.certificatoPath}
|
||||||
|
sezione="certificato"
|
||||||
|
giocatoreId={giocatoreId}
|
||||||
|
onCaricato={caricato("certificatoPath")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fileFoto={
|
||||||
|
<>
|
||||||
|
<Intestazione titolo="Foto tessera" completa={sezioni.foto} />
|
||||||
|
<CampoFile
|
||||||
|
label="Foto tessera"
|
||||||
|
path={corrente.fotoPath}
|
||||||
|
sezione="foto"
|
||||||
|
giocatoreId={giocatoreId}
|
||||||
|
onCaricato={caricato("fotoPath")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={salvaBozza}
|
||||||
|
disabled={!sporco || salva.isPending}
|
||||||
|
className="premi flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-grad py-3 text-sm font-bold uppercase text-accent-foreground shadow-pop disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{salva.isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
|
||||||
|
{sporco ? "Salva" : "Salvato"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!conTendina) return corpo;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SezioneTendina
|
<SezioneTendina
|
||||||
titolo="Dati per il tesseramento"
|
titolo="Dati per il tesseramento"
|
||||||
indice={indice}
|
indice={indice}
|
||||||
azione={<span className="text-xs font-bold tabular-nums text-muted-foreground">{perc}%</span>}
|
azione={<span className="text-xs font-bold tabular-nums text-muted-foreground">{perc}%</span>}
|
||||||
>
|
>
|
||||||
<div className="space-y-3 rounded-3xl bg-card p-4 shadow-card">
|
{corpo}
|
||||||
<div className="h-1.5 overflow-hidden rounded-full bg-secondary">
|
|
||||||
<div
|
|
||||||
className="h-full rounded-full bg-accent-grad transition-all"
|
|
||||||
style={{ width: `${perc}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Servono agli amministratori per il tesseramento CSI. Li vedi solo tu e loro.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<CampiProfilo
|
|
||||||
corrente={corrente}
|
|
||||||
aggiorna={aggiorna}
|
|
||||||
sezioni={sezioni}
|
|
||||||
fileDocumento={
|
|
||||||
<div className="divide-y divide-border">
|
|
||||||
<CampoFile
|
|
||||||
label="Foto fronte"
|
|
||||||
path={corrente.documentoFrontePath}
|
|
||||||
sezione="documento-fronte"
|
|
||||||
giocatoreId={giocatoreId}
|
|
||||||
onCaricato={caricato("documentoFrontePath")}
|
|
||||||
/>
|
|
||||||
<CampoFile
|
|
||||||
label="Foto retro"
|
|
||||||
path={corrente.documentoRetroPath}
|
|
||||||
sezione="documento-retro"
|
|
||||||
giocatoreId={giocatoreId}
|
|
||||||
onCaricato={caricato("documentoRetroPath")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
fileCertificato={
|
|
||||||
<CampoFile
|
|
||||||
label="Certificato medico"
|
|
||||||
path={corrente.certificatoPath}
|
|
||||||
sezione="certificato"
|
|
||||||
giocatoreId={giocatoreId}
|
|
||||||
onCaricato={caricato("certificatoPath")}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fileFoto={
|
|
||||||
<>
|
|
||||||
<Intestazione titolo="Foto tessera" completa={sezioni.foto} />
|
|
||||||
<CampoFile
|
|
||||||
label="Foto tessera"
|
|
||||||
path={corrente.fotoPath}
|
|
||||||
sezione="foto"
|
|
||||||
giocatoreId={giocatoreId}
|
|
||||||
onCaricato={caricato("fotoPath")}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={salvaBozza}
|
|
||||||
disabled={!sporco || salva.isPending}
|
|
||||||
className="premi flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-grad py-3 text-sm font-bold uppercase text-accent-foreground shadow-pop disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{salva.isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
|
|
||||||
{sporco ? "Salva" : "Salvato"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</SezioneTendina>
|
</SezioneTendina>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+121
-108
@@ -3,14 +3,8 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Flame, Camera, Trash2, Bell, LogOut, ShieldCheck, Bug, Lightbulb } from "lucide-react";
|
import { Flame, Camera, Trash2, Bell, LogOut, ShieldCheck, Bug, Lightbulb } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import { Card, PageHeader, StatTile, TeamLogo } from "@/components/crapp/ui-bits";
|
||||||
Card,
|
import { BarraSottosezioni } from "@/components/crapp/BarraSottosezioni";
|
||||||
PageHeader,
|
|
||||||
Section,
|
|
||||||
SezioneTendina,
|
|
||||||
StatTile,
|
|
||||||
TeamLogo,
|
|
||||||
} from "@/components/crapp/ui-bits";
|
|
||||||
import { Avatar } from "@/components/crapp/Avatar";
|
import { Avatar } from "@/components/crapp/Avatar";
|
||||||
import {
|
import {
|
||||||
caricaAvatar,
|
caricaAvatar,
|
||||||
@@ -126,7 +120,7 @@ function Profilo() {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
titolo={g.nome}
|
titolo={g.nome}
|
||||||
sottotitolo={`#${g.numero} · ${g.ruolo}`}
|
sottotitolo={`#${g.numero} · ${g.ruolo}`}
|
||||||
azione={<TeamLogo src="/logo-nerorosso.svg" className="h-11 w-11" />}
|
azione={<TeamLogo src="/logo-nerorosso.svg" className="h-14 w-14" />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Reveal className="-mt-6 px-5">
|
<Reveal className="-mt-6 px-5">
|
||||||
@@ -183,105 +177,124 @@ function Profilo() {
|
|||||||
</Card>
|
</Card>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
<Section titolo="Stagione" indice={1}>
|
<BarraSottosezioni
|
||||||
<div className="grid grid-cols-3 gap-2">
|
defaultId="stagione"
|
||||||
<StatTile valore={g.presenze} label="Presenze" hint={`${percPresenze}% del totale`} />
|
voci={[
|
||||||
<StatTile
|
{
|
||||||
valore={`${ultimoMese.percentuale}%`}
|
id: "stagione",
|
||||||
label="Presenze 30gg"
|
label: "Stagione",
|
||||||
hint={`${ultimoMese.presenti}/${ultimoMese.totali} eventi`}
|
contenuto: (
|
||||||
/>
|
<div className="space-y-4">
|
||||||
<StatTile
|
<div className="grid grid-cols-3 gap-2">
|
||||||
valore={
|
<StatTile
|
||||||
<span className="inline-flex items-center gap-1">
|
valore={g.presenze}
|
||||||
<Flame className="h-5 w-5 text-accent" />
|
label="Presenze"
|
||||||
{g.streak}
|
hint={`${percPresenze}% del totale`}
|
||||||
</span>
|
/>
|
||||||
}
|
<StatTile
|
||||||
label="Presenze di fila"
|
valore={`${ultimoMese.percentuale}%`}
|
||||||
/>
|
label="Presenze 30gg"
|
||||||
<StatTile valore={g.mediaVoto || "—"} label="Media voto" />
|
hint={`${ultimoMese.presenti}/${ultimoMese.totali} eventi`}
|
||||||
<StatTile valore={g.mvp} label="MVP" />
|
/>
|
||||||
<StatTile valore={g.palloni} label="Turni palloni" />
|
<StatTile
|
||||||
</div>
|
valore={
|
||||||
</Section>
|
<span className="inline-flex items-center gap-1">
|
||||||
|
<Flame className="h-5 w-5 text-accent" />
|
||||||
<Section titolo="Serie di presenze" indice={2}>
|
{g.streak}
|
||||||
<SerieGriglia g={g} />
|
</span>
|
||||||
</Section>
|
}
|
||||||
|
label="Presenze di fila"
|
||||||
<SezioneTendina titolo="Collezione badge" indice={3}>
|
/>
|
||||||
<CollezioneBadge g={g} votiSocial={votiSocial.data ?? []} />
|
<StatTile valore={g.mediaVoto || "—"} label="Media voto" />
|
||||||
</SezioneTendina>
|
<StatTile valore={g.mvp} label="MVP" />
|
||||||
|
<StatTile valore={g.palloni} label="Turni palloni" />
|
||||||
<ProfiloAmministrativo giocatoreId={g.id} indice={4} />
|
</div>
|
||||||
|
<SerieGriglia g={g} />
|
||||||
<Section titolo="Impostazioni">
|
</div>
|
||||||
<div className="divide-y divide-border overflow-hidden rounded-3xl bg-card shadow-card">
|
),
|
||||||
<button
|
},
|
||||||
type="button"
|
{
|
||||||
onClick={cambiaNotifiche}
|
id: "badge",
|
||||||
disabled={!supportate || inCorso}
|
label: "Badge",
|
||||||
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-left text-sm disabled:opacity-60"
|
contenuto: <CollezioneBadge g={g} votiSocial={votiSocial.data ?? []} />,
|
||||||
>
|
},
|
||||||
<span className="min-w-0">
|
{
|
||||||
<span className="block truncate">Notifiche turno palloni</span>
|
id: "tesseramento",
|
||||||
<span className="block text-xs text-muted-foreground">
|
label: "Tesseramento",
|
||||||
{supportate
|
contenuto: <ProfiloAmministrativo giocatoreId={g.id} conTendina={false} />,
|
||||||
? notifiche
|
},
|
||||||
? "Attive su questo dispositivo"
|
{
|
||||||
: "Ricevi l'avviso il giorno stesso e la volta dopo"
|
id: "impostazioni",
|
||||||
: "Non supportate su questo dispositivo"}
|
label: "Impostazioni",
|
||||||
</span>
|
contenuto: (
|
||||||
</span>
|
<div className="divide-y divide-border overflow-hidden rounded-3xl bg-card shadow-card">
|
||||||
<span
|
<button
|
||||||
className={cn(
|
type="button"
|
||||||
"grid h-8 w-8 shrink-0 place-items-center rounded-xl",
|
onClick={cambiaNotifiche}
|
||||||
notifiche
|
disabled={!supportate || inCorso}
|
||||||
? "bg-accent-grad text-accent-foreground"
|
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-left text-sm disabled:opacity-60"
|
||||||
: "bg-secondary text-muted-foreground",
|
>
|
||||||
)}
|
<span className="min-w-0">
|
||||||
>
|
<span className="block truncate">Notifiche turno palloni</span>
|
||||||
<Bell className="h-4 w-4" />
|
<span className="block text-xs text-muted-foreground">
|
||||||
</span>
|
{supportate
|
||||||
</button>
|
? notifiche
|
||||||
{admin ? (
|
? "Attive su questo dispositivo"
|
||||||
<Link
|
: "Ricevi l'avviso il giorno stesso e la volta dopo"
|
||||||
to="/admin"
|
: "Non supportate su questo dispositivo"}
|
||||||
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-sm transition-colors hover:bg-accent/5"
|
</span>
|
||||||
>
|
</span>
|
||||||
<span className="min-w-0 truncate">Dashboard amministratore</span>
|
<span
|
||||||
<ShieldCheck className="h-4 w-4 text-muted-foreground" />
|
className={cn(
|
||||||
</Link>
|
"grid h-8 w-8 shrink-0 place-items-center rounded-xl",
|
||||||
) : null}
|
notifiche
|
||||||
<a
|
? "bg-accent-grad text-accent-foreground"
|
||||||
href="https://github.com/ivancacciari1995-a11y/CRAPP/issues/new?template=bug_report.yml"
|
: "bg-secondary text-muted-foreground",
|
||||||
target="_blank"
|
)}
|
||||||
rel="noopener noreferrer"
|
>
|
||||||
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-sm transition-colors hover:bg-accent/5"
|
<Bell className="h-4 w-4" />
|
||||||
>
|
</span>
|
||||||
<span className="min-w-0 truncate">Segnala un bug</span>
|
</button>
|
||||||
<Bug className="h-4 w-4 text-muted-foreground" />
|
{admin ? (
|
||||||
</a>
|
<Link
|
||||||
<a
|
to="/admin"
|
||||||
href="https://github.com/ivancacciari1995-a11y/CRAPP/issues/new?template=feature_request.yml"
|
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-sm transition-colors hover:bg-accent/5"
|
||||||
target="_blank"
|
>
|
||||||
rel="noopener noreferrer"
|
<span className="min-w-0 truncate">Dashboard amministratore</span>
|
||||||
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-sm transition-colors hover:bg-accent/5"
|
<ShieldCheck className="h-4 w-4 text-muted-foreground" />
|
||||||
>
|
</Link>
|
||||||
<span className="min-w-0 truncate">Suggerisci una nuova funzionalità</span>
|
) : null}
|
||||||
<Lightbulb className="h-4 w-4 text-muted-foreground" />
|
<a
|
||||||
</a>
|
href="https://github.com/ivancacciari1995-a11y/CRAPP/issues/new?template=bug_report.yml"
|
||||||
<button
|
target="_blank"
|
||||||
type="button"
|
rel="noopener noreferrer"
|
||||||
onClick={logout}
|
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-sm transition-colors hover:bg-accent/5"
|
||||||
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-sm transition-colors hover:bg-accent/5"
|
>
|
||||||
>
|
<span className="min-w-0 truncate">Segnala un bug</span>
|
||||||
<span className="min-w-0 truncate">Esci</span>
|
<Bug className="h-4 w-4 text-muted-foreground" />
|
||||||
<LogOut className="h-4 w-4 text-muted-foreground" />
|
</a>
|
||||||
</button>
|
<a
|
||||||
</div>
|
href="https://github.com/ivancacciari1995-a11y/CRAPP/issues/new?template=feature_request.yml"
|
||||||
</Section>
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-sm transition-colors hover:bg-accent/5"
|
||||||
|
>
|
||||||
|
<span className="min-w-0 truncate">Suggerisci una nuova funzionalità</span>
|
||||||
|
<Lightbulb className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={logout}
|
||||||
|
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-sm transition-colors hover:bg-accent/5"
|
||||||
|
>
|
||||||
|
<span className="min-w-0 truncate">Esci</span>
|
||||||
|
<LogOut className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user