From f5ca841d92e326a2dd3ff65cb5653a698fe0a403 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Sat, 22 Aug 2026 13:55:16 +0200 Subject: [PATCH] longevity: il questionario in pagina, e il consenso che si puo negare davvero Sostituisce il prototipo del cliente (salvava anche con "No" al consenso sanitario, perche' controllava solo che il campo non fosse vuoto). Qui il consenso si verifica PRIMA di scrivere qualunque dato: se non e' affermativo non si apre nessuna sessione, e la risposta lo dice. Il rifiuto si registra comunque fra le note di profilo, con esito e versione del questionario: va dimostrato tanto quanto un assenso. - src/lib/longevity/vista.ts: campiDelQuestionario (dal registro, non da una lista nel codice) e salvaDalForm (consenso prima di tutto, poi delega a salvaCompilazione). - src/pages/api/longevity/questionario.ts: il client_code arriva SEMPRE da codicePerUtente sulla sessione, mai dal corpo della richiesta. - src/lib/longevity/db.ts: getLongevityDb/getIdentityDb, connessioni singole per processo (come getDb() del sito), cosi' l'endpoint non apre una nuova connessione a ogni richiesta. - src/components/longevity/Questionario.tsx + pages/longevity/questionario.astro: isola React a quattro blocchi (le tre aree di domande + note/consenso), la pagina non interroga mai il database da sola. --- src/components/longevity/Questionario.tsx | 174 ++++++++++++++++++++ src/lib/longevity/db.ts | 15 ++ src/lib/longevity/vista.ts | 99 +++++++++++ src/pages/api/longevity/questionario.ts | 53 ++++++ src/pages/longevity/questionario.astro | 22 +++ src/styles/longevity.css | 32 ++++ tests/longevity/questionario-pagina.test.ts | 87 ++++++++++ 7 files changed, 482 insertions(+) create mode 100644 src/components/longevity/Questionario.tsx create mode 100644 src/lib/longevity/vista.ts create mode 100644 src/pages/api/longevity/questionario.ts create mode 100644 src/pages/longevity/questionario.astro create mode 100644 tests/longevity/questionario-pagina.test.ts diff --git a/src/components/longevity/Questionario.tsx b/src/components/longevity/Questionario.tsx new file mode 100644 index 0000000..e60cfb6 --- /dev/null +++ b/src/components/longevity/Questionario.tsx @@ -0,0 +1,174 @@ +// Il questionario in pagina: quattro schermate — le tre aree di domande punteggiate, +// lette dal registro (props `campi`, costruite da `campiDelQuestionario`), più le note +// libere e il consenso. Avanti/indietro fra i blocchi, invio solo sull'ultimo. +// +// Il consenso qui NON blocca lato client la scelta "No": bloccarlo qui ripeterebbe +// l'errore del prototipo del cliente in un altro punto (un controllo che sembra +// proteggere e non protegge). La verifica che conta è quella del server +// (src/lib/longevity/vista.ts, salvaDalForm): se il consenso non è affermativo, la +// compilazione non si salva e la risposta lo dice — questa isola si limita a mostrarlo. +import { useMemo, useState } from 'react'; +import type { CampoQuestionario } from '../../lib/longevity/vista'; + +interface Props { + campi: CampoQuestionario[]; +} + +const CAMPI_LIBERI: { id: string; etichetta: string }[] = [ + { id: 'farmaci', etichetta: 'Farmaci assunti abitualmente' }, + { id: 'problematiche_attuali', etichetta: 'Problematiche di salute attuali' }, + { id: 'problematiche_pregresse', etichetta: 'Problematiche di salute pregresse' }, + { id: 'obiettivi', etichetta: 'Obiettivi del percorso' }, +]; + +const BLOCCHI = [ + { titolo: 'Sonno & Recupero', sottoDominio: 'questionario_sonno' }, + { titolo: 'Energia & Stress', sottoDominio: 'questionario_energia_stress' }, + { titolo: 'Stile di Vita', sottoDominio: 'questionario_lifestyle' }, +] as const; + +type Esito = { tipo: 'ok'; sessioneId: number } | { tipo: 'errore'; messaggio: string }; + +export default function Questionario({ campi }: Props) { + const [passo, setPasso] = useState(0); + const [risposte, setRisposte] = useState>({}); + const [liberi, setLiberi] = useState>({}); + const [consenso, setConsenso] = useState<'' | 'Si' | 'No'>(''); + const [eta, setEta] = useState(''); + const [inviando, setInviando] = useState(false); + const [esito, setEsito] = useState(null); + + // Le domande arrivano dal registro in ordine di test_id, non raggruppate: qui si + // ricostruiscono i tre blocchi filtrando sul sotto-dominio, senza toccare i dati. + const gruppi = useMemo( + () => BLOCCHI.map((b) => ({ ...b, campi: campi.filter((c) => c.sottoDominio === b.sottoDominio) })), + [campi] + ); + + const totalePassi = gruppi.length + 1; // + il blocco note/consenso + const ultimo = passo === totalePassi - 1; + + async function invia() { + setInviando(true); + setEsito(null); + + const risposteNumeriche: Record = {}; + for (const [id, v] of Object.entries(risposte)) { + const n = Number(v); + if (v !== '' && Number.isFinite(n)) risposteNumeriche[id] = n; + } + + try { + const res = await fetch('/api/longevity/questionario', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + risposte: risposteNumeriche, + liberi, + consensoSanitario: consenso, + eta: eta === '' ? undefined : Number(eta), + }), + }); + const corpo = await res.json(); + if (res.ok) setEsito({ tipo: 'ok', sessioneId: corpo.sessioneId }); + else setEsito({ tipo: 'errore', messaggio: corpo.error ?? 'Invio non riuscito.' }); + } catch { + setEsito({ tipo: 'errore', messaggio: 'Invio non riuscito: verifica la connessione.' }); + } finally { + setInviando(false); + } + } + + if (esito?.tipo === 'ok') { + return ( +
+

Questionario salvato, grazie.

+
+ ); + } + + return ( +
+

Passo {passo + 1} di {totalePassi}

+ + {passo < gruppi.length && ( +
+

{gruppi[passo].titolo}

+ {gruppi[passo].campi.map((c) => ( + + ))} +
+ )} + + {ultimo && ( +
+

Note e consenso

+ + {CAMPI_LIBERI.map((c) => ( +