From 13efce6e3614a6023caef8130c393668731231aa Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 13 May 2026 09:06:10 +0200 Subject: [PATCH] feat(training-unit-editor): enhance combo planning features and UI updates - Added a new function `compactComboPlanningCaption` to display planning status with archetype labels. - Introduced state management for a modal to edit combination planning profiles, improving user interaction. - Updated UI components to enhance the display and interaction of combination exercises, including styling adjustments for better usability. - Implemented keyboard event handling for modal closure, enhancing user experience. Co-Authored-By: Claude Sonnet 4.6 --- .../components/TrainingUnitSectionsEditor.jsx | 235 +++++++++++++----- 1 file changed, 178 insertions(+), 57 deletions(-) diff --git a/frontend/src/components/TrainingUnitSectionsEditor.jsx b/frontend/src/components/TrainingUnitSectionsEditor.jsx index c824305..201fc13 100644 --- a/frontend/src/components/TrainingUnitSectionsEditor.jsx +++ b/frontend/src/components/TrainingUnitSectionsEditor.jsx @@ -2,6 +2,7 @@ import React, { Fragment, useCallback, useEffect, useState } from 'react' import { GripVertical, Pencil } from 'lucide-react' import CombinationMethodProfileEditor from './CombinationMethodProfileEditor' import { comboPlanningProfileJsonForEditor } from '../utils/comboPlanningMethodProfile' +import { combinationArchetypeLabel } from '../constants/combinationArchetypes' import { defaultSection, exerciseRow, @@ -59,6 +60,20 @@ function gatherPlanningModuleOutline(items, startIdx, moduleId) { const MODULE_OUTLINE_PREVIEW_MAX = 8 +/** Statuszeile: Planungs‑Override vs. Katalog, inkl. Archetyp‑Label wenn bekannt. */ +function compactComboPlanningCaption(it) { + const overridden = + it.planning_method_profile != null && + typeof it.planning_method_profile === 'object' && + !Array.isArray(it.planning_method_profile) + const archRaw = String(it.catalog_method_archetype || '').trim() + const archLbl = archRaw ? combinationArchetypeLabel(archRaw) : null + if (overridden) { + return archLbl ? `${archLbl} · Planung angepasst` : 'Planung angepasst' + } + return archLbl ? `${archLbl} · wie Katalog` : 'wie im Katalog' +} + /** Stabile Farbzurodnung aus Modul-ID (nur Darstellung). */ function planningModulePalette(moduleId) { const id = normalizedPlanningModuleChainId(moduleId) @@ -290,6 +305,8 @@ export default function TrainingUnitSectionsEditor({ } const [textEdit, setTextEdit] = useState(null) + /** Kombi: Ablaufprofil in Modal statt einzuklappender Karte */ + const [comboPlanningModal, setComboPlanningModal] = useState(null) /** { sIdx: number, beforeIx: number } – Einfüge-Popup („+“ zwischen Zeilen) */ const [insertChooser, setInsertChooser] = useState(null) const [draggingPos, setDraggingPos] = useState(null) @@ -316,6 +333,27 @@ export default function TrainingUnitSectionsEditor({ return () => window.removeEventListener('keydown', onKey) }, [insertChooser]) + useEffect(() => { + if (!comboPlanningModal) return + const onKey = (e) => { + if (e.key === 'Escape') setComboPlanningModal(null) + } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [comboPlanningModal]) + + useEffect(() => { + if (!comboPlanningModal) return + const L = ensure(sections) + const { sIdx, iIdx } = comboPlanningModal + const row = L[sIdx]?.items?.[iIdx] + const ok = + row && + String(row.exercise_kind || '').toLowerCase().trim() === 'combination' && + row.exercise_id + if (!ok) setComboPlanningModal(null) + }, [sections, comboPlanningModal]) + const closeInsertChooser = useCallback(() => setInsertChooser(null), []) const insertSlotKeyPrefix = @@ -533,6 +571,23 @@ export default function TrainingUnitSectionsEditor({ const list = ensure(sections) + let comboPlanningModalItem = null + let comboPlanningModalSX = null + let comboPlanningModalIX = null + if (comboPlanningModal) { + const { sIdx, iIdx } = comboPlanningModal + const cand = list[sIdx]?.items?.[iIdx] + if ( + cand && + String(cand.exercise_kind || '').toLowerCase().trim() === 'combination' && + cand.exercise_id + ) { + comboPlanningModalItem = cand + comboPlanningModalSX = sIdx + comboPlanningModalIX = iIdx + } + } + return (
-
- - Ablaufprofil für diese Planung (Kombination) - - {it.planning_method_profile != null && - typeof it.planning_method_profile === 'object' && - !Array.isArray(it.planning_method_profile) - ? '— Anpassung aktiv' - : '— wie im Katalog'} - - -
-
- - -
- { - try { - const obj = JSON.parse(json || '{}') - if (obj && typeof obj === 'object' && !Array.isArray(obj)) { - updateItem(sIdx, iIdx, 'planning_method_profile', obj) - } - } catch { - /* Ungültiges JSON — Hinweis im Editor */ - } - }} - /> -
-
+ + Ablauf:  + {compactComboPlanningCaption(it)} + +
) : null} @@ -1322,6 +1348,101 @@ export default function TrainingUnitSectionsEditor({ ) : null} + {comboPlanningModalItem != null && + comboPlanningModalSX != null && + comboPlanningModalIX != null ? ( +
{ + if (e.target === e.currentTarget) setComboPlanningModal(null) + }} + > +
e.stopPropagation()} + style={{ + maxWidth: 'min(920px, 96vw)', + maxHeight: 'min(800px, 88vh)', + overflow: 'auto', + }} + > +

+ Ablaufprofil dieser Kombination für diese Planung +

+

+ + {(comboPlanningModalItem.exercise_title || '').trim() || + `Kombination #${comboPlanningModalItem.exercise_id}`} + + + ({compactComboPlanningCaption(comboPlanningModalItem)}) + +

+
+ + +
+ { + try { + const obj = JSON.parse(json || '{}') + if (obj && typeof obj === 'object' && !Array.isArray(obj)) { + updateItem(comboPlanningModalSX, comboPlanningModalIX, 'planning_method_profile', obj) + } + } catch { + /* Ungültiges JSON — Hinweis im Editor */ + } + }} + /> +
+ +
+
+
+ ) : null} + {textEdit ? (