c4eb598a3a
Il ruolo `campus` diventa `piattaforme` e non apre più solo la sezione Biohacking: copre tutte le aree riservate, con migrazione guardata sugli utenti esistenti. Nell'header la vecchia voce Biohacking lascia il posto a un menu Piattaforme con i due link, visibile solo a chi ha il ruolo, e /piattaforme diventa la pagina di atterraggio. Stress Index è portata dentro il sito: le nove viste dell'area professionisti diventano pagine Astro sotto /piattaforme/stress-index, con lo stile del sito al posto del design system del prototipo. React resta solo dove serve davvero — grafici recharts e scheda cliente — il resto è Astro con un filo di JS. I dati sono ancora quelli dimostrativi, raccolti in lib/stress-index/data.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
// Andamento medio dello studio, con selettore di periodo. L'aggregazione media stress e
|
|
// recupero di tutti i clienti sugli ultimi N rilievi settimanali.
|
|
import { useState } from 'react';
|
|
import DataChart from './DataChart';
|
|
import type { Measurement } from '../../../lib/stress-index/data';
|
|
|
|
const PERIODS = [
|
|
{ key: '30g', label: '30 giorni', points: 5 },
|
|
{ key: '90g', label: '90 giorni', points: 13 },
|
|
{ key: '6m', label: '6 mesi', points: 20 },
|
|
{ key: 'tutto', label: 'Tutto', points: 20 },
|
|
] as const;
|
|
|
|
interface Props { measurementsByClient: Record<string, Measurement[]> }
|
|
|
|
function aggregate(byClient: Record<string, Measurement[]>, points: number) {
|
|
const out: { date: string; stress: number; recovery: number }[] = [];
|
|
for (let fromEnd = points; fromEnd >= 1; fromEnd--) {
|
|
let stress = 0, recovery = 0, count = 0, date = '';
|
|
for (const series of Object.values(byClient)) {
|
|
const point = series[series.length - fromEnd];
|
|
if (!point) continue;
|
|
stress += point.stress;
|
|
recovery += point.recovery;
|
|
count += 1;
|
|
date = point.date;
|
|
}
|
|
if (count) out.push({ date, stress: Math.round(stress / count), recovery: Math.round(recovery / count) });
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export default function AndamentoChart({ measurementsByClient }: Props) {
|
|
const [period, setPeriod] = useState<string>('90g');
|
|
const active = PERIODS.find((p) => p.key === period) ?? PERIODS[1];
|
|
|
|
return (
|
|
<div>
|
|
<div className="si-chips">
|
|
{PERIODS.map((p) => (
|
|
<button key={p.key} type="button" className="si-chip" aria-pressed={p.key === period} onClick={() => setPeriod(p.key)}>
|
|
{p.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<DataChart
|
|
data={aggregate(measurementsByClient, active.points)}
|
|
xKey="date"
|
|
series={[
|
|
{ key: 'stress', label: 'Stress medio' },
|
|
{ key: 'recovery', label: 'Recupero medio' },
|
|
]}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|