// Scheda cliente: è l'unica vista davvero interattiva della piattaforma (tab, filtri sul // grafico, note, messaggi, tag), quindi vive come singola isola React. Lo stile arriva dalle // classi `.si-*` del foglio globale: qui non ci sono classi Tailwind del prototipo originale. import { useMemo, useState } from 'react'; import DataChart from './charts/DataChart'; import type { Alert, Client, Measurement, Note } from '../../lib/stress-index/data'; interface Props { client: Client; measurements: Measurement[]; alerts: Alert[]; notes: Note[]; } const TABS = [ { key: 'panoramica', label: 'Panoramica' }, { key: 'misurazioni', label: 'Misurazioni' }, { key: 'analytics', label: 'Analytics' }, { key: 'report', label: 'Report' }, { key: 'note', label: 'Note' }, { key: 'messaggi', label: 'Messaggi' }, { key: 'impostazioni', label: 'Impostazioni' }, ]; const METRICHE = [ { key: 'stress', label: 'Stress', color: '#b05a4e' }, { key: 'recovery', label: 'Recupero', color: '#6f8f6a' }, { key: 'balance', label: 'Equilibrio', color: '#c08a3e' }, { key: 'energy', label: 'Energia', color: '#9c8b70' }, ]; const PERIODI = [ { key: '4', label: 'Ultime 4' }, { key: '8', label: 'Ultime 8' }, { key: 'all', label: 'Tutte' }, ]; const REPORT = [ { date: '2026-07-01', label: 'Report mensile — Giugno 2026' }, { date: '2026-06-01', label: 'Report mensile — Maggio 2026' }, { date: '2026-05-01', label: 'Report mensile — Aprile 2026' }, ]; const MESSAGGI_INIZIALI = [ { from: 'cliente', text: "Buongiorno dottore, le invio l'esito della misurazione di stamattina.", when: '09:12' }, { from: 'professionista', text: 'Grazie, la vedo. I valori di recupero sono in miglioramento rispetto alla scorsa settimana.', when: '09:20' }, { from: 'cliente', text: 'Sì, mi sento meglio, dormo di più.', when: '09:22' }, { from: 'professionista', text: 'Continui così, ci sentiamo al prossimo controllo.', when: '09:25' }, ]; const qualitativa = (v: number) => (v >= 70 ? 'Alto' : v >= 40 ? 'Moderato' : 'Basso'); const livello = (v: number) => (v >= 70 ? 'bad' : v >= 45 ? 'warn' : 'ok'); /** Indicatore ad arco: il valore riempie un semicerchio da sinistra a destra. */ function Gauge({ value, label }: { value: number; label: string }) { const ratio = Math.max(0, Math.min(1, value / 100)); const R = 56; const len = Math.PI * R; return (
= 70 ? '#b05a4e' : value >= 45 ? '#c08a3e' : '#6f8f6a'} strokeDasharray={`${len * ratio} ${len}`} />

{value}/100

{label} · {qualitativa(value)}

); } export default function ClientDetail({ client, measurements, alerts, notes }: Props) { const [tab, setTab] = useState('panoramica'); const [periodo, setPeriodo] = useState('8'); const [metriche, setMetriche] = useState(METRICHE.map((m) => m.key)); const [noteCliente, setNoteCliente] = useState(notes); const [nuovaNota, setNuovaNota] = useState(''); const [messaggi, setMessaggi] = useState(MESSAGGI_INIZIALI); const [bozza, setBozza] = useState(''); const [tag, setTag] = useState(client.tags); const [nuovoTag, setNuovoTag] = useState(''); const [promemoria, setPromemoria] = useState(true); const [reportSemestrale, setReportSemestrale] = useState(false); const ultima = measurements[measurements.length - 1]; const stress = ultima?.stress ?? client.stress; const recovery = ultima?.recovery ?? 0; const balance = ultima?.balance ?? 0; const energy = ultima?.energy ?? 0; const datiGrafico = useMemo( () => (periodo === 'all' ? measurements : measurements.slice(-Number(periodo))), [measurements, periodo] ); const serieAttive = METRICHE.filter((m) => metriche.includes(m.key)); const ultimeTre = [...measurements].slice(-3).reverse(); const storico = [...measurements].reverse(); const toggleMetrica = (key: string) => setMetriche((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key])); const aggiungiNota = () => { const testo = nuovaNota.trim(); if (!testo) return; setNoteCliente((prev) => [{ clientId: client.id, text: testo, when: client.lastMeasurement }, ...prev]); setNuovaNota(''); }; const invia = () => { const testo = bozza.trim(); if (!testo) return; setMessaggi((prev) => [...prev, { from: 'professionista', text: testo, when: 'ora' }]); setBozza(''); }; const aggiungiTag = () => { const t = nuovoTag.trim(); if (!t || tag.includes(t)) return; setTag((prev) => [...prev, t]); setNuovoTag(''); }; return (
{client.initials}

{client.name}

{client.age} anni · {client.sex} · in carico dal {client.since}

{[ { label: 'Stress', value: stress, note: qualitativa(stress) }, { label: 'Recupero', value: recovery, note: qualitativa(recovery) }, { label: 'Misurazioni', value: measurements.length, note: 'totali' }, { label: 'In carico da', value: client.since, note: 'data iscrizione' }, ].map((k) => (

{k.label}

{k.value}

{k.note}

))}
{TABS.map((t) => ( ))}
{tab === 'panoramica' && (

Stato attuale

Andamento clinico

{PERIODI.map((p) => ( ))}
{METRICHE.map((m) => ( ))}

Alert attivi

{alerts.length === 0 ? (

Nessun alert attivo per questo cliente.

) : ( alerts.map((a, i) => (
)) )}

Ultime 3 misurazioni

    {ultimeTre.map((m, i) => (
  • {m.date} · {m.type} · stress {m.stress} {qualitativa(m.stress)}
  • ))}

Sintesi

{stress >= 70 ? `${client.name} mostra un livello di stress elevato nelle ultime misurazioni, con recupero notturno ridotto. Consigliata una revisione del carico settimanale e un lavoro sulla respirazione.` : `${client.name} mantiene un equilibrio complessivamente stabile. Continuare il monitoraggio periodico e mantenere le abitudini di recupero attuali.`}

)} {tab === 'misurazioni' && (

Storico misurazioni

{storico.map((m, i) => ( ))}
DataTipoStressRecuperoEquilibrioEnergiaDurata
{m.date} {m.type} {m.stress} {m.recovery} {m.balance} {m.energy} {m.durationMin} min
)} {tab === 'analytics' && (

Trend dei quattro parametri

Stress e recupero, ultime misurazioni

)} {tab === 'report' && (

Report generati

    {REPORT.map((r) => (
  • {r.date} · {r.label}
  • ))}
)} {tab === 'note' && (

Note cliente