From bbee44ecdc5f6d6c5ed633057550809a7d8091d2 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 27 Mar 2026 07:28:14 +0100 Subject: [PATCH] fix: Better error handling for goal types loading - Check if goal_type_definitions table exists - Detailed error messages - Fallback if goalTypes is empty - Prevent form opening without types Helps debugging Migration 024 issues. Co-Authored-By: Claude Opus 4.6 --- backend/routers/goals.py | 62 +++++++++++++++++++++++--------- frontend/src/pages/GoalsPage.jsx | 30 +++++++++------- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/backend/routers/goals.py b/backend/routers/goals.py index 7e82907..98b7521 100644 --- a/backend/routers/goals.py +++ b/backend/routers/goals.py @@ -14,6 +14,7 @@ from pydantic import BaseModel from typing import Optional, List from datetime import date, datetime, timedelta from decimal import Decimal +import traceback from db import get_db, get_cursor, r2d from auth import require_auth @@ -499,24 +500,51 @@ def list_goal_type_definitions(session: dict = Depends(require_auth)): Public endpoint - returns all available goal types for dropdown. """ - with get_db() as conn: - cur = get_cursor(conn) - cur.execute(""" - SELECT id, type_key, label_de, label_en, unit, icon, category, - source_table, source_column, aggregation_method, - calculation_formula, description, is_system, - created_at, updated_at - FROM goal_type_definitions - WHERE is_active = true - ORDER BY - CASE - WHEN is_system = true THEN 0 - ELSE 1 - END, - label_de - """) + try: + with get_db() as conn: + cur = get_cursor(conn) - return [r2d(row) for row in cur.fetchall()] + # Check if table exists first + cur.execute(""" + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_name = 'goal_type_definitions' + ) + """) + table_exists = cur.fetchone()[0] + + if not table_exists: + print("[ERROR] goal_type_definitions table does not exist!") + raise HTTPException( + status_code=500, + detail="Goal Types Tabelle existiert nicht. Migration 024 muss ausgeführt werden." + ) + + cur.execute(""" + SELECT id, type_key, label_de, label_en, unit, icon, category, + source_table, source_column, aggregation_method, + calculation_formula, description, is_system, + created_at, updated_at + FROM goal_type_definitions + WHERE is_active = true + ORDER BY + CASE + WHEN is_system = true THEN 0 + ELSE 1 + END, + label_de + """) + + return [r2d(row) for row in cur.fetchall()] + except HTTPException: + raise + except Exception as e: + print(f"[ERROR] list_goal_type_definitions failed: {e}") + print(traceback.format_exc()) + raise HTTPException( + status_code=500, + detail=f"Fehler beim Laden der Goal Types: {str(e)}" + ) @router.post("/goal-types") def create_goal_type_definition( diff --git a/frontend/src/pages/GoalsPage.jsx b/frontend/src/pages/GoalsPage.jsx index 100380f..5ba8e00 100644 --- a/frontend/src/pages/GoalsPage.jsx +++ b/frontend/src/pages/GoalsPage.jsx @@ -84,21 +84,23 @@ export default function GoalsPage() { // Convert types array to map for quick lookup const typesMap = {} - 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 - } - }) + 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) + setGoalTypes(typesData || []) setGoalTypesMap(typesMap) } catch (err) { console.error('Failed to load goals:', err) - setError('Fehler beim Laden der Ziele') + setError(`Fehler beim Laden: ${err.message || err.toString()}`) } finally { setLoading(false) } @@ -121,8 +123,12 @@ export default function GoalsPage() { } const handleCreateGoal = () => { + if (goalTypes.length === 0) { + setError('Keine Goal Types verfügbar. Bitte Admin kontaktieren.') + return + } setEditingGoal(null) - const firstType = goalTypes.length > 0 ? goalTypes[0].type_key : 'weight' + const firstType = goalTypes[0].type_key setFormData({ goal_type: firstType, is_primary: goals.length === 0, // First goal is primary by default