From 179aeb442a380031d6977528b552fc7fac2e6e86 Mon Sep 17 00:00:00 2001 From: "Adriano (goldrake)" Date: Sat, 26 Sep 2026 13:05:42 +0000 Subject: [PATCH] =?UTF-8?q?longevity:=20pagina=20del=20gestionale=20?= =?UTF-8?q?=E2=80=94=20scheda=20cliente=20(referto,=20sessioni,=20fuori=20?= =?UTF-8?q?range)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pages/longevity/gestionale/cliente/[code].astro: anagrafica, il referto dell'ultima sessione (stesso Radar/MacroScore di io.astro, qui per il trainer), la tabella delle sessioni e le misure fuori range (colore --lg-fuori-range). Un codice inesistente è 404. Gli esiti ?salvato/?fuori/?errore dell'API di check-up sono mostrati qui. Refs #2 --- .../longevity/gestionale/cliente/[code].astro | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 apps/platforms/src/pages/longevity/gestionale/cliente/[code].astro diff --git a/apps/platforms/src/pages/longevity/gestionale/cliente/[code].astro b/apps/platforms/src/pages/longevity/gestionale/cliente/[code].astro new file mode 100644 index 0000000..cb0de4e --- /dev/null +++ b/apps/platforms/src/pages/longevity/gestionale/cliente/[code].astro @@ -0,0 +1,151 @@ +--- +// La scheda di un cliente: anagrafica, il referto dell'ultima sessione (lo stesso che il +// cliente vede da solo su /longevity/io, qui per il trainer), l'elenco delle sessioni e le +// misure fuori range. Un codice che non esiste in anagrafica è un 404, non uno schermo +// vuoto — non c'è niente da mostrare per un fascicolo che non c'è. +import Longevity from '../../../../layouts/Longevity.astro'; +import Radar from '../../../../components/longevity/Radar'; +import MacroScore from '../../../../components/longevity/MacroScore.astro'; +import { getLongevityDb } from '../../../../lib/longevity/db'; +import { clienteDaCodice, etaDelCliente } from '../../../../lib/longevity/anagrafica'; +import { sessioniDi, misureFuoriRange } from '../../../../lib/longevity/gestionale'; +import { refertoDi, ultimaSessioneDi } from '../../../../lib/longevity/vista'; +import { formattaNumero } from '../../../../lib/longevity/formato'; + +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 oggi = new Date().toISOString().slice(0, 10); +const eta = etaDelCliente(cliente.client_code, oggi); +const sessioni = sessioniDi(db, cliente.client_code); +const fuoriRange = misureFuoriRange(db, cliente.client_code); +const ultimaId = ultimaSessioneDi(db, cliente.client_code); +const referto = ultimaId !== null ? refertoDi(db, ultimaId) : null; + +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' }); + +const params = Astro.url.searchParams; +const salvato = params.get('salvato'); +const fuoriCount = params.get('fuori'); +const errore = params.get('errore'); +--- + +
+
+

{cliente.cognome} {cliente.nome}

+

+ {cliente.client_code} · {eta !== null ? `${eta} anni` : 'età non registrata'} · + accesso {cliente.ha_accesso ? 'attivo' : 'non ancora attivato'} +

+
+ Nuovo check-up +
+ + {errore &&

{errore}

} + {salvato &&

Check-up salvato (sessione #{salvato}).

} + {fuoriCount && ( +

+ {fuoriCount} {Number(fuoriCount) === 1 ? 'misura è' : 'misure sono'} fuori dal range di + plausibilità: verificale più sotto. +

+ )} + +
+

Anagrafica

+

Email: {cliente.email ?? '—'}

+

Telefono: {cliente.telefono ?? '—'}

+

Data di nascita: {cliente.data_nascita ? formattaData(cliente.data_nascita) : '—'}

+
+ + {!referto && ( +
+

Nessuna sessione ancora: apri un check-up per cominciare il fascicolo.

+
+ )} + + {referto && ( +
+
+

Fitness Age

+
+ + {referto.fitnessAge !== null ? formattaNumero(referto.fitnessAge) : '—'} + + {referto.fitnessAge !== null && anni} +
+

+ Copertura dati: {Math.round(referto.fitnessAgeCopertura * 100)}% + {referto.fitnessAge === null && ' — non ancora sufficiente per un numero.'} +

+
+ +
+

Radar

+ +
+ +
+ {referto.macro.map((m) => ( + + ))} +
+
+ )} + +
+

Sessioni

+ {sessioni.length === 0 ? ( +

Nessuna sessione.

+ ) : ( + + + + + + {sessioni.map((s) => ( + + + + + + + + ))} + +
DataTipoOperatoreMisureFitness Age
{formattaData(s.data)}{ETICHETTA_TIPO[s.tipo]}{s.operatore ?? '—'}{s.numeroMisure}{s.fitnessAge !== null ? formattaNumero(s.fitnessAge) : '—'}
+ )} +
+ + {fuoriRange.length > 0 && ( +
+

Misure fuori range

+ + + + {fuoriRange.map((m) => ( + + + + + + ))} + +
DataTestValore
{formattaData(m.data)}{m.etichetta} + {m.valore !== null ? formattaNumero(m.valore) : '—'}{m.unita ? ` ${m.unita}` : ''} +
+
+ )} +