Piattaforme riservate: ruolo dedicato e port della dashboard Stress Index
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>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
// Dati della piattaforma Stress Index. Sono ancora i dati dimostrativi del prototipo
|
||||
// (nessun backend HRV collegato): stanno tutti qui, così quando arriverà la sorgente vera
|
||||
// basterà sostituire queste costanti con le query, lasciando intatte pagine e componenti.
|
||||
|
||||
export interface Client {
|
||||
id: string;
|
||||
name: string;
|
||||
initials: string;
|
||||
age: number;
|
||||
sex: 'M' | 'F';
|
||||
tags: string[];
|
||||
email: string;
|
||||
since: string;
|
||||
lastMeasurement: string;
|
||||
stress: number;
|
||||
alerts: number;
|
||||
}
|
||||
|
||||
export const clients: Client[] = [
|
||||
{ id: 'c1', name: 'L. Verdi', initials: 'LV', age: 34, sex: 'F', tags: ['ansia', 'sonno'], email: 'l.verdi@esempio.it', since: '2024-02-10', lastMeasurement: '2026-07-10', stress: 62, alerts: 1 },
|
||||
{ id: 'c2', name: 'A. Neri', initials: 'AN', age: 45, sex: 'M', tags: ['sport', 'recupero'], email: 'a.neri@esempio.it', since: '2023-11-05', lastMeasurement: '2026-07-11', stress: 38, alerts: 0 },
|
||||
{ id: 'c3', name: 'G. Rossi', initials: 'GR', age: 29, sex: 'M', tags: ['lavoro', 'stress'], email: 'g.rossi@esempio.it', since: '2024-05-20', lastMeasurement: '2026-07-09', stress: 78, alerts: 2 },
|
||||
{ id: 'c4', name: 'S. Ferrari', initials: 'SF', age: 52, sex: 'F', tags: ['menopausa', 'sonno'], email: 's.ferrari@esempio.it', since: '2023-08-14', lastMeasurement: '2026-07-12', stress: 55, alerts: 0 },
|
||||
{ id: 'c5', name: 'P. Colombo', initials: 'PC', age: 40, sex: 'M', tags: ['sport', 'agonismo'], email: 'p.colombo@esempio.it', since: '2024-01-30', lastMeasurement: '2026-07-08', stress: 44, alerts: 0 },
|
||||
{ id: 'c6', name: 'E. Ricci', initials: 'ER', age: 61, sex: 'F', tags: ['cronico', 'riabilitazione'], email: 'e.ricci@esempio.it', since: '2022-09-01', lastMeasurement: '2026-07-11', stress: 70, alerts: 1 },
|
||||
{ id: 'c7', name: 'D. Marino', initials: 'DM', age: 33, sex: 'M', tags: ['lavoro', 'burnout'], email: 'd.marino@esempio.it', since: '2024-03-18', lastMeasurement: '2026-07-07', stress: 82, alerts: 2 },
|
||||
{ id: 'c8', name: 'C. Greco', initials: 'CG', age: 27, sex: 'F', tags: ['sport', 'studentessa'], email: 'c.greco@esempio.it', since: '2024-06-02', lastMeasurement: '2026-07-12', stress: 30, alerts: 0 },
|
||||
{ id: 'c9', name: 'F. Bruno', initials: 'FB', age: 48, sex: 'M', tags: ['ipertensione', 'sonno'], email: 'f.bruno@esempio.it', since: '2023-04-25', lastMeasurement: '2026-07-06', stress: 66, alerts: 1 },
|
||||
{ id: 'c10', name: 'V. Costa', initials: 'VC', age: 36, sex: 'F', tags: ['ansia', 'lavoro'], email: 'v.costa@esempio.it', since: '2024-07-01', lastMeasurement: '2026-07-10', stress: 58, alerts: 1 },
|
||||
];
|
||||
|
||||
export interface Measurement {
|
||||
date: string;
|
||||
stress: number;
|
||||
recovery: number;
|
||||
balance: number;
|
||||
energy: number;
|
||||
type: string;
|
||||
durationMin: number;
|
||||
}
|
||||
|
||||
const clamp = (n: number) => Math.max(0, Math.min(100, Math.round(n)));
|
||||
const TYPES = ['Riposo', 'Lavoro', 'Sport', 'Sonno'];
|
||||
|
||||
// Serie settimanale deterministica: l'oscillazione deriva da id + indice, così i grafici
|
||||
// sono sempre uguali fra server e client (nessun Math.random che sfarfalla all'idratazione).
|
||||
function buildSeries(clientId: string, baseStress: number, endDate: string, points: number): Measurement[] {
|
||||
const end = new Date(endDate);
|
||||
const series: Measurement[] = [];
|
||||
for (let i = points - 1; i >= 0; i--) {
|
||||
const d = new Date(end);
|
||||
d.setDate(d.getDate() - i * 7);
|
||||
const seed = clientId.charCodeAt(clientId.length - 1) + i * 13;
|
||||
const wobble = Math.sin(seed) * 12;
|
||||
const stress = clamp(baseStress + wobble);
|
||||
series.push({
|
||||
date: d.toISOString().slice(0, 10),
|
||||
stress,
|
||||
recovery: clamp(100 - stress + Math.cos(seed) * 8),
|
||||
balance: clamp(70 - Math.abs(wobble) + Math.sin(seed * 2) * 6),
|
||||
energy: clamp(65 - (stress - 50) * 0.4 + Math.cos(seed * 1.5) * 7),
|
||||
type: TYPES[i % TYPES.length],
|
||||
durationMin: 15 + ((i * 5) % 40),
|
||||
});
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
export const measurementsByClient: Record<string, Measurement[]> = {
|
||||
c1: buildSeries('c1', 62, '2026-07-10', 16),
|
||||
c2: buildSeries('c2', 38, '2026-07-11', 14),
|
||||
c3: buildSeries('c3', 78, '2026-07-09', 18),
|
||||
c4: buildSeries('c4', 55, '2026-07-12', 12),
|
||||
c5: buildSeries('c5', 44, '2026-07-08', 15),
|
||||
c6: buildSeries('c6', 70, '2026-07-11', 20),
|
||||
c7: buildSeries('c7', 82, '2026-07-07', 17),
|
||||
c8: buildSeries('c8', 30, '2026-07-12', 10),
|
||||
c9: buildSeries('c9', 66, '2026-07-06', 13),
|
||||
c10: buildSeries('c10', 58, '2026-07-10', 11),
|
||||
};
|
||||
|
||||
export interface Alert {
|
||||
clientId: string;
|
||||
clientName: string;
|
||||
text: string;
|
||||
severity: 'warn' | 'error';
|
||||
when: string;
|
||||
}
|
||||
|
||||
export const alerts: Alert[] = [
|
||||
{ clientId: 'c7', clientName: 'D. Marino', text: 'Stress elevato per 3 misurazioni consecutive', severity: 'error', when: '2026-07-07' },
|
||||
{ clientId: 'c3', clientName: 'G. Rossi', text: 'Recupero notturno in calo', severity: 'warn', when: '2026-07-09' },
|
||||
{ clientId: 'c3', clientName: 'G. Rossi', text: 'Bilancio simpatico/parasimpatico sbilanciato', severity: 'warn', when: '2026-07-05' },
|
||||
{ clientId: 'c1', clientName: 'L. Verdi', text: 'Qualità del sonno sotto la soglia attesa', severity: 'warn', when: '2026-07-10' },
|
||||
{ clientId: 'c6', clientName: 'E. Ricci', text: 'Andamento stress in peggioramento nelle ultime 4 settimane', severity: 'warn', when: '2026-07-11' },
|
||||
{ clientId: 'c9', clientName: 'F. Bruno', text: 'Nessuna misurazione da oltre 7 giorni', severity: 'warn', when: '2026-07-13' },
|
||||
{ clientId: 'c10', clientName: 'V. Costa', text: 'Picco di stress rilevato dopo sessione di lavoro prolungata', severity: 'warn', when: '2026-07-10' },
|
||||
{ clientId: 'c7', clientName: 'D. Marino', text: 'Energia media sotto il 30% da due settimane', severity: 'error', when: '2026-07-01' },
|
||||
];
|
||||
|
||||
export interface Note {
|
||||
clientId?: string;
|
||||
text: string;
|
||||
when: string;
|
||||
}
|
||||
|
||||
export const notes: Note[] = [
|
||||
{ clientId: 'c1', text: 'Consigliata routine di respirazione serale, rivalutare tra 2 settimane.', when: '2026-07-10' },
|
||||
{ clientId: 'c3', text: 'Segnalato carico lavorativo elevato, proposto colloquio di follow-up.', when: '2026-07-09' },
|
||||
{ clientId: 'c7', text: 'Situazione di burnout in peggioramento, valutare invio a specialista.', when: '2026-07-07' },
|
||||
{ text: 'Aggiornamento protocollo misurazioni: aggiunta soglia energia minima.', when: '2026-07-05' },
|
||||
{ clientId: 'c6', text: 'Programma di riabilitazione in corso, buoni progressi sul recupero.', when: '2026-07-11' },
|
||||
];
|
||||
|
||||
export const studioKpis = {
|
||||
totalClients: 10,
|
||||
avgStress: 58,
|
||||
activeAlerts: 8,
|
||||
measurementsThisMonth: 46,
|
||||
retentionRate: 92,
|
||||
};
|
||||
|
||||
export const scoreDistribution = [
|
||||
{ range: '0-20', count: 0 },
|
||||
{ range: '21-40', count: 2 },
|
||||
{ range: '41-60', count: 3 },
|
||||
{ range: '61-80', count: 4 },
|
||||
{ range: '81-100', count: 1 },
|
||||
];
|
||||
|
||||
export const segments = [
|
||||
{ label: 'Sportivi', count: 3, avgStress: 37 },
|
||||
{ label: 'Stress lavorativo', count: 4, avgStress: 69 },
|
||||
{ label: 'Sonno/recupero', count: 3, avgStress: 61 },
|
||||
];
|
||||
|
||||
export const topPerformers = [
|
||||
{ clientId: 'c8', name: 'C. Greco', stress: 30 },
|
||||
{ clientId: 'c2', name: 'A. Neri', stress: 38 },
|
||||
{ clientId: 'c5', name: 'P. Colombo', stress: 44 },
|
||||
];
|
||||
|
||||
export const criticalClients = [
|
||||
{ clientId: 'c7', name: 'D. Marino', stress: 82 },
|
||||
{ clientId: 'c3', name: 'G. Rossi', stress: 78 },
|
||||
{ clientId: 'c6', name: 'E. Ricci', stress: 70 },
|
||||
];
|
||||
|
||||
export const sessions = [
|
||||
{ id: 's1', athleteId: 'c2', date: '2026-07-11', type: 'Corsa', durationMin: 45, load: 62, trimp: 58, hrAvg: 142, dfaA1: 0.72 },
|
||||
{ id: 's2', athleteId: 'c5', date: '2026-07-08', type: 'Ciclismo', durationMin: 90, load: 74, trimp: 96, hrAvg: 138, dfaA1: 0.61 },
|
||||
{ id: 's3', athleteId: 'c8', date: '2026-07-12', type: 'Nuoto', durationMin: 40, load: 50, trimp: 45, hrAvg: 128, dfaA1: 0.8 },
|
||||
{ id: 's4', athleteId: 'c2', date: '2026-07-04', type: 'Pesi', durationMin: 60, load: 68, trimp: 70, hrAvg: 145, dfaA1: 0.55 },
|
||||
{ id: 's5', athleteId: 'c5', date: '2026-07-01', type: 'Corsa', durationMin: 50, load: 58, trimp: 52, hrAvg: 135, dfaA1: 0.75 },
|
||||
];
|
||||
|
||||
export const athletes = [
|
||||
{ clientId: 'c2', name: 'A. Neri', sport: 'Corsa', weeklyLoad: 130 },
|
||||
{ clientId: 'c5', name: 'P. Colombo', sport: 'Ciclismo', weeklyLoad: 164 },
|
||||
{ clientId: 'c8', name: 'C. Greco', sport: 'Nuoto', weeklyLoad: 90 },
|
||||
];
|
||||
|
||||
export const sessionsPerWeek = [
|
||||
{ week: 'S1', count: 4 }, { week: 'S2', count: 5 }, { week: 'S3', count: 3 },
|
||||
{ week: 'S4', count: 6 }, { week: 'S5', count: 4 }, { week: 'S6', count: 5 },
|
||||
{ week: 'S7', count: 2 }, { week: 'S8', count: 5 }, { week: 'S9', count: 6 },
|
||||
{ week: 'S10', count: 4 }, { week: 'S11', count: 5 }, { week: 'S12', count: 3 },
|
||||
];
|
||||
|
||||
// "Oggi" della piattaforma: i dati dimostrativi sono ancorati a questa data, quindi i conteggi
|
||||
// "giorni fa" restano coerenti invece di crescere col passare del tempo reale.
|
||||
export const OGGI = '2026-07-13';
|
||||
|
||||
export function daysSince(date: string, from: string = OGGI): number {
|
||||
return Math.max(0, Math.round((Date.parse(from) - Date.parse(date)) / 86_400_000));
|
||||
}
|
||||
|
||||
export function findClient(id: string): Client | null {
|
||||
return clients.find((c) => c.id === id) ?? null;
|
||||
}
|
||||
|
||||
export const professional = {
|
||||
name: 'Dr. M. Bianchi',
|
||||
role: 'Osteopata',
|
||||
studio: 'Studio Bianchi — Osteopatia e Benessere',
|
||||
email: 'm.bianchi@esempio.it',
|
||||
};
|
||||
Reference in New Issue
Block a user