feat: add UsageBadge to action buttons (Phase 3)
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 12s

- Weight page: badge on "Eintrag hinzufügen" heading
- Settings: badges on export buttons (ZIP/JSON)
- Analysis: badges on pipeline and individual analysis titles
- Shows real-time usage status (e.g., "7/5" with red color)

Phase 3: Frontend Display complete

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-21 06:43:10 +01:00
parent 405abc1973
commit c59c71a1c7
3 changed files with 47 additions and 6 deletions

View File

@ -3,6 +3,7 @@ import { Brain, Pencil, Trash2, ChevronDown, ChevronUp, Check, X } from 'lucide-
import { api } from '../utils/api'
import { useAuth } from '../context/AuthContext'
import Markdown from '../utils/Markdown'
import UsageBadge from '../components/UsageBadge'
import dayjs from 'dayjs'
import 'dayjs/locale/de'
dayjs.locale('de')
@ -114,6 +115,7 @@ export default function Analysis() {
const [tab, setTab] = useState('run')
const [newResult, setNewResult] = useState(null)
const [pipelineLoading, setPipelineLoading] = useState(false)
const [aiUsage, setAiUsage] = useState(null) // Phase 3: Usage badge
const loadAll = async () => {
const [p, i] = await Promise.all([
@ -123,7 +125,15 @@ export default function Analysis() {
setPrompts(Array.isArray(p)?p:[])
setAllInsights(Array.isArray(i)?i:[])
}
useEffect(()=>{ loadAll() },[])
useEffect(()=>{
loadAll()
// Load feature usage for badges
api.getFeatureUsage().then(features => {
const aiFeature = features.find(f => f.feature_id === 'ai_calls')
setAiUsage(aiFeature)
}).catch(err => console.error('Failed to load usage:', err))
},[])
const runPipeline = async () => {
setPipelineLoading(true); setError(null); setNewResult(null)
@ -230,7 +240,10 @@ export default function Analysis() {
<div className="card" style={{marginBottom:16,borderColor:'var(--accent)',borderWidth:2}}>
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
<div style={{flex:1}}>
<div style={{fontWeight:700,fontSize:15,color:'var(--accent)'}}>🔬 Mehrstufige Gesamtanalyse</div>
<div style={{fontWeight:700,fontSize:15,color:'var(--accent)'}}>
🔬 Mehrstufige Gesamtanalyse
{aiUsage && <UsageBadge {...aiUsage} />}
</div>
<div style={{fontSize:12,color:'var(--text2)',marginTop:3,lineHeight:1.5}}>
3 spezialisierte KI-Calls parallel (Körper + Ernährung + Aktivität),
dann Synthese + Zielabgleich. Detaillierteste Auswertung.
@ -282,7 +295,10 @@ export default function Analysis() {
<div key={p.id} className="card section-gap">
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
<div style={{flex:1}}>
<div style={{fontWeight:600,fontSize:15}}>{SLUG_LABELS[p.slug]||p.name}</div>
<div style={{fontWeight:600,fontSize:15}}>
{SLUG_LABELS[p.slug]||p.name}
{aiUsage && <UsageBadge {...aiUsage} />}
</div>
{p.description && <div style={{fontSize:12,color:'var(--text3)',marginTop:2}}>{p.description}</div>}
{existing && (
<div style={{fontSize:11,color:'var(--text3)',marginTop:3}}>

View File

@ -1,4 +1,4 @@
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { Save, Download, Upload, Trash2, Plus, Check, Pencil, X, LogOut, Shield, Key, BarChart3 } from 'lucide-react'
import { useProfile } from '../context/ProfileContext'
import { useAuth } from '../context/AuthContext'
@ -6,6 +6,7 @@ import { Avatar } from './ProfileSelect'
import { api } from '../utils/api'
import AdminPanel from './AdminPanel'
import FeatureUsageOverview from '../components/FeatureUsageOverview'
import UsageBadge from '../components/UsageBadge'
const COLORS = ['#1D9E75','#378ADD','#D85A30','#EF9F27','#7F77DD','#D4537E','#639922','#888780']
@ -100,6 +101,15 @@ export default function SettingsPage() {
const [pinOpen, setPinOpen] = useState(false)
const [newPin, setNewPin] = useState('')
const [pinMsg, setPinMsg] = useState(null)
const [exportUsage, setExportUsage] = useState(null) // Phase 3: Usage badge
// Load feature usage for export badges
useEffect(() => {
api.getFeatureUsage().then(features => {
const exportFeature = features.find(f => f.feature_id === 'data_export')
setExportUsage(exportFeature)
}).catch(err => console.error('Failed to load usage:', err))
}, [])
const handleLogout = async () => {
if (!confirm('Ausloggen?')) return
@ -372,11 +382,13 @@ export default function SettingsPage() {
<button className="btn btn-primary btn-full"
onClick={()=>api.exportZip()}>
<Download size={14}/> ZIP exportieren
{exportUsage && <UsageBadge {...exportUsage} />}
<span style={{fontSize:11,opacity:0.8,marginLeft:6}}> je eine CSV pro Kategorie</span>
</button>
<button className="btn btn-secondary btn-full"
onClick={()=>api.exportJson()}>
<Download size={14}/> JSON exportieren
{exportUsage && <UsageBadge {...exportUsage} />}
<span style={{fontSize:11,opacity:0.8,marginLeft:6}}> maschinenlesbar, alles in einer Datei</span>
</button>
</>}

View File

@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react'
import { Pencil, Trash2, Check, X } from 'lucide-react'
import { api } from '../utils/api'
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, ReferenceLine } from 'recharts'
import UsageBadge from '../components/UsageBadge'
import dayjs from 'dayjs'
import 'dayjs/locale/de'
dayjs.locale('de')
@ -21,9 +22,18 @@ export default function WeightScreen() {
const [newNote, setNewNote] = useState('')
const [saving, setSaving] = useState(false)
const [saved, setSaved] = useState(false)
const [weightUsage, setWeightUsage] = useState(null) // Phase 3: Usage badge
const load = () => api.listWeight(365).then(data => setEntries(data))
useEffect(()=>{ load() },[])
useEffect(()=>{
load()
// Load feature usage for badge
api.getFeatureUsage().then(features => {
const weightFeature = features.find(f => f.feature_id === 'weight_entries')
setWeightUsage(weightFeature)
}).catch(err => console.error('Failed to load usage:', err))
},[])
const handleSave = async () => {
if (!newWeight) return
@ -59,7 +69,10 @@ export default function WeightScreen() {
{/* Eingabe */}
<div className="card section-gap">
<div className="card-title">Eintrag hinzufügen</div>
<div className="card-title">
Eintrag hinzufügen
{weightUsage && <UsageBadge {...weightUsage} />}
</div>
<div className="form-row">
<label className="form-label">Datum</label>
<input type="date" className="form-input" style={{width:140}}