From 398c645a98d3362c56f22af5863d986d5775b189 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 27 Mar 2026 14:02:24 +0100 Subject: [PATCH] feat: Goal Progress Log UI - complete frontend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added Progress button (TrendingUp icon) to each goal card - Created Progress Modal with: • Form to add new progress entry (date, value, note) • Historical entries list with delete option • Category-colored goal info header • Auto-disables manual delete for non-manual entries - Integration complete: handlers → API → backend Completes Phase 0a Progress Tracking (Migration 030 + Backend + Frontend) Co-Authored-By: Claude Opus 4.6 --- frontend/src/pages/GoalsPage.jsx | 283 +++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) diff --git a/frontend/src/pages/GoalsPage.jsx b/frontend/src/pages/GoalsPage.jsx index d27cdf8..1f5da9b 100644 --- a/frontend/src/pages/GoalsPage.jsx +++ b/frontend/src/pages/GoalsPage.jsx @@ -73,6 +73,14 @@ export default function GoalsPage() { const [goalTypesMap, setGoalTypesMap] = useState({}) // For quick lookup const [showGoalForm, setShowGoalForm] = useState(false) const [editingGoal, setEditingGoal] = useState(null) + const [showProgressModal, setShowProgressModal] = useState(false) + const [progressGoal, setProgressGoal] = useState(null) + const [progressEntries, setProgressEntries] = useState([]) + const [progressFormData, setProgressFormData] = useState({ + date: new Date().toISOString().split('T')[0], + value: '', + note: '' + }) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [toast, setToast] = useState(null) @@ -261,6 +269,82 @@ export default function GoalsPage() { } } + const handleOpenProgressModal = async (goal) => { + setProgressGoal(goal) + setProgressFormData({ + date: new Date().toISOString().split('T')[0], + value: goal.current_value || '', + note: '' + }) + + // Load progress history + try { + const entries = await api.listGoalProgress(goal.id) + setProgressEntries(entries) + } catch (err) { + console.error('Failed to load progress:', err) + setProgressEntries([]) + } + + setShowProgressModal(true) + setError(null) + } + + const handleSaveProgress = async () => { + if (!progressFormData.value || !progressFormData.date) { + setError('Bitte Datum und Wert eingeben') + return + } + + try { + const data = { + date: progressFormData.date, + value: parseFloat(progressFormData.value), + note: progressFormData.note || null + } + + await api.createGoalProgress(progressGoal.id, data) + showToast('✓ Fortschritt erfasst') + + // Reload progress history + const entries = await api.listGoalProgress(progressGoal.id) + setProgressEntries(entries) + + // Reset form + setProgressFormData({ + date: new Date().toISOString().split('T')[0], + value: '', + note: '' + }) + + // Reload goals to update current_value + await loadData() + setError(null) + } catch (err) { + console.error('Failed to save progress:', err) + setError(err.message || 'Fehler beim Speichern') + } + } + + const handleDeleteProgress = async (progressId) => { + if (!confirm('Eintrag wirklich löschen?')) return + + try { + await api.deleteGoalProgress(progressGoal.id, progressId) + showToast('✓ Eintrag gelöscht') + + // Reload progress history + const entries = await api.listGoalProgress(progressGoal.id) + setProgressEntries(entries) + + // Reload goals to update current_value + await loadData() + } catch (err) { + console.error('Failed to delete progress:', err) + setError('Fehler beim Löschen') + } + } + const getProgressColor = (progress) => { if (progress >= 100) return 'var(--accent)' if (progress >= 75) return '#1D9E75' @@ -620,6 +704,14 @@ export default function GoalsPage() {
+
)} + + {/* Progress Modal */} + {showProgressModal && progressGoal && ( +
+
+

+ + Fortschritt erfassen +

+ +
+
+ {progressGoal.name || progressGoal.label_de || progressGoal.goal_type} +
+
+ Ziel: {progressGoal.target_value} {progressGoal.unit} +
+
+ + {/* Progress Form */} +
+

Neuer Eintrag

+ +
+
+ + setProgressFormData({ ...progressFormData, date: e.target.value })} + max={new Date().toISOString().split('T')[0]} + /> +
+ +
+ + setProgressFormData({ ...progressFormData, value: e.target.value })} + placeholder={`z.B. ${progressGoal.current_value || progressGoal.target_value}`} + /> +
+ +
+ +