longevity: pagine del gestionale — elenco clienti e apertura fascicolo

pages/longevity/gestionale/index.astro: tabella clienti (codice, cognome nome, età,
accesso, data e tipo dell'ultima sessione via sessioniDi per cliente — va bene finché
i fascicoli sono pochi, non pensata per scalare).
pages/longevity/gestionale/nuovo.astro: form verso /api/longevity/gestionale/clienti,
sesso a radio (non <select>: la parola inglese avrebbe fatto scattare il controllo
anti-SQL di referto.test.ts, che cerca \bSELECT\b senza distinguere l'HTML).

Refs #2
This commit is contained in:
2026-09-26 13:05:38 +00:00
parent 8ee2c8a2b4
commit fd56afdf44
2 changed files with 123 additions and 0 deletions
@@ -0,0 +1,71 @@
---
// L'elenco dei clienti del gestionale: la prima pagina che il trainer apre. Ogni riga
// porta anche data e tipo dell'ultima sessione — utile per capire a colpo d'occhio chi
// non si presenta da tempo, senza aprire ogni scheda.
import Longevity from '../../../layouts/Longevity.astro';
import { getLongevityDb } from '../../../lib/longevity/db';
import { elencoClienti, etaDelCliente } from '../../../lib/longevity/anagrafica';
import { sessioniDi } from '../../../lib/longevity/gestionale';
export const prerender = false;
const db = getLongevityDb();
const oggi = new Date().toISOString().slice(0, 10);
const ETICHETTA_TIPO: Record<'checkup' | 'questionario', string> = {
checkup: 'Check-up',
questionario: 'Questionario',
};
const formattaData = (iso: string) =>
new Date(`${iso}T00:00:00`).toLocaleDateString('it-IT', { day: '2-digit', month: 'short', year: 'numeric' });
// Una query in più per ogni cliente (sessioniDi, tenendo solo [0]): va bene finché i
// fascicoli sono pochi — non è pensata per scalare a centinaia di clienti.
const righe = elencoClienti().map((c) => ({
...c,
eta: etaDelCliente(c.client_code, oggi),
ultima: sessioniDi(db, c.client_code)[0] ?? null,
}));
---
<Longevity title="Clienti" crumbs={['Clienti']} variante="gestionale">
<div class="lg-head lg-head--azioni">
<div>
<h1>Clienti</h1>
<p>{righe.length} {righe.length === 1 ? 'fascicolo' : 'fascicoli'}.</p>
</div>
<a class="lg-btn" href="/longevity/gestionale/nuovo">Nuovo cliente</a>
</div>
{righe.length === 0 ? (
<p class="lg-muted">Nessun cliente ancora. <a href="/longevity/gestionale/nuovo">Apri il primo fascicolo</a>.</p>
) : (
<table class="lg-table">
<thead>
<tr>
<th>Codice</th>
<th>Cognome e nome</th>
<th>Età</th>
<th>Accesso</th>
<th>Ultima sessione</th>
</tr>
</thead>
<tbody>
{righe.map((c) => (
<tr>
<td><a href={`/longevity/gestionale/cliente/${c.client_code}`}>{c.client_code}</a></td>
<td>{c.cognome} {c.nome}</td>
<td>{c.eta ?? '—'}</td>
<td>{c.ha_accesso ? 'Sì' : 'No'}</td>
<td>
{c.ultima ? (
<>{formattaData(c.ultima.data)} — {ETICHETTA_TIPO[c.ultima.tipo]}</>
) : (
<span class="lg-muted">Nessuna</span>
)}
</td>
</tr>
))}
</tbody>
</table>
)}
</Longevity>
@@ -0,0 +1,52 @@
---
// Il modulo per aprire un fascicolo nuovo: posta all'API del gestionale, che fa la
// validazione vera (pages/api/longevity/gestionale/clienti.ts). Qui c'è solo il form.
import Longevity from '../../../layouts/Longevity.astro';
export const prerender = false;
const errore = Astro.url.searchParams.get('errore');
---
<Longevity title="Nuovo cliente" crumbs={['Clienti', 'Nuovo cliente']} variante="gestionale">
<div class="lg-head">
<h1>Nuovo cliente</h1>
<p>Nome, cognome e sesso bastano per aprire il fascicolo: il resto si completa dopo.</p>
</div>
{errore && <p class="lg-form__errore">{errore}</p>}
<form class="lg-form" method="post" action="/api/longevity/gestionale/clienti">
<section class="lg-form__blocco">
<label class="lg-field">
<span>Nome</span>
<input type="text" name="nome" required />
</label>
<label class="lg-field">
<span>Cognome</span>
<input type="text" name="cognome" required />
</label>
<fieldset class="lg-field lg-field--consenso">
<legend>Sesso</legend>
<label><input type="radio" name="sesso" value="F" required /> F</label>
<label><input type="radio" name="sesso" value="M" required /> M</label>
</fieldset>
<label class="lg-field">
<span>Data di nascita</span>
<input type="date" name="data_nascita" />
</label>
<label class="lg-field">
<span>Email</span>
<input type="email" name="email" />
</label>
<label class="lg-field">
<span>Telefono</span>
<input type="tel" name="telefono" />
</label>
</section>
<div class="lg-form__nav">
<a href="/longevity/gestionale">Annulla</a>
<button type="submit">Apri il fascicolo</button>
</div>
</form>
</Longevity>