longevity: #14 unità B, grafici dei test per cliente e trainer

AndamentoTest.tsx (recharts, isola React come Progressione.tsx): un
LineChart per grafico del catalogo di andamento.ts, asse X temporale vero
(timestamp, scala 'time'), dx/sx come due linee distinte per tratteggio
(mai colore), tooltip con valore+unità ed extra (etichetta/unità dal
registro), righe di cambio test in gg/mm/aaaa.

progressione.astro: sezione «I tuoi test» (andamentoTest trainer:false),
visibile anche con meno di 2 sessioni — il grafico degli assi resta con
la sua condizione ≥2 invariata.

Nuova gestionale/cliente/[code]/andamento.astro (trainer:true, + grafico
degli assi se ≥2 punti), link «Andamento» in [code].astro accanto a
«Nuovo check-up» (.lg-btn, già ≥44px).

Modifica ammessa in andamento.ts: LineaAndamento.extra (etichetta/unità
dell'extraTestId dal registro) così il tooltip non indovina — test in
andamento.test.ts.

Refs #14
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ToarVnK21BEig85QQowuw
This commit is contained in:
2026-09-28 06:57:58 +00:00
parent a883a60a83
commit 1eb69e0317
8 changed files with 343 additions and 1 deletions
@@ -74,6 +74,31 @@ describe('andamentoTest: serie nel tempo, buchi e doppie linee', () => {
expect(rematore.linee[0]!.testId).toBe('f_row_carico_kg');
expect(rematore.linee[0]!.punti[0]).toEqual({ data: '2026-01-10', sessioneId: s1, valore: 60, extra: 5 });
});
it('la linea con extraTestId porta etichetta e unità del valore in tooltip, lette dal registro (unità B non indovina)', () => {
const db = dbPronto();
const s1 = checkup(db, '2026-01-10');
registraMisure(db, s1, 'manuale', [
{ test_id: 'f_row_carico_kg', valore_num: 60 },
{ test_id: 'f_row_rip', valore_num: 5 },
{ test_id: 'f_walk2km_min', valore_num: 18 },
{ test_id: 'f_walk2km_fc_bpm', valore_num: 140 },
{ test_id: 'f_peso_kg', valore_num: 80 },
]);
const sezioni = andamentoTest(db, 'ISL-0001', { trainer: true });
const rematore = sezioni.find((s) => s.titolo === 'Trazione')!.grafici.find((g) => g.titolo === 'Rematore')!;
// f_row_rip in registro.ts non ha `unita`: deve arrivare null, mai undefined (silenzioso).
expect(rematore.linee[0]!.extra).toEqual({ etichetta: 'Trazione/rematore, ripetizioni (test 5RM)', unita: null });
const camminata = sezioni.find((s) => s.titolo === 'Aerobico')!.grafici.find((g) => g.titolo === 'Camminata 2 km')!;
expect(camminata.linee[0]!.extra).toEqual({ etichetta: 'Camminata 2 km (UKK), frequenza cardiaca finale', unita: 'bpm' });
// Una linea senza extraTestId (es. Peso) non ha proprietà `extra`.
const peso = sezioni.find((s) => s.titolo === 'Pre-test')!.grafici.find((g) => g.titolo === 'Peso')!;
expect(peso.linee[0]!.extra).toBeUndefined();
});
});
describe('andamentoTest: cambio di test dentro la stessa sezione (Trazione)', () => {
@@ -121,6 +121,13 @@ describe('il guscio della piattaforma', () => {
// in un file fuori dai due moduli autorizzati.
expect(src).not.toContain('Identity');
});
it('progressione.astro usa andamento.ts con trainer:false (#14): il cliente non deve vedere i grafici riservati', () => {
const p = join(process.cwd(), 'src/pages/longevity/progressione.astro');
const src = readFileSync(p, 'utf8');
expect(src).toMatch(/from ['"`].*lib\/longevity\/andamento['"`]/);
expect(src).toMatch(/andamentoTest\([^)]*trainer:\s*false/);
});
});
describe('il gestionale del trainer (Unità 2b)', () => {
@@ -130,6 +137,7 @@ describe('il gestionale del trainer (Unità 2b)', () => {
'src/pages/longevity/gestionale/cliente/[code].astro',
'src/pages/longevity/gestionale/cliente/[code]/checkup.astro',
'src/pages/longevity/gestionale/cliente/[code]/consulenza.astro', // #15
'src/pages/longevity/gestionale/cliente/[code]/andamento.astro', // #14
];
it('ogni pagina del gestionale esiste e usa la variante "gestionale" del layout', () => {
@@ -170,6 +178,17 @@ describe('il gestionale del trainer (Unità 2b)', () => {
expect(src).toMatch(/\/longevity\/gestionale\/cliente\/\$\{cliente\.client_code\}\/accesso/);
});
it('la scheda cliente porta il link "Andamento" accanto a "Nuovo check-up" (#14)', () => {
const src = leggi('src/pages/longevity/gestionale/cliente/[code].astro');
expect(src).toMatch(/\/longevity\/gestionale\/cliente\/\$\{cliente\.client_code\}\/andamento/);
});
it('la pagina andamento del gestionale usa andamento.ts con trainer:true (#14): il trainer vede anche i grafici riservati', () => {
const src = leggi('src/pages/longevity/gestionale/cliente/[code]/andamento.astro');
expect(src).toMatch(/from ['"`].*lib\/longevity\/andamento['"`]/);
expect(src).toMatch(/andamentoTest\([^)]*trainer:\s*true/);
});
it('nessuna pagina longevity usa piu misureFuoriRange, rimossa da gestionale.ts a favore di misureDaVerificare (#7)', () => {
// Scandisce TUTTE le pagine sotto src/pages/longevity/, stesso criterio di
// referto.test.ts: un elenco scritto qui non protegge una pagina nuova.