Calendario: si vedono tutti gli orari validi, e il PDF ne fa una pagina ciascuno
Il popup mostrava solo l'orario del momento: chi guarda ad agosto vuole sapere com'e' settembre per intero, e una griglia sola gli fa credere che valga per sempre. Ora ci sono tutti quelli non ancora scaduti, ognuno con la sua etichetta - In vigore, oppure la data da cui parte - e quelli passati spariscono da soli. Il bottone scarica /calendario/tutti.pdf: un orario per pagina, con le giornate fuori griglia ripetute su ognuna, perche' un foglio stampato da solo deve bastare a se stesso. Gli id singoli restano validi per mandarne uno solo. calendarioDaMostrare (uno) diventa calendariDaMostrare (tutti): una funzione sola, non due che dicono cose simili - e' lo stesso difetto dei due link della pagina servizi, che erano due espressioni per lo stesso indirizzo e sono divergute. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,42 +1,40 @@
|
||||
---
|
||||
// Popup a pieno schermo con la griglia delle class in Sala Piccola.
|
||||
// Popup a pieno schermo con gli orari 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.
|
||||
// I calendari sono ricostruiti dal dato (`data/calendari.ts`) e non sono le immagini ricevute
|
||||
// dal cliente: si leggono da telefono, si scaricano in PDF, e per correggere una cella non
|
||||
// serve rifare uno screenshot.
|
||||
//
|
||||
// Si vedono tutti gli orari non ancora scaduti, uno sotto l'altro: chi guarda ad agosto vuole
|
||||
// sapere com'è settembre per intero. Quelli passati spariscono da soli.
|
||||
//
|
||||
// Si apre da qualunque elemento con `data-apri-calendario` nella stessa pagina.
|
||||
import { calendarioDaMostrare, avvisiAttivi, GIORNI, DISCIPLINE, type Giorno } from '../data/calendari';
|
||||
import { calendariDaMostrare, avvisiAttivi, GIORNI, DISCIPLINE, type Calendario, type Giorno } from '../data/calendari';
|
||||
import { t } from '../lib/content';
|
||||
|
||||
const adesso = new Date();
|
||||
const scelta = calendarioDaMostrare(adesso);
|
||||
const mostrati = calendariDaMostrare(adesso);
|
||||
// Giornate fuori griglia (eventi, chiusure): spariscono da sole quando sono passate.
|
||||
const avvisi = avvisiAttivi(adesso);
|
||||
const cal = scelta?.cal ?? null;
|
||||
const dal = cal ? new Date(cal.from).toLocaleDateString('it-IT', {
|
||||
|
||||
const dataDi = (iso: string) => new Date(iso).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)))
|
||||
: [];
|
||||
});
|
||||
// Legenda per griglia: elencare discipline che lì non compaiono farebbe cercare lezioni
|
||||
// che quel periodo non ha.
|
||||
const disciplinePresenti = (cal: Calendario) =>
|
||||
DISCIPLINE.filter((d) => cal.righe.some((r) => Object.values(r.celle).includes(d)));
|
||||
---
|
||||
{cal && (
|
||||
<dialog class="cal-dlg" aria-label={`${cal.sala} — ${cal.titolo}`}>
|
||||
{mostrati.length > 0 && (
|
||||
<dialog class="cal-dlg" aria-label="Calendario delle class">
|
||||
<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>
|
||||
)}
|
||||
<p class="cal-dlg__sala">{mostrati[0].cal.sala}</p>
|
||||
<h2 class="cal-dlg__titolo">Calendario delle class</h2>
|
||||
</div>
|
||||
<div class="cal-dlg__azioni">
|
||||
<a class="cal-dlg__btn" href={`/calendario/${cal.id}.pdf`} download>{t('calendario.pdf-label')}</a>
|
||||
<a class="cal-dlg__btn" href="/calendario/tutti.pdf" download>{t('calendario.pdf-label')}</a>
|
||||
<button type="button" class="cal-dlg__chiudi" data-chiudi-calendario aria-label={t('calendario.chiudi-label')}>×</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -50,43 +48,55 @@ const presenti = cal
|
||||
)}
|
||||
|
||||
<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>
|
||||
{mostrati.map(({ cal, stato }) => (
|
||||
<section class="cal-blocco">
|
||||
<div class="cal-blocco__top">
|
||||
<h3 class="cal-blocco__titolo">{cal.titolo}</h3>
|
||||
<span class:list={['cal-tag', stato === 'in-corso' && 'is-ora']}>
|
||||
{stato === 'in-corso' ? 'In vigore' : `${t('calendario.futuro-label')} ${dataDi(cal.from)}`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table class="cal-tab">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="cal-tab__ora-h">Ora</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>
|
||||
|
||||
<ul class="cal-legenda">
|
||||
{disciplinePresenti(cal).map((d) => (
|
||||
<li class={`is-${d.toLowerCase()}`}>{d}</li>
|
||||
))}
|
||||
</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>
|
||||
</ul>
|
||||
</section>
|
||||
))}
|
||||
</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>
|
||||
@@ -104,9 +114,9 @@ const presenti = cal
|
||||
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" è un link a `/calendario/<id>.pdf`, cioè un file vero generato dal
|
||||
// server: si salva sul telefono e si gira su WhatsApp. Il CSS di stampa più sotto resta
|
||||
// per chi preme Ctrl+P col popup aperto.
|
||||
// "Scarica in PDF" è un link a /calendario/tutti.pdf, cioè un file vero generato dal
|
||||
// server, un orario per pagina: si salva sul telefono e si gira su WhatsApp. Il CSS di
|
||||
// stampa più sotto resta per chi preme Ctrl+P col popup aperto.
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -126,10 +136,6 @@ const presenti = cal
|
||||
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 {
|
||||
display: inline-block; font-family: var(--font-heading); font-size: .68rem; font-weight: 600;
|
||||
@@ -144,8 +150,8 @@ const presenti = cal
|
||||
}
|
||||
.cal-dlg__chiudi:hover { border-color: var(--c-accent); color: var(--c-accent-dark); }
|
||||
|
||||
/* Giornate fuori griglia: stanno sopra la tabella perché sono l'eccezione a ciò che la
|
||||
tabella dice, e sotto passerebbero per una postilla. */
|
||||
/* Giornate fuori griglia: stanno sopra le tabelle perché sono l'eccezione a ciò che le
|
||||
tabelle dicono, e sotto passerebbero per una postilla. */
|
||||
.cal-avvisi {
|
||||
list-style: none; margin: 18px 0 0; padding: 12px 16px; background: var(--c-bg-alt);
|
||||
border-left: 3px solid var(--c-accent); display: flex; flex-direction: column; gap: 4px;
|
||||
@@ -153,13 +159,24 @@ const presenti = cal
|
||||
.cal-avvisi li { font-size: .84rem; color: var(--c-text); }
|
||||
.cal-avvisi strong { color: var(--c-heading); }
|
||||
|
||||
.cal-dlg__scroll { flex: 1; overflow: auto; margin: 18px 0 0; }
|
||||
.cal-blocco + .cal-blocco { margin-top: 34px; padding-top: 26px; border-top: 1px solid #e4e0d9; }
|
||||
.cal-blocco__top { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-bottom: 10px; }
|
||||
.cal-blocco__titolo { margin: 0; font-size: 1.05rem; }
|
||||
.cal-tag {
|
||||
font-family: var(--font-heading); font-size: .62rem; font-weight: 600; letter-spacing: .14em;
|
||||
text-transform: uppercase; padding: 4px 9px; background: var(--c-bg-alt); color: var(--c-text-light);
|
||||
}
|
||||
/* L'orario valido adesso si distingue a colpo d'occhio da quello che comincerà dopo: senza,
|
||||
due griglie affiancate si leggono come alternative fra cui scegliere. */
|
||||
.cal-tag.is-ora { background: var(--c-accent); color: #fff; }
|
||||
|
||||
/* 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);
|
||||
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);
|
||||
}
|
||||
@@ -169,7 +186,7 @@ const presenti = cal
|
||||
}
|
||||
.cal-tab__ora-h { color: var(--c-accent-dark) !important; }
|
||||
.cal-tab__ora {
|
||||
position: sticky; left: 0; z-index: 1; background: var(--c-bg);
|
||||
background: var(--c-bg);
|
||||
font-family: var(--font-heading); font-size: .78rem; font-weight: 600; color: var(--c-heading);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -185,8 +202,7 @@ const presenti = cal
|
||||
.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 { display: flex; flex-wrap: wrap; gap: 8px 16px; list-style: none; margin: 12px 0 0; padding: 0; }
|
||||
.cal-legenda li {
|
||||
font-family: var(--font-heading); font-size: .66rem; letter-spacing: .12em; padding: 5px 10px;
|
||||
color: var(--c-heading);
|
||||
@@ -195,11 +211,13 @@ const presenti = cal
|
||||
.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__giu { padding-top: 16px; }
|
||||
.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. */
|
||||
/* Stampa dalla pagina (Ctrl+P col popup aperto): resta solo il calendario, un orario per
|
||||
foglio. Il bottone scarica invece il PDF generato dal server, che è il documento buono da
|
||||
girare. `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; }
|
||||
@@ -209,8 +227,9 @@ const presenti = cal
|
||||
.cal-dlg__box { padding: 0; height: auto; }
|
||||
.cal-dlg__azioni { display: none; }
|
||||
.cal-dlg__scroll { overflow: visible; }
|
||||
.cal-blocco { break-inside: avoid; }
|
||||
.cal-blocco + .cal-blocco { break-before: page; border-top: 0; padding-top: 0; }
|
||||
.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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user