longevity: #15 pagine del flusso guidato: consulenza, check-up, scheda cliente
Nuova pagina consulenza.astro (radio per livello e obiettivo, niente select). checkup.astro mostra "Passo 3 di 4" e porta avanti flusso=nuovo, con «Salta» verso la scheda. La scheda cliente ([code].astro) mostra la sezione "Prima consulenza" (letta, Modifica/Compila) e, con flusso=questionario, il riquadro "Passo 4 di 4" che rimanda a "Crea accesso" se il cliente non lo ha ancora. Refs #15 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import { misureDaVerificare } from '../../../../lib/longevity/misure';
|
||||
import { refertoDi, ultimaSessioneDi } from '../../../../lib/longevity/vista';
|
||||
import { formattaNumero } from '../../../../lib/longevity/formato';
|
||||
import { vociDelGiornale } from '../../../../lib/longevity/giornale';
|
||||
import { consulenzaDi, OBIETTIVI } from '../../../../lib/longevity/consulenza';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
@@ -28,6 +29,8 @@ const daVerificare = misureDaVerificare(db, cliente.client_code);
|
||||
const ultimaId = ultimaSessioneDi(db, cliente.client_code);
|
||||
const referto = ultimaId !== null ? refertoDi(db, ultimaId) : null;
|
||||
const voci = vociDelGiornale(db, cliente.client_code); // #16: giornale di bordo, solo staff
|
||||
const consulenza = consulenzaDi(db, cliente.client_code); // #15: prima consulenza, solo staff
|
||||
const ETICHETTA_OBIETTIVO = new Map(OBIETTIVI.map((o) => [o.chiave, o.etichetta]));
|
||||
|
||||
const ETICHETTA_TIPO: Record<'checkup' | 'questionario', string> = {
|
||||
checkup: 'Check-up',
|
||||
@@ -55,6 +58,10 @@ const MESSAGGIO_GIORNALE: Record<'aggiunta' | 'cancellata', string> = {
|
||||
};
|
||||
const messaggioGiornale =
|
||||
giornaleEsito === 'aggiunta' || giornaleEsito === 'cancellata' ? MESSAGGIO_GIORNALE[giornaleEsito] : null;
|
||||
const consulenzaEsito = params.get('consulenza'); // #15: 'salvata', dal redirect dell'API consulenza
|
||||
// Passo 4 del flusso guidato per un cliente nuovo (#15): dopo il check-up si torna qui con
|
||||
// ?flusso=questionario — il questionario resta del cliente, quindi qui c'è solo l'indicazione.
|
||||
const flusso = params.get('flusso');
|
||||
---
|
||||
<Longevity
|
||||
title={`${cliente.cognome} ${cliente.nome}`}
|
||||
@@ -82,6 +89,20 @@ const messaggioGiornale =
|
||||
{corretto && <p class="lg-card">Check-up corretto (sessione #{corretto}): punteggi ricalcolati.</p>}
|
||||
{messaggioVerifica && <p class="lg-card">{messaggioVerifica}</p>}
|
||||
{messaggioGiornale && <p class="lg-card">{messaggioGiornale}</p>}
|
||||
{consulenzaEsito === 'salvata' && <p class="lg-card">Prima consulenza salvata.</p>}
|
||||
{flusso === 'questionario' && (
|
||||
<div class="lg-card">
|
||||
<p class="lg-score__label">Passo 4 di 4 — Questionario</p>
|
||||
{cliente.ha_accesso ? (
|
||||
<p>Il questionario lo compila il cliente dal suo accesso, anche sul tablet del centro.</p>
|
||||
) : (
|
||||
<>
|
||||
<p>Il questionario lo compila il cliente dal suo accesso, anche sul tablet del centro: crealo per continuare.</p>
|
||||
<a class="lg-btn" href={`/longevity/gestionale/cliente/${cliente.client_code}/accesso`}>Crea accesso al referto</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{fuoriCount && (
|
||||
<p class="lg-misura--fuori-range">
|
||||
{fuoriCount} {Number(fuoriCount) === 1 ? 'misura è' : 'misure sono'} fuori dal range di
|
||||
@@ -96,6 +117,31 @@ const messaggioGiornale =
|
||||
<p>Data di nascita: {cliente.data_nascita ? formattaData(cliente.data_nascita) : '—'}</p>
|
||||
</div>
|
||||
|
||||
<div class="lg-card" id="consulenza">
|
||||
<div class="lg-head--azioni">
|
||||
<p class="lg-score__label">Prima consulenza</p>
|
||||
<a class="lg-btn" href={`/longevity/gestionale/cliente/${cliente.client_code}/consulenza`}>
|
||||
{consulenza ? 'Modifica' : 'Compila'}
|
||||
</a>
|
||||
</div>
|
||||
{!consulenza ? (
|
||||
<p class="lg-muted">Non ancora compilata.</p>
|
||||
) : (
|
||||
<>
|
||||
<p>Livello: {consulenza.livello ?? '—'}</p>
|
||||
<p>Obiettivo primario: {consulenza.obiettivo_primario ? ETICHETTA_OBIETTIVO.get(consulenza.obiettivo_primario) ?? consulenza.obiettivo_primario : '—'}</p>
|
||||
<p>Obiettivo specifico: {consulenza.obiettivo_specifico ?? '—'}</p>
|
||||
<p>Data target: {consulenza.data_target ? formattaData(consulenza.data_target) : '—'}</p>
|
||||
<p>Note mediche: {consulenza.note_mediche ?? '—'}</p>
|
||||
<p>Storia sportiva: {consulenza.storia_sportiva ?? '—'}</p>
|
||||
<p>Limitazioni fisiche attuali: {consulenza.limitazioni ?? '—'}</p>
|
||||
<p>Disponibilità: {consulenza.disponibilita_giorni ?? '—'} giorni/settimana</p>
|
||||
<p>Fascia oraria preferita: {consulenza.fascia_oraria ?? '—'}</p>
|
||||
<p>Note screening: {consulenza.note_screening ?? '—'}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="lg-card" id="giornale">
|
||||
<p class="lg-score__label">Giornale di bordo</p>
|
||||
<p class="lg-muted">Solo lo staff lo vede. Una nota sbagliata si cancella e si riscrive: non si modifica.</p>
|
||||
|
||||
@@ -40,6 +40,9 @@ const valoriPreselezione = sessione
|
||||
? new Map([...valori.entries()].filter((kv): kv is [string, number] => kv[1] !== null))
|
||||
: valoriUltimoCheckup(db, cliente.client_code);
|
||||
const errore = Astro.url.searchParams.get('errore');
|
||||
// Passo 3 del flusso guidato per un cliente nuovo (?flusso=nuovo, #15): solo per un
|
||||
// check-up NUOVO — in correzione (?sessione=<id>) non si è mai dentro il flusso guidato.
|
||||
const flusso = !sessione ? Astro.url.searchParams.get('flusso') ?? '' : '';
|
||||
---
|
||||
<Longevity
|
||||
title={`${sessione ? 'Modifica' : 'Nuovo'} check-up — ${cliente.cognome} ${cliente.nome}`}
|
||||
@@ -49,6 +52,7 @@ const errore = Astro.url.searchParams.get('errore');
|
||||
<div class="lg-head">
|
||||
<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>
|
||||
{flusso === 'nuovo' && <p class="lg-muted">Passo 3 di 4 — Scheda Rapida</p>}
|
||||
</div>
|
||||
|
||||
{errore && <p class="lg-form__errore">{errore}</p>}
|
||||
@@ -56,6 +60,7 @@ const errore = Astro.url.searchParams.get('errore');
|
||||
<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} />}
|
||||
{flusso === 'nuovo' && <input type="hidden" name="flusso" value="nuovo" />}
|
||||
|
||||
<label class="lg-field">
|
||||
<span>Data</span>
|
||||
@@ -131,7 +136,11 @@ const errore = Astro.url.searchParams.get('errore');
|
||||
)}
|
||||
|
||||
<div class="lg-form__nav">
|
||||
{flusso === 'nuovo' ? (
|
||||
<a href={`/longevity/gestionale/cliente/${cliente.client_code}?flusso=questionario`}>Salta</a>
|
||||
) : (
|
||||
<a href={`/longevity/gestionale/cliente/${cliente.client_code}`}>Annulla</a>
|
||||
)}
|
||||
<button type="submit">{sessione ? 'Salva correzione' : 'Salva check-up'}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
// La scheda di prima consulenza (#15): livello, obiettivo, note dello screening iniziale.
|
||||
// Precompilata se esiste già — correggere e salvare SOVRASCRIVE, nessuno storico (decisione
|
||||
// di Adriano, 27/09/2026). L'invio va all'API del gestionale, che fa la validazione vera
|
||||
// (pages/api/longevity/gestionale/consulenza.ts) — qui c'è solo il form.
|
||||
//
|
||||
// Passo 2 del flusso guidato per un cliente nuovo (?flusso=nuovo, #15): il parametro passa
|
||||
// di pagina in pagina fino al questionario, e ogni passo ha un link «Salta».
|
||||
import Longevity from '../../../../../layouts/Longevity.astro';
|
||||
import { getLongevityDb } from '../../../../../lib/longevity/db';
|
||||
import { clienteDaCodice } from '../../../../../lib/longevity/anagrafica';
|
||||
import { consulenzaDi, OBIETTIVI } from '../../../../../lib/longevity/consulenza';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
const { code } = Astro.params;
|
||||
const cliente = code ? clienteDaCodice(code) : null;
|
||||
if (!cliente) return new Response('Cliente non trovato', { status: 404 });
|
||||
|
||||
const db = getLongevityDb();
|
||||
const scheda = consulenzaDi(db, cliente.client_code);
|
||||
const errore = Astro.url.searchParams.get('errore');
|
||||
const flusso = Astro.url.searchParams.get('flusso') ?? '';
|
||||
const query = flusso ? `?flusso=${encodeURIComponent(flusso)}` : '';
|
||||
---
|
||||
<Longevity
|
||||
title={`Prima consulenza — ${cliente.cognome} ${cliente.nome}`}
|
||||
crumbs={['Clienti', `${cliente.cognome} ${cliente.nome}`, 'Prima consulenza']}
|
||||
variante="gestionale"
|
||||
>
|
||||
<div class="lg-head">
|
||||
<h1>Prima consulenza</h1>
|
||||
<p>{cliente.cognome} {cliente.nome} — {cliente.client_code}. Tutti i campi sono facoltativi.</p>
|
||||
{flusso === 'nuovo' && <p class="lg-muted">Passo 2 di 4 — Prima consulenza</p>}
|
||||
</div>
|
||||
|
||||
{errore && <p class="lg-form__errore">{errore}</p>}
|
||||
|
||||
<form class="lg-form" method="post" action="/api/longevity/gestionale/consulenza">
|
||||
<input type="hidden" name="client_code" value={cliente.client_code} />
|
||||
<input type="hidden" name="flusso" value={flusso} />
|
||||
|
||||
<section class="lg-form__blocco">
|
||||
<fieldset class="lg-field lg-field--consenso">
|
||||
<legend>Livello</legend>
|
||||
{[1, 2, 3].map((n) => (
|
||||
<label>
|
||||
<input type="radio" name="livello" value={n} checked={scheda?.livello === n} /> {n}
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="lg-field lg-field--consenso">
|
||||
<legend>Obiettivo primario</legend>
|
||||
{OBIETTIVI.map((o) => (
|
||||
<label>
|
||||
<input
|
||||
type="radio" name="obiettivo_primario" value={o.chiave}
|
||||
checked={scheda?.obiettivo_primario === o.chiave}
|
||||
/> {o.etichetta}
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
|
||||
<label class="lg-field">
|
||||
<span>Obiettivo specifico concordato</span>
|
||||
<textarea name="obiettivo_specifico" rows="3">{scheda?.obiettivo_specifico ?? ''}</textarea>
|
||||
</label>
|
||||
|
||||
<label class="lg-field">
|
||||
<span>Data target</span>
|
||||
<input type="date" name="data_target" value={scheda?.data_target ?? ''} />
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class="lg-form__blocco">
|
||||
<h2>Screening</h2>
|
||||
<label class="lg-field">
|
||||
<span>Note mediche, allergie, infortuni pregressi</span>
|
||||
<textarea name="note_mediche" rows="3">{scheda?.note_mediche ?? ''}</textarea>
|
||||
</label>
|
||||
<label class="lg-field">
|
||||
<span>Storia sportiva</span>
|
||||
<textarea name="storia_sportiva" rows="3">{scheda?.storia_sportiva ?? ''}</textarea>
|
||||
</label>
|
||||
<label class="lg-field">
|
||||
<span>Limitazioni fisiche attuali</span>
|
||||
<textarea name="limitazioni" rows="3">{scheda?.limitazioni ?? ''}</textarea>
|
||||
</label>
|
||||
<label class="lg-field">
|
||||
<span>Disponibilità (giorni/settimana)</span>
|
||||
<input type="text" inputmode="numeric" name="disponibilita_giorni" value={scheda?.disponibilita_giorni ?? ''} />
|
||||
</label>
|
||||
<label class="lg-field">
|
||||
<span>Fascia oraria preferita</span>
|
||||
<input type="text" name="fascia_oraria" value={scheda?.fascia_oraria ?? ''} />
|
||||
</label>
|
||||
<label class="lg-field">
|
||||
<span>Note screening</span>
|
||||
<textarea name="note_screening" rows="3">{scheda?.note_screening ?? ''}</textarea>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<div class="lg-form__nav">
|
||||
{flusso === 'nuovo' ? (
|
||||
<a href={`/longevity/gestionale/cliente/${cliente.client_code}/checkup${query}`}>Salta</a>
|
||||
) : (
|
||||
<a href={`/longevity/gestionale/cliente/${cliente.client_code}#consulenza`}>Annulla</a>
|
||||
)}
|
||||
<button type="submit">Salva</button>
|
||||
</div>
|
||||
</form>
|
||||
</Longevity>
|
||||
Reference in New Issue
Block a user