Struttura: il sito esistente si sposta sotto apps/sito

Primo passo della separazione sito / piattaforme decisa da Adriano il 03/09:
due applicazioni autosufficienti, senza workspace di root, cosi' che ognuna
resti un progetto Astro completo ed estraibile.

Qui non cambia niente del codice: solo la radice. I pattern del .gitignore
che avevano una barra erano ancorati alla radice e dopo lo spostamento non
avrebbero piu' coperto niente (data/*.db*, uploads/*): resi validi a
qualunque profondita'.

Suite: 45 file, 347 test verdi prima e dopo lo spostamento.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
This commit is contained in:
AdrianoDev
2026-09-03 11:05:54 +00:00
parent c03edccd8e
commit 510d7ca4ec
551 changed files with 4 additions and 4 deletions
-195
View File
@@ -1,195 +0,0 @@
// 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' },
// ⚠️ Il VENERDI' non ha nessuna class in questo periodo, ed e' voluto: le tre che
// c'erano (MOBILITY 8.30, REFORMER 9.15 e 10.00) sono state tolte su richiesta del
// cliente l'01/09/2026, ed erano tutte di mattina. Non e' una trascrizione monca:
// confrontando questa griglia con quella dal 21 settembre — dove il venerdi' c'e' —
// la differenza salta all'occhio e sembra un errore. Non lo e'.
// La colonna resta VUOTA e non barrata: togliere le class non vuol dire che la sala
// sia chiusa, e nessuno ce l'ha detto (scelta di Adriano, 01/09).
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' } },
{ ora: '9.15', celle: { SAB: 'MOBILITY' } },
{ ora: '10.00', celle: { 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' }));
}