702 lines
25 KiB
React
702 lines
25 KiB
React
|
|
import React, { useState, useEffect } from 'react'
|
||
|
|
import api from '../utils/api'
|
||
|
|
import { useAuth } from '../context/AuthContext'
|
||
|
|
|
||
|
|
function TrainingPlanningPage() {
|
||
|
|
const { user } = useAuth()
|
||
|
|
const [groups, setGroups] = useState([])
|
||
|
|
const [selectedGroupId, setSelectedGroupId] = useState('')
|
||
|
|
const [units, setUnits] = useState([])
|
||
|
|
const [exercises, setExercises] = useState([])
|
||
|
|
const [loading, setLoading] = useState(true)
|
||
|
|
const [showModal, setShowModal] = useState(false)
|
||
|
|
const [editingUnit, setEditingUnit] = useState(null)
|
||
|
|
|
||
|
|
// Date range (default: next 30 days)
|
||
|
|
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)
|
||
|
|
|
||
|
|
// Form state
|
||
|
|
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: '',
|
||
|
|
exercises: []
|
||
|
|
})
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
loadData()
|
||
|
|
}, [])
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (selectedGroupId) {
|
||
|
|
loadUnits()
|
||
|
|
}
|
||
|
|
}, [selectedGroupId, startDate, endDate])
|
||
|
|
|
||
|
|
const loadData = async () => {
|
||
|
|
try {
|
||
|
|
const [groupsData, exercisesData] = await Promise.all([
|
||
|
|
api.listTrainingGroups({ status: 'active' }),
|
||
|
|
api.listExercises()
|
||
|
|
])
|
||
|
|
setGroups(groupsData)
|
||
|
|
setExercises(exercisesData)
|
||
|
|
|
||
|
|
// Auto-select first group if trainer owns it
|
||
|
|
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 {
|
||
|
|
await api.quickCreateTrainingUnit({
|
||
|
|
group_id: parseInt(selectedGroupId),
|
||
|
|
planned_date: date
|
||
|
|
})
|
||
|
|
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))
|
||
|
|
|
||
|
|
setEditingUnit(null)
|
||
|
|
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: '',
|
||
|
|
exercises: []
|
||
|
|
})
|
||
|
|
setShowModal(true)
|
||
|
|
}
|
||
|
|
|
||
|
|
const handleEdit = async (unit) => {
|
||
|
|
try {
|
||
|
|
const fullUnit = await api.getTrainingUnit(unit.id)
|
||
|
|
setEditingUnit(fullUnit)
|
||
|
|
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 || '',
|
||
|
|
exercises: fullUnit.exercises || []
|
||
|
|
})
|
||
|
|
setShowModal(true)
|
||
|
|
} catch (err) {
|
||
|
|
alert('Fehler beim Laden: ' + 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 payload = {
|
||
|
|
...formData,
|
||
|
|
group_id: parseInt(formData.group_id),
|
||
|
|
attendance_count: formData.attendance_count ? parseInt(formData.attendance_count) : null,
|
||
|
|
exercises: formData.exercises.map((ex, idx) => ({
|
||
|
|
exercise_id: ex.exercise_id,
|
||
|
|
order_index: idx,
|
||
|
|
planned_duration_min: ex.planned_duration_min ? parseInt(ex.planned_duration_min) : null,
|
||
|
|
actual_duration_min: ex.actual_duration_min ? parseInt(ex.actual_duration_min) : null,
|
||
|
|
notes: ex.notes || null,
|
||
|
|
modifications: ex.modifications || null
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
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 addExercise = () => {
|
||
|
|
setFormData(prev => ({
|
||
|
|
...prev,
|
||
|
|
exercises: [...prev.exercises, {
|
||
|
|
exercise_id: '',
|
||
|
|
planned_duration_min: '',
|
||
|
|
actual_duration_min: '',
|
||
|
|
notes: '',
|
||
|
|
modifications: ''
|
||
|
|
}]
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
const updateExercise = (index, field, value) => {
|
||
|
|
setFormData(prev => ({
|
||
|
|
...prev,
|
||
|
|
exercises: prev.exercises.map((ex, i) =>
|
||
|
|
i === index ? { ...ex, [field]: value } : ex
|
||
|
|
)
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
const removeExercise = (index) => {
|
||
|
|
setFormData(prev => ({
|
||
|
|
...prev,
|
||
|
|
exercises: prev.exercises.filter((_, i) => i !== index)
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
const moveExercise = (index, direction) => {
|
||
|
|
const newExercises = [...formData.exercises]
|
||
|
|
const target = index + direction
|
||
|
|
|
||
|
|
if (target < 0 || target >= newExercises.length) return
|
||
|
|
|
||
|
|
[newExercises[index], newExercises[target]] = [newExercises[target], newExercises[index]]
|
||
|
|
setFormData(prev => ({ ...prev, exercises: newExercises }))
|
||
|
|
}
|
||
|
|
|
||
|
|
if (loading) {
|
||
|
|
return (
|
||
|
|
<div style={{ padding: '2rem', textAlign: 'center' }}>
|
||
|
|
<div className="spinner"></div>
|
||
|
|
<p>Laden...</p>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
const selectedGroup = groups.find(g => g.id === parseInt(selectedGroupId))
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div style={{ padding: '2rem' }}>
|
||
|
|
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
||
|
|
<h1 style={{ marginBottom: '1.5rem' }}>Trainingsplanung</h1>
|
||
|
|
|
||
|
|
{/* Group & Date Controls */}
|
||
|
|
<div className="card" style={{ marginBottom: '1.5rem' }}>
|
||
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '1rem' }}>
|
||
|
|
<div>
|
||
|
|
<label className="form-label">Trainingsgruppe</label>
|
||
|
|
<select
|
||
|
|
className="form-input"
|
||
|
|
value={selectedGroupId}
|
||
|
|
onChange={(e) => setSelectedGroupId(e.target.value)}
|
||
|
|
>
|
||
|
|
<option value="">Bitte wählen</option>
|
||
|
|
{groups.map(g => (
|
||
|
|
<option key={g.id} value={g.id}>
|
||
|
|
{g.name} ({g.club_name})
|
||
|
|
</option>
|
||
|
|
))}
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="form-label">Von</label>
|
||
|
|
<input
|
||
|
|
type="date"
|
||
|
|
className="form-input"
|
||
|
|
value={startDate}
|
||
|
|
onChange={(e) => setStartDate(e.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label className="form-label">Bis</label>
|
||
|
|
<input
|
||
|
|
type="date"
|
||
|
|
className="form-input"
|
||
|
|
value={endDate}
|
||
|
|
onChange={(e) => setEndDate(e.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{selectedGroup && (
|
||
|
|
<div style={{ marginTop: '1rem', padding: '1rem', background: 'var(--surface2)', borderRadius: '8px' }}>
|
||
|
|
<p style={{ fontSize: '0.875rem', color: 'var(--text2)' }}>
|
||
|
|
📍 {selectedGroup.location || 'Kein Ort angegeben'}
|
||
|
|
{selectedGroup.weekday && ` · ${selectedGroup.weekday}`}
|
||
|
|
{selectedGroup.time_start && ` · ${selectedGroup.time_start.slice(0, 5)} - ${selectedGroup.time_end?.slice(0, 5)}`}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Action Buttons */}
|
||
|
|
{selectedGroupId && (
|
||
|
|
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1.5rem' }}>
|
||
|
|
<button className="btn btn-primary" onClick={handleCreate}>
|
||
|
|
+ Neue Trainingseinheit
|
||
|
|
</button>
|
||
|
|
<button className="btn btn-secondary" onClick={handleQuickCreate}>
|
||
|
|
⚡ Schnell erstellen
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Units List */}
|
||
|
|
{!selectedGroupId ? (
|
||
|
|
<div className="card">
|
||
|
|
<p style={{ color: 'var(--text2)', textAlign: 'center' }}>
|
||
|
|
Bitte wähle eine Trainingsgruppe aus
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
) : units.length === 0 ? (
|
||
|
|
<div className="card">
|
||
|
|
<p style={{ color: 'var(--text2)', textAlign: 'center' }}>
|
||
|
|
Keine Trainingseinheiten im gewählten Zeitraum
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<div style={{ display: 'grid', gap: '1rem' }}>
|
||
|
|
{units.map(unit => (
|
||
|
|
<div key={unit.id} className="card">
|
||
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', marginBottom: '1rem' }}>
|
||
|
|
<div>
|
||
|
|
<h3 style={{ marginBottom: '0.25rem' }}>
|
||
|
|
{unit.planned_date}
|
||
|
|
{unit.planned_time_start && ` · ${unit.planned_time_start.slice(0, 5)} - ${unit.planned_time_end?.slice(0, 5)}`}
|
||
|
|
</h3>
|
||
|
|
{unit.planned_focus && (
|
||
|
|
<p style={{ color: 'var(--text2)', fontSize: '0.875rem', marginBottom: '0.5rem' }}>
|
||
|
|
🎯 {unit.planned_focus}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
||
|
|
<span style={{
|
||
|
|
fontSize: '0.75rem',
|
||
|
|
padding: '0.25rem 0.5rem',
|
||
|
|
borderRadius: '4px',
|
||
|
|
background: unit.status === 'completed' ? '#2ea44f' : unit.status === 'cancelled' ? 'var(--danger)' : 'var(--surface2)',
|
||
|
|
color: unit.status === 'completed' || unit.status === 'cancelled' ? 'white' : 'var(--text2)'
|
||
|
|
}}>
|
||
|
|
{unit.status === 'planned' && '📅 Geplant'}
|
||
|
|
{unit.status === 'completed' && '✓ Durchgeführt'}
|
||
|
|
{unit.status === 'cancelled' && '✗ Abgesagt'}
|
||
|
|
</span>
|
||
|
|
{unit.attendance_count !== null && (
|
||
|
|
<span style={{
|
||
|
|
fontSize: '0.75rem',
|
||
|
|
padding: '0.25rem 0.5rem',
|
||
|
|
borderRadius: '4px',
|
||
|
|
background: 'var(--surface2)',
|
||
|
|
color: 'var(--text2)'
|
||
|
|
}}>
|
||
|
|
👥 {unit.attendance_count} Teilnehmer
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
||
|
|
<button
|
||
|
|
className="btn btn-secondary"
|
||
|
|
onClick={() => handleEdit(unit)}
|
||
|
|
>
|
||
|
|
Bearbeiten
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
className="btn"
|
||
|
|
style={{
|
||
|
|
background: 'var(--danger)',
|
||
|
|
color: 'white',
|
||
|
|
border: 'none'
|
||
|
|
}}
|
||
|
|
onClick={() => handleDelete(unit)}
|
||
|
|
>
|
||
|
|
Löschen
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{unit.notes && (
|
||
|
|
<p style={{ color: 'var(--text2)', fontSize: '0.875rem', marginTop: '0.5rem' }}>
|
||
|
|
💬 {unit.notes}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Create/Edit Modal */}
|
||
|
|
{showModal && (
|
||
|
|
<div style={{
|
||
|
|
position: 'fixed',
|
||
|
|
top: 0,
|
||
|
|
left: 0,
|
||
|
|
right: 0,
|
||
|
|
bottom: 0,
|
||
|
|
background: 'rgba(0,0,0,0.5)',
|
||
|
|
display: 'flex',
|
||
|
|
alignItems: 'center',
|
||
|
|
justifyContent: 'center',
|
||
|
|
zIndex: 1000,
|
||
|
|
padding: '1rem',
|
||
|
|
overflowY: 'auto'
|
||
|
|
}}>
|
||
|
|
<div style={{
|
||
|
|
background: 'var(--surface)',
|
||
|
|
borderRadius: '12px',
|
||
|
|
padding: '2rem',
|
||
|
|
maxWidth: '900px',
|
||
|
|
width: '100%',
|
||
|
|
maxHeight: '90vh',
|
||
|
|
overflowY: 'auto',
|
||
|
|
margin: '1rem'
|
||
|
|
}}>
|
||
|
|
<h2 style={{ marginBottom: '1.5rem' }}>
|
||
|
|
{editingUnit ? 'Trainingseinheit bearbeiten' : 'Neue Trainingseinheit'}
|
||
|
|
</h2>
|
||
|
|
|
||
|
|
<form onSubmit={handleSubmit}>
|
||
|
|
{/* Basic Info */}
|
||
|
|
<h3 style={{ marginBottom: '1rem' }}>Planung</h3>
|
||
|
|
|
||
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '1rem', marginBottom: '1rem' }}>
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Datum *</label>
|
||
|
|
<input
|
||
|
|
type="date"
|
||
|
|
className="form-input"
|
||
|
|
value={formData.planned_date}
|
||
|
|
onChange={(e) => updateFormField('planned_date', e.target.value)}
|
||
|
|
required
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Von</label>
|
||
|
|
<input
|
||
|
|
type="time"
|
||
|
|
className="form-input"
|
||
|
|
value={formData.planned_time_start}
|
||
|
|
onChange={(e) => updateFormField('planned_time_start', e.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Bis</label>
|
||
|
|
<input
|
||
|
|
type="time"
|
||
|
|
className="form-input"
|
||
|
|
value={formData.planned_time_end}
|
||
|
|
onChange={(e) => updateFormField('planned_time_end', e.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Trainingsfokus</label>
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
className="form-input"
|
||
|
|
value={formData.planned_focus}
|
||
|
|
onChange={(e) => updateFormField('planned_focus', e.target.value)}
|
||
|
|
placeholder="z.B. Kihon Grundlagen, Kata Heian Shodan"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Exercises */}
|
||
|
|
<h3 style={{ marginTop: '2rem', marginBottom: '1rem' }}>Übungen</h3>
|
||
|
|
|
||
|
|
{formData.exercises.length === 0 ? (
|
||
|
|
<p style={{ color: 'var(--text2)', marginBottom: '1rem' }}>
|
||
|
|
Noch keine Übungen hinzugefügt
|
||
|
|
</p>
|
||
|
|
) : (
|
||
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginBottom: '1rem' }}>
|
||
|
|
{formData.exercises.map((ex, idx) => (
|
||
|
|
<div key={idx} style={{
|
||
|
|
display: 'grid',
|
||
|
|
gridTemplateColumns: '30px 1fr 80px auto',
|
||
|
|
gap: '0.5rem',
|
||
|
|
alignItems: 'center',
|
||
|
|
padding: '0.5rem',
|
||
|
|
background: 'var(--surface2)',
|
||
|
|
borderRadius: '8px'
|
||
|
|
}}>
|
||
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={() => moveExercise(idx, -1)}
|
||
|
|
disabled={idx === 0}
|
||
|
|
style={{
|
||
|
|
padding: '2px',
|
||
|
|
fontSize: '0.75rem',
|
||
|
|
background: 'none',
|
||
|
|
border: 'none',
|
||
|
|
cursor: idx === 0 ? 'not-allowed' : 'pointer',
|
||
|
|
opacity: idx === 0 ? 0.3 : 1
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
▲
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={() => moveExercise(idx, 1)}
|
||
|
|
disabled={idx === formData.exercises.length - 1}
|
||
|
|
style={{
|
||
|
|
padding: '2px',
|
||
|
|
fontSize: '0.75rem',
|
||
|
|
background: 'none',
|
||
|
|
border: 'none',
|
||
|
|
cursor: idx === formData.exercises.length - 1 ? 'not-allowed' : 'pointer',
|
||
|
|
opacity: idx === formData.exercises.length - 1 ? 0.3 : 1
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
▼
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<select
|
||
|
|
className="form-input"
|
||
|
|
value={ex.exercise_id}
|
||
|
|
onChange={(e) => updateExercise(idx, 'exercise_id', parseInt(e.target.value))}
|
||
|
|
style={{ margin: 0 }}
|
||
|
|
>
|
||
|
|
<option value="">Übung wählen</option>
|
||
|
|
{exercises.map(exercise => (
|
||
|
|
<option key={exercise.id} value={exercise.id}>
|
||
|
|
{exercise.title}
|
||
|
|
</option>
|
||
|
|
))}
|
||
|
|
</select>
|
||
|
|
|
||
|
|
<input
|
||
|
|
type="number"
|
||
|
|
className="form-input"
|
||
|
|
value={ex.planned_duration_min}
|
||
|
|
onChange={(e) => updateExercise(idx, 'planned_duration_min', e.target.value)}
|
||
|
|
placeholder="min"
|
||
|
|
style={{ margin: 0 }}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
className="btn"
|
||
|
|
onClick={() => removeExercise(idx)}
|
||
|
|
style={{
|
||
|
|
padding: '0.5rem',
|
||
|
|
background: 'var(--danger)',
|
||
|
|
color: 'white',
|
||
|
|
border: 'none'
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
✗
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
className="btn btn-secondary"
|
||
|
|
onClick={addExercise}
|
||
|
|
style={{ marginBottom: '2rem' }}
|
||
|
|
>
|
||
|
|
+ Übung hinzufügen
|
||
|
|
</button>
|
||
|
|
|
||
|
|
{/* Durchführung (nur bei Edit) */}
|
||
|
|
{editingUnit && (
|
||
|
|
<>
|
||
|
|
<h3 style={{ marginTop: '2rem', marginBottom: '1rem' }}>Durchführung</h3>
|
||
|
|
|
||
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '1rem', marginBottom: '1rem' }}>
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Tatsächliches Datum</label>
|
||
|
|
<input
|
||
|
|
type="date"
|
||
|
|
className="form-input"
|
||
|
|
value={formData.actual_date}
|
||
|
|
onChange={(e) => updateFormField('actual_date', e.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Von</label>
|
||
|
|
<input
|
||
|
|
type="time"
|
||
|
|
className="form-input"
|
||
|
|
value={formData.actual_time_start}
|
||
|
|
onChange={(e) => updateFormField('actual_time_start', e.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Bis</label>
|
||
|
|
<input
|
||
|
|
type="time"
|
||
|
|
className="form-input"
|
||
|
|
value={formData.actual_time_end}
|
||
|
|
onChange={(e) => updateFormField('actual_time_end', e.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Teilnehmer</label>
|
||
|
|
<input
|
||
|
|
type="number"
|
||
|
|
className="form-input"
|
||
|
|
value={formData.attendance_count}
|
||
|
|
onChange={(e) => updateFormField('attendance_count', e.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Status</label>
|
||
|
|
<select
|
||
|
|
className="form-input"
|
||
|
|
value={formData.status}
|
||
|
|
onChange={(e) => updateFormField('status', e.target.value)}
|
||
|
|
>
|
||
|
|
<option value="planned">Geplant</option>
|
||
|
|
<option value="completed">Durchgeführt</option>
|
||
|
|
<option value="cancelled">Abgesagt</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Notes */}
|
||
|
|
<h3 style={{ marginTop: '2rem', marginBottom: '1rem' }}>Notizen</h3>
|
||
|
|
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Öffentliche Notizen</label>
|
||
|
|
<textarea
|
||
|
|
className="form-input"
|
||
|
|
rows={3}
|
||
|
|
value={formData.notes}
|
||
|
|
onChange={(e) => updateFormField('notes', e.target.value)}
|
||
|
|
placeholder="Für alle Teilnehmer sichtbar"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="form-row">
|
||
|
|
<label className="form-label">Trainernotizen</label>
|
||
|
|
<textarea
|
||
|
|
className="form-input"
|
||
|
|
rows={3}
|
||
|
|
value={formData.trainer_notes}
|
||
|
|
onChange={(e) => updateFormField('trainer_notes', e.target.value)}
|
||
|
|
placeholder="Nur für Trainer sichtbar"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Buttons */}
|
||
|
|
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '1.5rem' }}>
|
||
|
|
<button type="submit" className="btn btn-primary" style={{ flex: 1 }}>
|
||
|
|
{editingUnit ? 'Speichern' : 'Erstellen'}
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
className="btn btn-secondary"
|
||
|
|
onClick={() => setShowModal(false)}
|
||
|
|
>
|
||
|
|
Abbrechen
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</form>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export default TrainingPlanningPage
|