mitai-jinkendo/frontend/src/pages/GoalsPage.jsx
Lars db90f397e8
All checks were successful
Deploy Development / deploy (push) Successful in 49s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s
feat: auto-assign goal category based on goal type
Added intelligent category assignment:
- Weight, body_fat, lean_mass → Körper
- Strength, flexibility → Training
- BP, VO2Max, RHR, HRV → Gesundheit
- Sleep goals → Erholung
- Nutrition goals → Ernährung
- Unknown types → Sonstiges

Changes:
1. getCategoryForGoalType() mapping function
2. Auto-set category in handleGoalTypeChange()
3. Auto-set category in handleCreateGoal()

User can still manually change category if needed.

Fixes: Blood pressure goals wrongly categorized as 'Training'

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:09:34 +01:00

920 lines
34 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 Categories
const GOAL_CATEGORIES = {
body: { label: 'Körper', icon: '📉', color: '#D85A30', description: 'Gewicht, Körperfett, Muskelmasse' },
training: { label: 'Training', icon: '🏋️', color: '#378ADD', description: 'Kraft, Frequenz, Performance' },
nutrition: { label: 'Ernährung', icon: '🍎', color: '#1D9E75', description: 'Kalorien, Makros, Essgewohnheiten' },
recovery: { label: 'Erholung', icon: '😴', color: '#7B68EE', description: 'Schlaf, Regeneration, Ruhetage' },
health: { label: 'Gesundheit', icon: '❤️', color: '#E67E22', description: 'Vitalwerte, Blutdruck, HRV' },
other: { label: 'Sonstiges', icon: '📌', color: '#94A3B8', description: 'Weitere Ziele' }
}
// Priority Levels
const PRIORITY_LEVELS = {
1: { label: 'Hoch', stars: '⭐⭐⭐', color: 'var(--accent)' },
2: { label: 'Mittel', stars: '⭐⭐', color: '#94A3B8' },
3: { label: 'Niedrig', stars: '⭐', color: '#CBD5E1' }
}
// Auto-assign category based on goal_type
const getCategoryForGoalType = (goalType) => {
const mapping = {
// Body composition
'weight': 'body',
'body_fat': 'body',
'lean_mass': 'body',
// Training
'strength': 'training',
'flexibility': 'training',
'training_frequency': 'training',
// Health/Vitals
'vo2max': 'health',
'rhr': 'health',
'bp': 'health',
'hrv': 'health',
// Recovery
'sleep_quality': 'recovery',
'sleep_duration': 'recovery',
'rest_days': 'recovery',
// Nutrition
'calories': 'nutrition',
'protein': 'nutrition',
'healthy_eating': 'nutrition'
}
return mapping[goalType] || 'other'
}
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([]) // Kept for backward compat
const [groupedGoals, setGroupedGoals] = useState({}) // Category-grouped goals
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,
category: 'body',
priority: 2,
target_value: '',
unit: 'kg',
target_date: '',
name: '',
description: ''
})
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
setLoading(true)
setError(null)
try {
const [modeData, goalsData, groupedData, typesData, focusData] = await Promise.all([
api.getGoalMode(),
api.listGoals(),
api.listGoalsGrouped(), // v2.1: Load grouped by category
api.listGoalTypeDefinitions(), // Phase 1.5: Load from DB
api.getFocusAreas() // v2.0: Load focus areas
])
setGoalMode(modeData.goal_mode)
setGoals(goalsData)
setGroupedGoals(groupedData)
// Ensure all focus fields are present and numeric
const sanitizedFocus = {
weight_loss_pct: focusData?.weight_loss_pct ?? 0,
muscle_gain_pct: focusData?.muscle_gain_pct ?? 0,
strength_pct: focusData?.strength_pct ?? 0,
endurance_pct: focusData?.endurance_pct ?? 0,
flexibility_pct: focusData?.flexibility_pct ?? 0,
health_pct: focusData?.health_pct ?? 0,
custom: focusData?.custom,
updated_at: focusData?.updated_at
}
setFocusAreas(sanitizedFocus)
setFocusTemp(sanitizedFocus)
// 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
category: getCategoryForGoalType(firstType), // Auto-assign based on type
priority: 2,
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,
category: goal.category || 'other',
priority: goal.priority || 2,
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',
category: getCategoryForGoalType(type) // Auto-assign category
}))
}
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,
category: formData.category,
priority: formData.priority,
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={() => {
setFocusTemp(focusAreas) // Sync temp state before editing
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 rawValue = Number(focusTemp[area.key]) || 0
const weight = Math.round(rawValue / 10)
const sum = Object.entries(focusTemp)
.filter(([k]) => k.endsWith('_pct'))
.reduce((acc, [k, v]) => acc + (Number(v) || 0), 0)
const actualPercent = sum > 0 ? Math.round(rawValue / 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>
{/* Action Buttons */}
<div style={{ display: 'flex', gap: 12 }}>
<button
className="btn-primary"
onClick={async () => {
// Calculate sum (filter out NaN/undefined)
const sum = Object.entries(focusTemp)
.filter(([k]) => k.endsWith('_pct'))
.reduce((acc, [k, v]) => acc + (Number(v) || 0), 0)
if (sum === 0 || isNaN(sum)) {
setError('Mindestens ein Bereich muss gewichtet sein')
return
}
// Normalize to percentages (ensure no NaN values)
const normalized = {
weight_loss_pct: Math.round((Number(focusTemp.weight_loss_pct) || 0) / sum * 100),
muscle_gain_pct: Math.round((Number(focusTemp.muscle_gain_pct) || 0) / sum * 100),
strength_pct: Math.round((Number(focusTemp.strength_pct) || 0) / sum * 100),
endurance_pct: Math.round((Number(focusTemp.endurance_pct) || 0) / sum * 100),
flexibility_pct: Math.round((Number(focusTemp.flexibility_pct) || 0) / sum * 100),
health_pct: Math.round((Number(focusTemp.health_pct) || 0) / 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 - Category Grouped */}
<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>
{Object.keys(groupedGoals).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: 32 }}>
{Object.entries(GOAL_CATEGORIES).map(([catKey, catInfo]) => {
const categoryGoals = groupedGoals[catKey] || []
if (categoryGoals.length === 0) return null
return (
<div key={catKey}>
{/* Category Header */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: 12,
marginBottom: 16,
paddingBottom: 12,
borderBottom: `3px solid ${catInfo.color}`
}}>
<span style={{ fontSize: 28 }}>{catInfo.icon}</span>
<div style={{ flex: 1 }}>
<h3 style={{ margin: 0, color: catInfo.color, fontSize: 18 }}>{catInfo.label}</h3>
<p style={{ margin: 0, fontSize: 13, color: 'var(--text3)' }}>{catInfo.description}</p>
</div>
<span style={{
background: catInfo.color,
color: 'white',
padding: '4px 12px',
borderRadius: 16,
fontSize: 13,
fontWeight: 600
}}>
{categoryGoals.length} {categoryGoals.length === 1 ? 'Ziel' : 'Ziele'}
</span>
</div>
{/* Goals in Category */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{categoryGoals.map(goal => {
const typeInfo = goalTypesMap[goal.goal_type] || { label_de: goal.goal_type, unit: '', icon: '📊' }
const priorityInfo = PRIORITY_LEVELS[goal.priority] || PRIORITY_LEVELS[2]
return (
<div
key={goal.id}
className="card"
style={{
background: 'var(--surface2)',
border: `1px solid var(--border)`,
borderLeft: `5px solid ${catInfo.color}`
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16 }}>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8, flexWrap: 'wrap' }}>
<span style={{ fontSize: 20 }}>{goal.icon || typeInfo.icon}</span>
<span style={{ fontWeight: 600, fontSize: 16 }}>
{goal.name || goal.label_de || typeInfo.label_de || goal.goal_type}
</span>
<span style={{ fontSize: 16 }} title={priorityInfo.label}>
{priorityInfo.stars}
</span>
<span style={{
background: goal.status === 'active' ? '#E6F4F1' : '#F3F4F6',
color: goal.status === 'active' ? 'var(--accent)' : 'var(--text2)',
fontSize: 11,
padding: '3px 8px',
borderRadius: 4,
fontWeight: 500
}}>
{goal.status === 'active' ? 'AKTIV' : goal.status?.toUpperCase() || 'AKTIV'}
</span>
</div>
<div style={{ display: 'flex', gap: 20, marginBottom: 12, fontSize: 14, flexWrap: 'wrap' }}>
<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 style={{ color: catInfo.color }}>{goal.target_value} {goal.unit}</strong>
</div>
{goal.target_date && (
<div style={{ color: 'var(--text2)' }}>
<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,
color: 'var(--text2)'
}}>
<span>Fortschritt</span>
<span style={{ fontWeight: 600, color: 'var(--text1)' }}>{goal.progress_pct}%</span>
</div>
<div style={{
width: '100%',
height: 8,
background: 'var(--surface)',
borderRadius: 4,
overflow: 'hidden'
}}>
<div style={{
width: `${Math.min(100, Math.max(0, goal.progress_pct))}%`,
height: '100%',
background: catInfo.color,
transition: 'width 0.3s ease'
}} />
</div>
</div>
)}
</div>
<div style={{ display: 'flex', gap: 8, flexShrink: 0 }}>
<button
className="btn-secondary"
onClick={() => handleEditGoal(goal)}
style={{ padding: '6px 12px' }}
title="Bearbeiten"
>
<Pencil size={14} />
</button>
<button
className="btn-secondary"
onClick={() => handleDeleteGoal(goal.id)}
style={{ padding: '6px 12px', color: '#DC2626' }}
title="Löschen"
>
<Trash2 size={14} />
</button>
</div>
</div>
</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>
{/* Category & Priority */}
<div style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, marginTop: 20, color: 'var(--text1)' }}>
📂 Kategorisierung
</div>
<div style={{ display: 'flex', gap: 12, marginBottom: 16 }}>
{/* Category */}
<div style={{ flex: 1 }}>
<label style={{
display: 'block',
fontSize: 13,
fontWeight: 500,
marginBottom: 4,
color: 'var(--text2)'
}}>
Kategorie *
</label>
<select
className="form-input"
style={{ width: '100%' }}
value={formData.category}
onChange={e => setFormData(f => ({ ...f, category: e.target.value }))}
>
{Object.entries(GOAL_CATEGORIES).map(([key, info]) => (
<option key={key} value={key}>
{info.icon} {info.label}
</option>
))}
</select>
</div>
{/* Priority */}
<div style={{ flex: 1 }}>
<label style={{
display: 'block',
fontSize: 13,
fontWeight: 500,
marginBottom: 4,
color: 'var(--text2)'
}}>
Priorität *
</label>
<select
className="form-input"
style={{ width: '100%' }}
value={formData.priority}
onChange={e => setFormData(f => ({ ...f, priority: parseInt(e.target.value) }))}
>
{Object.entries(PRIORITY_LEVELS).map(([level, info]) => (
<option key={level} value={level}>
{info.stars} {info.label}
</option>
))}
</select>
</div>
</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>
)
}