shinkan-jinkendo/frontend/src/utils/exerciseListSelection.js
Lars 14b005e9b8
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 41s
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 1m18s
Enhance exercise selection and display features
- Introduced new CSS styles for exercise cards and selection sections, improving visual feedback for selected exercises.
- Updated ExerciseListCard to support a new `selectionPinned` prop, allowing for a badge display on selected exercises.
- Refactored selection handling in ExercisesListPageRoot to manage selected entries more effectively, replacing the previous Set-based approach.
- Enhanced SaveSelectedExercisesAsModuleModal to support appending exercises to existing modules, improving module management capabilities.
- Updated session state handling to include selected entries, ensuring persistence across sessions.
2026-05-20 07:03:15 +02:00

91 lines
2.8 KiB
JavaScript

/** Minimaler Snapshot einer Übung für die modulübergreifende Auswahl (filterunabhängig). */
export function snapshotExerciseForSelection(exercise) {
if (!exercise || exercise.id == null) return null
const id = Number(exercise.id)
if (!Number.isFinite(id) || id < 1) return null
return {
id,
title: exercise.title || '',
summary: exercise.summary || '',
visibility: exercise.visibility,
status: exercise.status,
exercise_kind: exercise.exercise_kind,
created_by: exercise.created_by,
focus_area: exercise.focus_area,
focus_area_names: exercise.focus_area_names,
style_direction_names: exercise.style_direction_names,
training_type_names: exercise.training_type_names,
media_count: exercise.media_count,
variant_count: exercise.variant_count,
media: Array.isArray(exercise.media) ? exercise.media : [],
}
}
export function normalizeSelectedEntries(raw) {
if (!Array.isArray(raw)) return []
const out = []
const seen = new Set()
for (const item of raw) {
const snap = snapshotExerciseForSelection(item)
if (!snap || seen.has(snap.id)) continue
seen.add(snap.id)
out.push(snap)
}
return out
}
export function mergeSelectedWithListEntries(selectedEntries, exercises) {
const byId = new Map()
for (const e of exercises || []) {
const id = Number(e?.id)
if (Number.isFinite(id) && id > 0) byId.set(id, e)
}
return (selectedEntries || []).map((entry) => byId.get(Number(entry.id)) || entry)
}
export function moduleItemToPayload(row, orderIndex) {
if ((row?.item_type || 'exercise') === 'note') {
return {
item_type: 'note',
order_index: orderIndex,
note_body: row.note_body ?? '',
}
}
const eid = Number(row.exercise_id)
if (!Number.isFinite(eid) || eid < 1) return null
const vidRaw = row.exercise_variant_id
const vid =
vidRaw === '' || vidRaw == null || row.exercise_kind === 'combination'
? null
: Number(vidRaw)
return {
item_type: 'exercise',
order_index: orderIndex,
exercise_id: eid,
exercise_variant_id: Number.isFinite(vid) && vid > 0 ? vid : null,
planned_duration_min:
row.planned_duration_min !== '' && row.planned_duration_min != null
? Number(row.planned_duration_min)
: null,
notes: row.notes != null && String(row.notes).trim() ? String(row.notes).trim() : null,
}
}
export function buildRowsPayload(rows) {
return rows
.map((row, idx) =>
moduleItemToPayload(
{
...row,
exercise_id: row.exercise_id,
exercise_variant_id: row.exercise_variant_id,
planned_duration_min: row.planned_duration_min,
notes: row.notes,
exercise_kind: row.exercise_kind,
},
idx
)
)
.filter(Boolean)
}