import { useState, useEffect } from 'react' import { api } from '../utils/api' import AdminPageNav from '../components/AdminPageNav' import PageSectionNav from '../components/PageSectionNav' const CATALOG_SUBTABS = [ { id: 'focus-areas', label: 'Fokusbereiche' }, { id: 'training-styles', label: 'Stilrichtungen' }, { id: 'training-types', label: 'Trainingsstil' }, { id: 'hierarchy', label: 'Hierarchie' }, { id: 'target-groups', label: 'Zielgruppen' }, { id: 'target-groups-matrix', label: 'Zuordnungen' }, { id: 'training-characters', label: 'Trainingscharakter' }, { id: 'skill-categories', label: 'Fähigkeitskategorien' }, { id: 'trainer-assignments', label: 'Trainer-Zuordnungen' }, ] export default function AdminCatalogsPage() { const [activeTab, setActiveTab] = useState('focus-areas') const [loading, setLoading] = useState(false) const [error, setError] = useState('') // Focus Areas const [focusAreas, setFocusAreas] = useState([]) const [editingFA, setEditingFA] = useState(null) const [newFA, setNewFA] = useState({ name: '', description: '', color: '#1D9E75', icon: '' }) // Style Directions (Stilrichtungen) const [trainingStyles, setTrainingStyles] = useState([]) const [editingTS, setEditingTS] = useState(null) const [newTS, setNewTS] = useState({ name: '', description: '', focus_area_id: null }) // Training Characters const [trainingCharacters, setTrainingCharacters] = useState([]) const [editingTC, setEditingTC] = useState(null) const [newTC, setNewTC] = useState({ name: '', description: '' }) // Training Types (Breitensport, Leistungssport, etc.) const [trainingTypes, setTrainingTypes] = useState([]) const [editingTT, setEditingTT] = useState(null) const [newTT, setNewTT] = useState({ name: '', abbreviation: '', description: '', focus_area_id: null }) // Skill Categories const [skillCategories, setSkillCategories] = useState([]) const [editingSC, setEditingSC] = useState(null) const [newSC, setNewSC] = useState({ name: '', description: '', parent_category_id: null }) // Target Groups (Global - unabhängig von Stilen) const [targetGroups, setTargetGroups] = useState([]) const [editingTG, setEditingTG] = useState(null) const [newTG, setNewTG] = useState({ name: '', description: '', min_age: null, max_age: null }) // Trainer Focus Areas const [trainerAssignments, setTrainerAssignments] = useState([]) const [profiles, setProfiles] = useState([]) const [newAssignment, setNewAssignment] = useState({ profile_id: '', focus_area_id: '' }) // Hierarchy (Tree-View) const [hierarchyData, setHierarchyData] = useState([]) const [expandedNodes, setExpandedNodes] = useState(new Set()) // M:N Assignment Matrix const [assignments, setAssignments] = useState([]) const [matrixLoading, setMatrixLoading] = useState(false) useEffect(() => { loadData() }, [activeTab]) async function loadData() { setLoading(true) setError('') try { if (activeTab === 'focus-areas') { const data = await api.listFocusAreas() setFocusAreas(data) } else if (activeTab === 'training-styles') { const data = await api.listStyleDirections() setTrainingStyles(data) } else if (activeTab === 'training-characters') { const data = await api.listTrainingCharacters() setTrainingCharacters(data) } else if (activeTab === 'training-types') { const data = await api.listTrainingTypes() setTrainingTypes(data) } else if (activeTab === 'skill-categories') { const data = await api.listSkillCategories() setSkillCategories(data) } else if (activeTab === 'target-groups') { const groups = await api.listTargetGroups() setTargetGroups(groups) } else if (activeTab === 'trainer-assignments') { const [assignments, profs, areas] = await Promise.all([ api.listTrainerFocusAreas(), fetch('/api/profiles').then(r => r.json()), api.listFocusAreas() ]) setTrainerAssignments(assignments) setProfiles(profs) setFocusAreas(areas) } else if (activeTab === 'hierarchy') { const data = await api.getStyleDirectionsHierarchy() setHierarchyData(data) } else if (activeTab === 'target-groups-matrix') { const [styles, groups, assigns] = await Promise.all([ api.listStyleDirections(), api.listTargetGroups(), api.listStyleDirectionTargetGroups() ]) setTrainingStyles(styles) setTargetGroups(groups) setAssignments(assigns) } } catch (e) { setError(e.message) } finally { setLoading(false) } } // Focus Areas async function createFocusArea() { try { await api.createFocusArea(newFA) setNewFA({ name: '', description: '', color: '#1D9E75', icon: '' }) loadData() } catch (e) { setError(e.message) } } async function updateFocusArea(id, data) { try { await api.updateFocusArea(id, data) setEditingFA(null) loadData() } catch (e) { setError(e.message) } } async function deleteFocusArea(id) { if (!confirm('Fokusbereich wirklich löschen?')) return try { await api.deleteFocusArea(id) loadData() } catch (e) { setError(e.message) } } // Style Directions (formerly Training Styles) async function createStyleDirection() { try { await api.createStyleDirection(newTS) setNewTS({ name: '', description: '', focus_area_id: null }) loadData() } catch (e) { setError(e.message) } } async function updateStyleDirection(id, data) { try { await api.updateStyleDirection(id, data) setEditingTS(null) loadData() } catch (e) { setError(e.message) } } async function deleteStyleDirection(id) { if (!confirm('Stilrichtung wirklich löschen?')) return try { await api.deleteStyleDirection(id) loadData() } catch (e) { setError(e.message) } } // Training Characters async function createTrainingCharacter() { try { await api.createTrainingCharacter(newTC) setNewTC({ name: '', description: '' }) loadData() } catch (e) { setError(e.message) } } async function updateTrainingCharacter(id, data) { try { await api.updateTrainingCharacter(id, data) setEditingTC(null) loadData() } catch (e) { setError(e.message) } } async function deleteTrainingCharacter(id) { if (!confirm('Trainingscharakter wirklich löschen?')) return try { await api.deleteTrainingCharacter(id) loadData() } catch (e) { setError(e.message) } } // Training Types async function createTrainingType() { try { await api.createTrainingType(newTT) setNewTT({ name: '', abbreviation: '', description: '', focus_area_id: null }) loadData() } catch (e) { setError(e.message) } } async function updateTrainingType(id, data) { try { await api.updateTrainingType(id, data) setEditingTT(null) loadData() } catch (e) { setError(e.message) } } async function deleteTrainingType(id) { if (!confirm('Trainingsstil wirklich löschen?')) return try { await api.deleteTrainingType(id) loadData() } catch (e) { setError(e.message) } } // Skill Categories async function createSkillCategory() { try { await api.createSkillCategory(newSC) setNewSC({ name: '', description: '', parent_category_id: null }) loadData() } catch (e) { setError(e.message) } } async function updateSkillCategory(id, data) { try { await api.updateSkillCategory(id, data) setEditingSC(null) loadData() } catch (e) { setError(e.message) } } async function deleteSkillCategory(id) { if (!confirm('Fähigkeitskategorie wirklich löschen?')) return try { await api.deleteSkillCategory(id) loadData() } catch (e) { setError(e.message) } } // Target Groups async function createTargetGroup() { try { await api.createTargetGroup(newTG) setNewTG({ name: '', description: '', min_age: null, max_age: null }) loadData() } catch (e) { setError(e.message) } } async function updateTargetGroup(id, data) { try { await api.updateTargetGroup(id, data) setEditingTG(null) loadData() } catch (e) { setError(e.message) } } async function deleteTargetGroup(id) { if (!confirm('Zielgruppe wirklich löschen?')) return try { await api.deleteTargetGroup(id) loadData() } catch (e) { setError(e.message) } } // Trainer Assignments async function assignTrainer() { try { await api.assignTrainerFocusArea(newAssignment) setNewAssignment({ profile_id: '', focus_area_id: '' }) loadData() } catch (e) { setError(e.message) } } async function removeAssignment(id) { if (!confirm('Zuordnung wirklich entfernen?')) return try { await api.deleteTrainerFocusArea(id) loadData() } catch (e) { setError(e.message) } } return (
{fa.description}
{ts.focus_area_icon} {ts.focus_area_name}
)} {ts.parent_style_name && (→ Untergeordnet: {ts.parent_style_name}
)}{ts.description}
{tc.description}
{tt.description}
} {tt.focus_area_name && (Fokusbereich: {tt.focus_area_icon} {tt.focus_area_name}
)}→ {sc.parent_category_name}
)}{sc.description}
Alter: {tg.min_age || '∞'}-{tg.max_age || '∞'} Jahre
)} {tg.description && ({tg.description}
)}{ta.focus_area_icon} {ta.focus_area_name}
Hierarchische Ansicht der Katalog-Struktur. Zuordnungen verwalten Sie im Tab "Zuordnungen".
{fa.style_directions?.length || 0} Trainingsstil(e)
{ts.target_groups?.length || 0} Zielgruppe(n)
{tg.description}
)}Ordnen Sie Zielgruppen den Trainingsstilen zu. Eine Zielgruppe kann mehreren Stilen zugeordnet sein.
| Trainingsstil | {targetGroups.map(tg => ({tg.name} | ))}
|---|---|
|
{ts.name}
{ts.focus_area_name && (
{ts.focus_area_name}
)}
|
{targetGroups.map(tg => {
const assignment = assignments.find(
a => a.style_direction_id === ts.id && a.target_group_id === tg.id
)
const isAssigned = !!assignment
return (
{ try { if (isAssigned) { await api.deleteStyleDirectionTargetGroup(assignment.id) } else { await api.createStyleDirectionTargetGroup({ style_direction_id: ts.id, target_group_id: tg.id, is_primary: false }) } loadData() } catch (e) { setError(e.message) } }} style={{ width: '18px', height: '18px', cursor: 'pointer' }} /> {isAssigned && assignment.is_primary && ( ★ )} | ) })}
Keine Daten verfügbar. Bitte erstellen Sie zuerst Trainingsstile und Zielgruppen.