longevity: pagina dettaglio sessione e modifica check-up dalla scheda (#12)
Nuova pagina .../sessione/[id].astro: dettaglio misure per asse, con link "Modifica" per un checkup. checkup.astro diventa correzione con ?sessione=<id> (campi precompilati, attivi alla data della sessione). La scheda cliente linka ogni sessione e mostra l'esito della correzione. Refs #12 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TkWhSnSLvAyRCoWBuCpXvp
This commit is contained in:
@@ -36,6 +36,7 @@ const formattaData = (iso: string) =>
|
||||
|
||||
const params = Astro.url.searchParams;
|
||||
const salvato = params.get('salvato');
|
||||
const corretto = params.get('corretto'); // #12: redirect di correggiCheckup dopo una correzione
|
||||
const fuoriCount = params.get('fuori');
|
||||
const errore = params.get('errore');
|
||||
const verificata = params.get('verificata');
|
||||
@@ -69,6 +70,7 @@ const messaggioVerifica =
|
||||
|
||||
{errore && <p class="lg-form__errore">{errore}</p>}
|
||||
{salvato && <p class="lg-card">Check-up salvato (sessione #{salvato}).</p>}
|
||||
{corretto && <p class="lg-card">Check-up corretto (sessione #{corretto}): punteggi ricalcolati.</p>}
|
||||
{messaggioVerifica && <p class="lg-card">{messaggioVerifica}</p>}
|
||||
{fuoriCount && (
|
||||
<p class="lg-misura--fuori-range">
|
||||
@@ -131,7 +133,9 @@ const messaggioVerifica =
|
||||
<tbody>
|
||||
{sessioni.map((s) => (
|
||||
<tr>
|
||||
<td>{formattaData(s.data)}</td>
|
||||
<td>
|
||||
<a href={`/longevity/gestionale/cliente/${cliente.client_code}/sessione/${s.id}`}>{formattaData(s.data)}</a>
|
||||
</td>
|
||||
<td>{ETICHETTA_TIPO[s.tipo]}</td>
|
||||
<td>{s.operatore ?? '—'}</td>
|
||||
<td>{s.numeroMisure}</td>
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
// Il modulo del check-up manuale: i campi letti dal registro (campiDelCheckup),
|
||||
// raggruppati per asse come il modulo cartaceo. L'invio va all'API del gestionale, che fa
|
||||
// la validazione vera (pages/api/longevity/gestionale/checkup.ts) — qui c'è solo il form.
|
||||
//
|
||||
// Con ?sessione=<id> (#12) lo stesso form diventa una correzione: data e campi
|
||||
// precompilati dalla sessione, hidden sessione_id in più, campi attivi alla data DELLA
|
||||
// SESSIONE (non a oggi) — un test dismesso da allora resterebbe comunque compilabile.
|
||||
import Longevity from '../../../../../layouts/Longevity.astro';
|
||||
import { getLongevityDb } from '../../../../../lib/longevity/db';
|
||||
import { clienteDaCodice } from '../../../../../lib/longevity/anagrafica';
|
||||
import { campiDelCheckup } from '../../../../../lib/longevity/gestionale';
|
||||
import { campiDelCheckup, misureDellaSessione } from '../../../../../lib/longevity/gestionale';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
@@ -13,28 +17,42 @@ const { code } = Astro.params;
|
||||
const cliente = code ? clienteDaCodice(code) : null;
|
||||
if (!cliente) return new Response('Cliente non trovato', { status: 404 });
|
||||
|
||||
const db = getLongevityDb();
|
||||
const oggi = new Date().toISOString().slice(0, 10);
|
||||
const gruppi = campiDelCheckup(getLongevityDb(), oggi);
|
||||
|
||||
const sessioneParam = Astro.url.searchParams.get('sessione');
|
||||
const sessioneId = sessioneParam !== null ? Number(sessioneParam) : null;
|
||||
const sessione = sessioneId !== null && Number.isInteger(sessioneId) ? misureDellaSessione(db, sessioneId) : null;
|
||||
// Una sessione di un altro cliente o non di tipo checkup non è un modulo modificabile da
|
||||
// questa scheda: stesso trattamento di un codice cliente inesistente.
|
||||
if (sessioneId !== null && (!sessione || sessione.client_code !== cliente.client_code || sessione.tipo !== 'checkup')) {
|
||||
return new Response('Sessione non trovata', { status: 404 });
|
||||
}
|
||||
|
||||
const dataForm = sessione?.data ?? oggi;
|
||||
const gruppi = campiDelCheckup(db, dataForm);
|
||||
const valori = new Map(sessione?.misure.map((m) => [m.test_id, m.valore_num]) ?? []);
|
||||
const errore = Astro.url.searchParams.get('errore');
|
||||
---
|
||||
<Longevity
|
||||
title={`Check-up — ${cliente.cognome} ${cliente.nome}`}
|
||||
crumbs={['Clienti', `${cliente.cognome} ${cliente.nome}`, 'Nuovo check-up']}
|
||||
title={`${sessione ? 'Modifica' : 'Nuovo'} check-up — ${cliente.cognome} ${cliente.nome}`}
|
||||
crumbs={['Clienti', `${cliente.cognome} ${cliente.nome}`, sessione ? 'Modifica check-up' : 'Nuovo check-up']}
|
||||
variante="gestionale"
|
||||
>
|
||||
<div class="lg-head">
|
||||
<h1>Nuovo check-up</h1>
|
||||
<p>{cliente.cognome} {cliente.nome} — {cliente.client_code}. Lascia vuoto ciò che non hai misurato oggi.</p>
|
||||
<h1>{sessione ? 'Modifica check-up' : 'Nuovo check-up'}</h1>
|
||||
<p>{cliente.cognome} {cliente.nome} — {cliente.client_code}. Lascia vuoto ciò che non hai misurato.</p>
|
||||
</div>
|
||||
|
||||
{errore && <p class="lg-form__errore">{errore}</p>}
|
||||
|
||||
<form class="lg-form" method="post" action="/api/longevity/gestionale/checkup">
|
||||
<input type="hidden" name="client_code" value={cliente.client_code} />
|
||||
{sessione && <input type="hidden" name="sessione_id" value={sessione.id} />}
|
||||
|
||||
<label class="lg-field">
|
||||
<span>Data</span>
|
||||
<input type="date" name="data" value={oggi} required />
|
||||
<input type="date" name="data" value={dataForm} required />
|
||||
</label>
|
||||
|
||||
{gruppi.map((g) => (
|
||||
@@ -43,7 +61,7 @@ const errore = Astro.url.searchParams.get('errore');
|
||||
{g.campi.map((c) => (
|
||||
<label class="lg-field">
|
||||
<span>{c.etichetta}{c.unita ? ` (${c.unita})` : ''}</span>
|
||||
<input type="text" inputmode="decimal" name={c.id} />
|
||||
<input type="text" inputmode="decimal" name={c.id} value={valori.get(c.id) ?? ''} />
|
||||
</label>
|
||||
))}
|
||||
</section>
|
||||
@@ -51,7 +69,7 @@ const errore = Astro.url.searchParams.get('errore');
|
||||
|
||||
<div class="lg-form__nav">
|
||||
<a href={`/longevity/gestionale/cliente/${cliente.client_code}`}>Annulla</a>
|
||||
<button type="submit">Salva check-up</button>
|
||||
<button type="submit">{sessione ? 'Salva correzione' : 'Salva check-up'}</button>
|
||||
</div>
|
||||
</form>
|
||||
</Longevity>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
// Il dettaglio di una sessione (#12): data, tipo, operatore, misure con valore/unità e
|
||||
// stato di fuori-range/verifica. Da qui si apre la correzione di un check-up
|
||||
// (checkup.astro?sessione=<id>) — un questionario resta di sola lettura, la sua correzione
|
||||
// non è questa issue. Un id che non esiste, o di un altro cliente, è un 404: non c'è niente
|
||||
// da mostrare per una sessione che non è la sua.
|
||||
import Longevity from '../../../../../../layouts/Longevity.astro';
|
||||
import { getLongevityDb } from '../../../../../../lib/longevity/db';
|
||||
import { clienteDaCodice } from '../../../../../../lib/longevity/anagrafica';
|
||||
import { misureDellaSessione, campiDelCheckup, type MisuraDiSessione } from '../../../../../../lib/longevity/gestionale';
|
||||
import { formattaNumero } from '../../../../../../lib/longevity/formato';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const { code, id } = Astro.params;
|
||||
const cliente = code ? clienteDaCodice(code) : null;
|
||||
if (!cliente) return new Response('Cliente non trovato', { status: 404 });
|
||||
|
||||
const sessioneId = id ? Number(id) : NaN;
|
||||
const db = getLongevityDb();
|
||||
const sessione = Number.isInteger(sessioneId) ? misureDellaSessione(db, sessioneId) : null;
|
||||
if (!sessione || sessione.client_code !== cliente.client_code) {
|
||||
return new Response('Sessione non trovata', { status: 404 });
|
||||
}
|
||||
|
||||
const ETICHETTA_TIPO: Record<'checkup' | 'questionario', string> = {
|
||||
checkup: 'Check-up', questionario: 'Questionario',
|
||||
};
|
||||
const ETICHETTA_VERIFICA: Record<'confermata' | 'scartata', string> = {
|
||||
confermata: 'Confermata', scartata: 'Scartata',
|
||||
};
|
||||
const formattaData = (iso: string) =>
|
||||
new Date(`${iso}T00:00:00`).toLocaleDateString('it-IT', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
|
||||
// Un check-up si raggruppa per asse come il modulo che lo ha compilato (campiDelCheckup);
|
||||
// un questionario non ha un raggruppamento del genere nel codice — resta un'unica sezione,
|
||||
// già ordinata per asse da misureDellaSessione.
|
||||
type Sezione = { asse: string | null; misure: MisuraDiSessione[] };
|
||||
let sezioni: Sezione[];
|
||||
if (sessione.tipo === 'checkup') {
|
||||
const perTestId = new Map(sessione.misure.map((m) => [m.test_id, m]));
|
||||
sezioni = campiDelCheckup(db, sessione.data)
|
||||
.map((g) => ({
|
||||
asse: g.asse,
|
||||
misure: g.campi.map((c) => perTestId.get(c.id)).filter((m): m is MisuraDiSessione => m !== undefined),
|
||||
}))
|
||||
.filter((s) => s.misure.length > 0);
|
||||
} else {
|
||||
sezioni = [{ asse: null, misure: sessione.misure }];
|
||||
}
|
||||
---
|
||||
<Longevity
|
||||
title={`Sessione del ${formattaData(sessione.data)} — ${cliente.cognome} ${cliente.nome}`}
|
||||
crumbs={['Clienti', `${cliente.cognome} ${cliente.nome}`, `Sessione del ${formattaData(sessione.data)}`]}
|
||||
variante="gestionale"
|
||||
>
|
||||
<div class="lg-head lg-head--azioni">
|
||||
<div>
|
||||
<h1>{ETICHETTA_TIPO[sessione.tipo]} del {formattaData(sessione.data)}</h1>
|
||||
<p>{cliente.cognome} {cliente.nome} — {cliente.client_code} · operatore {sessione.operatore ?? '—'}</p>
|
||||
</div>
|
||||
<div>
|
||||
{sessione.tipo === 'checkup' && (
|
||||
<a class="lg-btn" href={`/longevity/gestionale/cliente/${cliente.client_code}/checkup?sessione=${sessione.id}`}>Modifica</a>
|
||||
)}
|
||||
<a class="lg-btn" href={`/longevity/gestionale/cliente/${cliente.client_code}`}>Torna alla scheda</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{sezioni.map((s) => (
|
||||
<div class="lg-card">
|
||||
{s.asse && <p class="lg-score__label">{s.asse}</p>}
|
||||
<table class="lg-table">
|
||||
<thead><tr><th>Test</th><th>Valore</th><th>Stato</th></tr></thead>
|
||||
<tbody>
|
||||
{s.misure.map((m) => (
|
||||
<tr>
|
||||
<td>{m.etichetta}</td>
|
||||
<td class:list={{ 'lg-misura--fuori-range': m.fuori_range === 1 }}>
|
||||
{m.valore_num !== null ? formattaNumero(m.valore_num) : (m.valore_txt ?? '—')}
|
||||
{m.unita ? ` ${m.unita}` : ''}
|
||||
</td>
|
||||
<td>
|
||||
{m.fuori_range === 0
|
||||
? '—'
|
||||
: m.verifica
|
||||
? ETICHETTA_VERIFICA[m.verifica]
|
||||
: 'Da verificare'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
))}
|
||||
</Longevity>
|
||||
Reference in New Issue
Block a user