feat: add UsageBadge to action buttons (Phase 3)
- 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:
parent
405abc1973
commit
c59c71a1c7
|
|
@ -3,6 +3,7 @@ import { Brain, Pencil, Trash2, ChevronDown, ChevronUp, Check, X } from 'lucide-
|
||||||
import { api } from '../utils/api'
|
import { api } from '../utils/api'
|
||||||
import { useAuth } from '../context/AuthContext'
|
import { useAuth } from '../context/AuthContext'
|
||||||
import Markdown from '../utils/Markdown'
|
import Markdown from '../utils/Markdown'
|
||||||
|
import UsageBadge from '../components/UsageBadge'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import 'dayjs/locale/de'
|
import 'dayjs/locale/de'
|
||||||
dayjs.locale('de')
|
dayjs.locale('de')
|
||||||
|
|
@ -114,6 +115,7 @@ export default function Analysis() {
|
||||||
const [tab, setTab] = useState('run')
|
const [tab, setTab] = useState('run')
|
||||||
const [newResult, setNewResult] = useState(null)
|
const [newResult, setNewResult] = useState(null)
|
||||||
const [pipelineLoading, setPipelineLoading] = useState(false)
|
const [pipelineLoading, setPipelineLoading] = useState(false)
|
||||||
|
const [aiUsage, setAiUsage] = useState(null) // Phase 3: Usage badge
|
||||||
|
|
||||||
const loadAll = async () => {
|
const loadAll = async () => {
|
||||||
const [p, i] = await Promise.all([
|
const [p, i] = await Promise.all([
|
||||||
|
|
@ -123,7 +125,15 @@ export default function Analysis() {
|
||||||
setPrompts(Array.isArray(p)?p:[])
|
setPrompts(Array.isArray(p)?p:[])
|
||||||
setAllInsights(Array.isArray(i)?i:[])
|
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 () => {
|
const runPipeline = async () => {
|
||||||
setPipelineLoading(true); setError(null); setNewResult(null)
|
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 className="card" style={{marginBottom:16,borderColor:'var(--accent)',borderWidth:2}}>
|
||||||
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
|
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
|
||||||
<div style={{flex:1}}>
|
<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}}>
|
<div style={{fontSize:12,color:'var(--text2)',marginTop:3,lineHeight:1.5}}>
|
||||||
3 spezialisierte KI-Calls parallel (Körper + Ernährung + Aktivität),
|
3 spezialisierte KI-Calls parallel (Körper + Ernährung + Aktivität),
|
||||||
dann Synthese + Zielabgleich. Detaillierteste Auswertung.
|
dann Synthese + Zielabgleich. Detaillierteste Auswertung.
|
||||||
|
|
@ -282,7 +295,10 @@ export default function Analysis() {
|
||||||
<div key={p.id} className="card section-gap">
|
<div key={p.id} className="card section-gap">
|
||||||
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
|
<div style={{display:'flex',alignItems:'flex-start',gap:12}}>
|
||||||
<div style={{flex:1}}>
|
<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>}
|
{p.description && <div style={{fontSize:12,color:'var(--text3)',marginTop:2}}>{p.description}</div>}
|
||||||
{existing && (
|
{existing && (
|
||||||
<div style={{fontSize:11,color:'var(--text3)',marginTop:3}}>
|
<div style={{fontSize:11,color:'var(--text3)',marginTop:3}}>
|
||||||
|
|
|
||||||
|
|
@ -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 { Save, Download, Upload, Trash2, Plus, Check, Pencil, X, LogOut, Shield, Key, BarChart3 } from 'lucide-react'
|
||||||
import { useProfile } from '../context/ProfileContext'
|
import { useProfile } from '../context/ProfileContext'
|
||||||
import { useAuth } from '../context/AuthContext'
|
import { useAuth } from '../context/AuthContext'
|
||||||
|
|
@ -6,6 +6,7 @@ import { Avatar } from './ProfileSelect'
|
||||||
import { api } from '../utils/api'
|
import { api } from '../utils/api'
|
||||||
import AdminPanel from './AdminPanel'
|
import AdminPanel from './AdminPanel'
|
||||||
import FeatureUsageOverview from '../components/FeatureUsageOverview'
|
import FeatureUsageOverview from '../components/FeatureUsageOverview'
|
||||||
|
import UsageBadge from '../components/UsageBadge'
|
||||||
|
|
||||||
const COLORS = ['#1D9E75','#378ADD','#D85A30','#EF9F27','#7F77DD','#D4537E','#639922','#888780']
|
const COLORS = ['#1D9E75','#378ADD','#D85A30','#EF9F27','#7F77DD','#D4537E','#639922','#888780']
|
||||||
|
|
||||||
|
|
@ -100,6 +101,15 @@ export default function SettingsPage() {
|
||||||
const [pinOpen, setPinOpen] = useState(false)
|
const [pinOpen, setPinOpen] = useState(false)
|
||||||
const [newPin, setNewPin] = useState('')
|
const [newPin, setNewPin] = useState('')
|
||||||
const [pinMsg, setPinMsg] = useState(null)
|
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 () => {
|
const handleLogout = async () => {
|
||||||
if (!confirm('Ausloggen?')) return
|
if (!confirm('Ausloggen?')) return
|
||||||
|
|
@ -372,11 +382,13 @@ export default function SettingsPage() {
|
||||||
<button className="btn btn-primary btn-full"
|
<button className="btn btn-primary btn-full"
|
||||||
onClick={()=>api.exportZip()}>
|
onClick={()=>api.exportZip()}>
|
||||||
<Download size={14}/> ZIP exportieren
|
<Download size={14}/> ZIP exportieren
|
||||||
|
{exportUsage && <UsageBadge {...exportUsage} />}
|
||||||
<span style={{fontSize:11,opacity:0.8,marginLeft:6}}>— je eine CSV pro Kategorie</span>
|
<span style={{fontSize:11,opacity:0.8,marginLeft:6}}>— je eine CSV pro Kategorie</span>
|
||||||
</button>
|
</button>
|
||||||
<button className="btn btn-secondary btn-full"
|
<button className="btn btn-secondary btn-full"
|
||||||
onClick={()=>api.exportJson()}>
|
onClick={()=>api.exportJson()}>
|
||||||
<Download size={14}/> JSON exportieren
|
<Download size={14}/> JSON exportieren
|
||||||
|
{exportUsage && <UsageBadge {...exportUsage} />}
|
||||||
<span style={{fontSize:11,opacity:0.8,marginLeft:6}}>— maschinenlesbar, alles in einer Datei</span>
|
<span style={{fontSize:11,opacity:0.8,marginLeft:6}}>— maschinenlesbar, alles in einer Datei</span>
|
||||||
</button>
|
</button>
|
||||||
</>}
|
</>}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react'
|
||||||
import { Pencil, Trash2, Check, X } from 'lucide-react'
|
import { Pencil, Trash2, Check, X } from 'lucide-react'
|
||||||
import { api } from '../utils/api'
|
import { api } from '../utils/api'
|
||||||
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, ReferenceLine } from 'recharts'
|
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, ReferenceLine } from 'recharts'
|
||||||
|
import UsageBadge from '../components/UsageBadge'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import 'dayjs/locale/de'
|
import 'dayjs/locale/de'
|
||||||
dayjs.locale('de')
|
dayjs.locale('de')
|
||||||
|
|
@ -21,9 +22,18 @@ export default function WeightScreen() {
|
||||||
const [newNote, setNewNote] = useState('')
|
const [newNote, setNewNote] = useState('')
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [saved, setSaved] = 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))
|
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 () => {
|
const handleSave = async () => {
|
||||||
if (!newWeight) return
|
if (!newWeight) return
|
||||||
|
|
@ -59,7 +69,10 @@ export default function WeightScreen() {
|
||||||
|
|
||||||
{/* Eingabe */}
|
{/* Eingabe */}
|
||||||
<div className="card section-gap">
|
<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">
|
<div className="form-row">
|
||||||
<label className="form-label">Datum</label>
|
<label className="form-label">Datum</label>
|
||||||
<input type="date" className="form-input" style={{width:140}}
|
<input type="date" className="form-input" style={{width:140}}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user