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,203 @@
|
||||
---
|
||||
// Popup a pieno schermo con la griglia delle class in Sala Piccola.
|
||||
//
|
||||
// Il calendario è ricostruito dal dato (`data/calendari.ts`) e non è l'immagine ricevuta dal
|
||||
// cliente: si legge da telefono, si stampa, e per correggere una cella non serve rifare uno
|
||||
// screenshot. Quale griglia compare lo decide la data — quella in vigore, o la prossima
|
||||
// dichiarando da quando entra.
|
||||
//
|
||||
// Si apre da qualunque elemento con `data-apri-calendario` nella stessa pagina.
|
||||
import { calendarioDaMostrare, GIORNI, DISCIPLINE, type Giorno } from '../data/calendari';
|
||||
import { t } from '../lib/content';
|
||||
|
||||
const scelta = calendarioDaMostrare(new Date());
|
||||
const cal = scelta?.cal ?? null;
|
||||
const dal = cal ? new Date(cal.from).toLocaleDateString('it-IT', {
|
||||
day: 'numeric', month: 'long', year: 'numeric', timeZone: 'Europe/Rome',
|
||||
}) : '';
|
||||
// Discipline davvero presenti in questa griglia: una legenda che elenca anche ciò che non c'è
|
||||
// fa cercare lezioni che non esistono.
|
||||
const presenti = cal
|
||||
? DISCIPLINE.filter((d) => cal.righe.some((r) => Object.values(r.celle).includes(d)))
|
||||
: [];
|
||||
---
|
||||
{cal && (
|
||||
<dialog class="cal-dlg" aria-label={`${cal.sala} — ${cal.titolo}`}>
|
||||
<div class="cal-dlg__box">
|
||||
<header class="cal-dlg__top">
|
||||
<div>
|
||||
<p class="cal-dlg__sala">{cal.sala}</p>
|
||||
<h2 class="cal-dlg__titolo">{cal.titolo}</h2>
|
||||
{scelta!.futuro && (
|
||||
<p class="cal-dlg__futuro">{t('calendario.futuro-label')} {dal}</p>
|
||||
)}
|
||||
</div>
|
||||
<div class="cal-dlg__azioni">
|
||||
<button type="button" class="cal-dlg__btn" data-stampa-calendario>{t('calendario.pdf-label')}</button>
|
||||
<button type="button" class="cal-dlg__chiudi" data-chiudi-calendario aria-label={t('calendario.chiudi-label')}>×</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="cal-dlg__scroll">
|
||||
<table class="cal-tab">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="cal-tab__ora-h">{cal.sala}</th>
|
||||
{GIORNI.map((g) => (
|
||||
<th scope="col" class:list={['cal-tab__gg', cal.chiusi.includes(g) && 'is-chiuso']}>
|
||||
{g}
|
||||
{cal.noteGiorno?.[g] && <span class="cal-tab__nota">{cal.noteGiorno[g]}</span>}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{cal.righe.map((r) => (
|
||||
<tr class:list={[r.clou && 'is-clou']}>
|
||||
<th scope="row" class="cal-tab__ora">{r.ora}</th>
|
||||
{GIORNI.map((g: Giorno) => {
|
||||
const chiuso = cal.chiusi.includes(g);
|
||||
const lezione = r.celle[g];
|
||||
return (
|
||||
<td class:list={['cal-tab__cella', chiuso && 'is-chiuso', lezione && `is-${lezione.toLowerCase()}`]}>
|
||||
{chiuso ? <span class="cal-tab__chiuso-txt">{t('calendario.chiuso-label')}</span> : lezione ?? ''}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<footer class="cal-dlg__giu">
|
||||
<ul class="cal-legenda">
|
||||
{presenti.map((d) => (
|
||||
<li class={`is-${d.toLowerCase()}`}>{d}</li>
|
||||
))}
|
||||
</ul>
|
||||
<p class="cal-dlg__nota">{t('calendario.nota')}</p>
|
||||
</footer>
|
||||
</div>
|
||||
</dialog>
|
||||
)}
|
||||
|
||||
<script>
|
||||
// Apertura/chiusura del popup. Nessuna libreria: <dialog> nativo dà da solo il fondale, il
|
||||
// focus trap e la chiusura con Esc.
|
||||
const dlg = document.querySelector<HTMLDialogElement>('.cal-dlg');
|
||||
if (dlg) {
|
||||
document.querySelectorAll<HTMLElement>('[data-apri-calendario]').forEach((el) => {
|
||||
el.addEventListener('click', (e) => { e.preventDefault(); dlg.showModal(); });
|
||||
});
|
||||
dlg.querySelector('[data-chiudi-calendario]')?.addEventListener('click', () => dlg.close());
|
||||
// Click sul fondale: il target è il dialog stesso solo quando si preme fuori dal riquadro.
|
||||
dlg.addEventListener('click', (e) => { if (e.target === dlg) dlg.close(); });
|
||||
// "Scarica in PDF" = stampa con la destinazione "Salva come PDF": la pagina di stampa è
|
||||
// già impaginata per la sola griglia (vedi @media print), quindi non serve generare nulla
|
||||
// lato server né caricare una libreria.
|
||||
dlg.querySelector('[data-stampa-calendario]')?.addEventListener('click', () => window.print());
|
||||
}
|
||||
</script>
|
||||
|
||||
<style is:global>
|
||||
.cal-dlg {
|
||||
width: 100%; max-width: none; height: 100%; max-height: none; margin: 0; padding: 0;
|
||||
border: 0; background: transparent; color: var(--c-text);
|
||||
}
|
||||
.cal-dlg::backdrop { background: rgba(43,43,43,.75); }
|
||||
.cal-dlg__box {
|
||||
display: flex; flex-direction: column; height: 100%; background: var(--c-bg);
|
||||
padding: 28px clamp(16px, 4vw, 48px) 24px;
|
||||
}
|
||||
.cal-dlg__top { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; }
|
||||
.cal-dlg__sala {
|
||||
margin: 0; font-family: var(--font-heading); font-size: .72rem; font-weight: 600;
|
||||
letter-spacing: .25em; text-transform: uppercase; color: var(--c-accent-dark);
|
||||
}
|
||||
.cal-dlg__titolo { margin: 6px 0 0; font-size: 1.5rem; }
|
||||
.cal-dlg__futuro {
|
||||
margin: 6px 0 0; font-family: var(--font-heading); font-size: .72rem; letter-spacing: .12em;
|
||||
text-transform: uppercase; color: var(--c-text-light);
|
||||
}
|
||||
.cal-dlg__azioni { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
|
||||
.cal-dlg__btn {
|
||||
font-family: var(--font-heading); font-size: .68rem; font-weight: 600; letter-spacing: .2em;
|
||||
text-transform: uppercase; border: 0; cursor: pointer; padding: 13px 22px;
|
||||
background: var(--c-accent); color: #fff; transition: background .2s;
|
||||
}
|
||||
.cal-dlg__btn:hover { background: var(--c-accent-dark); }
|
||||
.cal-dlg__chiudi {
|
||||
width: 42px; height: 42px; border: 1px solid #ddd; background: #fff; cursor: pointer;
|
||||
font-size: 1.4rem; line-height: 1; color: var(--c-heading);
|
||||
}
|
||||
.cal-dlg__chiudi:hover { border-color: var(--c-accent); color: var(--c-accent-dark); }
|
||||
|
||||
/* La griglia ha otto colonne: su telefono scorre in orizzontale invece di comprimersi
|
||||
fino a diventare illeggibile, e la colonna delle ore resta ferma. */
|
||||
.cal-dlg__scroll { flex: 1; overflow: auto; margin: 22px 0 0; }
|
||||
.cal-tab { border-collapse: collapse; width: 100%; min-width: 720px; }
|
||||
.cal-tab th, .cal-tab td { border: 1px solid #e4e0d9; padding: 9px 10px; text-align: left; }
|
||||
.cal-tab thead th {
|
||||
position: sticky; top: 0; z-index: 2; background: var(--c-bg-alt);
|
||||
font-family: var(--font-heading); font-size: .7rem; font-weight: 600; letter-spacing: .16em;
|
||||
text-transform: uppercase; color: var(--c-heading);
|
||||
}
|
||||
.cal-tab__nota {
|
||||
display: block; font-size: .62rem; letter-spacing: .06em; text-transform: none;
|
||||
color: var(--c-text-light); font-weight: 400;
|
||||
}
|
||||
.cal-tab__ora-h { color: var(--c-accent-dark) !important; }
|
||||
.cal-tab__ora {
|
||||
position: sticky; left: 0; z-index: 1; background: var(--c-bg);
|
||||
font-family: var(--font-heading); font-size: .78rem; font-weight: 600; color: var(--c-heading);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cal-tab tr.is-clou .cal-tab__ora { background: #f7f2e6; }
|
||||
.cal-tab__cella { font-family: var(--font-heading); font-size: .7rem; letter-spacing: .1em; color: var(--c-heading); }
|
||||
/* Ogni disciplina ha un fondo tenue e una barra a sinistra: la barra si vede anche quando il
|
||||
browser stampa senza sfondi. */
|
||||
.cal-tab__cella.is-reformer { background: #ece5d7; box-shadow: inset 3px 0 0 var(--c-accent-dark); }
|
||||
.cal-tab__cella.is-pilates { background: #f6efe0; box-shadow: inset 3px 0 0 #cbb98f; }
|
||||
.cal-tab__cella.is-yoga { background: #e3eadf; box-shadow: inset 3px 0 0 #8ba57f; }
|
||||
.cal-tab__cella.is-mobility { background: #f0e3e3; box-shadow: inset 3px 0 0 #b78e8e; }
|
||||
.cal-tab__cella.is-chiuso { background: repeating-linear-gradient(135deg, #f5f3f0, #f5f3f0 6px, #ece8e2 6px, #ece8e2 12px); }
|
||||
.cal-tab__chiuso-txt { font-size: .62rem; letter-spacing: .12em; color: var(--c-text-light); }
|
||||
.cal-tab thead th.is-chiuso { color: var(--c-text-light); }
|
||||
|
||||
.cal-dlg__giu { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 24px; padding-top: 16px; }
|
||||
.cal-legenda { display: flex; flex-wrap: wrap; gap: 8px 16px; list-style: none; margin: 0; padding: 0; }
|
||||
.cal-legenda li {
|
||||
font-family: var(--font-heading); font-size: .66rem; letter-spacing: .12em; padding: 5px 10px;
|
||||
color: var(--c-heading);
|
||||
}
|
||||
.cal-legenda li.is-reformer { background: #ece5d7; box-shadow: inset 3px 0 0 var(--c-accent-dark); }
|
||||
.cal-legenda li.is-pilates { background: #f6efe0; box-shadow: inset 3px 0 0 #cbb98f; }
|
||||
.cal-legenda li.is-yoga { background: #e3eadf; box-shadow: inset 3px 0 0 #8ba57f; }
|
||||
.cal-legenda li.is-mobility { background: #f0e3e3; box-shadow: inset 3px 0 0 #b78e8e; }
|
||||
.cal-dlg__nota { margin: 0; font-size: .8rem; color: var(--c-text-light); }
|
||||
|
||||
/* Stampa (ossia "Salva come PDF"): resta solo la griglia, in orizzontale e a piena pagina.
|
||||
`visibility` invece di `display` perché il dialog è annidato nel corpo della pagina e
|
||||
nascondere gli antenati nasconderebbe anche lui. */
|
||||
@media print {
|
||||
@page { size: A4 landscape; margin: 10mm; }
|
||||
body { visibility: hidden; }
|
||||
.cal-dlg, .cal-dlg * { visibility: visible; }
|
||||
.cal-dlg { position: absolute; inset: 0; height: auto; }
|
||||
.cal-dlg::backdrop { background: transparent; }
|
||||
.cal-dlg__box { padding: 0; height: auto; }
|
||||
.cal-dlg__azioni { display: none; }
|
||||
.cal-dlg__scroll { overflow: visible; }
|
||||
.cal-tab { min-width: 0; }
|
||||
.cal-tab thead th, .cal-tab__ora { position: static; }
|
||||
.cal-tab th, .cal-tab td { padding: 5px 6px; }
|
||||
.cal-tab__cella { font-size: .62rem; }
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.cal-dlg__box { padding: 20px 14px 18px; }
|
||||
.cal-dlg__titolo { font-size: 1.2rem; }
|
||||
.cal-dlg__btn { padding: 11px 16px; letter-spacing: .12em; }
|
||||
}
|
||||
</style>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -386,6 +386,15 @@ export const contentSeed: SeedEntry[] = [
|
||||
T('promo.faq.6.q', 'Posso chiedere un rimborso?'),
|
||||
H('promo.faq.6.a', 'Per richieste di rimborso o problemi con l\'acquisto scrivici su <span class="hl">WhatsApp</span> o via email. Valutiamo ogni situazione in modo diretto e umano.'),
|
||||
|
||||
// --- Calendario delle class (popup dalle pagine servizio) ---
|
||||
// La griglia è dato (`data/calendari.ts`); qui ci sono solo le etichette attorno.
|
||||
T('calendario.link-label', 'Calendario', []),
|
||||
T('calendario.pdf-label', 'Scarica in PDF', []),
|
||||
T('calendario.chiudi-label', 'Chiudi', []),
|
||||
T('calendario.futuro-label', 'In vigore dal', []),
|
||||
T('calendario.chiuso-label', 'Chiuso', []),
|
||||
T('calendario.nota', 'Gli orari possono subire variazioni. Le lezioni si prenotano dall\'app Bsport.'),
|
||||
|
||||
// --- Pagina promo, stato "nessuna promozione in corso" ---
|
||||
// Fra un'edizione e la successiva la pagina resta raggiungibile (è nel menu e nei link già
|
||||
// girati ai clienti): mostra questo invece dei prezzi di una promozione finita.
|
||||
|
||||
@@ -7,12 +7,18 @@ import { performanceClassPricing } from '../../data/pricing';
|
||||
import { serviceIcons } from '../../data/service-icons';
|
||||
import T from '../../components/content/T.astro';
|
||||
import TImg from '../../components/content/TImg.astro';
|
||||
import CalendarioModal from '../../components/CalendarioModal.astro';
|
||||
import { calendarioDaMostrare, SERVIZI_CON_CALENDARIO } from '../../data/calendari';
|
||||
import { t } from '../../lib/content';
|
||||
export const prerender = false;
|
||||
|
||||
const service = getService(Astro.params.slug!);
|
||||
if (!service) return new Response(null, { status: 404 });
|
||||
const slug = service.slug;
|
||||
// Il calendario della sala si apre dalle pagine delle class. La voce compare solo se c'è
|
||||
// davvero una griglia da mostrare (in vigore o prossima): un link che apre un popup vuoto è
|
||||
// peggio di nessun link.
|
||||
const conCalendario = SERVIZI_CON_CALENDARIO.includes(slug) && calendarioDaMostrare(new Date()) !== null;
|
||||
const others = services.filter((p) => p.slug !== service.slug && p.grid !== false);
|
||||
const images = import.meta.glob<{ default: ImageMetadata }>('../../assets/img/program-*.jpg', { eager: true });
|
||||
const imgOf = (slug: string) => Object.entries(images).find(([p]) => p.includes(`program-${slug}.`))?.[1]?.default;
|
||||
@@ -59,6 +65,16 @@ const tones = ['tan', 'taupe', 'dark'];
|
||||
<T tag={`service.${o.slug}.title`} />
|
||||
</a>
|
||||
))}
|
||||
{conCalendario && (
|
||||
<button type="button" class="prog__other prog__other--calendario" data-apri-calendario>
|
||||
<span class="prog__icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" width="26" height="26" aria-hidden="true">
|
||||
<path d="M4 6h16v14H4zM4 10h16M8 3v4M16 3v4M8 14h3M8 17h3M14 14h2" />
|
||||
</svg>
|
||||
</span>
|
||||
{t('calendario.link-label')}
|
||||
</button>
|
||||
)}
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
@@ -95,6 +111,8 @@ const tones = ['tan', 'taupe', 'dark'];
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{conCalendario && <CalendarioModal />}
|
||||
</Base>
|
||||
|
||||
<style>
|
||||
@@ -109,6 +127,9 @@ const tones = ['tan', 'taupe', 'dark'];
|
||||
.prog__cat { font-family: var(--font-heading); font-size: .72rem; letter-spacing: .18em; text-transform: uppercase; color: var(--c-text); margin-bottom: 10px; }
|
||||
.prog__other { display: flex; align-items: center; gap: 14px; text-decoration: none; color: var(--c-text); margin-bottom: 14px; font-family: var(--font-heading); font-size: .78rem; letter-spacing: .14em; text-transform: uppercase; }
|
||||
.prog__other:hover { color: var(--c-accent-dark); }
|
||||
/* La voce del calendario apre un popup, quindi è un bottone e non un link: qui gli si
|
||||
toglie l'aspetto da bottone perché nella colonna sia indistinguibile dalle altre voci. */
|
||||
.prog__other--calendario { background: none; border: 0; padding: 0; cursor: pointer; width: 100%; text-align: left; }
|
||||
.prog__icon { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; background: var(--c-accent); color: #fff; flex-shrink: 0; }
|
||||
.price__group { border-top: 1px solid #ddd5ca; padding-block: 18px; }
|
||||
.price__group summary { cursor: pointer; font-family: var(--font-heading); letter-spacing: .12em; text-transform: uppercase; font-size: .85rem; color: var(--c-heading); }
|
||||
|
||||
Reference in New Issue
Block a user