Calendario delle class: popup a pieno schermo dalle pagine Performance e Holistic
Le due griglie fornite dal cliente (1-18 settembre, dal 21 in poi) sono trascritte come dato e ridisegnate nello stile del sito: si leggono da telefono, si stampano in PDF e per correggere una cella non serve rifare uno screenshot. Quale griglia compare lo decide la data, come per le edizioni della promo: quella in vigore, oppure - se nessuna lo e' ancora - la prossima, dichiarando da quando entra. Serve perche' il link esiste da subito e ad agosto un "nessun calendario" non direbbe niente a chi vuole gli orari di settembre. Il popup e' un <dialog> nativo: fondale, Esc e trappola del focus senza librerie. "Scarica in PDF" stampa la sola griglia, gia' impaginata in orizzontale dal CSS di stampa. Le discipline hanno una barra colorata a sinistra oltre al fondo, cosi' restano distinguibili anche quando il browser stampa senza sfondi. Quattordici test sul dato: confini al secondo in ora italiana, fasce orarie uniche e in ordine, nessuna lezione in un giorno di chiusura, e il buco vero fra il 19 e il 20 settembre resta dichiarato invece che nascosto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
// Calendari delle class in Sala Piccola.
|
||||
//
|
||||
// Trascritti dai due fogli forniti dal cliente il 10/08/2026 (immagini su Wasabi, cartella
|
||||
// del progetto). Sono dato e non immagini di proposito: così la griglia si legge da telefono,
|
||||
// si stampa in PDF e si corregge una cella senza rifare uno screenshot.
|
||||
//
|
||||
// Ogni calendario porta la sua finestra di validità: la pagina mostra quello in vigore, e
|
||||
// quando nessuno lo è ancora mostra il prossimo dichiarando da quando entra. Il passaggio da
|
||||
// un orario all'altro avviene da solo, senza deploy — stesso principio delle edizioni della
|
||||
// pagina promo (`data/promo-edizioni.ts`).
|
||||
|
||||
export const GIORNI = ['LUN', 'MAR', 'MER', 'GIO', 'VEN', 'SAB', 'DOM'] as const;
|
||||
export type Giorno = (typeof GIORNI)[number];
|
||||
|
||||
/** Le discipline della sala: il colore di ognuna è definito nel componente del calendario. */
|
||||
export const DISCIPLINE = ['REFORMER', 'PILATES', 'YOGA', 'MOBILITY'] as const;
|
||||
export type Disciplina = (typeof DISCIPLINE)[number];
|
||||
|
||||
export interface RigaCalendario {
|
||||
ora: string;
|
||||
/** Fascia messa in risalto nel foglio del cliente (le righe gialle). */
|
||||
clou?: boolean;
|
||||
celle: Partial<Record<Giorno, Disciplina>>;
|
||||
}
|
||||
|
||||
export interface Calendario {
|
||||
id: string;
|
||||
/** Come lo chiama il cliente: è anche l'etichetta mostrata in pagina. */
|
||||
titolo: string;
|
||||
sala: string;
|
||||
/** Validità, ISO con offset esplicito (il server gira in UTC). `to: null` = a tempo indeterminato. */
|
||||
from: string;
|
||||
to: string | null;
|
||||
/** Giorni in cui la sala è chiusa: colonna barrata, non vuota — vuoto e chiuso non sono lo stesso. */
|
||||
chiusi: Giorno[];
|
||||
/** Precisazione su una colonna, es. il sabato aperto una volta sola. */
|
||||
noteGiorno?: Partial<Record<Giorno, string>>;
|
||||
righe: RigaCalendario[];
|
||||
}
|
||||
|
||||
export const calendari: Calendario[] = [
|
||||
{
|
||||
id: 'settembre-1-18',
|
||||
titolo: "Dall'1 al 18 settembre",
|
||||
sala: 'Sala piccola',
|
||||
from: '2026-09-01T00:00:00+02:00',
|
||||
to: '2026-09-18T23:59:59+02:00',
|
||||
chiusi: ['DOM'],
|
||||
noteGiorno: { SAB: 'solo il 12 settembre' },
|
||||
righe: [
|
||||
{ ora: '6.15', celle: {} },
|
||||
{ ora: '7.00', clou: true, celle: { MAR: 'YOGA' } },
|
||||
{ ora: '7.45', clou: true, celle: { MAR: 'PILATES', MER: 'REFORMER' } },
|
||||
{ ora: '8.30', celle: { MER: 'REFORMER', VEN: 'MOBILITY' } },
|
||||
{ ora: '9.15', celle: { VEN: 'REFORMER', SAB: 'MOBILITY' } },
|
||||
{ ora: '10.00', celle: { VEN: 'REFORMER', SAB: 'PILATES' } },
|
||||
{ ora: '10.45', celle: { SAB: 'REFORMER' } },
|
||||
{ ora: '11.30', celle: { SAB: 'REFORMER' } },
|
||||
{ ora: '12.15', clou: true, celle: { MAR: 'REFORMER', MER: 'REFORMER', GIO: 'REFORMER' } },
|
||||
{ ora: '13.00', clou: true, celle: { MAR: 'REFORMER', MER: 'PILATES', GIO: 'REFORMER' } },
|
||||
{ ora: '13.45', celle: {} },
|
||||
{ ora: '14.30', celle: { MAR: 'REFORMER' } },
|
||||
{ ora: '15.15', celle: { MAR: 'MOBILITY' } },
|
||||
{ ora: '16.00', celle: {} },
|
||||
{ ora: '16.45', celle: { MAR: 'REFORMER' } },
|
||||
{ ora: '17.30', clou: true, celle: { MAR: 'REFORMER', GIO: 'REFORMER' } },
|
||||
{ ora: '18.15', clou: true, celle: { MAR: 'REFORMER', GIO: 'REFORMER' } },
|
||||
{ ora: '19.00', clou: true, celle: { MAR: 'REFORMER', GIO: 'REFORMER' } },
|
||||
{ ora: '19.45', clou: true, celle: { MAR: 'REFORMER', GIO: 'PILATES' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'settembre-21-in-poi',
|
||||
titolo: 'Dal 21 settembre in poi',
|
||||
sala: 'Sala piccola',
|
||||
from: '2026-09-21T00:00:00+02:00',
|
||||
to: null,
|
||||
chiusi: ['DOM'],
|
||||
righe: [
|
||||
{ ora: '6.15', celle: { MER: 'MOBILITY' } },
|
||||
{ ora: '7.00', clou: true, celle: { MAR: 'YOGA', MER: 'REFORMER' } },
|
||||
{ ora: '7.45', clou: true, celle: { MAR: 'PILATES', MER: 'REFORMER', VEN: 'PILATES' } },
|
||||
{ ora: '8.30', celle: { MAR: 'REFORMER', MER: 'REFORMER', VEN: 'MOBILITY' } },
|
||||
{ ora: '9.15', celle: { VEN: 'REFORMER', SAB: 'MOBILITY' } },
|
||||
{ ora: '10.00', celle: { VEN: 'REFORMER', SAB: 'PILATES' } },
|
||||
{ ora: '10.45', celle: { SAB: 'REFORMER' } },
|
||||
{ ora: '11.30', celle: { GIO: 'YOGA', SAB: 'REFORMER' } },
|
||||
{ ora: '12.15', clou: true, celle: { MAR: 'REFORMER', MER: 'REFORMER', GIO: 'REFORMER' } },
|
||||
{ ora: '13.00', clou: true, celle: { MAR: 'REFORMER', MER: 'PILATES', GIO: 'REFORMER' } },
|
||||
{ ora: '13.45', celle: {} },
|
||||
{ ora: '14.30', celle: { MAR: 'REFORMER' } },
|
||||
{ ora: '15.15', celle: { MAR: 'MOBILITY' } },
|
||||
{ ora: '16.00', celle: {} },
|
||||
{ ora: '16.45', celle: { MAR: 'REFORMER' } },
|
||||
// ⚠️ Mercoledì 17.30 e 18.15: nel foglio ricevuto quelle due celle sono coperte dal
|
||||
// riquadro "Opzioni Incolla" di Excel, quindi non sono leggibili. Qui restano vuote
|
||||
// perché inventarle sarebbe peggio: vanno confermate dal cliente.
|
||||
{ ora: '17.30', clou: true, celle: { LUN: 'PILATES', MAR: 'REFORMER', GIO: 'REFORMER' } },
|
||||
{ ora: '18.15', clou: true, celle: { LUN: 'REFORMER', MAR: 'REFORMER', GIO: 'REFORMER' } },
|
||||
{ ora: '19.00', clou: true, celle: { LUN: 'REFORMER', MAR: 'REFORMER', GIO: 'REFORMER' } },
|
||||
{ ora: '19.45', clou: true, celle: { LUN: 'REFORMER', MAR: 'REFORMER', GIO: 'PILATES' } },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/** Servizi dalla cui pagina si apre il calendario della sala. */
|
||||
export const SERVIZI_CON_CALENDARIO = ['performance-class', 'holistic-class'];
|
||||
|
||||
/**
|
||||
* Il calendario da mostrare: quello in vigore adesso, oppure — se nessuno lo è ancora — il
|
||||
* primo che entrerà in vigore, segnalato come futuro. `null` solo se non ce n'è nessuno da
|
||||
* mostrare, e in quel caso la voce non compare in pagina.
|
||||
*
|
||||
* Serve il ripiego sul futuro perché il link esiste **prima** che il primo orario cominci: un
|
||||
* "nessun calendario disponibile" ad agosto non direbbe niente a chi vuole sapere gli orari
|
||||
* di settembre.
|
||||
*/
|
||||
export function calendarioDaMostrare(now: Date, list: Calendario[] = calendari):
|
||||
{ cal: Calendario; futuro: boolean } | null {
|
||||
const t = now.getTime();
|
||||
const attivo = list.find((c) => t >= Date.parse(c.from) && (c.to === null || t <= Date.parse(c.to)));
|
||||
if (attivo) return { cal: attivo, futuro: false };
|
||||
const prossimi = list.filter((c) => Date.parse(c.from) > t).sort((a, b) => Date.parse(a.from) - Date.parse(b.from));
|
||||
return prossimi.length ? { cal: prossimi[0], futuro: true } : null;
|
||||
}
|
||||
Reference in New Issue
Block a user