Aggiunge lo swipe al calendario e ripulisce le schermate
Calendario: il mese si cambiava solo con due frecce da 36 px. Ora si scorre, e il punto d'arrivo si sceglie proiettando la velocità di rilascio invece che dalla posizione del dito, così un colpo secco «lancia» il mese. La griglia entra ed esce dallo stesso lato del gesto, `dragElastic` dà resistenza progressiva al bordo al posto di uno stop netto. Le celle con più tipi di evento generavano un gradiente a fette e ci mettevano sopra un'ombra bianca sul numero per tenerlo leggibile: un cerotto su un problema di contrasto. Ora fondo neutro e un puntino per tipo, con il numero sempre su superficie piena. L'etichetta accessibile dice quanti eventi ci sono, non solo che ce ne sono. Profilo: le quattro switch «Notifiche convocazioni», «Promemoria allenamenti», «Cambi orario» e «Bacheca squadra» erano `defaultChecked` e non facevano niente — promettevano una funzione che non esiste. Via anche la conferma nativa prima di *cambiare* la foto: non è un'azione distruttiva, e chiedere conferma per tutto insegna a rispondere sì senza leggere. Resta su quella che la rimuove, che è irreversibile. Home: la sezione «Da confermare» mostrava titolo e contenitore vuoto quando non c'era altro da confermare, mentre tutte le altre sezioni hanno un empty state. Squadra: i badge in riga avevano solo `title=`, che su touch non appare mai — aggiunto il testo per gli screen reader. I filtri per criterio erano alti ~26 px. Più, in tutte le schermate: la primitiva `Card` al posto delle classi ripetute, i bersagli sotto i 44 px portati in misura e il floor tipografico a 12 px. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -90,7 +90,7 @@ function messaggioErrore(e: unknown, fallback: string): string {
|
|||||||
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="text-[10px] font-bold uppercase tracking-wide text-muted-foreground">
|
<span className="text-xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 block">{children}</span>
|
<span className="mt-1 block">{children}</span>
|
||||||
@@ -375,7 +375,7 @@ function Documento({
|
|||||||
onClick={scarica}
|
onClick={scarica}
|
||||||
disabled={!path || inCorso}
|
disabled={!path || inCorso}
|
||||||
className={cn(
|
className={cn(
|
||||||
"premi flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-bold uppercase disabled:opacity-60",
|
"premi flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-bold uppercase disabled:opacity-60",
|
||||||
statoClasse[stato],
|
statoClasse[stato],
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -464,7 +464,7 @@ function SchedaGiocatore({
|
|||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-bold uppercase",
|
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-bold uppercase",
|
||||||
statoClasse[g.numeroTessera ? "presente" : "assente"],
|
statoClasse[g.numeroTessera ? "presente" : "assente"],
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
+82
-41
@@ -2,9 +2,11 @@ import { useEffect, useState } from "react";
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { CalendarPlus, ChevronLeft, ChevronRight, X } from "lucide-react";
|
import { CalendarPlus, ChevronLeft, ChevronRight, X } from "lucide-react";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { molla, proietta } from "@/lib/molla";
|
||||||
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
||||||
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
import { Card, PageHeader, Section } from "@/components/crapp/ui-bits";
|
||||||
import { compleanniEventi, useEventi, type Evento } from "@/lib/eventi";
|
import { compleanniEventi, useEventi, type Evento } from "@/lib/eventi";
|
||||||
import { useRosa } from "@/lib/rosa";
|
import { useRosa } from "@/lib/rosa";
|
||||||
import { useGiocatoreCorrente } from "@/lib/user-store";
|
import { useGiocatoreCorrente } from "@/lib/user-store";
|
||||||
@@ -55,8 +57,12 @@ function useMeseNav(initial?: { anno: number; mese: number }) {
|
|||||||
const oggi = new Date();
|
const oggi = new Date();
|
||||||
const [anno, setAnno] = useState(initial?.anno ?? oggi.getFullYear());
|
const [anno, setAnno] = useState(initial?.anno ?? oggi.getFullYear());
|
||||||
const [mese, setMese] = useState(initial?.mese ?? oggi.getMonth());
|
const [mese, setMese] = useState(initial?.mese ?? oggi.getMonth());
|
||||||
|
// Serve a far entrare e uscire la griglia dallo stesso lato del gesto:
|
||||||
|
// se un mese esce a sinistra, il precedente deve rientrare da sinistra.
|
||||||
|
const [direzione, setDirezione] = useState(0);
|
||||||
|
|
||||||
const precedente = () => {
|
const precedente = () => {
|
||||||
|
setDirezione(-1);
|
||||||
if (mese === 0) {
|
if (mese === 0) {
|
||||||
setMese(11);
|
setMese(11);
|
||||||
setAnno((a) => a - 1);
|
setAnno((a) => a - 1);
|
||||||
@@ -66,6 +72,7 @@ function useMeseNav(initial?: { anno: number; mese: number }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const successivo = () => {
|
const successivo = () => {
|
||||||
|
setDirezione(1);
|
||||||
if (mese === 11) {
|
if (mese === 11) {
|
||||||
setMese(0);
|
setMese(0);
|
||||||
setAnno((a) => a + 1);
|
setAnno((a) => a + 1);
|
||||||
@@ -74,7 +81,7 @@ function useMeseNav(initial?: { anno: number; mese: number }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { anno, mese, precedente, successivo };
|
return { anno, mese, direzione, precedente, successivo };
|
||||||
}
|
}
|
||||||
|
|
||||||
function giorniDelMese(anno: number, mese: number) {
|
function giorniDelMese(anno: number, mese: number) {
|
||||||
@@ -102,7 +109,8 @@ function Calendario() {
|
|||||||
const admin = useIsAdmin();
|
const admin = useIsAdmin();
|
||||||
const { eventi } = useEventi();
|
const { eventi } = useEventi();
|
||||||
const rosa = useRosa();
|
const rosa = useRosa();
|
||||||
const { anno, mese, precedente, successivo } = useMeseNav();
|
const ridotto = useReducedMotion();
|
||||||
|
const { anno, mese, direzione, precedente, successivo } = useMeseNav();
|
||||||
const { giorni, offsetLunedi } = giorniDelMese(anno, mese);
|
const { giorni, offsetLunedi } = giorniDelMese(anno, mese);
|
||||||
const mesePrefix = `${anno}-${pad2(mese + 1)}`;
|
const mesePrefix = `${anno}-${pad2(mese + 1)}`;
|
||||||
|
|
||||||
@@ -129,12 +137,8 @@ function Calendario() {
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
// Prossimi 4 eventi da oggi in avanti (indipendenti dal mese selezionato nella griglia).
|
// Prossimi 4 eventi da oggi in avanti (indipendenti dal mese selezionato nella griglia).
|
||||||
const oggiIso = oggi
|
const oggiIso = oggi ? `${oggi.anno}-${pad2(oggi.mese + 1)}-${pad2(oggi.giorno)}` : null;
|
||||||
? `${oggi.anno}-${pad2(oggi.mese + 1)}-${pad2(oggi.giorno)}`
|
const prossimiEventi = oggiIso ? eventi.filter((e) => e.data >= oggiIso).slice(0, 4) : [];
|
||||||
: null;
|
|
||||||
const prossimiEventi = oggiIso
|
|
||||||
? eventi.filter((e) => e.data >= oggiIso).slice(0, 4)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -147,8 +151,9 @@ function Calendario() {
|
|||||||
key={v}
|
key={v}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setVista(v)}
|
onClick={() => setVista(v)}
|
||||||
|
aria-pressed={vista === v}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 rounded-full py-2 text-xs font-bold uppercase tracking-wide transition-colors",
|
"min-h-11 flex-1 rounded-full text-xs font-bold uppercase tracking-wide transition-colors",
|
||||||
vista === v ? "bg-card shadow-card text-foreground" : "text-muted-foreground",
|
vista === v ? "bg-card shadow-card text-foreground" : "text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -160,35 +165,61 @@ function Calendario() {
|
|||||||
|
|
||||||
{vista === "mese" ? (
|
{vista === "mese" ? (
|
||||||
<Section titolo={mesiIT[mese]!}>
|
<Section titolo={mesiIT[mese]!}>
|
||||||
<div className="rounded-3xl bg-card p-4 shadow-card">
|
<Card>
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<div className="mb-3 flex items-center justify-between">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={precedente}
|
onClick={precedente}
|
||||||
className="grid h-9 w-9 place-items-center rounded-full bg-secondary text-foreground active:scale-95"
|
className="grid h-11 w-11 place-items-center rounded-full bg-secondary text-foreground active:scale-95"
|
||||||
aria-label="Mese precedente"
|
aria-label="Mese precedente"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-5 w-5" />
|
<ChevronLeft className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
<span className="font-display text-xl uppercase tracking-wide">
|
<span className="font-display-sm text-xl uppercase">
|
||||||
{mesiIT[mese]} {anno}
|
{mesiIT[mese]} {anno}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={successivo}
|
onClick={successivo}
|
||||||
className="grid h-9 w-9 place-items-center rounded-full bg-secondary text-foreground active:scale-95"
|
className="grid h-11 w-11 place-items-center rounded-full bg-secondary text-foreground active:scale-95"
|
||||||
aria-label="Mese successivo"
|
aria-label="Mese successivo"
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-5 w-5" />
|
<ChevronRight className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-7 gap-1 text-center text-[11px] font-bold text-muted-foreground">
|
<div className="grid grid-cols-7 gap-1 text-center text-xs font-bold text-muted-foreground">
|
||||||
{giorniIT.map((g, i) => (
|
{giorniIT.map((g, i) => (
|
||||||
<span key={i}>{g}</span>
|
<span key={i}>{g}</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 grid grid-cols-7 gap-1">
|
{/*
|
||||||
|
Il mese si cambia anche con lo swipe: il punto d'arrivo si
|
||||||
|
decide proiettando la velocità di rilascio (come la
|
||||||
|
decelerazione dello scroll iOS), non dalla posizione del dito.
|
||||||
|
`dragElastic` dà la resistenza progressiva al bordo invece di
|
||||||
|
uno stop netto.
|
||||||
|
*/}
|
||||||
|
<div className="relative mt-2 overflow-hidden">
|
||||||
|
<AnimatePresence initial={false} mode="popLayout" custom={direzione}>
|
||||||
|
<motion.div
|
||||||
|
key={mesePrefix}
|
||||||
|
custom={direzione}
|
||||||
|
drag="x"
|
||||||
|
dragConstraints={{ left: 0, right: 0 }}
|
||||||
|
dragElastic={0.18}
|
||||||
|
dragMomentum={false}
|
||||||
|
onDragEnd={(_, info) => {
|
||||||
|
const arrivo = info.offset.x + proietta(info.velocity.x);
|
||||||
|
if (arrivo < -60) successivo();
|
||||||
|
else if (arrivo > 60) precedente();
|
||||||
|
}}
|
||||||
|
initial={ridotto ? { opacity: 0 } : { opacity: 0, x: direzione * 48 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={ridotto ? { opacity: 0 } : { opacity: 0, x: direzione * -48 }}
|
||||||
|
transition={ridotto ? { duration: 0.2 } : molla.foglio}
|
||||||
|
className="grid touch-pan-y grid-cols-7 gap-1"
|
||||||
|
>
|
||||||
{Array.from({ length: offsetLunedi }).map((_, i) => (
|
{Array.from({ length: offsetLunedi }).map((_, i) => (
|
||||||
<span key={`v${i}`} />
|
<span key={`v${i}`} />
|
||||||
))}
|
))}
|
||||||
@@ -197,22 +228,10 @@ function Calendario() {
|
|||||||
const eventiGiorno = eventiPerGiorno.get(giorno) ?? [];
|
const eventiGiorno = eventiPerGiorno.get(giorno) ?? [];
|
||||||
const haEventi = eventiGiorno.length > 0;
|
const haEventi = eventiGiorno.length > 0;
|
||||||
const tipiGiorno = Array.from(new Set(eventiGiorno.map((e) => e.tipo)));
|
const tipiGiorno = Array.from(new Set(eventiGiorno.map((e) => e.tipo)));
|
||||||
const coloreTipo: Record<Evento["tipo"], string> = {
|
// Un solo tipo: cella piena, il colore è l'informazione.
|
||||||
partita: "var(--accent)",
|
// Più tipi: fondo neutro e un puntino per tipo. Prima si
|
||||||
allenamento: "var(--training)",
|
// generava un gradiente a fette, che rendeva il numero
|
||||||
evento: "var(--warning)",
|
// illeggibile e costringeva a un'ombra bianca di ripiego.
|
||||||
compleanno: "var(--success)",
|
|
||||||
};
|
|
||||||
const sfondo =
|
|
||||||
tipiGiorno.length > 1
|
|
||||||
? `linear-gradient(135deg, ${tipiGiorno
|
|
||||||
.map((t, idx) => {
|
|
||||||
const da = (idx / tipiGiorno.length) * 100;
|
|
||||||
const a = ((idx + 1) / tipiGiorno.length) * 100;
|
|
||||||
return `${coloreTipo[t]} ${da}%, ${coloreTipo[t]} ${a}%`;
|
|
||||||
})
|
|
||||||
.join(", ")})`
|
|
||||||
: undefined;
|
|
||||||
const tipo = tipiGiorno.length === 1 ? tipiGiorno[0] : undefined;
|
const tipo = tipiGiorno.length === 1 ? tipiGiorno[0] : undefined;
|
||||||
const isOggi =
|
const isOggi =
|
||||||
!!oggi && oggi.anno === anno && oggi.mese === mese && oggi.giorno === giorno;
|
!!oggi && oggi.anno === anno && oggi.mese === mese && oggi.giorno === giorno;
|
||||||
@@ -222,7 +241,6 @@ function Calendario() {
|
|||||||
key={giorno}
|
key={giorno}
|
||||||
type={haEventi ? "button" : undefined}
|
type={haEventi ? "button" : undefined}
|
||||||
onClick={haEventi ? () => apriGiorno(giorno) : undefined}
|
onClick={haEventi ? () => apriGiorno(giorno) : undefined}
|
||||||
style={sfondo ? { backgroundImage: sfondo } : undefined}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative grid aspect-square place-items-center rounded-xl text-sm font-semibold",
|
"relative grid aspect-square place-items-center rounded-xl text-sm font-semibold",
|
||||||
tipo === "partita" && "bg-accent text-accent-foreground",
|
tipo === "partita" && "bg-accent text-accent-foreground",
|
||||||
@@ -230,21 +248,44 @@ function Calendario() {
|
|||||||
tipo === "evento" && "bg-warning text-warning-foreground",
|
tipo === "evento" && "bg-warning text-warning-foreground",
|
||||||
tipo === "compleanno" && "bg-success text-success-foreground",
|
tipo === "compleanno" && "bg-success text-success-foreground",
|
||||||
!tipo && !haEventi && "text-muted-foreground",
|
!tipo && !haEventi && "text-muted-foreground",
|
||||||
!tipo && haEventi && "text-foreground",
|
!tipo && haEventi && "bg-secondary text-foreground",
|
||||||
haEventi && "cursor-pointer transition-transform active:scale-90",
|
haEventi && "cursor-pointer transition-transform active:scale-90",
|
||||||
isOggi && "ring-2 ring-foreground ring-offset-1 ring-offset-card",
|
isOggi && "ring-2 ring-foreground ring-offset-1 ring-offset-card",
|
||||||
)}
|
)}
|
||||||
aria-label={haEventi ? `Eventi del ${giorno}` : undefined}
|
aria-label={
|
||||||
|
haEventi
|
||||||
|
? `${giorno} ${mesiIT[mese]}: ${eventiGiorno.length} ${eventiGiorno.length === 1 ? "evento" : "eventi"}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
aria-current={isOggi ? "date" : undefined}
|
aria-current={isOggi ? "date" : undefined}
|
||||||
>
|
>
|
||||||
<span className="relative drop-shadow-[0_1px_1px_rgba(255,255,255,0.5)]">
|
<span>{giorno}</span>
|
||||||
{giorno}
|
{tipiGiorno.length > 1 ? (
|
||||||
|
<span aria-hidden="true" className="absolute bottom-1 flex gap-0.5">
|
||||||
|
{tipiGiorno.slice(0, 3).map((t) => (
|
||||||
|
<i
|
||||||
|
key={t}
|
||||||
|
className={cn(
|
||||||
|
"h-1 w-1 rounded-full",
|
||||||
|
t === "partita" && "bg-accent",
|
||||||
|
t === "allenamento" && "bg-training",
|
||||||
|
t === "evento" && "bg-warning",
|
||||||
|
t === "compleanno" && "bg-success",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</span>
|
</span>
|
||||||
|
) : null}
|
||||||
</Cella>
|
</Cella>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex flex-wrap gap-3 text-[11px] font-semibold text-muted-foreground">
|
<p className="mt-3 text-xs text-muted-foreground">
|
||||||
|
Scorri a destra o sinistra per cambiare mese.
|
||||||
|
</p>
|
||||||
|
<div className="mt-3 flex flex-wrap gap-3 text-xs font-semibold text-muted-foreground">
|
||||||
<span className="inline-flex items-center gap-1">
|
<span className="inline-flex items-center gap-1">
|
||||||
<i className="h-2.5 w-2.5 rounded-full bg-accent" /> Partita
|
<i className="h-2.5 w-2.5 rounded-full bg-accent" /> Partita
|
||||||
</span>
|
</span>
|
||||||
@@ -258,7 +299,7 @@ function Calendario() {
|
|||||||
<i className="h-2.5 w-2.5 rounded-full bg-success" /> Compleanni
|
<i className="h-2.5 w-2.5 rounded-full bg-success" /> Compleanni
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -303,10 +344,10 @@ function Calendario() {
|
|||||||
<Drawer open={drawerAperto} onOpenChange={setDrawerAperto}>
|
<Drawer open={drawerAperto} onOpenChange={setDrawerAperto}>
|
||||||
<DrawerContent className="rounded-t-[24px] border-border bg-background px-4 pb-6 pt-2">
|
<DrawerContent className="rounded-t-[24px] border-border bg-background px-4 pb-6 pt-2">
|
||||||
<DrawerHeader className="relative px-0 pb-2 text-left">
|
<DrawerHeader className="relative px-0 pb-2 text-left">
|
||||||
<DrawerTitle className="font-display text-2xl uppercase tracking-wide">
|
<DrawerTitle className="font-display-lg text-2xl uppercase">
|
||||||
{giornoSelezionato ? `${giornoSelezionato} ${mesiIT[mese]}` : "Eventi"}
|
{giornoSelezionato ? `${giornoSelezionato} ${mesiIT[mese]}` : "Eventi"}
|
||||||
</DrawerTitle>
|
</DrawerTitle>
|
||||||
<DrawerClose className="absolute right-0 top-1 grid h-8 w-8 place-items-center rounded-full bg-secondary text-foreground transition-transform active:scale-90">
|
<DrawerClose className="absolute right-0 top-0 grid h-11 w-11 place-items-center rounded-full bg-secondary text-foreground transition-transform active:scale-90">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
<span className="sr-only">Chiudi</span>
|
<span className="sr-only">Chiudi</span>
|
||||||
</DrawerClose>
|
</DrawerClose>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ function Classifica() {
|
|||||||
|
|
||||||
<Section titolo="Classifica">
|
<Section titolo="Classifica">
|
||||||
<div className="overflow-hidden rounded-3xl bg-card shadow-card">
|
<div className="overflow-hidden rounded-3xl bg-card shadow-card">
|
||||||
<div className="grid grid-cols-[2rem_minmax(0,1fr)_2rem_2.5rem_2.5rem] gap-2 border-b border-border px-3 py-2 text-[10px] font-bold uppercase text-muted-foreground">
|
<div className="grid grid-cols-[2rem_minmax(0,1fr)_2rem_2.5rem_2.5rem] gap-2 border-b border-border px-3 py-2 text-xs font-bold uppercase text-muted-foreground">
|
||||||
<span>#</span>
|
<span>#</span>
|
||||||
<span>Squadra</span>
|
<span>Squadra</span>
|
||||||
<span className="text-center">G</span>
|
<span className="text-center">G</span>
|
||||||
@@ -141,7 +141,7 @@ function Classifica() {
|
|||||||
{m.casa ? "CRAP Volley" : m.avversario} vs{" "}
|
{m.casa ? "CRAP Volley" : m.avversario} vs{" "}
|
||||||
{m.casa ? m.avversario : "CRAP Volley"}
|
{m.casa ? m.avversario : "CRAP Volley"}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{formatData(m.data)} · MVP {m.mvp || "da votare"}
|
{formatData(m.data)} · MVP {m.mvp || "da votare"}
|
||||||
{m.scout ? " · scoutata" : ""}
|
{m.scout ? " · scoutata" : ""}
|
||||||
</p>
|
</p>
|
||||||
@@ -162,7 +162,7 @@ function Classifica() {
|
|||||||
<span
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-lg px-2 py-1 text-[11px] font-semibold tabular-nums",
|
"rounded-lg px-2 py-1 text-xs font-semibold tabular-nums",
|
||||||
p[0] > p[1] ? "bg-secondary" : "bg-muted text-muted-foreground",
|
p[0] > p[1] ? "bg-secondary" : "bg-muted text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -170,7 +170,7 @@ function Classifica() {
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{eventoId && !m.mvp ? (
|
{eventoId && !m.mvp ? (
|
||||||
<span className="ml-auto inline-flex items-center gap-0.5 text-[11px] 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" />
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ function GestioneEventi() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => aggiorna(daCategoria(t.id))}
|
onClick={() => aggiorna(daCategoria(t.id))}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full py-2 text-[11px] font-bold uppercase transition-colors",
|
"rounded-full py-2 text-xs font-bold uppercase transition-colors",
|
||||||
categoriaEvento(bozza) === t.id
|
categoriaEvento(bozza) === t.id
|
||||||
? "bg-card shadow-card text-foreground"
|
? "bg-card shadow-card text-foreground"
|
||||||
: "text-muted-foreground",
|
: "text-muted-foreground",
|
||||||
@@ -281,7 +281,9 @@ function GestioneEventi() {
|
|||||||
|
|
||||||
<Section titolo="Eventi in calendario">
|
<Section titolo="Eventi in calendario">
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<p className="text-center text-xs text-muted-foreground">Carico gli eventi…</p>
|
<p aria-busy="true" className="text-center text-xs text-muted-foreground">
|
||||||
|
Carico gli eventi…
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{eventi.map((e) => (
|
{eventi.map((e) => (
|
||||||
@@ -291,14 +293,14 @@ function GestioneEventi() {
|
|||||||
>
|
>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="truncate text-sm font-bold leading-tight">{e.titolo}</p>
|
<p className="truncate text-sm font-bold leading-tight">{e.titolo}</p>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{formatData(e.data)} · {e.ora} · {e.luogo || "luogo da definire"}
|
{formatData(e.data)} · {e.ora} · {e.luogo || "luogo da definire"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setBozza(e)}
|
onClick={() => setBozza(e)}
|
||||||
className="grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-secondary text-foreground active:scale-95"
|
className="grid h-11 w-11 shrink-0 place-items-center rounded-xl bg-secondary text-foreground active:scale-95"
|
||||||
aria-label={`Modifica ${e.titolo}`}
|
aria-label={`Modifica ${e.titolo}`}
|
||||||
>
|
>
|
||||||
<Pencil className="h-4 w-4" />
|
<Pencil className="h-4 w-4" />
|
||||||
@@ -306,7 +308,7 @@ function GestioneEventi() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => rimuovi(e.id)}
|
onClick={() => rimuovi(e.id)}
|
||||||
className="grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-destructive/10 text-destructive active:scale-95"
|
className="grid h-11 w-11 shrink-0 place-items-center rounded-xl bg-destructive/10 text-destructive active:scale-95"
|
||||||
aria-label={`Elimina ${e.titolo}`}
|
aria-label={`Elimina ${e.titolo}`}
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
@@ -323,7 +325,7 @@ function GestioneEventi() {
|
|||||||
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
function Campo({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="text-[10px] font-bold uppercase tracking-wide text-muted-foreground">
|
<span className="text-xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 block">{children}</span>
|
<span className="mt-1 block">{children}</span>
|
||||||
@@ -345,7 +347,7 @@ function Interruttore({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full px-3 py-1.5 text-[11px] font-bold uppercase transition-colors",
|
"rounded-full px-3 py-1.5 text-xs font-bold uppercase transition-colors",
|
||||||
attivo ? "bg-accent text-accent-foreground" : "bg-secondary text-muted-foreground",
|
attivo ? "bg-accent text-accent-foreground" : "bg-secondary text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
+20
-13
@@ -2,7 +2,7 @@ import { createFileRoute, Link } from "@tanstack/react-router";
|
|||||||
import { Flame, ChevronRight } from "lucide-react";
|
import { Flame, ChevronRight } from "lucide-react";
|
||||||
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard";
|
||||||
import { PromemoriaPalloni } from "@/components/crapp/PromemoriaPalloni";
|
import { PromemoriaPalloni } from "@/components/crapp/PromemoriaPalloni";
|
||||||
import { Section, StatTile, TeamLogo } from "@/components/crapp/ui-bits";
|
import { Card, Section, StatTile, TeamLogo } from "@/components/crapp/ui-bits";
|
||||||
import { CompletaProfilo } from "@/components/crapp/ProfiloAmministrativo";
|
import { CompletaProfilo } from "@/components/crapp/ProfiloAmministrativo";
|
||||||
import { Reveal } from "@/components/motion/Reveal";
|
import { Reveal } from "@/components/motion/Reveal";
|
||||||
import { Barra } from "@/components/motion/Barra";
|
import { Barra } from "@/components/motion/Barra";
|
||||||
@@ -41,6 +41,7 @@ function Index() {
|
|||||||
const oggi = new Date().toISOString().slice(0, 10);
|
const oggi = new Date().toISOString().slice(0, 10);
|
||||||
const prossimi: Evento[] = eventi.filter((e) => e.data >= oggi).slice(0, 3);
|
const prossimi: Evento[] = eventi.filter((e) => e.data >= oggi).slice(0, 3);
|
||||||
const prossimo = prossimi[0] ?? null;
|
const prossimo = prossimi[0] ?? null;
|
||||||
|
const daConfermare = prossimi.slice(1);
|
||||||
const linkProssimo = prossimo ? linkPerEvento(prossimo) : null;
|
const linkProssimo = prossimo ? linkPerEvento(prossimo) : null;
|
||||||
const { data: csi } = useCsi();
|
const { data: csi } = useCsi();
|
||||||
const noi = csi?.classifica.find((r) => isNostraSquadra(r.squadra));
|
const noi = csi?.classifica.find((r) => isNostraSquadra(r.squadra));
|
||||||
@@ -66,10 +67,10 @@ function Index() {
|
|||||||
<Reveal as="section" className="bg-hero px-5 pb-10 pt-7 text-primary-foreground">
|
<Reveal as="section" className="bg-hero px-5 pb-10 pt-7 text-primary-foreground">
|
||||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4">
|
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-4">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary-foreground/60">
|
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-primary-foreground/80">
|
||||||
Ciao {giocatore.nome.split(" ")[0]}
|
Ciao {giocatore.nome.split(" ")[0]}
|
||||||
</p>
|
</p>
|
||||||
<h1 className="font-display text-4xl uppercase leading-none">CrAPP</h1>
|
<h1 className="font-display-lg text-4xl uppercase leading-none">CrAPP</h1>
|
||||||
</div>
|
</div>
|
||||||
<TeamLogo className="h-12 w-12" />
|
<TeamLogo className="h-12 w-12" />
|
||||||
</div>
|
</div>
|
||||||
@@ -77,20 +78,20 @@ function Index() {
|
|||||||
<div className="mt-6 grid grid-cols-3 gap-2 text-center">
|
<div className="mt-6 grid grid-cols-3 gap-2 text-center">
|
||||||
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
||||||
<p className="font-display text-2xl leading-none">{noi ? `${noi.pos}º` : "—"}</p>
|
<p className="font-display text-2xl leading-none">{noi ? `${noi.pos}º` : "—"}</p>
|
||||||
<p className="text-[10px] uppercase text-primary-foreground/60">In classifica</p>
|
<p className="text-xs uppercase text-primary-foreground/80">In classifica</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
||||||
<p className="font-display text-2xl leading-none">
|
<p className="font-display text-2xl leading-none">
|
||||||
{noi ? `${noi.vinte}-${noi.perse}` : "—"}
|
{noi ? `${noi.vinte}-${noi.perse}` : "—"}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] uppercase text-primary-foreground/60">Bilancio</p>
|
<p className="text-xs uppercase text-primary-foreground/80">Bilancio</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
<div className="rounded-2xl bg-primary-foreground/10 p-3">
|
||||||
<p className="inline-flex items-center gap-1 font-display text-2xl leading-none">
|
<p className="inline-flex items-center gap-1 font-display text-2xl leading-none">
|
||||||
<Flame className="h-4 w-4 text-accent" />
|
<Flame className="h-4 w-4 text-accent" />
|
||||||
{giocatore.streak}
|
{giocatore.streak}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] uppercase text-primary-foreground/60">Streak</p>
|
<p className="text-xs uppercase text-primary-foreground/80">Streak</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
@@ -122,10 +123,16 @@ function Index() {
|
|||||||
|
|
||||||
<Section titolo="Da confermare" indice={3}>
|
<Section titolo="Da confermare" indice={3}>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{prossimi.slice(1).map((e) => {
|
{daConfermare.length > 0 ? (
|
||||||
|
daConfermare.map((e) => {
|
||||||
const link = linkPerEvento(e);
|
const link = linkPerEvento(e);
|
||||||
return <EventoCard key={e.id} evento={e} {...(link ? { linkTo: link } : {})} />;
|
return <EventoCard key={e.id} evento={e} {...(link ? { linkTo: link } : {})} />;
|
||||||
})}
|
})
|
||||||
|
) : (
|
||||||
|
<p className="rounded-3xl bg-card p-4 text-xs text-muted-foreground shadow-card">
|
||||||
|
Nient'altro da confermare: sei in pari.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
@@ -161,7 +168,7 @@ function Index() {
|
|||||||
{ultima.parziali.map((p, i) => (
|
{ultima.parziali.map((p, i) => (
|
||||||
<span
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
className="rounded-lg bg-secondary px-2 py-1 text-[11px] font-semibold tabular-nums"
|
className="rounded-lg bg-secondary px-2 py-1 text-xs font-semibold tabular-nums"
|
||||||
>
|
>
|
||||||
{p[0]}-{p[1]}
|
{p[0]}-{p[1]}
|
||||||
</span>
|
</span>
|
||||||
@@ -178,7 +185,7 @@ function Index() {
|
|||||||
{corpo}
|
{corpo}
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<div className="premi rounded-3xl bg-card p-4 shadow-card">{corpo}</div>
|
<Card>{corpo}</Card>
|
||||||
);
|
);
|
||||||
})()
|
})()
|
||||||
) : (
|
) : (
|
||||||
@@ -201,7 +208,7 @@ function Index() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{obiettivo ? (
|
{obiettivo ? (
|
||||||
<div className="premi rounded-3xl bg-card p-4 shadow-card">
|
<Card>
|
||||||
<div className="flex items-center gap-2 text-sm font-bold">
|
<div className="flex items-center gap-2 text-sm font-bold">
|
||||||
<span className="text-base leading-none">{obiettivo.emoji}</span> {obiettivo.titolo}
|
<span className="text-base leading-none">{obiettivo.emoji}</span> {obiettivo.titolo}
|
||||||
</div>
|
</div>
|
||||||
@@ -213,8 +220,8 @@ function Index() {
|
|||||||
<p className="mt-1 text-xs font-semibold text-accent">
|
<p className="mt-1 text-xs font-semibold text-accent">
|
||||||
{microcopyObiettivo(obiettivo)}
|
{microcopyObiettivo(obiettivo)}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-[11px] text-muted-foreground">{obiettivo.impatto}</p>
|
<p className="mt-1 text-xs text-muted-foreground">{obiettivo.impatto}</p>
|
||||||
</div>
|
</Card>
|
||||||
) : null}
|
) : null}
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createFileRoute, Link } from "@tanstack/react-router";
|
import { createFileRoute, Link } from "@tanstack/react-router";
|
||||||
import { ArrowLeft, MapPin, Clock, Users, Trophy, Swords, Download } from "lucide-react";
|
import { ArrowLeft, MapPin, Clock, Users, Trophy, Swords, Download } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { PageHeader, Section } from "@/components/crapp/ui-bits";
|
import { Card, PageHeader, Section } from "@/components/crapp/ui-bits";
|
||||||
import { formatData } from "@/lib/crapp-data";
|
import { formatData } from "@/lib/crapp-data";
|
||||||
import { convocatiEvento, useEvento } from "@/lib/eventi";
|
import { convocatiEvento, useEvento } from "@/lib/eventi";
|
||||||
import { useRosa } from "@/lib/rosa";
|
import { useRosa } from "@/lib/rosa";
|
||||||
@@ -210,7 +210,7 @@ function PartitaDetail() {
|
|||||||
|
|
||||||
{scout && totaliTeam ? (
|
{scout && totaliTeam ? (
|
||||||
<Section titolo="Report tecnico">
|
<Section titolo="Report tecnico">
|
||||||
<div className="rounded-3xl bg-card p-4 shadow-card">
|
<Card>
|
||||||
<div className="grid grid-cols-4 gap-2 text-center">
|
<div className="grid grid-cols-4 gap-2 text-center">
|
||||||
{[
|
{[
|
||||||
{ l: "Punti", v: totaliTeam.punti },
|
{ l: "Punti", v: totaliTeam.punti },
|
||||||
@@ -220,13 +220,13 @@ function PartitaDetail() {
|
|||||||
].map((t) => (
|
].map((t) => (
|
||||||
<div key={t.l} className="rounded-2xl bg-secondary p-2.5">
|
<div key={t.l} className="rounded-2xl bg-secondary p-2.5">
|
||||||
<p className="font-display text-xl leading-none">{t.v}</p>
|
<p className="font-display text-xl leading-none">{t.v}</p>
|
||||||
<p className="mt-1 text-[10px] font-semibold uppercase text-muted-foreground">
|
<p className="mt-1 text-xs font-semibold uppercase text-muted-foreground">
|
||||||
{t.l}
|
{t.l}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-4 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mt-4 text-xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Dettaglio giocatori (uso interno allenatori)
|
Dettaglio giocatori (uso interno allenatori)
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
@@ -262,7 +262,7 @@ function PartitaDetail() {
|
|||||||
<Download className="h-4 w-4" /> Esporta CSV
|
<Download className="h-4 w-4" /> Esporta CSV
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</Card>
|
||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
+13
-27
@@ -3,7 +3,7 @@ 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 { PageHeader, Section, SezioneTendina, StatTile } from "@/components/crapp/ui-bits";
|
import { Card, PageHeader, Section, SezioneTendina, StatTile } from "@/components/crapp/ui-bits";
|
||||||
import { Avatar } from "@/components/crapp/Avatar";
|
import { Avatar } from "@/components/crapp/Avatar";
|
||||||
import {
|
import {
|
||||||
caricaAvatar,
|
caricaAvatar,
|
||||||
@@ -97,15 +97,12 @@ function Profilo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const percPresenze = g.totaliEventi
|
const percPresenze = g.totaliEventi ? Math.round((g.presenze / g.totaliEventi) * 100) : 0;
|
||||||
? Math.round((g.presenze / g.totaliEventi) * 100)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
async function onFile(e: React.ChangeEvent<HTMLInputElement>) {
|
async function onFile(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
e.target.value = "";
|
e.target.value = "";
|
||||||
if (!file || !g) return;
|
if (!file || !g) return;
|
||||||
if (!confirm("Aggiornare l'immagine profilo?")) return;
|
|
||||||
try {
|
try {
|
||||||
await caricaAvatar(g.id, file);
|
await caricaAvatar(g.id, file);
|
||||||
setBust(Date.now());
|
setBust(Date.now());
|
||||||
@@ -121,7 +118,7 @@ function Profilo() {
|
|||||||
<PageHeader titolo={g.nome} sottotitolo={`#${g.numero} · ${g.ruolo}`} />
|
<PageHeader titolo={g.nome} sottotitolo={`#${g.numero} · ${g.ruolo}`} />
|
||||||
|
|
||||||
<Reveal className="-mt-6 px-5">
|
<Reveal className="-mt-6 px-5">
|
||||||
<div className="premi rounded-3xl bg-card p-4 shadow-card">
|
<Card>
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Avatar id={g.id} fallback={g.iniziali} className="h-20 w-20 text-2xl" bust={bust} />
|
<Avatar id={g.id} fallback={g.iniziali} className="h-20 w-20 text-2xl" bust={bust} />
|
||||||
@@ -135,7 +132,7 @@ function Profilo() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => inputRef.current?.click()}
|
onClick={() => inputRef.current?.click()}
|
||||||
className="absolute -bottom-1 -right-1 grid h-8 w-8 place-items-center rounded-full bg-accent-grad text-accent-foreground shadow-pop"
|
className="absolute -bottom-1 -right-1 grid h-11 w-11 place-items-center rounded-full bg-accent-grad text-accent-foreground shadow-pop"
|
||||||
aria-label="Cambia foto"
|
aria-label="Cambia foto"
|
||||||
>
|
>
|
||||||
<Camera className="h-4 w-4" />
|
<Camera className="h-4 w-4" />
|
||||||
@@ -146,7 +143,7 @@ function Profilo() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => inputRef.current?.click()}
|
onClick={() => inputRef.current?.click()}
|
||||||
className="flex-1 rounded-xl bg-secondary px-3 py-2 text-xs font-bold uppercase tracking-wide"
|
className="premi min-h-11 flex-1 rounded-xl bg-secondary px-3 text-xs font-bold uppercase tracking-wide"
|
||||||
>
|
>
|
||||||
Cambia immagine profilo
|
Cambia immagine profilo
|
||||||
</button>
|
</button>
|
||||||
@@ -164,14 +161,14 @@ function Profilo() {
|
|||||||
toast.error("Non sono riuscito a rimuovere l'immagine");
|
toast.error("Non sono riuscito a rimuovere l'immagine");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="grid h-9 w-9 place-items-center rounded-xl bg-secondary text-muted-foreground"
|
className="grid h-11 w-11 shrink-0 place-items-center rounded-xl bg-secondary text-muted-foreground"
|
||||||
aria-label="Rimuovi immagine"
|
aria-label="Rimuovi immagine"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
<Section titolo="Stagione" indice={1}>
|
<Section titolo="Stagione" indice={1}>
|
||||||
@@ -213,11 +210,11 @@ function Profilo() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={cambiaNotifiche}
|
onClick={cambiaNotifiche}
|
||||||
disabled={!supportate || inCorso}
|
disabled={!supportate || inCorso}
|
||||||
className="flex w-full items-center justify-between gap-3 px-4 py-3 text-left text-sm disabled:opacity-60"
|
className="flex min-h-11 w-full items-center justify-between gap-3 px-4 py-3 text-left text-sm disabled:opacity-60"
|
||||||
>
|
>
|
||||||
<span className="min-w-0">
|
<span className="min-w-0">
|
||||||
<span className="block truncate">Notifiche turno palloni</span>
|
<span className="block truncate">Notifiche turno palloni</span>
|
||||||
<span className="block text-[11px] text-muted-foreground">
|
<span className="block text-xs text-muted-foreground">
|
||||||
{supportate
|
{supportate
|
||||||
? notifiche
|
? notifiche
|
||||||
? "Attive su questo dispositivo"
|
? "Attive su questo dispositivo"
|
||||||
@@ -236,21 +233,10 @@ function Profilo() {
|
|||||||
<Bell className="h-4 w-4" />
|
<Bell className="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
{[
|
|
||||||
"Notifiche convocazioni",
|
|
||||||
"Promemoria allenamenti",
|
|
||||||
"Cambi orario",
|
|
||||||
"Bacheca squadra",
|
|
||||||
].map((v) => (
|
|
||||||
<label key={v} className="flex items-center justify-between gap-3 px-4 py-3 text-sm">
|
|
||||||
<span className="min-w-0 truncate">{v}</span>
|
|
||||||
<input type="checkbox" defaultChecked className="h-5 w-9 accent-[var(--accent)]" />
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
{admin ? (
|
{admin ? (
|
||||||
<Link
|
<Link
|
||||||
to="/admin"
|
to="/admin"
|
||||||
className="flex 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">Dashboard amministratore</span>
|
<span className="min-w-0 truncate">Dashboard amministratore</span>
|
||||||
<ShieldCheck className="h-4 w-4 text-muted-foreground" />
|
<ShieldCheck className="h-4 w-4 text-muted-foreground" />
|
||||||
@@ -260,7 +246,7 @@ function Profilo() {
|
|||||||
href="https://github.com/ivancacciari1995-a11y/CRAPP/issues/new?template=bug_report.yml"
|
href="https://github.com/ivancacciari1995-a11y/CRAPP/issues/new?template=bug_report.yml"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex 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">Segnala un bug</span>
|
||||||
<Bug className="h-4 w-4 text-muted-foreground" />
|
<Bug className="h-4 w-4 text-muted-foreground" />
|
||||||
@@ -269,7 +255,7 @@ function Profilo() {
|
|||||||
href="https://github.com/ivancacciari1995-a11y/CRAPP/issues/new?template=feature_request.yml"
|
href="https://github.com/ivancacciari1995-a11y/CRAPP/issues/new?template=feature_request.yml"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex 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">Suggerisci una nuova funzionalità</span>
|
<span className="min-w-0 truncate">Suggerisci una nuova funzionalità</span>
|
||||||
<Lightbulb className="h-4 w-4 text-muted-foreground" />
|
<Lightbulb className="h-4 w-4 text-muted-foreground" />
|
||||||
@@ -277,7 +263,7 @@ function Profilo() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={logout}
|
onClick={logout}
|
||||||
className="flex 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">Esci</span>
|
<span className="min-w-0 truncate">Esci</span>
|
||||||
<LogOut className="h-4 w-4 text-muted-foreground" />
|
<LogOut className="h-4 w-4 text-muted-foreground" />
|
||||||
|
|||||||
+14
-13
@@ -3,6 +3,7 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|||||||
import { Undo2, Save, CheckCircle2, Radio, Lock, CalendarX2, LogOut } from "lucide-react";
|
import { Undo2, Save, CheckCircle2, Radio, Lock, CalendarX2, LogOut } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Card } from "@/components/crapp/ui-bits";
|
||||||
import { formatData } from "@/lib/crapp-data";
|
import { formatData } from "@/lib/crapp-data";
|
||||||
import type { Evento } from "@/lib/eventi";
|
import type { Evento } from "@/lib/eventi";
|
||||||
import { useRosa } from "@/lib/rosa";
|
import { useRosa } from "@/lib/rosa";
|
||||||
@@ -322,7 +323,7 @@ function ScoutBoard({
|
|||||||
<>
|
<>
|
||||||
<header className="sticky top-0 z-30 bg-hero px-5 pb-4 pt-6 text-primary-foreground">
|
<header className="sticky top-0 z-30 bg-hero px-5 pb-4 pt-6 text-primary-foreground">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<span className="inline-flex items-center gap-1.5 rounded-full bg-primary-foreground/10 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide">
|
<span className="inline-flex items-center gap-1.5 rounded-full bg-primary-foreground/10 px-2.5 py-1 text-xs font-bold uppercase tracking-wide">
|
||||||
<Radio className="h-3 w-3 text-accent" /> Scout live · Set {setCorrente}
|
<Radio className="h-3 w-3 text-accent" /> Scout live · Set {setCorrente}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -346,14 +347,14 @@ function ScoutBoard({
|
|||||||
|
|
||||||
<div className="mt-3 grid grid-cols-[1fr_auto_1fr] items-center gap-2 text-center">
|
<div className="mt-3 grid grid-cols-[1fr_auto_1fr] items-center gap-2 text-center">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="truncate text-[11px] uppercase text-primary-foreground/60">CRAP Volley</p>
|
<p className="truncate text-xs uppercase text-primary-foreground/60">CRAP Volley</p>
|
||||||
<p className="font-display text-5xl leading-none text-accent">{puntiNoi}</p>
|
<p className="font-display text-5xl leading-none text-accent">{puntiNoi}</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="font-display text-2xl leading-none text-primary-foreground/50">
|
<p className="font-display text-2xl leading-none text-primary-foreground/50">
|
||||||
{setNostri}-{setLoro}
|
{setNostri}-{setLoro}
|
||||||
</p>
|
</p>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="truncate text-[11px] uppercase text-primary-foreground/60">
|
<p className="truncate text-xs uppercase text-primary-foreground/60">
|
||||||
{avversario || "Avversario"}
|
{avversario || "Avversario"}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-display text-5xl leading-none">{puntiLoro}</p>
|
<p className="font-display text-5xl leading-none">{puntiLoro}</p>
|
||||||
@@ -365,7 +366,7 @@ function ScoutBoard({
|
|||||||
{setChiusi.map((p, i) => (
|
{setChiusi.map((p, i) => (
|
||||||
<span
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
className="rounded-lg bg-primary-foreground/10 px-2 py-0.5 text-[11px] font-semibold tabular-nums"
|
className="rounded-lg bg-primary-foreground/10 px-2 py-0.5 text-xs font-semibold tabular-nums"
|
||||||
>
|
>
|
||||||
{p[0]}-{p[1]}
|
{p[0]}-{p[1]}
|
||||||
</span>
|
</span>
|
||||||
@@ -375,7 +376,7 @@ function ScoutBoard({
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section className="px-5 pt-4">
|
<section className="px-5 pt-4">
|
||||||
<p className="mb-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
1. Tocca il giocatore
|
1. Tocca il giocatore
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-4 gap-2">
|
<div className="grid grid-cols-4 gap-2">
|
||||||
@@ -393,10 +394,10 @@ function ScoutBoard({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p className="font-display text-2xl leading-none">{g.numero}</p>
|
<p className="font-display text-2xl leading-none">{g.numero}</p>
|
||||||
<p className="mt-1 text-[10px] font-bold leading-tight">{g.nome}</p>
|
<p className="mt-1 text-xs font-bold leading-tight">{g.nome}</p>
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-[10px] tabular-nums",
|
"text-xs tabular-nums",
|
||||||
attivo ? "text-accent-foreground/70" : "text-muted-foreground",
|
attivo ? "text-accent-foreground/70" : "text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -409,7 +410,7 @@ function ScoutBoard({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="px-5 pt-4">
|
<section className="px-5 pt-4">
|
||||||
<p className="mb-2 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mb-2 text-xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
2. Tocca l'azione
|
2. Tocca l'azione
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
@@ -447,7 +448,7 @@ function ScoutBoard({
|
|||||||
|
|
||||||
<section className="px-5 pt-4">
|
<section className="px-5 pt-4">
|
||||||
<div className="rounded-3xl bg-card p-3 shadow-card">
|
<div className="rounded-3xl bg-card p-3 shadow-card">
|
||||||
<p className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="text-xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Ultime azioni
|
Ultime azioni
|
||||||
</p>
|
</p>
|
||||||
{ultime.length === 0 ? (
|
{ultime.length === 0 ? (
|
||||||
@@ -467,7 +468,7 @@ function ScoutBoard({
|
|||||||
<span className="truncate">{g ? `#${g.numero} ${g.nome}` : "Avversario"}</span>
|
<span className="truncate">{g ? `#${g.numero} ${g.nome}` : "Avversario"}</span>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 rounded-full px-2 py-0.5 text-[10px] font-bold uppercase",
|
"shrink-0 rounded-full px-2 py-0.5 text-xs font-bold uppercase",
|
||||||
azioniMeta[a.tipo].className,
|
azioniMeta[a.tipo].className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -482,8 +483,8 @@ function ScoutBoard({
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="px-5 pt-4">
|
<section className="px-5 pt-4">
|
||||||
<div className="rounded-3xl bg-card p-4 shadow-card">
|
<Card>
|
||||||
<label className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<label className="text-xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Avversario
|
Avversario
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -507,7 +508,7 @@ function ScoutBoard({
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="grid grid-cols-2 gap-2 px-5 pb-4 pt-4">
|
<section className="grid grid-cols-2 gap-2 px-5 pb-4 pt-4">
|
||||||
|
|||||||
+31
-29
@@ -30,7 +30,7 @@ import {
|
|||||||
|
|
||||||
function RuoloBadge({ ruolo }: { ruolo: string }) {
|
function RuoloBadge({ ruolo }: { ruolo: string }) {
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center rounded-full bg-accent px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-accent-foreground">
|
<span className="inline-flex items-center rounded-full bg-accent px-2 py-0.5 text-xs font-bold uppercase tracking-wide text-accent-foreground">
|
||||||
{ruolo}
|
{ruolo}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -113,7 +113,7 @@ function Squadra() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAperto(isOpen ? null : g.id)}
|
onClick={() => setAperto(isOpen ? null : g.id)}
|
||||||
className="flex w-full items-center gap-3 p-3 text-left active:scale-[0.99]"
|
className="flex min-h-11 w-full items-center gap-3 p-3 text-left active:scale-[0.99]"
|
||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -125,7 +125,7 @@ function Squadra() {
|
|||||||
<span className="block truncate text-sm font-bold leading-tight">{g.nome}</span>
|
<span className="block truncate text-sm font-bold leading-tight">{g.nome}</span>
|
||||||
<span className="mt-1 flex items-center gap-2">
|
<span className="mt-1 flex items-center gap-2">
|
||||||
<RuoloBadge ruolo={g.ruolo} />
|
<RuoloBadge ruolo={g.ruolo} />
|
||||||
<span className="text-[11px] text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{g.numero ? `#${g.numero}` : "n° da definire"}
|
{g.numero ? `#${g.numero}` : "n° da definire"}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -135,8 +135,17 @@ function Squadra() {
|
|||||||
{sbloccati.map((b) => {
|
{sbloccati.map((b) => {
|
||||||
const Icon = b.def.icon;
|
const Icon = b.def.icon;
|
||||||
return (
|
return (
|
||||||
<span key={b.def.id} className="cursor-help" title={b.def.nome}>
|
<span
|
||||||
<Icon className={cn("h-3 w-3", gradoMeta[b.grado!].text)} />
|
key={b.def.id}
|
||||||
|
title={`${b.def.nome} — ${gradoMeta[b.grado!].label}`}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className={cn("h-3.5 w-3.5", gradoMeta[b.grado!].text)}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<span className="sr-only">
|
||||||
|
{b.def.nome}: {gradoMeta[b.grado!].label}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -170,14 +179,14 @@ function Squadra() {
|
|||||||
].map((s) => (
|
].map((s) => (
|
||||||
<div key={s.l} className="rounded-2xl bg-secondary p-2.5 text-center">
|
<div key={s.l} className="rounded-2xl bg-secondary p-2.5 text-center">
|
||||||
<p className="font-display text-xl leading-none">{s.v}</p>
|
<p className="font-display text-xl leading-none">{s.v}</p>
|
||||||
<p className="mt-1 text-[10px] font-semibold uppercase text-muted-foreground">
|
<p className="mt-1 text-xs font-semibold uppercase text-muted-foreground">
|
||||||
{s.l}
|
{s.l}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-4 text-[11px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="mt-4 text-xs font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
Badge sbloccati · {collezioneBadge(g).ottenuti}/{collezioneBadge(g).totali}
|
Badge sbloccati · {collezioneBadge(g).ottenuti}/{collezioneBadge(g).totali}
|
||||||
</p>
|
</p>
|
||||||
{sbloccati.length === 0 ? (
|
{sbloccati.length === 0 ? (
|
||||||
@@ -194,18 +203,13 @@ function Squadra() {
|
|||||||
<div className={cn("rounded-2xl p-2.5 ring-1", meta.bg, meta.ring)}>
|
<div className={cn("rounded-2xl p-2.5 ring-1", meta.bg, meta.ring)}>
|
||||||
<Icon className={cn("h-4 w-4", meta.text)} />
|
<Icon className={cn("h-4 w-4", meta.text)} />
|
||||||
<p className="mt-1 text-xs font-bold leading-tight">{b.def.nome}</p>
|
<p className="mt-1 text-xs font-bold leading-tight">{b.def.nome}</p>
|
||||||
<p className="text-[10px] text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{b.valore} {b.def.unita}
|
{b.valore} {b.def.unita}
|
||||||
{b.prossimaSoglia
|
{b.prossimaSoglia
|
||||||
? ` · ${b.prossimaSoglia} per ${gradoMeta[b.prossimo!].label.toLowerCase()}`
|
? ` · ${b.prossimaSoglia} per ${gradoMeta[b.prossimo!].label.toLowerCase()}`
|
||||||
: ""}
|
: ""}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p className={cn("mt-1.5 text-xs font-bold uppercase", meta.text)}>
|
||||||
className={cn(
|
|
||||||
"mt-1.5 text-[10px] font-bold uppercase",
|
|
||||||
meta.text,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{meta.label}
|
{meta.label}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,8 +244,9 @@ function Squadra() {
|
|||||||
key={c.id}
|
key={c.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setCriterio(c.id)}
|
onClick={() => setCriterio(c.id)}
|
||||||
|
aria-pressed={criterio === c.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0 flex-1 truncate rounded-full px-1.5 py-1.5 text-center text-[10px] font-bold uppercase transition-colors",
|
"min-h-11 min-w-0 flex-1 truncate rounded-full px-1.5 text-center text-xs font-bold uppercase transition-colors",
|
||||||
criterio === c.id
|
criterio === c.id
|
||||||
? "bg-accent text-accent-foreground shadow-pop"
|
? "bg-accent text-accent-foreground shadow-pop"
|
||||||
: "bg-secondary text-muted-foreground",
|
: "bg-secondary text-muted-foreground",
|
||||||
@@ -264,7 +269,7 @@ function Squadra() {
|
|||||||
{g.nome}
|
{g.nome}
|
||||||
{i === 0 ? <Crown className="ml-1 inline h-3.5 w-3.5 text-warning" /> : null}
|
{i === 0 ? <Crown className="ml-1 inline h-3.5 w-3.5 text-warning" /> : null}
|
||||||
</p>
|
</p>
|
||||||
<p className="truncate text-[11px] text-muted-foreground">
|
<p className="truncate text-xs text-muted-foreground">
|
||||||
#{g.numero} · {g.ruolo} · {g.streak} presenze consecutive
|
#{g.numero} · {g.ruolo} · {g.streak} presenze consecutive
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -273,13 +278,12 @@ function Squadra() {
|
|||||||
{valore(g, criterio) || "—"}
|
{valore(g, criterio) || "—"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 h-1.5 overflow-hidden rounded-full bg-secondary">
|
<Barra
|
||||||
<div
|
percentuale={Math.max(6, (valore(g, criterio) / max) * 100)}
|
||||||
className="h-full rounded-full bg-accent-grad"
|
altezza="h-1.5"
|
||||||
style={{ width: `${Math.max(6, (valore(g, criterio) / max) * 100)}%` }}
|
trackClassName="mt-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</SezioneTendina>
|
</SezioneTendina>
|
||||||
@@ -290,7 +294,7 @@ function Squadra() {
|
|||||||
<div className="mb-3 rounded-3xl bg-hero p-4 text-primary-foreground shadow-card">
|
<div className="mb-3 rounded-3xl bg-hero p-4 text-primary-foreground shadow-card">
|
||||||
<div className="flex items-end justify-between gap-3">
|
<div className="flex items-end justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-[11px] font-semibold uppercase tracking-wide text-primary-foreground/60">
|
<p className="text-xs font-semibold uppercase tracking-wide text-primary-foreground/60">
|
||||||
Progresso collettivo
|
Progresso collettivo
|
||||||
</p>
|
</p>
|
||||||
<p className="font-display text-4xl leading-none">
|
<p className="font-display text-4xl leading-none">
|
||||||
@@ -326,7 +330,7 @@ function Squadra() {
|
|||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide",
|
"rounded-full px-2 py-0.5 text-xs font-bold uppercase tracking-wide",
|
||||||
fatto
|
fatto
|
||||||
? "bg-success text-success-foreground"
|
? "bg-success text-success-foreground"
|
||||||
: "bg-secondary text-muted-foreground",
|
: "bg-secondary text-muted-foreground",
|
||||||
@@ -341,7 +345,7 @@ function Squadra() {
|
|||||||
{o.scadenza ? ` · entro il ${formatData(o.scadenza)}` : ""}
|
{o.scadenza ? ` · entro il ${formatData(o.scadenza)}` : ""}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-xs font-semibold text-accent">{microcopyObiettivo(o)}</p>
|
<p className="mt-1 text-xs font-semibold text-accent">{microcopyObiettivo(o)}</p>
|
||||||
<p className="mt-0.5 text-[11px] text-muted-foreground">{o.impatto}</p>
|
<p className="mt-0.5 text-xs text-muted-foreground">{o.impatto}</p>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -358,9 +362,7 @@ function Squadra() {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Icon className="h-5 w-5 text-accent" />
|
<Icon className="h-5 w-5 text-accent" />
|
||||||
<p className="text-sm font-bold leading-tight">{b.nome}</p>
|
<p className="text-sm font-bold leading-tight">{b.nome}</p>
|
||||||
<p className="ml-auto text-[10px] text-muted-foreground">
|
<p className="ml-auto text-xs text-muted-foreground">{descrizioneSoglie(b)}</p>
|
||||||
{descrizioneSoglie(b)}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 grid grid-cols-3 gap-1.5">
|
<div className="mt-2 grid grid-cols-3 gap-1.5">
|
||||||
{gradiOrdine.map((grado) => {
|
{gradiOrdine.map((grado) => {
|
||||||
@@ -376,11 +378,11 @@ function Squadra() {
|
|||||||
key={grado}
|
key={grado}
|
||||||
className={cn("rounded-2xl p-2 text-center ring-1", meta.bg, meta.ring)}
|
className={cn("rounded-2xl p-2 text-center ring-1", meta.bg, meta.ring)}
|
||||||
>
|
>
|
||||||
<p className={cn("text-[10px] font-bold uppercase", meta.text)}>
|
<p className={cn("text-xs font-bold uppercase", meta.text)}>
|
||||||
{meta.label}
|
{meta.label}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-display text-lg leading-none">{b.soglie[grado]}</p>
|
<p className="font-display text-lg leading-none">{b.soglie[grado]}</p>
|
||||||
<p className="text-[10px] text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{quanti}/{rosa.length}
|
{quanti}/{rosa.length}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user