import { useEffect, useState } from "react"; import { createFileRoute } from "@tanstack/react-router"; import { CalendarPlus, ChevronLeft, ChevronRight, X } from "lucide-react"; import { Link } from "@tanstack/react-router"; import { cn } from "@/lib/utils"; import { EventoCard, linkPerEvento } from "@/components/crapp/EventoCard"; import { PageHeader, Section } from "@/components/crapp/ui-bits"; import { isAdmin } from "@/lib/crapp-data"; import { compleanniEventi, useEventi, type Evento } from "@/lib/eventi"; import { useGiocatoreCorrente } from "@/lib/user-store"; import { useRosaBase } from "@/lib/rosa-base"; import { Drawer, DrawerClose, DrawerContent, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer"; export const Route = createFileRoute("/calendario")({ head: () => ({ meta: [ { title: "Calendario squadra — CrAPP" }, { name: "description", content: "Allenamenti, partite ed eventi extra del CRAP Volley con vista mensile e lista.", }, { property: "og:title", content: "Calendario squadra — CrAPP" }, { property: "og:description", content: "Vista mensile e lista eventi con promemoria per la squadra.", }, ], }), component: Calendario, }); const giorniIT = ["L", "M", "M", "G", "V", "S", "D"]; const mesiIT = [ "Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre", ] as const; function useMeseNav(initial = { anno: 2026, mese: 7 }) { const [anno, setAnno] = useState(initial.anno); const [mese, setMese] = useState(initial.mese); const precedente = () => { if (mese === 0) { setMese(11); setAnno((a) => a - 1); } else { setMese((m) => m - 1); } }; const successivo = () => { if (mese === 11) { setMese(0); setAnno((a) => a + 1); } else { setMese((m) => m + 1); } }; return { anno, mese, precedente, successivo }; } function giorniDelMese(anno: number, mese: number) { const giorni = new Date(Date.UTC(anno, mese + 1, 0)).getUTCDate(); const primoGiorno = new Date(Date.UTC(anno, mese, 1)).getUTCDay(); const offsetLunedi = (primoGiorno + 6) % 7; return { giorni, offsetLunedi }; } function pad2(n: number) { return String(n).padStart(2, "0"); } function Calendario() { const [vista, setVista] = useState<"mese" | "lista">("mese"); // SSR-safe: la data di oggi arriva solo dopo il mount. const [oggi, setOggi] = useState<{ anno: number; mese: number; giorno: number } | null>(null); useEffect(() => { const d = new Date(); setOggi({ anno: d.getFullYear(), mese: d.getMonth(), giorno: d.getDate() }); }, []); const [giornoSelezionato, setGiornoSelezionato] = useState(null); const [drawerAperto, setDrawerAperto] = useState(false); const io = useGiocatoreCorrente(); const giocatori = useRosaBase(); const { eventi } = useEventi(); const { anno, mese, precedente, successivo } = useMeseNav(); const { giorni, offsetLunedi } = giorniDelMese(anno, mese); const mesePrefix = `${anno}-${pad2(mese + 1)}`; const compleanni = compleanniEventi(giocatori, anno); const compleanniMese = compleanni.filter((c) => c.data.startsWith(mesePrefix)); const eventiMese = eventi.filter((e) => e.data.startsWith(mesePrefix)); const eventiPerGiorno = new Map(); for (const e of [...compleanniMese, ...eventiMese]) { const g = Number(e.data.slice(8, 10)); const lista = eventiPerGiorno.get(g) ?? []; lista.push(e); eventiPerGiorno.set(g, lista); } function apriGiorno(giorno: number) { if (!eventiPerGiorno.has(giorno)) return; setGiornoSelezionato(giorno); setDrawerAperto(true); } const eventiGiornoSelezionato = giornoSelezionato ? (eventiPerGiorno.get(giornoSelezionato) ?? []) : []; return ( <>
{(["mese", "lista"] as const).map((v) => ( ))}
{vista === "mese" ? (
{mesiIT[mese]} {anno}
{giorniIT.map((g, i) => ( {g} ))}
{Array.from({ length: offsetLunedi }).map((_, i) => ( ))} {Array.from({ length: giorni }).map((_, i) => { const giorno = i + 1; const eventiGiorno = eventiPerGiorno.get(giorno) ?? []; const haEventi = eventiGiorno.length > 0; const tipiGiorno = Array.from(new Set(eventiGiorno.map((e) => e.tipo))); const coloreTipo: Record = { partita: "var(--accent)", allenamento: "var(--training)", evento: "var(--warning)", 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 isOggi = !!oggi && oggi.anno === anno && oggi.mese === mese && oggi.giorno === giorno; const Cella = haEventi ? "button" : "div"; return ( apriGiorno(giorno) : undefined} style={sfondo ? { backgroundImage: sfondo } : undefined} className={cn( "relative grid aspect-square place-items-center rounded-xl text-sm font-semibold", tipo === "partita" && "bg-accent text-accent-foreground", tipo === "allenamento" && "bg-training text-training-foreground", tipo === "evento" && "bg-warning text-warning-foreground", tipo === "compleanno" && "bg-success text-success-foreground", !tipo && !haEventi && "text-muted-foreground", !tipo && haEventi && "text-foreground", haEventi && "cursor-pointer transition-transform active:scale-90", isOggi && "ring-2 ring-foreground ring-offset-1 ring-offset-card", )} aria-label={haEventi ? `Eventi del ${giorno}` : undefined} aria-current={isOggi ? "date" : undefined} > {giorno} ); })}
Partita Allenamento Eventi Compleanni
) : null} {io && isAdmin(io) ? (
Gestisci eventi
) : null}
{eventiMese.length > 0 ? ( eventiMese.map((e) => { const link = linkPerEvento(e); return ; }) ) : (

Nessun evento in {mesiIT[mese]!.toLowerCase()}

)}
{compleanniMese.length > 0 ? ( compleanniMese.map((c) => ) ) : (

Nessun compleanno in {mesiIT[mese]!.toLowerCase()}

)}
{giornoSelezionato ? `${giornoSelezionato} ${mesiIT[mese]}` : "Eventi"} Chiudi
{eventiGiornoSelezionato.length > 0 ? ( eventiGiornoSelezionato.map((e) => { const link = linkPerEvento(e); return ; }) ) : (

Nessun evento in questa data

)}
); }