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 { refertoDi, ultimaSessioneDi } from '../../../../lib/longevity/vista';
|
||||||
import { formattaNumero } from '../../../../lib/longevity/formato';
|
import { formattaNumero } from '../../../../lib/longevity/formato';
|
||||||
import { vociDelGiornale } from '../../../../lib/longevity/giornale';
|
import { vociDelGiornale } from '../../../../lib/longevity/giornale';
|
||||||
|
import { consulenzaDi, OBIETTIVI } from '../../../../lib/longevity/consulenza';
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
@@ -28,6 +29,8 @@ const daVerificare = misureDaVerificare(db, cliente.client_code);
|
|||||||
const ultimaId = ultimaSessioneDi(db, cliente.client_code);
|
const ultimaId = ultimaSessioneDi(db, cliente.client_code);
|
||||||
const referto = ultimaId !== null ? refertoDi(db, ultimaId) : null;
|
const referto = ultimaId !== null ? refertoDi(db, ultimaId) : null;
|
||||||
const voci = vociDelGiornale(db, cliente.client_code); // #16: giornale di bordo, solo staff
|
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> = {
|
const ETICHETTA_TIPO: Record<'checkup' | 'questionario', string> = {
|
||||||
checkup: 'Check-up',
|
checkup: 'Check-up',
|
||||||
@@ -55,6 +58,10 @@ const MESSAGGIO_GIORNALE: Record<'aggiunta' | 'cancellata', string> = {
|
|||||||
};
|
};
|
||||||
const messaggioGiornale =
|
const messaggioGiornale =
|
||||||
giornaleEsito === 'aggiunta' || giornaleEsito === 'cancellata' ? MESSAGGIO_GIORNALE[giornaleEsito] : null;
|
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
|
<Longevity
|
||||||
title={`${cliente.cognome} ${cliente.nome}`}
|
title={`${cliente.cognome} ${cliente.nome}`}
|
||||||
@@ -82,6 +89,20 @@ const messaggioGiornale =
|
|||||||
{corretto && <p class="lg-card">Check-up corretto (sessione #{corretto}): punteggi ricalcolati.</p>}
|
{corretto && <p class="lg-card">Check-up corretto (sessione #{corretto}): punteggi ricalcolati.</p>}
|
||||||
{messaggioVerifica && <p class="lg-card">{messaggioVerifica}</p>}
|
{messaggioVerifica && <p class="lg-card">{messaggioVerifica}</p>}
|
||||||
{messaggioGiornale && <p class="lg-card">{messaggioGiornale}</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 && (
|
{fuoriCount && (
|
||||||
<p class="lg-misura--fuori-range">
|
<p class="lg-misura--fuori-range">
|
||||||
{fuoriCount} {Number(fuoriCount) === 1 ? 'misura è' : 'misure sono'} fuori dal range di
|
{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>
|
<p>Data di nascita: {cliente.data_nascita ? formattaData(cliente.data_nascita) : '—'}</p>
|
||||||
</div>
|
</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">
|
<div class="lg-card" id="giornale">
|
||||||
<p class="lg-score__label">Giornale di bordo</p>
|
<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>
|
<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))
|
? new Map([...valori.entries()].filter((kv): kv is [string, number] => kv[1] !== null))
|
||||||
: valoriUltimoCheckup(db, cliente.client_code);
|
: valoriUltimoCheckup(db, cliente.client_code);
|
||||||
const errore = Astro.url.searchParams.get('errore');
|
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
|
<Longevity
|
||||||
title={`${sessione ? 'Modifica' : 'Nuovo'} check-up — ${cliente.cognome} ${cliente.nome}`}
|
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">
|
<div class="lg-head">
|
||||||
<h1>{sessione ? 'Modifica check-up' : 'Nuovo check-up'}</h1>
|
<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>
|
<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>
|
</div>
|
||||||
|
|
||||||
{errore && <p class="lg-form__errore">{errore}</p>}
|
{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">
|
<form class="lg-form" method="post" action="/api/longevity/gestionale/checkup">
|
||||||
<input type="hidden" name="client_code" value={cliente.client_code} />
|
<input type="hidden" name="client_code" value={cliente.client_code} />
|
||||||
{sessione && <input type="hidden" name="sessione_id" value={sessione.id} />}
|
{sessione && <input type="hidden" name="sessione_id" value={sessione.id} />}
|
||||||
|
{flusso === 'nuovo' && <input type="hidden" name="flusso" value="nuovo" />}
|
||||||
|
|
||||||
<label class="lg-field">
|
<label class="lg-field">
|
||||||
<span>Data</span>
|
<span>Data</span>
|
||||||
@@ -131,7 +136,11 @@ const errore = Astro.url.searchParams.get('errore');
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div class="lg-form__nav">
|
<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>
|
<a href={`/longevity/gestionale/cliente/${cliente.client_code}`}>Annulla</a>
|
||||||
|
)}
|
||||||
<button type="submit">{sessione ? 'Salva correzione' : 'Salva check-up'}</button>
|
<button type="submit">{sessione ? 'Salva correzione' : 'Salva check-up'}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</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