diff --git a/frontend/src/app.css b/frontend/src/app.css index 8e66dd1..757bb70 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -6548,9 +6548,110 @@ html.modal-scroll-locked .app-main { min-width: 0; } +/* Übungsformular — Register-Tabs & farbige Bereiche */ +.exercise-form-edit { + padding-top: 4px; +} +.exercise-form-edit__tabbar { + display: flex; + align-items: stretch; + margin: 0 0 16px; + padding: 0 0 12px; + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 6; + background: var(--surface); +} +.exercise-form-edit__tabbar .admin-page-subtabs { + flex: 1; + min-width: 0; +} +.exercise-form-panel { + padding: 4px 0 8px 14px; + margin-bottom: 4px; + border-left: 3px solid var(--border); +} +.exercise-form-panel--basics { + border-left-color: var(--accent); +} +.exercise-form-panel--guide { + border-left-color: color-mix(in srgb, #2563eb 70%, var(--accent)); +} +.exercise-form-panel--classify { + border-left-color: color-mix(in srgb, #7c3aed 65%, var(--accent)); +} +.exercise-form-panel--combo { + border-left-color: color-mix(in srgb, #d97706 70%, var(--accent-dark)); +} +.exercise-form-panel--variants { + border-left-color: color-mix(in srgb, #0891b2 70%, var(--accent)); +} +.exercise-form-panel--media { + border-left-color: color-mix(in srgb, var(--text3) 55%, var(--border)); +} +.exercise-form-panel__title { + margin: 0 0 4px; + font-size: 1.05rem; + font-weight: 700; +} +.exercise-form-panel__hint { + margin: 0 0 14px; + font-size: 12px; + color: var(--text3); + line-height: 1.45; +} +.exercise-form-panel__body { + min-width: 0; +} +.exercise-form-type-box { + padding: 12px; + border-radius: 10px; + border: 1px solid var(--border); + background: var(--surface2); + margin-bottom: 12px; +} +.exercise-form-type-box__hint { + margin: 8px 0 0; + font-size: 12px; + color: var(--text2); + line-height: 1.45; +} +.exercise-form-inline-tab-link { + padding: 0; + border: none; + background: none; + color: var(--accent-dark); + font: inherit; + font-weight: 700; + text-decoration: underline; + cursor: pointer; +} +.exercise-form-subsection { + padding: 12px 0; + border-top: 1px dashed var(--border); + margin-top: 8px; +} +.exercise-form-subsection:first-child { + border-top: none; + margin-top: 0; + padding-top: 0; +} +.exercise-form-subsection__title { + margin: 0 0 6px; + font-size: 0.95rem; + font-weight: 700; +} +.exercise-form-subsection__hint { + margin: 0 0 10px; + font-size: 12px; + color: var(--text3); + line-height: 1.4; +} + /* Übungsformular — Klassifikation & Meta-Chips */ .exercise-form-meta-panel { - margin: 4px 0 16px; + margin: 0; padding: 14px; border: 1px solid var(--border); border-radius: 12px; diff --git a/frontend/src/components/exercises/ExerciseFormLayout.jsx b/frontend/src/components/exercises/ExerciseFormLayout.jsx new file mode 100644 index 0000000..9ce6aba --- /dev/null +++ b/frontend/src/components/exercises/ExerciseFormLayout.jsx @@ -0,0 +1,32 @@ +import React from 'react' +import PageSectionNav from '../PageSectionNav' + +export function ExerciseFormTabBar({ activeTab, onChange, items }) { + return ( +
+ +
+ ) +} + +export function ExerciseFormPanel({ tab, activeTab, tone = 'default', title, hint, children }) { + if (activeTab !== tab) return null + return ( +
+ {title ?

{title}

: null} + {hint ?

{hint}

: null} +
{children}
+
+ ) +} diff --git a/frontend/src/components/exercises/ExerciseFormPageRoot.jsx b/frontend/src/components/exercises/ExerciseFormPageRoot.jsx index f9504a7..cdf6f7d 100644 --- a/frontend/src/components/exercises/ExerciseFormPageRoot.jsx +++ b/frontend/src/components/exercises/ExerciseFormPageRoot.jsx @@ -26,9 +26,10 @@ import { } from '../../utils/activeClub' import { COMBINATION_ARCHETYPE_OPTIONS, ARCHETYPE_DEFAULT_REP_SERIES_COUNT, defaultRepSeriesCountForArchetype } from '../../constants/combinationArchetypes' import { readSlotProfilesV1, normalizeAdvanceMode, parseComboRepSeriesCountUi } from '../../utils/combinationMethodProfileUi' -import { GripVertical } from 'lucide-react' +import { GripVertical, FileText, BookOpen, Tags, Layers, GitBranch, Image as ImageIcon } from 'lucide-react' import UnsavedChangesPrompt from '../UnsavedChangesPrompt' import PageFormEditorChrome from '../PageFormEditorChrome' +import { ExerciseFormTabBar, ExerciseFormPanel } from './ExerciseFormLayout' import { useNavReturn } from '../../hooks/useNavReturn' import { EXERCISES_LIST_PATH, @@ -499,9 +500,45 @@ function ExerciseFormPageRoot() { const [variantSavingId, setVariantSavingId] = useState(null) const [variantBusy, setVariantBusy] = useState(false) const [variantEditSelection, setVariantEditSelection] = useState(null) - const variantsDetailsRef = useRef(null) + const [activeFormTab, setActiveFormTab] = useState('stammdaten') const variantsSavedSnapshotRef = useRef({}) + const exerciseFormTabs = useMemo(() => { + const tabs = [ + { id: 'stammdaten', label: 'Stammdaten', icon: FileText }, + { id: 'anleitung', label: 'Anleitung', icon: BookOpen }, + { id: 'einordnung', label: 'Einordnung', icon: Tags }, + ] + if (formData.exercise_kind === 'combination') { + tabs.push({ id: 'kombination', label: 'Kombination', icon: Layers }) + } + if (isEdit) { + if (formData.exercise_kind !== 'combination') { + tabs.push({ + id: 'varianten', + label: variants.length > 0 ? `Varianten (${variants.length})` : 'Varianten', + icon: GitBranch, + }) + } + tabs.push({ id: 'medien', label: 'Medien & Mehr', icon: ImageIcon }) + } else { + tabs.push({ id: 'varianten', label: 'Varianten', icon: GitBranch, disabled: true }) + tabs.push({ id: 'medien', label: 'Medien & Mehr', icon: ImageIcon, disabled: true }) + } + return tabs + }, [formData.exercise_kind, isEdit, variants.length]) + + useEffect(() => { + const allowed = new Set(exerciseFormTabs.filter((t) => !t.disabled).map((t) => t.id)) + if (!allowed.has(activeFormTab)) setActiveFormTab('stammdaten') + }, [exerciseFormTabs, activeFormTab]) + + useEffect(() => { + if (formData.exercise_kind === 'combination' && activeFormTab === 'varianten') { + setActiveFormTab('kombination') + } + }, [formData.exercise_kind, activeFormTab]) + const syncVariantsSavedSnapshot = useCallback((rows) => { const snap = {} for (const v of rows || []) { @@ -657,10 +694,10 @@ function ExerciseFormPageRoot() { }, [variants, variantEditSelection]) useEffect(() => { - if (variantEditSelection != null && variantsDetailsRef.current) { - variantsDetailsRef.current.open = true + if (variantEditSelection != null && isEdit && formData.exercise_kind !== 'combination') { + setActiveFormTab('varianten') } - }, [variantEditSelection]) + }, [variantEditSelection, isEdit, formData.exercise_kind]) const updateFormField = (field, value) => { setFormDirty(true) @@ -1246,8 +1283,25 @@ function ExerciseFormPageRoot() {

) : null} -
+
+ + +
-
-

Übungstyp

+
+ {formData.exercise_kind === 'combination' ? ( +

+ Stationen und Ablaufprofil im Tab{' '} + + . +

+ ) : null} +
+ +
+
+ + + updateFormField('duration_min', e.target.value ? parseInt(e.target.value, 10) : '') + } + /> +
+
+ + + updateFormField('duration_max', e.target.value ? parseInt(e.target.value, 10) : '') + } + /> +
+
+ +
+
+ + + updateFormField('group_size_min', e.target.value ? parseInt(e.target.value, 10) : '') + } + /> +
+
+ + + updateFormField('group_size_max', e.target.value ? parseInt(e.target.value, 10) : '') + } + /> +
+
+ +
+ +