feat: enhance TrainingUnitSectionsEditor with heading accessory support
- Added a new `headingAccessory` prop to TrainingUnitSectionsEditor for customizable header content. - Updated the component to conditionally render the heading and accessory in a flexible toolbar layout. - Refactored TrainingFrameworkProgramEditPage and TrainingPlanningPage to utilize the new heading accessory feature, improving user interaction and layout consistency.
This commit is contained in:
parent
2bfe67879f
commit
4080088d42
|
|
@ -46,6 +46,7 @@ export default function TrainingUnitSectionsEditor({
|
|||
showExecutionExtras = false,
|
||||
heading = 'Abschnitte & Übungen',
|
||||
hideHeading = false,
|
||||
headingAccessory = null,
|
||||
wideExerciseGrid = false,
|
||||
enableItemDragReorder = true,
|
||||
enableSectionDragReorder = true,
|
||||
|
|
@ -353,8 +354,39 @@ export default function TrainingUnitSectionsEditor({
|
|||
(wideExerciseGrid ? ' training-unit-sections-editor--wide' : '')
|
||||
}
|
||||
>
|
||||
{!hideHeading ? (
|
||||
<h3 style={{ margin: '0 0 0.75rem', fontSize: '1rem' }}>{heading}</h3>
|
||||
{(!hideHeading || headingAccessory) ? (
|
||||
<div
|
||||
className="tu-editor-heading-toolbar"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
marginBottom: '0.75rem',
|
||||
}}
|
||||
>
|
||||
{!hideHeading ? (
|
||||
<h3 style={{ margin: 0, fontSize: '1rem', flex: '1 1 200px', minWidth: 0 }}>
|
||||
{heading}
|
||||
</h3>
|
||||
) : headingAccessory ? (
|
||||
<span style={{ flex: '1 1 auto', minWidth: 0 }} />
|
||||
) : null}
|
||||
{headingAccessory ? (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '8px',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{headingAccessory}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{list.map((sec, sIdx) => {
|
||||
const planMin = sectionPlannedMinutes(sec)
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import ExercisePeekModal from '../components/ExercisePeekModal'
|
|||
import TrainingUnitSectionsEditor from '../components/TrainingUnitSectionsEditor'
|
||||
import {
|
||||
defaultSection,
|
||||
exerciseRow,
|
||||
normalizeUnitToForm,
|
||||
enrichSectionsWithVariants,
|
||||
buildSectionsPayload,
|
||||
hydrateExercisePlanningRow,
|
||||
} from '../utils/trainingUnitSectionsForm'
|
||||
|
||||
const DND_FW_SLOT = 'application/x-shinkan-framework-slot'
|
||||
|
|
@ -52,27 +52,6 @@ async function enrichFrameworkSlotSections(slots) {
|
|||
return out
|
||||
}
|
||||
|
||||
async function hydrateExerciseForSlotRow(exercise) {
|
||||
let variants = Array.isArray(exercise?.variants) ? exercise.variants : []
|
||||
let title = exercise?.title || ''
|
||||
const id = exercise?.id
|
||||
if (!id) return null
|
||||
if (!variants.length) {
|
||||
try {
|
||||
const full = await api.getExercise(id)
|
||||
variants = Array.isArray(full?.variants) ? full.variants : []
|
||||
title = full?.title || title
|
||||
} catch {
|
||||
variants = []
|
||||
}
|
||||
}
|
||||
const row = exerciseRow()
|
||||
row.exercise_id = id
|
||||
row.exercise_variant_id = ''
|
||||
row.exercise_title = title
|
||||
row.variants = variants
|
||||
return row
|
||||
}
|
||||
function goalHoverText(g) {
|
||||
const t = (g.title || '').trim() || 'Ohne Titel'
|
||||
const n = (g.notes || '').trim()
|
||||
|
|
@ -1138,7 +1117,7 @@ export default function TrainingFrameworkProgramEditPage() {
|
|||
const { slotIdx, sectionIndex: sIdx } = sectionPickerCtx
|
||||
const rows = []
|
||||
for (const ex of picked) {
|
||||
const row = await hydrateExerciseForSlotRow(ex)
|
||||
const row = await hydrateExercisePlanningRow(ex)
|
||||
if (row) rows.push(row)
|
||||
}
|
||||
if (!rows.length) return
|
||||
|
|
@ -1166,7 +1145,7 @@ export default function TrainingFrameworkProgramEditPage() {
|
|||
if (sectionPickerCtx.multi) return
|
||||
if (typeof sectionPickerCtx.itemIndex !== 'number') return
|
||||
const { slotIdx, sectionIndex: sIdx, itemIndex: iIdx } = sectionPickerCtx
|
||||
const row = await hydrateExerciseForSlotRow(exercise)
|
||||
const row = await hydrateExercisePlanningRow(exercise)
|
||||
if (!row) return
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
|
|
|
|||
|
|
@ -7,34 +7,12 @@ import ExercisePeekModal from '../components/ExercisePeekModal'
|
|||
import TrainingUnitSectionsEditor from '../components/TrainingUnitSectionsEditor'
|
||||
import {
|
||||
defaultSection,
|
||||
exerciseRow,
|
||||
normalizeUnitToForm,
|
||||
enrichSectionsWithVariants,
|
||||
buildSectionsPayload,
|
||||
hydrateExercisePlanningRow,
|
||||
} from '../utils/trainingUnitSectionsForm'
|
||||
|
||||
async function hydrateExerciseForPickerRow(exercise) {
|
||||
let variants = Array.isArray(exercise?.variants) ? exercise.variants : []
|
||||
let title = exercise?.title || ''
|
||||
const id = exercise?.id
|
||||
if (!id) return null
|
||||
if (!variants.length) {
|
||||
try {
|
||||
const full = await api.getExercise(id)
|
||||
variants = Array.isArray(full?.variants) ? full.variants : []
|
||||
title = full?.title || title
|
||||
} catch {
|
||||
variants = []
|
||||
}
|
||||
}
|
||||
const row = exerciseRow()
|
||||
row.exercise_id = id
|
||||
row.exercise_variant_id = ''
|
||||
row.exercise_title = title
|
||||
row.variants = variants
|
||||
return row
|
||||
}
|
||||
|
||||
function TrainingPlanningPage() {
|
||||
const { user } = useAuth()
|
||||
const [groups, setGroups] = useState([])
|
||||
|
|
@ -634,7 +612,7 @@ function TrainingPlanningPage() {
|
|||
background: 'var(--surface)',
|
||||
borderRadius: '12px',
|
||||
padding: 'clamp(12px, 3vw, 2rem)',
|
||||
maxWidth: 'min(960px, 100%)',
|
||||
maxWidth: 'min(1100px, 100%)',
|
||||
width: '100%',
|
||||
maxHeight: '92vh',
|
||||
overflowY: 'auto',
|
||||
|
|
@ -715,26 +693,16 @@ function TrainingPlanningPage() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginTop: '2rem',
|
||||
marginBottom: '0.75rem',
|
||||
flexWrap: 'wrap',
|
||||
gap: '0.5rem'
|
||||
}}
|
||||
>
|
||||
<h3 style={{ margin: 0 }}>Abschnitte & Übungen</h3>
|
||||
<button type="button" className="btn btn-secondary" onClick={handleSaveAsTemplate}>
|
||||
Vorlage aus Aufbau speichern
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '2rem' }}>
|
||||
<TrainingUnitSectionsEditor
|
||||
hideHeading
|
||||
heading="Abschnitte & Übungen"
|
||||
headingAccessory={
|
||||
<button type="button" className="btn btn-secondary" onClick={handleSaveAsTemplate}>
|
||||
Vorlage aus Aufbau speichern
|
||||
</button>
|
||||
}
|
||||
sections={formData.sections}
|
||||
wideExerciseGrid
|
||||
onSectionsChange={(updater) =>
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
|
|
@ -754,6 +722,7 @@ function TrainingPlanningPage() {
|
|||
}
|
||||
showExecutionExtras={!!editingUnit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '1.75rem' }} />
|
||||
|
||||
|
|
@ -867,7 +836,7 @@ function TrainingPlanningPage() {
|
|||
const { sIdx } = exercisePickerTarget
|
||||
const rows = []
|
||||
for (const ex of picked) {
|
||||
const row = await hydrateExerciseForPickerRow(ex)
|
||||
const row = await hydrateExercisePlanningRow(ex)
|
||||
if (row) rows.push(row)
|
||||
}
|
||||
if (!rows.length) return
|
||||
|
|
@ -884,7 +853,7 @@ function TrainingPlanningPage() {
|
|||
}
|
||||
onSelectExercise={async (ex) => {
|
||||
if (!exercisePickerTarget || exercisePickerTarget.multi) return
|
||||
const row = await hydrateExerciseForPickerRow(ex)
|
||||
const row = await hydrateExercisePlanningRow(ex)
|
||||
if (!row) return
|
||||
const { sIdx, iIdx } = exercisePickerTarget
|
||||
if (typeof iIdx !== 'number') return
|
||||
|
|
|
|||
|
|
@ -18,6 +18,28 @@ export function exerciseRow() {
|
|||
}
|
||||
}
|
||||
|
||||
export async function hydrateExercisePlanningRow(exercise) {
|
||||
let variants = Array.isArray(exercise?.variants) ? exercise.variants : []
|
||||
let title = exercise?.title || ''
|
||||
const id = exercise?.id
|
||||
if (!id) return null
|
||||
if (!variants.length) {
|
||||
try {
|
||||
const full = await api.getExercise(id)
|
||||
variants = Array.isArray(full?.variants) ? full.variants : []
|
||||
title = full?.title || title
|
||||
} catch {
|
||||
variants = []
|
||||
}
|
||||
}
|
||||
const row = exerciseRow()
|
||||
row.exercise_id = id
|
||||
row.exercise_variant_id = ''
|
||||
row.exercise_title = title
|
||||
row.variants = variants
|
||||
return row
|
||||
}
|
||||
|
||||
export function noteRow() {
|
||||
return { item_type: 'note', note_body: '' }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user