diff --git a/frontend/src/pages/CaliperScreen.jsx b/frontend/src/pages/CaliperScreen.jsx
index f81bf2e..8940d8c 100644
--- a/frontend/src/pages/CaliperScreen.jsx
+++ b/frontend/src/pages/CaliperScreen.jsx
@@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom'
import { api } from '../utils/api'
import { calcBodyFat, getBfCategory, METHOD_POINTS } from '../utils/calc'
import { CALIPER_POINTS, CALIPER_METHODS } from '../utils/guideData'
+import UsageBadge from '../components/UsageBadge'
import dayjs from 'dayjs'
function emptyForm() {
@@ -15,7 +16,7 @@ function emptyForm() {
}
}
-function CaliperForm({ form, setForm, profile, onSave, onCancel, saveLabel='Speichern' }) {
+function CaliperForm({ form, setForm, profile, onSave, onCancel, saveLabel='Speichern', saving=false, error=null, usage=null }) {
const sex = profile?.sex||'m'
const age = profile?.dob ? Math.floor((Date.now()-new Date(profile.dob))/(365.25*24*3600*1000)) : 30
const weight = form.weight || 80
@@ -65,8 +66,21 @@ function CaliperForm({ form, setForm, profile, onSave, onCancel, saveLabel='Spei
set('notes',e.target.value)}/>
+ {error && (
+
+ {error}
+
+ )}
-
+
{onCancel && }
@@ -78,12 +92,26 @@ export default function CaliperScreen() {
const [profile, setProfile] = useState(null)
const [form, setForm] = useState(emptyForm())
const [editing, setEditing] = useState(null)
+ const [saving, setSaving] = useState(false)
const [saved, setSaved] = useState(false)
+ const [error, setError] = useState(null)
+ const [caliperUsage, setCaliperUsage] = useState(null) // Phase 4: Usage badge
const nav = useNavigate()
const load = () => Promise.all([api.listCaliper(), api.getProfile()])
.then(([e,p])=>{ setEntries(e); setProfile(p) })
- useEffect(()=>{ load() },[])
+
+ const loadUsage = () => {
+ api.getFeatureUsage().then(features => {
+ const caliperFeature = features.find(f => f.feature_id === 'caliper_entries')
+ setCaliperUsage(caliperFeature)
+ }).catch(err => console.error('Failed to load usage:', err))
+ }
+
+ useEffect(()=>{
+ load()
+ loadUsage()
+ },[])
const buildPayload = (f, bfPct, sex) => {
const weight = profile?.weight || null
@@ -97,11 +125,23 @@ export default function CaliperScreen() {
}
const handleSave = async (bfPct, sex) => {
- const payload = buildPayload(form, bfPct, sex)
- await api.upsertCaliper(payload)
- setSaved(true); await load()
- setTimeout(()=>setSaved(false),2000)
- setForm(emptyForm())
+ setSaving(true)
+ setError(null)
+ try {
+ const payload = buildPayload(form, bfPct, sex)
+ await api.upsertCaliper(payload)
+ setSaved(true)
+ await load()
+ await loadUsage() // Reload usage after save
+ setTimeout(()=>setSaved(false),2000)
+ setForm(emptyForm())
+ } catch (err) {
+ console.error('Save failed:', err)
+ setError(err.message || 'Fehler beim Speichern')
+ setTimeout(()=>setError(null), 5000)
+ } finally {
+ setSaving(false)
+ }
}
const handleUpdate = async (bfPct, sex) => {
@@ -125,9 +165,13 @@ export default function CaliperScreen() {
-
Neue Messung
+
+ Neue Messung
+ {caliperUsage && }
+
+ onSave={handleSave} saveLabel={saved?'✓ Gespeichert!':'Speichern'}
+ saving={saving} error={error} usage={caliperUsage}/>
diff --git a/frontend/src/pages/CircumScreen.jsx b/frontend/src/pages/CircumScreen.jsx
index 22b37e4..8d876b8 100644
--- a/frontend/src/pages/CircumScreen.jsx
+++ b/frontend/src/pages/CircumScreen.jsx
@@ -3,6 +3,7 @@ import { Pencil, Trash2, Check, X, Camera, BookOpen } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { api } from '../utils/api'
import { CIRCUMFERENCE_POINTS } from '../utils/guideData'
+import UsageBadge from '../components/UsageBadge'
import dayjs from 'dayjs'
const FIELDS = ['c_neck','c_chest','c_waist','c_belly','c_hip','c_thigh','c_calf','c_arm']
@@ -16,18 +17,32 @@ export default function CircumScreen() {
const [editing, setEditing] = useState(null)
const [saving, setSaving] = useState(false)
const [saved, setSaved] = useState(false)
+ const [error, setError] = useState(null)
const [photoFile, setPhotoFile] = useState(null)
const [photoPreview, setPhotoPreview] = useState(null)
+ const [circumUsage, setCircumUsage] = useState(null) // Phase 4: Usage badge
const fileRef = useRef()
const nav = useNavigate()
const load = () => api.listCirc().then(setEntries)
- useEffect(()=>{ load() },[])
+
+ const loadUsage = () => {
+ api.getFeatureUsage().then(features => {
+ const circumFeature = features.find(f => f.feature_id === 'circumference_entries')
+ setCircumUsage(circumFeature)
+ }).catch(err => console.error('Failed to load usage:', err))
+ }
+
+ useEffect(()=>{
+ load()
+ loadUsage()
+ },[])
const set = (k,v) => setForm(f=>({...f,[k]:v}))
const handleSave = async () => {
setSaving(true)
+ setError(null)
try {
const payload = {}
payload.date = form.date
@@ -38,10 +53,18 @@ export default function CircumScreen() {
payload.photo_id = pr.id
}
await api.upsertCirc(payload)
- setSaved(true); await load()
+ setSaved(true)
+ await load()
+ await loadUsage() // Reload usage after save
setTimeout(()=>setSaved(false),2000)
setForm(empty()); setPhotoFile(null); setPhotoPreview(null)
- } finally { setSaving(false) }
+ } catch (err) {
+ console.error('Save failed:', err)
+ setError(err.message || 'Fehler beim Speichern')
+ setTimeout(()=>setError(null), 5000)
+ } finally {
+ setSaving(false)
+ }
}
const startEdit = (e) => setEditing({...e})
@@ -72,7 +95,10 @@ export default function CircumScreen() {
{/* Eingabe */}
-
Neue Messung
+
+ Neue Messung
+ {circumUsage && }
+
set('date',e.target.value)}/>
@@ -99,8 +125,22 @@ export default function CircumScreen() {
-
diff --git a/frontend/src/pages/WeightScreen.jsx b/frontend/src/pages/WeightScreen.jsx
index ffdfdcf..d6d2e13 100644
--- a/frontend/src/pages/WeightScreen.jsx
+++ b/frontend/src/pages/WeightScreen.jsx
@@ -22,28 +22,42 @@ export default function WeightScreen() {
const [newNote, setNewNote] = useState('')
const [saving, setSaving] = useState(false)
const [saved, setSaved] = useState(false)
+ const [error, setError] = useState(null)
const [weightUsage, setWeightUsage] = useState(null) // Phase 3: Usage badge
const load = () => api.listWeight(365).then(data => setEntries(data))
- useEffect(()=>{
- load()
+ const loadUsage = () => {
// 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))
+ }
+
+ useEffect(()=>{
+ load()
+ loadUsage()
},[])
const handleSave = async () => {
if (!newWeight) return
setSaving(true)
+ setError(null)
try {
await api.upsertWeight(newDate, parseFloat(newWeight), newNote)
- setSaved(true); await load()
+ setSaved(true)
+ await load()
+ await loadUsage() // Reload usage after save
setTimeout(()=>setSaved(false), 2000)
setNewWeight(''); setNewNote('')
- } finally { setSaving(false) }
+ } catch (err) {
+ console.error('Save failed:', err)
+ setError(err.message || 'Fehler beim Speichern')
+ setTimeout(()=>setError(null), 5000)
+ } finally {
+ setSaving(false)
+ }
}
const handleUpdate = async () => {
@@ -92,9 +106,21 @@ export default function WeightScreen() {
value={newNote} onChange={e=>setNewNote(e.target.value)}/>
-
+ {error && (
+
+ {error}
+
+ )}
+
{saved ? <> Gespeichert!>
: saving ? <> …>
+ : (weightUsage && !weightUsage.allowed) ? '🔒 Limit erreicht'
: 'Speichern'}