feat: refactor TrainingUnitSectionsEditor and enhance TrainingPlanningPage layout
All checks were successful
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 7s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Successful in 23s

- Replaced the tu-ex-run-block with a new tu-ex-debrief layout using CSS Grid for improved structure and responsiveness.
- Updated the TrainingUnitSectionsEditor component to utilize the new layout, enhancing the user experience for inputting modifications and actual durations.
- Introduced a sectionsEditMode state in TrainingPlanningPage to manage different editing modes (planning, refine, debrief) for better user guidance.
- Adjusted the visibility of execution extras based on the current editing mode, streamlining the interface for users.
This commit is contained in:
Lars 2026-05-06 07:55:35 +02:00
parent 56ea36ea25
commit 00b22a756f
3 changed files with 133 additions and 25 deletions

View File

@ -3445,22 +3445,60 @@ a.analysis-split__nav-item {
white-space: nowrap;
}
.tu-ex-run-block {
display: block;
.tu-ex-debrief {
display: grid;
grid-template-columns: minmax(0, 1fr) 4.75rem;
gap: 10px 14px;
align-items: start;
width: 100%;
margin-top: 10px;
font-size: 0.78rem;
padding-top: 10px;
border-top: 1px solid var(--border2, rgba(0, 0, 0, 0.08));
box-sizing: border-box;
}
.tu-ex-run-block__controls {
.tu-ex-debrief__grow {
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 5px;
gap: 5px;
}
.tu-ex-run-block__controls .form-input:first-of-type {
max-width: 120px;
.tu-ex-debrief__textarea {
width: 100%;
max-width: 100%;
box-sizing: border-box;
min-height: 4.75rem;
resize: vertical;
font-size: 0.88rem;
line-height: 1.45;
}
.tu-ex-debrief__ist {
display: flex;
flex-direction: column;
gap: 5px;
align-items: stretch;
justify-self: end;
width: 4.75rem;
flex-shrink: 0;
}
.tu-ex-debrief__ist .tu-ex-duration {
width: 100%;
margin: 0;
}
@media (max-width: 520px) {
.tu-ex-debrief {
grid-template-columns: 1fr;
}
.tu-ex-debrief__ist {
width: 100%;
max-width: 10rem;
justify-self: start;
}
}
.tu-textedit-backdrop {

View File

@ -763,30 +763,34 @@ export default function TrainingUnitSectionsEditor({
</div>
{showExecutionExtras ? (
<label className="tu-ex-run-block form-label">
Ist-Dauer / Anpassungen
<span className="tu-ex-run-block__controls">
<div className="tu-ex-debrief">
<div className="tu-ex-debrief__grow">
<span className="tu-item-row__meta-label">Abweichungen beim Durchführen</span>
<textarea
className="form-input tu-ex-debrief__textarea"
rows={3}
value={it.modifications || ''}
onChange={(e) =>
updateItem(sIdx, iIdx, 'modifications', e.target.value)
}
placeholder="Was lief anders? Anpassungen für spätere Planung…"
/>
</div>
<div className="tu-ex-debrief__ist">
<span className="tu-item-row__meta-label">Ist (Min)</span>
<input
type="number"
className="form-input"
className="form-input tu-ex-duration"
min={1}
value={it.actual_duration_min}
onChange={(e) =>
updateItem(sIdx, iIdx, 'actual_duration_min', e.target.value)
}
placeholder="IST min"
placeholder="IST"
title="Tatsächliche Dauer (Minuten); dieselbe Spaltenbreite wie „Min“ (Plan) oben"
/>
<textarea
className="form-input"
rows={2}
value={it.modifications || ''}
onChange={(e) =>
updateItem(sIdx, iIdx, 'modifications', e.target.value)
}
placeholder="Abweichungen beim Durchführen"
/>
</span>
</label>
</div>
</div>
) : null}
</div>
)

View File

@ -118,6 +118,8 @@ function TrainingPlanningPage() {
const [loading, setLoading] = useState(true)
const [showModal, setShowModal] = useState(false)
const [editingUnit, setEditingUnit] = useState(null)
/** Abschnitts-Editor: Planung / strukturelle Überarbeitung (ohne Ist-Felder) / Nachbereitung (Ist & Abweichungen) */
const [sectionsEditMode, setSectionsEditMode] = useState('planning')
const [draftPlanTemplateId, setDraftPlanTemplateId] = useState('')
const [quickTemplateId, setQuickTemplateId] = useState('')
const [exercisePickerOpen, setExercisePickerOpen] = useState(false)
@ -505,6 +507,7 @@ function TrainingPlanningPage() {
sections: [defaultSection('Hauptteil')],
...sessionAssignDefaults()
})
setSectionsEditMode('planning')
setShowModal(true)
}
@ -532,6 +535,7 @@ function TrainingPlanningPage() {
sections: [defaultSection('Hauptteil')],
...sessionAssignDefaults()
})
setSectionsEditMode('planning')
setShowModal(true)
}
@ -593,6 +597,7 @@ function TrainingPlanningPage() {
return xs
})(),
})
setSectionsEditMode(fullUnit.status === 'completed' ? 'debrief' : 'planning')
setShowModal(true)
} catch (err) {
alert('Fehler beim Laden: ' + err.message)
@ -2254,6 +2259,67 @@ function TrainingPlanningPage() {
</div>
<div style={{ marginTop: '2rem' }}>
{editingUnit ? (
<div style={{ marginBottom: '1rem' }}>
<div
role="radiogroup"
aria-label="Modus für Abschnitte und Übungen"
style={{
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
gap: '10px',
}}
>
<span className="form-label" style={{ marginBottom: 0, fontSize: '0.82rem' }}>
Ablauf bearbeiten als
</span>
<div
style={{
display: 'inline-flex',
borderRadius: '10px',
border: '1.5px solid var(--border2)',
overflow: 'hidden',
background: 'var(--surface2)',
}}
>
{[
{ id: 'planning', label: 'Planung' },
{ id: 'refine', label: 'Überarbeiten' },
{ id: 'debrief', label: 'Nachbereitung' },
].map((opt, i) => (
<button
key={opt.id}
type="button"
role="radio"
aria-checked={sectionsEditMode === opt.id}
onClick={() => setSectionsEditMode(opt.id)}
style={{
border: 'none',
padding: '8px 14px',
fontWeight: 600,
fontSize: '0.85rem',
cursor: 'pointer',
background: sectionsEditMode === opt.id ? 'var(--accent-dark)' : 'transparent',
color: sectionsEditMode === opt.id ? '#fff' : 'var(--text1)',
whiteSpace: 'nowrap',
...(i > 0 ? { borderLeft: '1.5px solid var(--border2)' } : {}),
}}
>
{opt.label}
</button>
))}
</div>
</div>
<p style={{ margin: '0.35rem 0 0', fontSize: '0.82rem', color: 'var(--text3)', lineHeight: 1.45 }}>
{sectionsEditMode === 'debrief'
? 'IstMinuten rechts in derselben Spaltenbreite wie „Min“ (Plan); Abweichungen als Text über die volle Breite.'
: sectionsEditMode === 'refine'
? 'Struktur, Übungen und geplante Zeiten anpassen — ohne IstAngaben in den Zeilen (wie Planung).'
: 'Ablauf und geplante Minuten bearbeiten. IstWerte und Abweichungen nur unter „Nachbereitung“.'}
</p>
</div>
) : null}
<TrainingUnitSectionsEditor
heading="Abschnitte & Übungen"
headingAccessory={
@ -2279,7 +2345,7 @@ function TrainingPlanningPage() {
onPeekExercise={(id, variantId) =>
setPlanningPeekCtx({ exerciseId: id, variantId: variantId ?? null })
}
showExecutionExtras={!!editingUnit}
showExecutionExtras={Boolean(editingUnit) && sectionsEditMode === 'debrief'}
/>
</div>