refactor(ui): enhance styling and structure of training unit sections and combination plan bracket
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m5s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 11s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m8s
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m5s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 11s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m8s
- Updated CSS for training unit sections to improve layout and responsiveness, ensuring combo planning strips are displayed correctly. - Refactored CombinationPlanBracket component to accept additional class names for better customization. - Removed unused functions and streamlined imports in TrainingUnitSectionsEditor for cleaner code. - Reintroduced ExercisePickerModal with improved placement in ExerciseFormPage for better user experience.
This commit is contained in:
parent
9da29a2231
commit
930a786315
|
|
@ -5414,22 +5414,80 @@ a.analysis-split__nav-item {
|
|||
0 2px 12px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
/* Kombinations‑Strip: volle Breite unter der Zeile, begrenzte Textbreite — Hauptzeile (Name/Min.) nicht verdrängen */
|
||||
/* Kombinationszeile: immer unter Hauptzeile (Titel / Minuten / Aktionen), nicht daneben */
|
||||
.training-unit-sections-editor .tu-item-row--exercise.tu-item-row--combo {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .tu-item-row--exercise.tu-item-row--combo .tu-item-row__mainline {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Kombinations‑Strip: volle Breite; oben „Ablauf bearbeiten“, darunter Klammer‑Vorschau */
|
||||
.training-unit-sections-editor .tu-combo-planning-strip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
padding: 10px 12px 12px;
|
||||
border-top: 1px solid color-mix(in srgb, var(--border2) 85%, var(--accent) 12%);
|
||||
background: color-mix(in srgb, var(--surface2) 65%, var(--surface));
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .tu-combo-planning-strip__meta {
|
||||
width: 100%;
|
||||
max-width: min(100%, 42rem);
|
||||
.training-unit-sections-editor--item-drag .tu-item-row--combo .tu-combo-planning-strip {
|
||||
padding-left: 44px;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .tu-combo-planning-strip__toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .tu-combo-planning-strip__meta--fallback {
|
||||
font-size: 0.78rem;
|
||||
color: var(--text2);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .tu-combo-planning-strip__bracket-wrap {
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .tu-combo-planning-strip > .btn {
|
||||
align-self: flex-start;
|
||||
.training-unit-sections-editor .combo-plan-bracket--planning-embed {
|
||||
font-size: 0.93rem;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__station {
|
||||
padding: 8px 9px;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__chip {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__globals-title {
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__head-main {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__kicker {
|
||||
font-size: 0.62rem;
|
||||
}
|
||||
|
||||
.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__archetype {
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.tu-planning-mod-tag {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export default function CombinationPlanBracket({
|
|||
/** 'none' | 'link' (Router) | 'button' (z. B. ExercisePeekModal / PWA-sicher) */
|
||||
candidateInteraction = 'none',
|
||||
onCandidatePeek,
|
||||
className,
|
||||
}) {
|
||||
const arch = typeof methodArchetype === 'string' ? methodArchetype.trim() : ''
|
||||
const archLabel = arch ? combinationArchetypeLabel(arch) : null
|
||||
|
|
@ -59,7 +60,7 @@ export default function CombinationPlanBracket({
|
|||
const coachHint = arch ? archetypeCoachHint(arch) : ''
|
||||
|
||||
return (
|
||||
<div className="combo-plan-bracket">
|
||||
<div className={['combo-plan-bracket', className].filter(Boolean).join(' ')}>
|
||||
<div className="combo-plan-bracket__accent" aria-hidden />
|
||||
<div className="combo-plan-bracket__body">
|
||||
<header className="combo-plan-bracket__head">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { GripVertical, Pencil } from 'lucide-react'
|
|||
import CombinationMethodProfileEditor from './CombinationMethodProfileEditor'
|
||||
import CombinationPlanBracket from './CombinationPlanBracket'
|
||||
import { comboPlanningProfileJsonForEditor, effectiveComboMethodProfile } from '../utils/comboPlanningMethodProfile'
|
||||
import { combinationArchetypeLabel, sortCombinationSlotsForDisplay } from '../constants/combinationArchetypes'
|
||||
import { sortCombinationSlotsForDisplay } from '../constants/combinationArchetypes'
|
||||
import {
|
||||
cloneJsonSerializablePlanningProfile,
|
||||
comboSlotsOutlineForProfileEditor,
|
||||
|
|
@ -13,7 +13,6 @@ import {
|
|||
sectionPlannedMinutes,
|
||||
} from '../utils/trainingUnitSectionsForm'
|
||||
import api from '../utils/api'
|
||||
import { effectiveStationTimingSummary, readSlotProfilesV1 } from '../utils/combinationMethodProfileUi'
|
||||
import { isCompactTagLegendMode } from '../config/planningModuleUx'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
|
||||
|
|
@ -74,60 +73,6 @@ function compactComboPlanningCaption(it) {
|
|||
return overridden ? 'Planung angepasst' : 'wie Katalog'
|
||||
}
|
||||
|
||||
/** Globale Eckdaten aus effective profile (optional unter Stationenliste). */
|
||||
function comboRoughGlobalTimingHint(profileObj, archetypeKey) {
|
||||
if (!profileObj || typeof profileObj !== 'object' || Array.isArray(profileObj)) return null
|
||||
const bits = []
|
||||
const rounds = profileObj.rounds
|
||||
const ws = profileObj.work_seconds
|
||||
const rb = profileObj.rest_between_rounds_sec
|
||||
const hint = profileObj.hint_step_duration_sec
|
||||
const globRest = profileObj.rest_between_sets_sec
|
||||
if (rounds != null && rounds !== '') bits.push(`${rounds} Runden`)
|
||||
if (ws != null && ws !== '') bits.push(`${ws}s Arbeit`)
|
||||
if (rb != null && rb !== '') bits.push(`Pause ${rb}s`)
|
||||
if (globRest != null && globRest !== '') bits.push(`Sets-Pause ${globRest}s`)
|
||||
if (hint != null && hint !== '') bits.push(`Orientierung ~${hint}s`)
|
||||
const arch = (archetypeKey || '').trim()
|
||||
if (arch === 'time_domain_interval') {
|
||||
const iw = profileObj.interval_work_sec
|
||||
const ir = profileObj.interval_rest_sec
|
||||
const ig = profileObj.interval_groups
|
||||
if (iw != null && iw !== '') bits.push(`${iw}s Intervall`)
|
||||
if (ir != null && ir !== '') bits.push(`${ir}s Erholung`)
|
||||
if (ig != null && ig !== '') bits.push(`${ig} Gruppen`)
|
||||
}
|
||||
return bits.length ? bits.join(' · ') : null
|
||||
}
|
||||
|
||||
/** Pro Station eine kompakte Textzeile für die Planungsliste. */
|
||||
function comboPlanningStripBulletTexts(it) {
|
||||
const slots = sortCombinationSlotsForDisplay(it.combination_slots || [])
|
||||
if (!slots.length) return []
|
||||
const mp = effectiveComboMethodProfile(it.catalog_method_profile || {}, it.planning_method_profile)
|
||||
const archRaw = String(it.catalog_method_archetype || '').trim()
|
||||
const byIx = new Map(readSlotProfilesV1(mp).map((r) => [Number(r.slot_index), r]))
|
||||
const titles = it.combo_member_title_by_id || {}
|
||||
return slots.map((slot, idx) => {
|
||||
const siRaw = slot.slot_index
|
||||
const siParsed =
|
||||
siRaw === '' || siRaw == null ? idx : typeof siRaw === 'number' ? siRaw : parseInt(String(siRaw), 10)
|
||||
const ix = Number.isFinite(siParsed) ? siParsed : idx
|
||||
const stationLbl = ((slot.title || '').trim() || `Station ${idx + 1}`)
|
||||
const candIds = (slot.candidate_exercise_ids || [])
|
||||
.map((raw) => (typeof raw === 'number' ? raw : parseInt(String(raw), 10)))
|
||||
.filter((n) => Number.isFinite(n))
|
||||
const namesJoined =
|
||||
candIds.length === 0
|
||||
? '(keine Übung)'
|
||||
: candIds.map((id) => titles[String(id)] || `Übung ${id}`).join(' ↔ ')
|
||||
const timing = effectiveStationTimingSummary(archRaw, mp, byIx.get(ix))
|
||||
let line = `${stationLbl}: ${namesJoined}`
|
||||
if (timing) line += ` · ${timing}`
|
||||
return line
|
||||
})
|
||||
}
|
||||
|
||||
/** Stabile Farbzurodnung aus Modul-ID (nur Darstellung). */
|
||||
function planningModulePalette(moduleId) {
|
||||
const id = normalizedPlanningModuleChainId(moduleId)
|
||||
|
|
@ -703,7 +648,8 @@ export default function TrainingUnitSectionsEditor({
|
|||
<div
|
||||
className={
|
||||
'training-unit-sections-editor' +
|
||||
(wideExerciseGrid ? ' training-unit-sections-editor--wide' : '')
|
||||
(wideExerciseGrid ? ' training-unit-sections-editor--wide' : '') +
|
||||
(enableItemDragReorder ? ' training-unit-sections-editor--item-drag' : '')
|
||||
}
|
||||
>
|
||||
{(!hideHeading || headingAccessory) ? (
|
||||
|
|
@ -1017,10 +963,6 @@ export default function TrainingUnitSectionsEditor({
|
|||
|
||||
const stripArchRaw =
|
||||
isCombination && it.exercise_id ? String(it.catalog_method_archetype || '').trim() : ''
|
||||
const stripArchLbl =
|
||||
stripArchRaw && isCombination ? combinationArchetypeLabel(stripArchRaw) : null
|
||||
const stripBullets =
|
||||
isCombination && it.exercise_id ? comboPlanningStripBulletTexts(it) : []
|
||||
const stripMpEff =
|
||||
isCombination && it.exercise_id
|
||||
? effectiveComboMethodProfile(
|
||||
|
|
@ -1028,17 +970,15 @@ export default function TrainingUnitSectionsEditor({
|
|||
it.planning_method_profile,
|
||||
)
|
||||
: null
|
||||
const stripGlobalRough =
|
||||
isCombination && it.exercise_id && stripMpEff
|
||||
? comboRoughGlobalTimingHint(stripMpEff, stripArchRaw)
|
||||
: null
|
||||
|
||||
return (
|
||||
<Fragment key={`${insertSlotKeyPrefix}sec-${sIdx}-blk-${iIdx}`}>
|
||||
{!planningCompactLegend &&
|
||||
renderModulePlanningHead(modBandTitle, modOutline, showModuleBand)}
|
||||
<div
|
||||
className={`${rowCommon} tu-item-row--exercise${fromModClass}`}
|
||||
className={`${rowCommon} tu-item-row--exercise${fromModClass}${
|
||||
isCombination && it.exercise_id ? ' tu-item-row--combo' : ''
|
||||
}`}
|
||||
{...dndRowProps}
|
||||
style={modBorderVarStyle}
|
||||
>
|
||||
|
|
@ -1215,70 +1155,11 @@ export default function TrainingUnitSectionsEditor({
|
|||
</div>
|
||||
|
||||
{isCombination && it.exercise_id ? (
|
||||
<div
|
||||
className="tu-combo-planning-strip"
|
||||
style={{
|
||||
padding: '8px 12px 10px',
|
||||
paddingLeft: enableItemDragReorder ? 44 : 12,
|
||||
borderTop: '1px solid var(--border)',
|
||||
background: 'var(--surface2)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="tu-combo-planning-strip__meta"
|
||||
style={{
|
||||
fontSize: '0.78rem',
|
||||
color: 'var(--text2)',
|
||||
lineHeight: 1.45,
|
||||
}}
|
||||
title="Stationen und grobe Zeiten aus Katalog bzw. Planungs-Anpassung — Details unter „Ablauf bearbeiten“ oder „Vorschau“"
|
||||
>
|
||||
<div style={{ marginBottom: stripBullets.length || stripGlobalRough ? 6 : 0 }}>
|
||||
<strong style={{ color: 'var(--text1)', fontWeight: 600 }}>Archetyp: </strong>
|
||||
<span style={{ color: 'var(--text1)' }}>
|
||||
{stripArchLbl || stripArchRaw || '—'}
|
||||
</span>
|
||||
<span style={{ marginLeft: 10, fontWeight: 500, whiteSpace: 'nowrap' }}>
|
||||
{compactComboPlanningCaption(it)}
|
||||
</span>
|
||||
</div>
|
||||
{stripGlobalRough ? (
|
||||
<div
|
||||
style={{
|
||||
marginBottom: stripBullets.length ? 6 : 0,
|
||||
fontSize: '0.74rem',
|
||||
color: 'var(--text3)',
|
||||
}}
|
||||
>
|
||||
<strong style={{ color: 'var(--text2)', fontWeight: 600 }}>Block: </strong>
|
||||
{stripGlobalRough}
|
||||
</div>
|
||||
) : null}
|
||||
{stripBullets.length > 0 ? (
|
||||
<ul
|
||||
style={{
|
||||
margin: 0,
|
||||
paddingLeft: '1.05rem',
|
||||
fontSize: '0.74rem',
|
||||
color: 'var(--text2)',
|
||||
}}
|
||||
>
|
||||
{stripBullets.map((line, bi) => (
|
||||
<li key={`combo-strip-${sIdx}-${iIdx}-${bi}`} style={{ marginBottom: 2 }}>
|
||||
{line}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div style={{ fontSize: '0.74rem', color: 'var(--text3)', fontStyle: 'italic' }}>
|
||||
Stationen laden oder noch keine Kombi-Stationen im Katalog …
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="tu-combo-planning-strip">
|
||||
<div className="tu-combo-planning-strip__toolbar">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary framework-ctrl framework-ctrl--xs"
|
||||
style={{ flexShrink: 0 }}
|
||||
aria-haspopup="dialog"
|
||||
aria-label="Ablaufprofil Kombination für diese Planung bearbeiten"
|
||||
onClick={() => setComboPlanningModal({ sIdx, iIdx })}
|
||||
|
|
@ -1286,6 +1167,37 @@ export default function TrainingUnitSectionsEditor({
|
|||
Ablauf bearbeiten…
|
||||
</button>
|
||||
</div>
|
||||
{(it.combination_slots || []).length > 0 ? (
|
||||
<div className="tu-combo-planning-strip__bracket-wrap">
|
||||
<CombinationPlanBracket
|
||||
className="combo-plan-bracket--planning-embed"
|
||||
methodArchetype={stripArchRaw}
|
||||
methodProfile={stripMpEff || {}}
|
||||
combinationSlots={sortCombinationSlotsForDisplay(it.combination_slots)}
|
||||
planningAdjusted={
|
||||
it.planning_method_profile != null &&
|
||||
typeof it.planning_method_profile === 'object' &&
|
||||
!Array.isArray(it.planning_method_profile)
|
||||
}
|
||||
candidateInteraction={onPeekExercise ? 'button' : 'none'}
|
||||
onCandidatePeek={
|
||||
onPeekExercise
|
||||
? (exId) => onPeekExercise(Number(exId), null, undefined)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="tu-combo-planning-strip__meta tu-combo-planning-strip__meta--fallback"
|
||||
title="Stationen aus dem Katalog — nach ersten Laden oder wenn die Kombination noch keine Slots hat."
|
||||
>
|
||||
<div style={{ fontSize: '0.74rem', color: 'var(--text3)', fontStyle: 'italic', margin: 0 }}>
|
||||
Stationen werden geladen oder die Kombination hat im Katalog noch keine Stationsliste …
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showExecutionExtras ? (
|
||||
|
|
|
|||
|
|
@ -2403,6 +2403,17 @@ function ExerciseFormPage() {
|
|||
}
|
||||
/>
|
||||
)}
|
||||
{reportTarget && (
|
||||
<ReportContentModal
|
||||
targetType="media_asset"
|
||||
targetId={reportTarget.media_asset_id || reportTarget.id}
|
||||
targetLabel={reportTarget.title || reportTarget.original_filename || `Medium #${reportTarget.id}`}
|
||||
onClose={() => setReportTarget(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ExercisePickerModal
|
||||
open={comboStationPickerIx !== null}
|
||||
onClose={() => setComboStationPickerIx(null)}
|
||||
|
|
@ -2415,16 +2426,6 @@ function ExerciseFormPage() {
|
|||
setComboStationPickerIx(null)
|
||||
}}
|
||||
/>
|
||||
{reportTarget && (
|
||||
<ReportContentModal
|
||||
targetType="media_asset"
|
||||
targetId={reportTarget.media_asset_id || reportTarget.id}
|
||||
targetLabel={reportTarget.title || reportTarget.original_filename || `Medium #${reportTarget.id}`}
|
||||
onClose={() => setReportTarget(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: '16px' }}>
|
||||
<strong>KI-Ausbaustufe:</strong> Backend laut Spec{' '}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user