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,121 @@
|
||||
---
|
||||
// Modulo Sport: carico di lavoro e sessioni degli atleti seguiti. Le tre viste sono già
|
||||
// tutte renderizzate: il cambio scheda è solo un mostra/nascondi, non serve un'isola.
|
||||
import StressIndex from '../../../../layouts/StressIndex.astro';
|
||||
import Card from '../../../../components/stress-index/Card.astro';
|
||||
import Kpi from '../../../../components/stress-index/Kpi.astro';
|
||||
import DataChart from '../../../../components/stress-index/charts/DataChart';
|
||||
import { sessions, athletes, sessionsPerWeek } from '../../../../lib/stress-index/data';
|
||||
export const prerender = false;
|
||||
|
||||
const trimpMedio = Math.round(sessions.reduce((s, x) => s + x.trimp, 0) / sessions.length);
|
||||
const sessioniSettimana = sessionsPerWeek[sessionsPerWeek.length - 1]?.count ?? 0;
|
||||
const nomeAtleta = (id: string) => athletes.find((a) => a.clientId === id)?.name ?? id;
|
||||
const TABS = [
|
||||
{ key: 'dashboard', label: 'Dashboard' },
|
||||
{ key: 'sessioni', label: 'Sessioni' },
|
||||
{ key: 'atleti', label: 'Atleti' },
|
||||
];
|
||||
---
|
||||
<StressIndex title="Sport" crumbs={['Sport']}>
|
||||
<div class="si-head">
|
||||
<h1>Sport</h1>
|
||||
<p>Sessioni, carico e monitoraggio degli atleti</p>
|
||||
</div>
|
||||
|
||||
<div class="si-tabs" role="tablist" id="si-sport-tabs">
|
||||
{TABS.map((t, i) => (
|
||||
<button type="button" role="tab" class="si-tabs__btn" data-tab={t.key} aria-selected={i === 0 ? 'true' : 'false'}>{t.label}</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<section data-panel="dashboard" class="si-stack">
|
||||
<div class="si-grid si-grid--kpi">
|
||||
<Kpi label="Sessioni totali" value={sessions.length} />
|
||||
<Kpi label="Sessioni questa settimana" value={sessioniSettimana} />
|
||||
<Kpi label="TRIMP medio" value={trimpMedio} />
|
||||
<Kpi label="Atleti attivi" value={athletes.length} />
|
||||
</div>
|
||||
|
||||
<Card title="Sessioni per settimana">
|
||||
<DataChart client:visible type="bar" data={sessionsPerWeek} xKey="week" series={[{ key: 'count', label: 'Sessioni' }]} />
|
||||
</Card>
|
||||
|
||||
<Card title="Ultime sessioni">
|
||||
<div class="si-scroll-x">
|
||||
<table class="si-table">
|
||||
<thead><tr><th>Atleta</th><th>Data</th><th>Sport</th><th>Durata</th><th>TRIMP</th><th>HR medio</th><th>DFA A1</th></tr></thead>
|
||||
<tbody>
|
||||
{sessions.map((s) => (
|
||||
<tr>
|
||||
<td><strong>{nomeAtleta(s.athleteId)}</strong></td>
|
||||
<td class="si-table__num">{s.date}</td>
|
||||
<td>{s.type}</td>
|
||||
<td class="si-table__num">{s.durationMin} min</td>
|
||||
<td class="si-table__num">{s.trimp}</td>
|
||||
<td class="si-table__num">{s.hrAvg}</td>
|
||||
<td class="si-table__num">{s.dfaA1}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<section data-panel="sessioni" hidden>
|
||||
<Card title="Tutte le sessioni">
|
||||
<div class="si-scroll-x">
|
||||
<table class="si-table">
|
||||
<thead><tr><th>Atleta</th><th>Data</th><th>Sport</th><th>Durata</th><th>Carico</th><th>TRIMP</th><th>HR medio</th><th>DFA A1</th></tr></thead>
|
||||
<tbody>
|
||||
{sessions.map((s) => (
|
||||
<tr>
|
||||
<td><strong>{nomeAtleta(s.athleteId)}</strong></td>
|
||||
<td class="si-table__num">{s.date}</td>
|
||||
<td>{s.type}</td>
|
||||
<td class="si-table__num">{s.durationMin} min</td>
|
||||
<td class="si-table__num">{s.load}</td>
|
||||
<td class="si-table__num">{s.trimp}</td>
|
||||
<td class="si-table__num">{s.hrAvg}</td>
|
||||
<td class="si-table__num">{s.dfaA1}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<section data-panel="atleti" hidden>
|
||||
<Card title="Atleti monitorati">
|
||||
<table class="si-table">
|
||||
<thead><tr><th>Atleta</th><th>Sport</th><th>Carico settimanale</th></tr></thead>
|
||||
<tbody>
|
||||
{athletes.map((a) => (
|
||||
<tr>
|
||||
<td><a href={`/piattaforme/stress-index/clienti/${a.clientId}`}>{a.name}</a></td>
|
||||
<td>{a.sport}</td>
|
||||
<td class="si-table__num">{a.weeklyLoad}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
</section>
|
||||
</StressIndex>
|
||||
|
||||
<script>
|
||||
// Schede: un solo pannello visibile alla volta, selezione riflessa su aria-selected.
|
||||
const tabs = document.getElementById('si-sport-tabs');
|
||||
if (tabs) {
|
||||
const buttons = Array.from(tabs.querySelectorAll<HTMLButtonElement>('.si-tabs__btn'));
|
||||
const panels = Array.from(document.querySelectorAll<HTMLElement>('[data-panel]'));
|
||||
for (const btn of buttons) {
|
||||
btn.addEventListener('click', () => {
|
||||
for (const b of buttons) b.setAttribute('aria-selected', String(b === btn));
|
||||
for (const p of panels) p.hidden = p.dataset.panel !== btn.dataset.tab;
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user