refactor: mehrstufiger Quality-Filter statt Toggle (#24)
All checks were successful
Deploy Development / deploy (push) Successful in 51s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 14s

Statt einfachem On/Off Toggle jetzt 4 Qualitätsstufen:
- 📊 Alle (kein Filter)
- ✓ Hochwertig (excellent + good + acceptable)
- ✓✓ Sehr gut (excellent + good)
-  Exzellent (nur excellent)

UI:
- Button-Group (Segmented Control) mit 4 Stufen
- Beschreibung welche Labels inkludiert werden
- Anzeige: X von Y Aktivitäten (wenn gefiltert)

User-Feedback: Stufenweiser Filter ist flexibler als binärer Toggle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-23 22:04:29 +01:00
parent 9ec774e956
commit 848ba0a815

View File

@ -588,16 +588,22 @@ function NutritionSection({ nutrition, weights, profile, insights, onRequest, lo
// Activity Section // Activity Section
function ActivitySection({ activities, insights, onRequest, loadingSlug, filterActiveSlugs }) { function ActivitySection({ activities, insights, onRequest, loadingSlug, filterActiveSlugs }) {
const [period, setPeriod] = useState(30) const [period, setPeriod] = useState(30)
const [qualityFilter, setQualityFilter] = useState(false) // Issue #24: Quality-Filter Toggle const [qualityLevel, setQualityLevel] = useState('all') // Issue #24: Quality-Filter (all, quality, very_good, excellent)
if (!activities?.length) return ( if (!activities?.length) return (
<EmptySection text="Noch keine Aktivitätsdaten." to="/activity" toLabel="Aktivität erfassen"/> <EmptySection text="Noch keine Aktivitätsdaten." to="/activity" toLabel="Aktivität erfassen"/>
) )
const cutoff = dayjs().subtract(period,'day').format('YYYY-MM-DD') const cutoff = dayjs().subtract(period,'day').format('YYYY-MM-DD')
// Issue #24: Filter nach Datum UND Quality-Label
const filtA = activities.filter(d => // Issue #24: Mehrstufiger Quality-Filter
(period===9999 || d.date>=cutoff) && const filtA = activities.filter(d => {
(!qualityFilter || ['excellent', 'good', 'acceptable'].includes(d.quality_label)) if (period !== 9999 && d.date < cutoff) return false
)
if (qualityLevel === 'all') return true
if (qualityLevel === 'quality') return ['excellent', 'good', 'acceptable'].includes(d.quality_label)
if (qualityLevel === 'very_good') return ['excellent', 'good'].includes(d.quality_label)
if (qualityLevel === 'excellent') return d.quality_label === 'excellent'
return true
})
const byDate={} const byDate={}
filtA.forEach(a=>{ byDate[a.date]=(byDate[a.date]||0)+(a.kcal_active||0) }) filtA.forEach(a=>{ byDate[a.date]=(byDate[a.date]||0)+(a.kcal_active||0) })
@ -626,20 +632,34 @@ function ActivitySection({ activities, insights, onRequest, loadingSlug, filterA
<SectionHeader title="🏋️ Aktivität" to="/activity" toLabel="Alle Einträge" lastUpdated={activities[0]?.date}/> <SectionHeader title="🏋️ Aktivität" to="/activity" toLabel="Alle Einträge" lastUpdated={activities[0]?.date}/>
<PeriodSelector value={period} onChange={setPeriod}/> <PeriodSelector value={period} onChange={setPeriod}/>
{/* Issue #24: Quality-Filter Toggle */} {/* Issue #24: Mehrstufiger Quality-Filter */}
<div style={{marginBottom:12,display:'flex',alignItems:'center',gap:8}}> <div style={{marginBottom:12}}>
<label style={{display:'flex',alignItems:'center',gap:6,fontSize:12,cursor:'pointer', <div style={{fontSize:11,fontWeight:600,color:'var(--text3)',marginBottom:6}}>QUALITÄTSFILTER</div>
padding:'6px 10px',borderRadius:8,background:qualityFilter?'var(--accent-light)':'var(--surface2)', <div style={{display:'flex',gap:4}}>
border:`1px solid ${qualityFilter?'var(--accent)':'var(--border)'}`, {[
color:qualityFilter?'var(--accent)':'var(--text2)',transition:'all 0.2s'}}> {v:'all', l:'Alle', icon:'📊'},
<input type="checkbox" checked={qualityFilter} onChange={e=>setQualityFilter(e.target.checked)} {v:'quality', l:'Hochwertig', icon:'✓'},
style={{width:14,height:14,cursor:'pointer',accentColor:'var(--accent)'}}/> {v:'very_good', l:'Sehr gut', icon:'✓✓'},
<span style={{fontWeight:qualityFilter?600:400}}>Nur qualitativ hochwertige Aktivitäten</span> {v:'excellent', l:'Exzellent', icon:'⭐'}
</label> ].map(o => (
{qualityFilter && ( <button key={o.v} onClick={() => setQualityLevel(o.v)}
<span style={{fontSize:11,color:'var(--text3)'}}> style={{flex:1,padding:'6px 8px',borderRadius:8,fontSize:11,fontWeight:500,
({filtA.length} von {activities.filter(d=>period===9999||d.date>=cutoff).length}) border:'1.5px solid',cursor:'pointer',fontFamily:'var(--font)',transition:'all 0.2s',
</span> background:qualityLevel===o.v?'var(--accent)':'transparent',
borderColor:qualityLevel===o.v?'var(--accent)':'var(--border2)',
color:qualityLevel===o.v?'white':'var(--text2)'}}>
<span style={{marginRight:4}}>{o.icon}</span>
{o.l}
</button>
))}
</div>
{qualityLevel !== 'all' && (
<div style={{fontSize:10,color:'var(--text3)',marginTop:6}}>
{filtA.length} von {activities.filter(d=>period===9999||d.date>=cutoff).length} Aktivitäten
{qualityLevel === 'quality' && ' (excellent, good, acceptable)'}
{qualityLevel === 'very_good' && ' (excellent, good)'}
{qualityLevel === 'excellent' && ' (nur excellent)'}
</div>
)} )}
</div> </div>