mitai-jinkendo/frontend/src/components/QuickWeightEntry.jsx
Lars 3d498d03c1
All checks were successful
Deploy Development / deploy (push) Successful in 48s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s
feat: Enhance dashboard widget configuration and introduce new widgets
- Updated the dashboard layout schema to include new widgets: DashboardGreeting, QuickWeightToday, BodyStatStrip, StatusPills, ProfileGoalsProgress, TrendKcalWeight, NutritionActivitySummary, RecoverySleepRest, and TrainingTypeDistribution.
- Improved widget configuration validation to support new features, including chart days for trend and distribution widgets.
- Refactored the default lab layout to align with the updated widget catalog and ensure proper default activation.
- Bumped app_dashboard version to 1.6.0 to reflect the addition of new widgets and configuration enhancements.
2026-04-07 14:19:45 +02:00

114 lines
3.3 KiB
JavaScript

import { useState, useEffect } from 'react'
import { Check } from 'lucide-react'
import dayjs from 'dayjs'
import { api } from '../utils/api'
/**
* Tagesgewicht erfassen (wie Dashboard „Gewicht heute“).
*/
export default function QuickWeightEntry({ onSaved }) {
const [input, setInput] = useState('')
const [saving, setSaving] = useState(false)
const [saved, setSaved] = useState(false)
const [error, setError] = useState(null)
const [weightUsage, setWeightUsage] = useState(null)
const today = dayjs().format('YYYY-MM-DD')
const loadUsage = () => {
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(() => {
api.weightStats().then((s) => {
if (s?.latest?.date === today) setInput(String(s.latest.weight))
})
loadUsage()
}, [today])
const handleSave = async () => {
const w = parseFloat(input)
if (!w || w < 20 || w > 300) return
setSaving(true)
setError(null)
try {
await api.upsertWeight(today, w)
setSaved(true)
await loadUsage()
onSaved?.()
setTimeout(() => setSaved(false), 2000)
} catch (err) {
console.error('Save failed:', err)
setError(err.message || 'Fehler beim Speichern')
setTimeout(() => setError(null), 5000)
} finally {
setSaving(false)
}
}
const isDisabled = saving || !input || (weightUsage && !weightUsage.allowed)
const tooltipText =
weightUsage && !weightUsage.allowed
? `Limit erreicht (${weightUsage.used}/${weightUsage.limit}). Kontaktiere den Admin oder warte bis zum nächsten Reset.`
: ''
return (
<div>
{error && (
<div
style={{
padding: '8px 10px',
background: 'var(--danger-bg)',
border: '1px solid var(--danger)',
borderRadius: 8,
fontSize: 12,
color: 'var(--danger)',
marginBottom: 8,
}}
>
{error}
</div>
)}
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<input
type="number"
min={20}
max={300}
step={0.1}
className="form-input"
style={{ flex: 1, fontSize: 17, fontWeight: 600, textAlign: 'center' }}
placeholder="kg eingeben"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && !isDisabled && handleSave()}
/>
<span style={{ fontSize: 13, color: 'var(--text3)' }}>kg</span>
<div title={tooltipText} style={{ display: 'inline-block' }}>
<button
type="button"
className="btn btn-primary"
style={{ padding: '8px 14px', cursor: isDisabled ? 'not-allowed' : 'pointer' }}
onClick={handleSave}
disabled={isDisabled}
>
{saved ? (
<Check size={15} />
) : saving ? (
<div className="spinner" style={{ width: 14, height: 14 }} />
) : weightUsage && !weightUsage.allowed ? (
'🔒 Limit'
) : (
'Speichern'
)}
</button>
</div>
</div>
</div>
)
}