diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a83ecf1..eb41e9d 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -29,6 +29,7 @@ import AdminCouponsPage from './pages/AdminCouponsPage' import AdminUserRestrictionsPage from './pages/AdminUserRestrictionsPage' import AdminTrainingTypesPage from './pages/AdminTrainingTypesPage' import AdminActivityMappingsPage from './pages/AdminActivityMappingsPage' +import AdminTrainingProfiles from './pages/AdminTrainingProfiles' import SubscriptionPage from './pages/SubscriptionPage' import SleepPage from './pages/SleepPage' import RestDaysPage from './pages/RestDaysPage' @@ -180,6 +181,7 @@ function AppShell() { }/> }/> }/> + }/> }/> diff --git a/frontend/src/pages/AdminPanel.jsx b/frontend/src/pages/AdminPanel.jsx index 94b9822..dc23c50 100644 --- a/frontend/src/pages/AdminPanel.jsx +++ b/frontend/src/pages/AdminPanel.jsx @@ -444,6 +444,11 @@ export default function AdminPanel() { 🔗 Activity-Mappings (lernendes System) + + + diff --git a/frontend/src/pages/AdminTrainingProfiles.jsx b/frontend/src/pages/AdminTrainingProfiles.jsx new file mode 100644 index 0000000..0173a06 --- /dev/null +++ b/frontend/src/pages/AdminTrainingProfiles.jsx @@ -0,0 +1,297 @@ +import { useState, useEffect } from 'react' +import { api } from '../utils/api' +import '../app.css' + +export default function AdminTrainingProfiles() { + const [stats, setStats] = useState(null) + const [trainingTypes, setTrainingTypes] = useState([]) + const [templates, setTemplates] = useState([]) + const [selectedType, setSelectedType] = useState(null) + const [editingProfile, setEditingProfile] = useState(null) + const [profileJson, setProfileJson] = useState('') + const [loading, setLoading] = useState(true) + const [error, setError] = useState('') + const [success, setSuccess] = useState('') + + useEffect(() => { + load() + }, []) + + const load = async () => { + try { + setLoading(true) + const [typesData, statsData, templatesData] = await Promise.all([ + api.get('/admin/training-types'), + api.get('/admin/training-types/profiles/stats'), + api.get('/admin/training-types/profiles/templates') + ]) + setTrainingTypes(typesData) + setStats(statsData) + setTemplates(templatesData) + } catch (e) { + setError(e.message) + } finally { + setLoading(false) + } + } + + const openEditor = (type) => { + setSelectedType(type) + setEditingProfile(type.profile || null) + setProfileJson(JSON.stringify(type.profile || {}, null, 2)) + setError('') + setSuccess('') + } + + const closeEditor = () => { + setSelectedType(null) + setEditingProfile(null) + setProfileJson('') + } + + const saveProfile = async () => { + try { + // Validate JSON + const profile = JSON.parse(profileJson) + + // Update training type + await api.put(`/admin/training-types/${selectedType.id}`, { profile }) + + setSuccess(`Profil für "${selectedType.name_de}" gespeichert`) + closeEditor() + load() + } catch (e) { + setError(e.message || 'Ungültiges JSON') + } + } + + const applyTemplate = async (typeId, templateKey) => { + if (!confirm(`Template "${templateKey}" auf diesen Trainingstyp anwenden?`)) return + + try { + await api.post(`/admin/training-types/${typeId}/profile/apply-template`, { + template_key: templateKey + }) + setSuccess('Template erfolgreich angewendet') + load() + } catch (e) { + setError(e.message) + } + } + + const batchReEvaluate = async () => { + if (!confirm('Alle Aktivitäten neu evaluieren? Das kann einige Sekunden dauern.')) return + + try { + const result = await api.post('/evaluation/batch') + setSuccess( + `Batch-Evaluation abgeschlossen: ${result.stats.evaluated} evaluiert, ` + + `${result.stats.skipped} übersprungen, ${result.stats.errors} Fehler` + ) + } catch (e) { + setError(e.message) + } + } + + if (loading) return
+ + return ( +
+

Training Type Profiles

+

+ Konfiguriere Bewertungsprofile für Trainingstypen +

+ + {error && ( +
+ {error} +
+ )} + + {success && ( +
+ {success} +
+ )} + + {/* Statistics */} + {stats && ( +
+

Übersicht

+
+
+
{stats.total}
+
Trainingstypen gesamt
+
+
+
{stats.configured}
+
Profile konfiguriert
+
+
+
{stats.unconfigured}
+
Noch keine Profile
+
+
+ + +
+ )} + + {/* Training Types List */} +
+

Trainingstypen

+ +
+ {trainingTypes.map(type => ( +
+
{type.icon || '📊'}
+
+
+ {type.name_de} + {type.profile && ( + + ✓ Profil + + )} +
+
+ {type.category} {type.subcategory && `· ${type.subcategory}`} +
+
+ +
+ {/* Template Buttons */} + {templates.map(template => ( + + ))} + + +
+
+ ))} +
+
+ + {/* Profile Editor Modal */} + {selectedType && ( +
+
+
+

{selectedType.icon} {selectedType.name_de} - Profil bearbeiten

+ +
+ +

+ JSON-basierter Editor. Siehe Dokumentation für vollständige Struktur. +

+ +