diff --git a/backend/routers/goals.py b/backend/routers/goals.py index 8a0fdea..76220e2 100644 --- a/backend/routers/goals.py +++ b/backend/routers/goals.py @@ -508,6 +508,108 @@ def _calculate_norm_category(test_type: str, value: float, unit: str) -> Optiona # Goal Type Definitions (Phase 1.5 - Flexible Goal System) # ============================================================================ +@router.get("/schema-info") +def get_schema_info(session: dict = Depends(require_auth)): + """ + Get available tables and columns for goal type creation. + + Admin-only endpoint for building custom goal types. + Returns structure with descriptions for UX guidance. + """ + pid = session['profile_id'] + + # Check admin role + with get_db() as conn: + cur = get_cursor(conn) + cur.execute("SELECT role FROM profiles WHERE id = %s", (pid,)) + profile = cur.fetchone() + + if not profile or profile['role'] != 'admin': + raise HTTPException(status_code=403, detail="Admin-Zugriff erforderlich") + + # Define relevant tables with descriptions + # Only include tables that make sense for goal tracking + schema = { + "weight_log": { + "description": "Gewichtsverlauf", + "columns": { + "weight": {"type": "DECIMAL", "description": "Körpergewicht in kg"} + } + }, + "caliper_log": { + "description": "Caliper-Messungen (Hautfalten)", + "columns": { + "body_fat_pct": {"type": "DECIMAL", "description": "Körperfettanteil in %"}, + "sum_mm": {"type": "DECIMAL", "description": "Summe Hautfalten in mm"} + } + }, + "circumference_log": { + "description": "Umfangsmessungen", + "columns": { + "c_neck": {"type": "DECIMAL", "description": "Nackenumfang in cm"}, + "c_chest": {"type": "DECIMAL", "description": "Brustumfang in cm"}, + "c_waist": {"type": "DECIMAL", "description": "Taillenumfang in cm"}, + "c_hips": {"type": "DECIMAL", "description": "Hüftumfang in cm"}, + "c_thigh_l": {"type": "DECIMAL", "description": "Oberschenkel links in cm"}, + "c_thigh_r": {"type": "DECIMAL", "description": "Oberschenkel rechts in cm"}, + "c_calf_l": {"type": "DECIMAL", "description": "Wade links in cm"}, + "c_calf_r": {"type": "DECIMAL", "description": "Wade rechts in cm"}, + "c_bicep_l": {"type": "DECIMAL", "description": "Bizeps links in cm"}, + "c_bicep_r": {"type": "DECIMAL", "description": "Bizeps rechts in cm"} + } + }, + "activity_log": { + "description": "Trainingseinheiten", + "columns": { + "id": {"type": "UUID", "description": "ID (für Zählung von Einheiten)"}, + "duration_minutes": {"type": "INTEGER", "description": "Trainingsdauer in Minuten"}, + "perceived_exertion": {"type": "INTEGER", "description": "Belastungsempfinden (1-10)"}, + "quality_rating": {"type": "INTEGER", "description": "Qualitätsbewertung (1-10)"} + } + }, + "nutrition_log": { + "description": "Ernährungstagebuch", + "columns": { + "calories": {"type": "INTEGER", "description": "Kalorien in kcal"}, + "protein_g": {"type": "DECIMAL", "description": "Protein in g"}, + "carbs_g": {"type": "DECIMAL", "description": "Kohlenhydrate in g"}, + "fat_g": {"type": "DECIMAL", "description": "Fett in g"} + } + }, + "sleep_log": { + "description": "Schlafprotokoll", + "columns": { + "total_minutes": {"type": "INTEGER", "description": "Gesamtschlafdauer in Minuten"} + } + }, + "vitals_baseline": { + "description": "Vitalwerte (morgens)", + "columns": { + "resting_hr": {"type": "INTEGER", "description": "Ruhepuls in bpm"}, + "hrv_rmssd": {"type": "INTEGER", "description": "Herzratenvariabilität (RMSSD) in ms"}, + "vo2_max": {"type": "DECIMAL", "description": "VO2 Max in ml/kg/min"}, + "spo2": {"type": "INTEGER", "description": "Sauerstoffsättigung in %"}, + "respiratory_rate": {"type": "INTEGER", "description": "Atemfrequenz pro Minute"} + } + }, + "blood_pressure_log": { + "description": "Blutdruckmessungen", + "columns": { + "systolic": {"type": "INTEGER", "description": "Systolischer Blutdruck in mmHg"}, + "diastolic": {"type": "INTEGER", "description": "Diastolischer Blutdruck in mmHg"}, + "pulse": {"type": "INTEGER", "description": "Puls in bpm"} + } + }, + "rest_days": { + "description": "Ruhetage", + "columns": { + "id": {"type": "UUID", "description": "ID (für Zählung von Ruhetagen)"} + } + } + } + + return schema + @router.get("/goal-types") def list_goal_type_definitions(session: dict = Depends(require_auth)): """ diff --git a/frontend/src/pages/AdminGoalTypesPage.jsx b/frontend/src/pages/AdminGoalTypesPage.jsx index 33ecff4..ca8ec55 100644 --- a/frontend/src/pages/AdminGoalTypesPage.jsx +++ b/frontend/src/pages/AdminGoalTypesPage.jsx @@ -4,6 +4,7 @@ import { api } from '../utils/api' export default function AdminGoalTypesPage() { const [goalTypes, setGoalTypes] = useState([]) + const [schemaInfo, setSchemaInfo] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showForm, setShowForm] = useState(false) @@ -42,9 +43,14 @@ export default function AdminGoalTypesPage() { setLoading(true) setError(null) try { - const data = await api.listGoalTypeDefinitions() - console.log('[DEBUG] Loaded goal types:', data) - setGoalTypes(data || []) + const [typesData, schema] = await Promise.all([ + api.listGoalTypeDefinitions(), + api.getSchemaInfo() + ]) + console.log('[DEBUG] Loaded goal types:', typesData) + console.log('[DEBUG] Loaded schema info:', schema) + setGoalTypes(typesData || []) + setSchemaInfo(schema || {}) } catch (err) { console.error('[ERROR] Failed to load goal types:', err) setError(`Fehler beim Laden der Goal Types: ${err.message || err.toString()}`) @@ -375,25 +381,59 @@ export default function AdminGoalTypesPage() {