// 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>; } 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>; 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 sono vuote: nel foglio ricevuto le due celle erano coperte // dal riquadro "Opzioni Incolla" di Excel, e il 17/08/2026 Adriano ha confermato che // lì non c'è niente. Il vuoto qui è una risposta, non una lettura mancata. { 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' } }, ], }, ]; /** * Giornate che non stanno in nessuna griglia: eventi e chiusure. * * Il 19 e il 20 settembre non compaiono in nessuno dei due fogli, e la ragione non è una * dimenticanza — il sabato c'è la presentazione della nuova apertura e la domenica si sta * chiusi (detto da Adriano il 17/08/2026). Scritto qui, quel vuoto smette di sembrare un * errore di trascrizione e diventa un'informazione per chi legge gli orari. */ export interface AvvisoCalendario { id: string; /** `null` = visibile da subito. */ from: string | null; /** Ultimo istante in cui ha senso mostrarlo: dopo, sparisce da solo. */ to: string; quando: string; testo: string; } export const avvisi: AvvisoCalendario[] = [ { id: 'presentazione-19-settembre', from: null, to: '2026-09-19T23:59:59+02:00', quando: 'Sabato 19 settembre', testo: 'Presentazione della nuova apertura: nessuna class in programma.', }, { id: 'chiusura-20-settembre', from: null, to: '2026-09-20T23:59:59+02:00', quando: 'Domenica 20 settembre', testo: 'Chiuso.', }, ]; /** Gli avvisi ancora validi, in ordine di scadenza: i più imminenti per primi. */ export function avvisiAttivi(now: Date, list: AvvisoCalendario[] = avvisi): AvvisoCalendario[] { const t = now.getTime(); return list .filter((a) => (a.from === null || t >= Date.parse(a.from)) && t <= Date.parse(a.to)) .sort((x, y) => Date.parse(x.to) - Date.parse(y.to)); } /** * Le schede servizio da cui si apre il calendario della sala. * * Sono le due class che si fanno davvero: la Performance Class è uscita di scena il * 17/08/2026 (il cliente non la eroga più) e con lei la sua scheda. */ export const PAGINE_CON_CALENDARIO = ['holistic-class', 'reformer-class']; /** * Le card della griglia /services che portano il bottone del calendario. * * È un elenco a parte, e non un doppione del precedente: nella griglia la card si chiama * «Class» ma ha slug `performance-class`, e da lì si va alla vista Training con Holistic e * Reformer. Il calendario è lo stesso, il punto da cui si apre no. */ export const CARD_CON_CALENDARIO = ['performance-class']; export interface CalendarioMostrato { cal: Calendario; stato: 'in-corso' | 'futuro'; } /** * I calendari da mostrare: quello in vigore e quelli che verranno, in ordine di partenza. * * Si vedono **tutti insieme**, non solo quello del momento: chi guarda a fine agosto vuole * sapere com'è settembre per intero, e vedere una griglia sola gli farebbe credere che sia * quella per sempre. Ognuna porta il proprio stato, così l'orario in vigore adesso non si * confonde con quello che comincerà dopo. * * Gli orari **scaduti spariscono da soli**: dal 21 settembre resta il solo foglio valido, e * nessuno rischia di leggere le lezioni di un periodo finito. */ export function calendariDaMostrare(now: Date, list: Calendario[] = calendari): CalendarioMostrato[] { const t = now.getTime(); return list .filter((c) => c.to === null || t <= Date.parse(c.to)) .sort((a, b) => Date.parse(a.from) - Date.parse(b.from)) .map((cal) => ({ cal, stato: t >= Date.parse(cal.from) ? 'in-corso' : 'futuro' })); }