feat(v9d): integrate training type UI components
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s

Phase 1b - UI Integration:
===========================

ActivityPage:
- Replace old activity type dropdown with TrainingTypeSelect
- Add training_type_id, training_category, training_subcategory to form
- Two-level selection (category → subcategory)

Dashboard:
- Add TrainingTypeDistribution card (pie chart)
- Shows last 28 days activity distribution by type
- Conditional rendering (only if activities exist)

Still TODO:
- History: Add type badge display (next commit)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-21 14:56:11 +01:00
parent df01ee3de3
commit 08cead49fe
2 changed files with 33 additions and 6 deletions

View File

@ -3,6 +3,7 @@ import { Upload, Pencil, Trash2, Check, X, CheckCircle } from 'lucide-react'
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from 'recharts'
import { api } from '../utils/api'
import UsageBadge from '../components/UsageBadge'
import TrainingTypeSelect from '../components/TrainingTypeSelect'
import dayjs from 'dayjs'
import 'dayjs/locale/de'
dayjs.locale('de')
@ -18,7 +19,10 @@ function empty() {
date: dayjs().format('YYYY-MM-DD'),
activity_type: 'Traditionelles Krafttraining',
duration_min: '', kcal_active: '',
hr_avg: '', hr_max: '', rpe: '', notes: ''
hr_avg: '', hr_max: '', rpe: '', notes: '',
training_type_id: null,
training_category: null,
training_subcategory: null
}
}
@ -89,11 +93,19 @@ function EntryForm({ form, setForm, onSave, onCancel, saveLabel='Speichern', sav
<input type="date" className="form-input" style={{width:140}} value={form.date} onChange={e=>set('date',e.target.value)}/>
<span className="form-unit"/>
</div>
<div className="form-row">
<label className="form-label">Trainingsart</label>
<select className="form-select" value={form.activity_type} onChange={e=>set('activity_type',e.target.value)}>
{ACTIVITY_TYPES.map(t=><option key={t} value={t}>{t}</option>)}
</select>
<div style={{marginBottom:12}}>
<TrainingTypeSelect
value={form.training_type_id}
onChange={(typeId, category, subcategory) => {
setForm(f => ({
...f,
training_type_id: typeId,
training_category: category,
training_subcategory: subcategory
}))
}}
required={false}
/>
</div>
<div className="form-row">
<label className="form-label">Dauer</label>

View File

@ -10,6 +10,7 @@ import { useProfile } from '../context/ProfileContext'
import { getBfCategory } from '../utils/calc'
import TrialBanner from '../components/TrialBanner'
import EmailVerificationBanner from '../components/EmailVerificationBanner'
import TrainingTypeDistribution from '../components/TrainingTypeDistribution'
import { getInterpretation, getStatusColor, getStatusBg } from '../utils/interpret'
import Markdown from '../utils/Markdown'
import dayjs from 'dayjs'
@ -470,6 +471,20 @@ export default function Dashboard() {
)}
</div>
{/* Training Type Distribution */}
{activities.length > 0 && (
<div className="card section-gap" style={{marginBottom:16}}>
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:12}}>
<div style={{fontWeight:600,fontSize:13}}>🏋 Trainingstyp-Verteilung</div>
<button style={{background:'none',border:'none',fontSize:12,color:'var(--accent)',cursor:'pointer'}}
onClick={()=>nav('/activity')}>
Details
</button>
</div>
<TrainingTypeDistribution days={28} />
</div>
)}
{/* Latest AI insight */}
<div className="card section-gap">
<div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:8}}>