import React, { useState, useEffect, useCallback } from 'react' import api from '../utils/api' import { useAuth } from '../context/AuthContext' function defaultSection(title = 'Hauptteil') { return { title, guidance_notes: '', items: [] } } function exerciseRow() { return { item_type: 'exercise', exercise_id: '', exercise_variant_id: '', planned_duration_min: '', actual_duration_min: '', notes: '', modifications: '' } } function noteRow() { return { item_type: 'note', note_body: '' } } function normalizeUnitToForm(fullUnit) { if (fullUnit.sections && fullUnit.sections.length) { return fullUnit.sections.map((sec) => ({ title: sec.title, guidance_notes: sec.guidance_notes || '', items: (sec.items || []).map((it) => { if (it.item_type === 'note') { return { item_type: 'note', note_body: it.note_body || '' } } return { item_type: 'exercise', exercise_id: it.exercise_id, exercise_variant_id: it.exercise_variant_id ?? '', planned_duration_min: it.planned_duration_min !== null && it.planned_duration_min !== undefined ? String(it.planned_duration_min) : '', actual_duration_min: it.actual_duration_min !== null && it.actual_duration_min !== undefined ? String(it.actual_duration_min) : '', notes: it.notes ?? '', modifications: it.modifications ?? '' } }) })) } if (fullUnit.exercises && fullUnit.exercises.length) { return [ { title: 'Übungen', guidance_notes: '', items: fullUnit.exercises.map((ex) => ({ item_type: 'exercise', exercise_id: ex.exercise_id, exercise_variant_id: ex.exercise_variant_id ?? '', planned_duration_min: ex.planned_duration_min !== null && ex.planned_duration_min !== undefined ? String(ex.planned_duration_min) : '', actual_duration_min: ex.actual_duration_min !== null && ex.actual_duration_min !== undefined ? String(ex.actual_duration_min) : '', notes: ex.notes ?? '', modifications: ex.modifications ?? '' })) } ] } return [defaultSection()] } function parseMin(v) { if (v === '' || v === null || v === undefined) return null const n = parseInt(String(v), 10) return Number.isFinite(n) ? n : null } function buildSectionsPayload(sections) { return sections.map((sec, si) => ({ order_index: si, title: (sec.title || '').trim() || 'Abschnitt', guidance_notes: sec.guidance_notes?.trim() ? sec.guidance_notes.trim() : null, items: (sec.items || []) .map((it, ii) => { if (it.item_type === 'note') { return { item_type: 'note', order_index: ii, note_body: it.note_body ?? '' } } if (it.exercise_id === '' || it.exercise_id == null || Number.isNaN(Number(it.exercise_id))) { return null } const vid = it.exercise_variant_id return { item_type: 'exercise', order_index: ii, exercise_id: parseInt(it.exercise_id, 10), exercise_variant_id: vid !== '' && vid != null && !Number.isNaN(Number(vid)) ? parseInt(vid, 10) : null, planned_duration_min: parseMin(it.planned_duration_min), actual_duration_min: parseMin(it.actual_duration_min), notes: it.notes?.trim() ? it.notes.trim() : null, modifications: it.modifications?.trim() ? it.modifications.trim() : null } }) .filter(Boolean) })) } function sectionPlannedMinutes(sec) { return (sec.items || []).reduce((sum, it) => { if (it.item_type !== 'exercise') return sum const m = parseMin(it.planned_duration_min) return sum + (m || 0) }, 0) } function TrainingPlanningPage() { const { user } = useAuth() const [groups, setGroups] = useState([]) const [selectedGroupId, setSelectedGroupId] = useState('') const [units, setUnits] = useState([]) const [exercises, setExercises] = useState([]) const [planTemplates, setPlanTemplates] = useState([]) const [loading, setLoading] = useState(true) const [showModal, setShowModal] = useState(false) const [editingUnit, setEditingUnit] = useState(null) const [draftPlanTemplateId, setDraftPlanTemplateId] = useState('') const [quickTemplateId, setQuickTemplateId] = useState('') const today = new Date().toISOString().split('T')[0] const thirtyDaysLater = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] const [startDate, setStartDate] = useState(today) const [endDate, setEndDate] = useState(thirtyDaysLater) const [formData, setFormData] = useState({ group_id: '', planned_date: '', planned_time_start: '', planned_time_end: '', planned_focus: '', actual_date: '', actual_time_start: '', actual_time_end: '', attendance_count: '', status: 'planned', notes: '', trainer_notes: '', sections: [defaultSection()] }) useEffect(() => { loadData() }, []) useEffect(() => { if (selectedGroupId) { loadUnits() } }, [selectedGroupId, startDate, endDate]) const loadPlanTemplates = useCallback(async () => { try { const tpl = await api.listTrainingPlanTemplates() setPlanTemplates(tpl) } catch (e) { console.error('Vorlagen laden:', e) } }, []) const loadData = async () => { try { const [groupsData, exercisesData] = await Promise.all([ api.listTrainingGroups({ status: 'active' }), api.listExercises({ include_variants: true }) ]) setGroups(groupsData) setExercises(exercisesData) await loadPlanTemplates() if (groupsData.length > 0) { const ownGroup = groupsData.find((g) => g.trainer_id === user?.id) if (ownGroup) { setSelectedGroupId(ownGroup.id) } else if (groupsData.length === 1) { setSelectedGroupId(groupsData[0].id) } } } catch (err) { console.error('Failed to load data:', err) alert('Fehler beim Laden: ' + err.message) } finally { setLoading(false) } } const loadUnits = async () => { if (!selectedGroupId) return try { const unitsData = await api.listTrainingUnits({ group_id: selectedGroupId, start_date: startDate, end_date: endDate }) setUnits(unitsData) } catch (err) { console.error('Failed to load units:', err) } } const handleQuickCreate = async () => { if (!selectedGroupId) { alert('Bitte wähle zuerst eine Trainingsgruppe') return } const date = prompt('Datum für neue Trainingseinheit (YYYY-MM-DD):', today) if (!date) return try { const body = { group_id: parseInt(selectedGroupId, 10), planned_date: date } if (quickTemplateId) { body.plan_template_id = parseInt(quickTemplateId, 10) } await api.quickCreateTrainingUnit(body) await loadUnits() } catch (err) { alert('Fehler beim Erstellen: ' + err.message) } } const handleCreate = () => { if (!selectedGroupId) { alert('Bitte wähle zuerst eine Trainingsgruppe') return } const group = groups.find((g) => g.id === parseInt(selectedGroupId, 10)) setEditingUnit(null) setDraftPlanTemplateId('') setFormData({ group_id: selectedGroupId, planned_date: today, planned_time_start: group?.time_start?.slice(0, 5) || '', planned_time_end: group?.time_end?.slice(0, 5) || '', planned_focus: '', actual_date: '', actual_time_start: '', actual_time_end: '', attendance_count: '', status: 'planned', notes: '', trainer_notes: '', sections: [defaultSection('Hauptteil')] }) setShowModal(true) } const applyTemplateFromSelect = async (templateId) => { setDraftPlanTemplateId(templateId) if (!templateId) return try { const tpl = await api.getTrainingPlanTemplate(parseInt(templateId, 10)) setFormData((fd) => ({ ...fd, sections: (tpl.sections || []).length ? tpl.sections.map((s) => ({ title: s.title, guidance_notes: s.guidance_text || '', items: [] })) : [defaultSection()] })) } catch (err) { alert('Vorlage laden: ' + err.message) } } const handleEdit = async (unit) => { try { const fullUnit = await api.getTrainingUnit(unit.id) setEditingUnit(fullUnit) setDraftPlanTemplateId(fullUnit.plan_template_id ? String(fullUnit.plan_template_id) : '') setFormData({ group_id: fullUnit.group_id, planned_date: fullUnit.planned_date || '', planned_time_start: fullUnit.planned_time_start?.slice(0, 5) || '', planned_time_end: fullUnit.planned_time_end?.slice(0, 5) || '', planned_focus: fullUnit.planned_focus || '', actual_date: fullUnit.actual_date || '', actual_time_start: fullUnit.actual_time_start?.slice(0, 5) || '', actual_time_end: fullUnit.actual_time_end?.slice(0, 5) || '', attendance_count: fullUnit.attendance_count ?? '', status: fullUnit.status || 'planned', notes: fullUnit.notes || '', trainer_notes: fullUnit.trainer_notes || '', sections: normalizeUnitToForm(fullUnit) }) setShowModal(true) } catch (err) { alert('Fehler beim Laden: ' + err.message) } } const handleSaveAsTemplate = async () => { const name = window.prompt('Name für die neue Trainingsvorlage (nur Abschnitts-Gliederung):') if (!name?.trim()) return try { await api.createTrainingPlanTemplate({ name: name.trim(), sections: formData.sections.map((s) => ({ title: s.title || 'Abschnitt', guidance_text: s.guidance_notes?.trim() ? s.guidance_notes.trim() : null })) }) await loadPlanTemplates() alert('Vorlage gespeichert.') } catch (err) { alert('Speichern: ' + err.message) } } const handleDelete = async (unit) => { if (!confirm(`Trainingseinheit vom ${unit.planned_date} wirklich löschen?`)) return try { await api.deleteTrainingUnit(unit.id) await loadUnits() } catch (err) { alert('Fehler beim Löschen: ' + err.message) } } const handleSubmit = async (e) => { e.preventDefault() if (!formData.group_id || !formData.planned_date) { alert('Gruppe und Datum sind Pflichtfelder') return } try { const sectionsPayload = buildSectionsPayload(formData.sections) const payload = { planned_date: formData.planned_date, planned_time_start: formData.planned_time_start || null, planned_time_end: formData.planned_time_end || null, planned_focus: formData.planned_focus || null, actual_date: formData.actual_date || null, actual_time_start: formData.actual_time_start || null, actual_time_end: formData.actual_time_end || null, attendance_count: formData.attendance_count ? parseInt(formData.attendance_count, 10) : null, status: formData.status || 'planned', notes: formData.notes || null, trainer_notes: formData.trainer_notes || null, sections: sectionsPayload } if (!editingUnit) { payload.group_id = parseInt(formData.group_id, 10) if (draftPlanTemplateId) { payload.plan_template_id = parseInt(draftPlanTemplateId, 10) } } if (editingUnit) { await api.updateTrainingUnit(editingUnit.id, payload) } else { await api.createTrainingUnit(payload) } setShowModal(false) await loadUnits() } catch (err) { alert('Fehler beim Speichern: ' + err.message) } } const updateFormField = (field, value) => { setFormData((prev) => ({ ...prev, [field]: value })) } const updateSectionField = (sIdx, field, value) => { setFormData((prev) => ({ ...prev, sections: prev.sections.map((s, i) => (i === sIdx ? { ...s, [field]: value } : s)) })) } const addSection = () => { setFormData((prev) => ({ ...prev, sections: [...prev.sections, defaultSection(`Abschnitt ${prev.sections.length + 1}`)] })) } const removeSection = (sIdx) => { setFormData((prev) => { const next = prev.sections.filter((_, i) => i !== sIdx) return { ...prev, sections: next.length ? next : [defaultSection()] } }) } const moveSection = (sIdx, dir) => { setFormData((prev) => { const ta = sIdx + dir if (ta < 0 || ta >= prev.sections.length) return prev const copy = [...prev.sections] ;[copy[sIdx], copy[ta]] = [copy[ta], copy[sIdx]] return { ...prev, sections: copy } }) } const addItem = (sIdx, kind) => { setFormData((prev) => ({ ...prev, sections: prev.sections.map((s, i) => { if (i !== sIdx) return s const row = kind === 'note' ? noteRow() : exerciseRow() return { ...s, items: [...s.items, row] } }) })) } const updateItem = (sIdx, iIdx, field, value) => { setFormData((prev) => ({ ...prev, sections: prev.sections.map((s, si) => { if (si !== sIdx) return s return { ...s, items: s.items.map((it, ii) => { if (ii !== iIdx) return it const next = { ...it, [field]: value } if (field === 'exercise_id') next.exercise_variant_id = '' return next }) } }) })) } const removeItem = (sIdx, iIdx) => { setFormData((prev) => ({ ...prev, sections: prev.sections.map((s, i) => i !== sIdx ? s : { ...s, items: s.items.filter((_, j) => j !== iIdx) } ) })) } const moveItem = (sIdx, iIdx, dir) => { setFormData((prev) => ({ ...prev, sections: prev.sections.map((s, si) => { if (si !== sIdx) return s const items = [...s.items] const ta = iIdx + dir if (ta < 0 || ta >= items.length) return s ;[items[iIdx], items[ta]] = [items[ta], items[iIdx]] return { ...s, items } }) })) } if (loading) { return (
Laden...
{selectedGroup.location || 'Kein Ort angegeben'} {selectedGroup.weekday && ` · ${selectedGroup.weekday}`} {selectedGroup.time_start && ` · ${selectedGroup.time_start.slice(0, 5)} - ${selectedGroup.time_end?.slice(0, 5)}`}
Bitte wähle eine Trainingsgruppe aus
Keine Trainingseinheiten im gewählten Zeitraum
Fokus: {unit.planned_focus}
)}{unit.notes}
)}Lädt die Abschnitte und Hinweise aus der Vorlage; Übungen fügst du hier ein.