mitai-jinkendo/frontend/src/pages/GoalsPage.jsx
Lars 92cc309489
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s
feat: relative weight sliders for focus areas
Improved UX for focus area configuration:
- Sliders now use relative weights (0-10) instead of percentages
- System automatically normalizes to percentages (sum=100%)
- Live preview shows "weight → percent%" (e.g., "5 → 50%")
- No more manual balancing required from user

User sets: Kraft=5, Ausdauer=3, Flexibilität=2
System calculates: 50%, 30%, 20%

Addresses user feedback: "Summe muss 100% sein" not user-friendly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 12:10:56 +01:00

843 lines
30 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react'
import { Target, Plus, Pencil, Trash2, TrendingUp, Calendar } from 'lucide-react'
import { api } from '../utils/api'
import dayjs from 'dayjs'
import 'dayjs/locale/de'
dayjs.locale('de')
// Goal Mode Definitions
const GOAL_MODES = [
{
id: 'weight_loss',
icon: '📉',
label: 'Gewichtsreduktion',
description: 'Kaloriendefizit, Fettabbau',
color: '#D85A30'
},
{
id: 'strength',
icon: '💪',
label: 'Kraftaufbau',
description: 'Muskelwachstum, progressive Belastung',
color: '#378ADD'
},
{
id: 'endurance',
icon: '🏃',
label: 'Ausdauer',
description: 'VO2Max, aerobe Kapazität',
color: '#1D9E75'
},
{
id: 'recomposition',
icon: '⚖️',
label: 'Körperkomposition',
description: 'Gleichzeitig Fett ab- & Muskeln aufbauen',
color: '#7B68EE'
},
{
id: 'health',
icon: '❤️',
label: 'Allgemeine Gesundheit',
description: 'Ausgewogen, präventiv',
color: '#E67E22'
}
]
export default function GoalsPage() {
const [goalMode, setGoalMode] = useState(null)
const [focusAreas, setFocusAreas] = useState(null)
const [focusEditing, setFocusEditing] = useState(false)
const [focusTemp, setFocusTemp] = useState({
weight_loss_pct: 0,
muscle_gain_pct: 0,
strength_pct: 0,
endurance_pct: 0,
flexibility_pct: 0,
health_pct: 0
})
const [goals, setGoals] = useState([])
const [goalTypes, setGoalTypes] = useState([]) // Dynamic from DB (Phase 1.5)
const [goalTypesMap, setGoalTypesMap] = useState({}) // For quick lookup
const [showGoalForm, setShowGoalForm] = useState(false)
const [editingGoal, setEditingGoal] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [toast, setToast] = useState(null)
// Form state
const [formData, setFormData] = useState({
goal_type: 'weight',
is_primary: false,
target_value: '',
unit: 'kg',
target_date: '',
name: '',
description: ''
})
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
setLoading(true)
setError(null)
try {
const [modeData, goalsData, typesData, focusData] = await Promise.all([
api.getGoalMode(),
api.listGoals(),
api.listGoalTypeDefinitions(), // Phase 1.5: Load from DB
api.getFocusAreas() // v2.0: Load focus areas
])
setGoalMode(modeData.goal_mode)
setGoals(goalsData)
setFocusAreas(focusData)
setFocusTemp(focusData) // Initialize temp state
// Convert types array to map for quick lookup
const typesMap = {}
if (typesData && Array.isArray(typesData)) {
typesData.forEach(type => {
typesMap[type.type_key] = {
label: type.label_de,
unit: type.unit,
icon: type.icon || '📊',
category: type.category,
is_system: type.is_system
}
})
}
setGoalTypes(typesData || [])
setGoalTypesMap(typesMap)
} catch (err) {
console.error('Failed to load goals:', err)
setError(`Fehler beim Laden: ${err.message || err.toString()}`)
} finally {
setLoading(false)
}
}
const showToast = (message, duration = 2000) => {
setToast(message)
setTimeout(() => setToast(null), duration)
}
const handleGoalModeChange = async (newMode) => {
try {
await api.updateGoalMode(newMode)
setGoalMode(newMode)
showToast('✓ Trainingsmodus aktualisiert')
} catch (err) {
console.error('Failed to update goal mode:', err)
setError('Fehler beim Aktualisieren des Trainingsmodus')
}
}
const handleCreateGoal = () => {
if (goalTypes.length === 0) {
setError('Keine Goal Types verfügbar. Bitte Admin kontaktieren.')
return
}
setEditingGoal(null)
const firstType = goalTypes[0].type_key
setFormData({
goal_type: firstType,
is_primary: goals.length === 0, // First goal is primary by default
target_value: '',
unit: goalTypesMap[firstType]?.unit || 'kg',
target_date: '',
name: '',
description: ''
})
setShowGoalForm(true)
}
const handleEditGoal = (goal) => {
setEditingGoal(goal.id)
setFormData({
goal_type: goal.goal_type,
is_primary: goal.is_primary,
target_value: goal.target_value,
unit: goal.unit,
target_date: goal.target_date || '',
name: goal.name || '',
description: goal.description || ''
})
setShowGoalForm(true)
}
const handleGoalTypeChange = (type) => {
setFormData(f => ({
...f,
goal_type: type,
unit: goalTypesMap[type]?.unit || 'unit'
}))
}
const handleSaveGoal = async () => {
if (!formData.target_value) {
setError('Bitte Zielwert eingeben')
return
}
try {
const data = {
goal_type: formData.goal_type,
is_primary: formData.is_primary,
target_value: parseFloat(formData.target_value),
unit: formData.unit,
target_date: formData.target_date || null,
name: formData.name || null,
description: formData.description || null
}
console.log('[DEBUG] Saving goal:', { editingGoal, data })
if (editingGoal) {
await api.updateGoal(editingGoal, data)
showToast('✓ Ziel aktualisiert')
} else {
await api.createGoal(data)
showToast('✓ Ziel erstellt')
}
await loadData()
setShowGoalForm(false)
setEditingGoal(null)
} catch (err) {
console.error('Failed to save goal:', err)
setError(err.message || 'Fehler beim Speichern')
}
}
const handleDeleteGoal = async (goalId) => {
if (!confirm('Ziel wirklich löschen?')) return
try {
await api.deleteGoal(goalId)
showToast('✓ Ziel gelöscht')
await loadData()
} catch (err) {
console.error('Failed to delete goal:', err)
setError('Fehler beim Löschen')
}
}
const getProgressColor = (progress) => {
if (progress >= 100) return 'var(--accent)'
if (progress >= 75) return '#1D9E75'
if (progress >= 50) return '#378ADD'
if (progress >= 25) return '#E67E22'
return '#D85A30'
}
if (loading) {
return (
<div className="page">
<div style={{ textAlign: 'center', padding: '40px' }}>
<div className="spinner"></div>
</div>
</div>
)
}
return (
<div className="page">
<div className="page-header">
<h1><Target size={24} /> Ziele</h1>
</div>
{error && (
<div className="card" style={{ background: '#FEF2F2', border: '1px solid #FCA5A5', marginBottom: 16 }}>
<p style={{ color: '#DC2626', margin: 0 }}>{error}</p>
</div>
)}
{toast && (
<div style={{
position: 'fixed',
top: 16,
right: 16,
background: 'var(--accent)',
color: 'white',
padding: '12px 20px',
borderRadius: 8,
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
zIndex: 1000
}}>
{toast}
</div>
)}
{/* Focus Areas (v2.0) */}
<div className="card" style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<h2 style={{ margin: 0 }}>🎯 Fokus-Bereiche</h2>
{!focusEditing && focusAreas && (
<button
className="btn-secondary"
onClick={() => setFocusEditing(true)}
style={{ padding: '6px 12px' }}
>
<Pencil size={14} /> Anpassen
</button>
)}
</div>
<p style={{ color: 'var(--text2)', fontSize: 14, marginBottom: 16 }}>
Setze relative Gewichte für deine Trainingsziele. Das System berechnet automatisch die Prozentanteile.
{focusAreas && !focusAreas.custom && (
<span style={{ display: 'block', marginTop: 4, fontStyle: 'italic' }}>
Aktuell abgeleitet aus Trainingsmodus "{goalMode}" - klicke "Anpassen" für individuelle Gewichtung
</span>
)}
</p>
{focusEditing ? (
<>
{/* Sliders */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 20, marginBottom: 20 }}>
{[
{ key: 'weight_loss_pct', label: 'Fettabbau', icon: '📉', color: '#D85A30' },
{ key: 'muscle_gain_pct', label: 'Muskelaufbau', icon: '💪', color: '#378ADD' },
{ key: 'strength_pct', label: 'Kraftsteigerung', icon: '🏋️', color: '#7B68EE' },
{ key: 'endurance_pct', label: 'Ausdauer', icon: '🏃', color: '#1D9E75' },
{ key: 'flexibility_pct', label: 'Beweglichkeit', icon: '🤸', color: '#E67E22' },
{ key: 'health_pct', label: 'Gesundheit', icon: '❤️', color: '#F59E0B' }
].map(area => {
const weight = Math.round(focusTemp[area.key] / 10)
const sum = Object.values(focusTemp).reduce((a, b) => a + b, 0)
const actualPercent = sum > 0 ? Math.round(focusTemp[area.key] / sum * 100) : 0
return (
<div key={area.key}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontSize: 20 }}>{area.icon}</span>
<span style={{ fontWeight: 500 }}>{area.label}</span>
</div>
<span style={{
fontSize: 16,
fontWeight: 600,
color: area.color,
minWidth: 80,
textAlign: 'right'
}}>
{weight} {actualPercent}%
</span>
</div>
<input
type="range"
min="0"
max="10"
step="1"
value={weight}
onChange={e => setFocusTemp(f => ({ ...f, [area.key]: parseInt(e.target.value) * 10 }))}
style={{
width: '100%',
height: 8,
borderRadius: 4,
background: `linear-gradient(to right, ${area.color} 0%, ${area.color} ${weight * 10}%, var(--border) ${weight * 10}%, var(--border) 100%)`,
outline: 'none',
cursor: 'pointer'
}}
/>
</div>
)
})}
</div>
{/* Weight Total Display */}
<div style={{
padding: 12,
background: 'var(--surface2)',
border: '1px solid var(--border)',
borderRadius: 8,
marginBottom: 16
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontWeight: 600, color: 'var(--text2)' }}>
Gewichtung gesamt:
</span>
<span style={{ fontSize: 18, fontWeight: 600, color: 'var(--text1)' }}>
{Object.values(focusTemp).reduce((a, b) => a + b, 0) / 10}
</span>
</div>
<div style={{ fontSize: 12, marginTop: 4, color: 'var(--text3)' }}>
💡 Die Prozentanteile werden automatisch berechnet
</div>
</div>
{/* Action Buttons */}
<div style={{ display: 'flex', gap: 12 }}>
<button
className="btn-primary"
onClick={async () => {
const sum = Object.values(focusTemp).reduce((a, b) => a + b, 0)
if (sum === 0) {
setError('Mindestens ein Bereich muss gewichtet sein')
return
}
// Normalize to percentages
const normalized = {}
Object.keys(focusTemp).forEach(key => {
normalized[key] = Math.round(focusTemp[key] / sum * 100)
})
// Ensure sum is exactly 100 (adjust largest value if needed due to rounding)
const normalizedSum = Object.values(normalized).reduce((a, b) => a + b, 0)
if (normalizedSum !== 100) {
const largest = Object.entries(normalized).reduce((max, [k, v]) => v > max[1] ? [k, v] : max, ['', 0])
normalized[largest[0]] += (100 - normalizedSum)
}
try {
await api.updateFocusAreas(normalized)
showToast('✓ Fokus-Bereiche aktualisiert')
await loadData()
setFocusEditing(false)
setError(null)
} catch (err) {
setError(err.message || 'Fehler beim Speichern')
}
}}
style={{ flex: 1 }}
>
Speichern
</button>
<button
className="btn-secondary"
onClick={() => {
setFocusTemp(focusAreas)
setFocusEditing(false)
setError(null)
}}
style={{ flex: 1 }}
>
Abbrechen
</button>
</div>
</>
) : focusAreas && (
/* Display Mode */
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 12 }}>
{[
{ key: 'weight_loss_pct', label: 'Fettabbau', icon: '📉', color: '#D85A30' },
{ key: 'muscle_gain_pct', label: 'Muskelaufbau', icon: '💪', color: '#378ADD' },
{ key: 'strength_pct', label: 'Kraftsteigerung', icon: '🏋️', color: '#7B68EE' },
{ key: 'endurance_pct', label: 'Ausdauer', icon: '🏃', color: '#1D9E75' },
{ key: 'flexibility_pct', label: 'Beweglichkeit', icon: '🤸', color: '#E67E22' },
{ key: 'health_pct', label: 'Gesundheit', icon: '❤️', color: '#F59E0B' }
].filter(area => focusAreas[area.key] > 0).map(area => (
<div
key={area.key}
style={{
padding: 12,
background: 'var(--surface2)',
border: '1px solid var(--border)',
borderRadius: 8,
textAlign: 'center'
}}
>
<div style={{ fontSize: 24, marginBottom: 4 }}>{area.icon}</div>
<div style={{ fontSize: 12, color: 'var(--text2)', marginBottom: 4 }}>{area.label}</div>
<div style={{ fontSize: 20, fontWeight: 700, color: area.color }}>{focusAreas[area.key]}%</div>
</div>
))}
</div>
)}
</div>
{/* Tactical Goals List */}
<div className="card">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<h2 style={{ margin: 0 }}>🎯 Konkrete Ziele</h2>
<button className="btn-primary" onClick={handleCreateGoal}>
<Plus size={16} /> Ziel hinzufügen
</button>
</div>
{goals.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px 20px', color: 'var(--text2)' }}>
<Target size={48} style={{ opacity: 0.3, marginBottom: 16 }} />
<p>Noch keine Ziele definiert</p>
<button className="btn-primary" onClick={handleCreateGoal} style={{ marginTop: 16 }}>
Erstes Ziel erstellen
</button>
</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{goals.map(goal => {
const typeInfo = goalTypesMap[goal.goal_type] || { label: goal.goal_type, unit: '', icon: '📊' }
return (
<div
key={goal.id}
className="card"
style={{
background: 'var(--surface2)',
border: goal.is_primary ? '2px solid var(--accent)' : '1px solid var(--border)'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
<span style={{ fontSize: 20 }}>{typeInfo.icon}</span>
<span style={{ fontWeight: 600 }}>
{goal.name || typeInfo.label}
</span>
{goal.is_primary && (
<span style={{
background: 'var(--accent)',
color: 'white',
fontSize: 11,
padding: '2px 8px',
borderRadius: 4
}}>
PRIMÄR
</span>
)}
<span style={{
background: goal.status === 'active' ? '#E6F4F1' : '#F3F4F6',
color: goal.status === 'active' ? 'var(--accent)' : 'var(--text2)',
fontSize: 11,
padding: '2px 8px',
borderRadius: 4
}}>
{goal.status === 'active' ? 'AKTIV' : goal.status?.toUpperCase()}
</span>
</div>
<div style={{ display: 'flex', gap: 24, marginBottom: 12, fontSize: 14 }}>
<div>
<span style={{ color: 'var(--text2)' }}>Start:</span>{' '}
<strong>{goal.start_value} {goal.unit}</strong>
</div>
<div>
<span style={{ color: 'var(--text2)' }}>Aktuell:</span>{' '}
<strong>{goal.current_value || '—'} {goal.unit}</strong>
</div>
<div>
<span style={{ color: 'var(--text2)' }}>Ziel:</span>{' '}
<strong>{goal.target_value} {goal.unit}</strong>
</div>
{goal.target_date && (
<div>
<Calendar size={14} style={{ verticalAlign: 'middle', marginRight: 4 }} />
{dayjs(goal.target_date).format('DD.MM.YYYY')}
</div>
)}
</div>
{goal.progress_pct !== null && (
<div>
<div style={{
display: 'flex',
justifyContent: 'space-between',
fontSize: 12,
marginBottom: 4
}}>
<span>Fortschritt</span>
<span style={{ fontWeight: 600 }}>{goal.progress_pct}%</span>
</div>
<div style={{
width: '100%',
height: 6,
background: 'var(--surface)',
borderRadius: 3,
overflow: 'hidden'
}}>
<div style={{
width: `${Math.min(100, Math.max(0, goal.progress_pct))}%`,
height: '100%',
background: getProgressColor(goal.progress_pct),
transition: 'width 0.3s ease'
}} />
</div>
{goal.on_track !== null && (
<div style={{ marginTop: 8, fontSize: 12 }}>
{goal.on_track ? (
<span style={{ color: 'var(--accent)' }}>
Ziel voraussichtlich erreichbar bis {dayjs(goal.target_date).format('DD.MM.YYYY')}
</span>
) : (
<span style={{ color: '#D85A30' }}>
Prognose: {goal.projection_date ? dayjs(goal.projection_date).format('DD.MM.YYYY') : 'Offen'}
{goal.target_date && ' (später als geplant)'}
</span>
)}
</div>
)}
</div>
)}
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button
className="btn-secondary"
onClick={() => handleEditGoal(goal)}
style={{ padding: '6px 12px' }}
>
<Pencil size={14} />
</button>
<button
className="btn-secondary"
onClick={() => handleDeleteGoal(goal.id)}
style={{ padding: '6px 12px', color: '#DC2626' }}
>
<Trash2 size={14} />
</button>
</div>
</div>
</div>
)
})}
</div>
)}
</div>
{/* Goal Form Modal */}
{showGoalForm && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'center',
zIndex: 1000,
padding: 16,
paddingTop: 40,
overflowY: 'auto'
}}>
<div className="card" style={{
maxWidth: 500,
width: '100%',
marginBottom: 40
}}>
<div className="card-title">
{editingGoal ? 'Ziel bearbeiten' : 'Neues Ziel'}
</div>
{error && (
<div style={{
padding: 10,
background: '#FCEBEB',
border: '1px solid #D85A30',
borderRadius: 8,
fontSize: 13,
color: '#D85A30',
marginBottom: 16
}}>
{error}
</div>
)}
{/* Zieltyp */}
<div style={{ marginBottom: 16 }}>
<label style={{
display: 'block',
fontSize: 13,
fontWeight: 600,
marginBottom: 4,
color: 'var(--text2)'
}}>
Zieltyp
</label>
<select
className="form-input"
style={{ width: '100%' }}
value={formData.goal_type}
onChange={e => handleGoalTypeChange(e.target.value)}
>
{goalTypes.map(type => (
<option key={type.type_key} value={type.type_key}>
{type.icon} {type.label_de}
</option>
))}
</select>
{/* Warning for incomplete goal types */}
{['bp', 'strength', 'flexibility'].includes(formData.goal_type) && (
<div style={{
marginTop: 8,
padding: 8,
background: '#FEF3C7',
border: '1px solid #F59E0B',
borderRadius: 6,
fontSize: 12,
color: '#92400E'
}}>
Dieser Zieltyp ist aktuell eingeschränkt:
{formData.goal_type === 'bp' && ' Blutdruck benötigt 2 Werte (geplant für v2.0)'}
{formData.goal_type === 'strength' && ' Keine Datenquelle vorhanden (geplant für v2.0)'}
{formData.goal_type === 'flexibility' && ' Keine Datenquelle vorhanden (geplant für v2.0)'}
</div>
)}
</div>
{/* Name */}
<div style={{ marginBottom: 16 }}>
<label style={{
display: 'block',
fontSize: 13,
fontWeight: 500,
marginBottom: 4,
color: 'var(--text2)'
}}>
Name (optional)
</label>
<input
type="text"
className="form-input"
style={{ width: '100%', textAlign: 'left' }}
value={formData.name}
onChange={e => setFormData(f => ({ ...f, name: e.target.value }))}
placeholder="z.B. Sommerfigur 2026"
/>
</div>
{/* Zielwert */}
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, marginTop: 20, color: 'var(--text1)' }}>
🎯 Zielwert
</div>
<div style={{ marginBottom: 16 }}>
<label style={{
display: 'block',
fontSize: 13,
fontWeight: 500,
marginBottom: 4,
color: 'var(--text2)'
}}>
Wert *
</label>
<div style={{ display: 'flex', gap: 8 }}>
<input
type="number"
step="0.01"
className="form-input"
style={{ flex: 1 }}
value={formData.target_value}
onChange={e => setFormData(f => ({ ...f, target_value: e.target.value }))}
placeholder="Zielwert eingeben"
/>
<div style={{
padding: '8px 16px',
background: 'var(--surface2)',
border: '1px solid var(--border)',
borderRadius: 8,
fontSize: 14,
fontWeight: 600,
color: 'var(--text2)',
minWidth: 70,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
{formData.unit}
</div>
</div>
</div>
{/* Zieldatum */}
<div style={{ marginBottom: 16 }}>
<label style={{
display: 'block',
fontSize: 13,
fontWeight: 500,
marginBottom: 4,
color: 'var(--text2)'
}}>
Zieldatum (optional)
</label>
<input
type="date"
className="form-input"
style={{ width: '100%', textAlign: 'left' }}
value={formData.target_date}
onChange={e => setFormData(f => ({ ...f, target_date: e.target.value }))}
/>
</div>
{/* Beschreibung */}
<div style={{ marginBottom: 16 }}>
<label style={{
display: 'block',
fontSize: 13,
fontWeight: 500,
marginBottom: 4,
color: 'var(--text2)'
}}>
Beschreibung (optional)
</label>
<textarea
className="form-input"
style={{ width: '100%', minHeight: 80, textAlign: 'left' }}
value={formData.description}
onChange={e => setFormData(f => ({ ...f, description: e.target.value }))}
rows={3}
placeholder="Warum ist dir dieses Ziel wichtig?"
/>
</div>
{/* Primärziel */}
<div style={{
padding: 12,
background: 'var(--surface2)',
borderRadius: 8,
marginBottom: 20
}}>
<label style={{
display: 'flex',
alignItems: 'flex-start',
gap: 10,
cursor: 'pointer'
}}>
<input
type="checkbox"
checked={formData.is_primary}
onChange={e => setFormData(f => ({ ...f, is_primary: e.target.checked }))}
style={{ marginTop: 2 }}
/>
<div>
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 2 }}>
Als Primärziel setzen
</div>
<div style={{ fontSize: 12, color: 'var(--text2)' }}>
Dein Primärziel hat höchste Priorität in Analysen und Charts
</div>
</div>
</label>
</div>
{/* Buttons */}
<button
className="btn btn-primary btn-full"
onClick={handleSaveGoal}
style={{ marginBottom: 8 }}
>
{editingGoal ? 'Aktualisieren' : 'Ziel erstellen'}
</button>
<button
className="btn btn-secondary btn-full"
onClick={() => {
setShowGoalForm(false)
setEditingGoal(null)
setError(null)
}}
>
Abbrechen
</button>
</div>
</div>
)}
</div>
)
}