shinkan-jinkendo/frontend/src/components/exercises/ExerciseCatalogAssocEditor.jsx
Lars 9b3f594007
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
Implement Exercise Form Enhancements with New Meta Panel
- Added a new meta panel for exercise classification and target groups, improving the organization of exercise attributes.
- Introduced ExerciseCatalogAssocEditor component to manage focus areas, training styles, and target groups, enhancing user interaction.
- Refactored CSS styles for the new meta panel and associated components, ensuring a cohesive design and improved responsiveness.
- Removed the MultiAssocBlock component to streamline the code and improve maintainability.
2026-05-21 14:40:50 +02:00

98 lines
3.1 KiB
JavaScript

import React from 'react'
/**
* Kompakte Katalog-Zuordnung (Fokus, Stile, Zielgruppen …) als Chip-Zeilen.
*/
export default function ExerciseCatalogAssocEditor({
title,
rows,
setRows,
options,
idKey,
emptyLabel,
showPrimary = true,
}) {
const setPrimary = (idx) => {
setRows(rows.map((r, i) => ({ ...r, is_primary: i === idx })))
}
const updateRow = (idx, patch) => {
const next = rows.map((r, i) => (i === idx ? { ...r, ...patch } : r))
if (patch.is_primary === true) {
next.forEach((r, i) => {
if (i !== idx) r.is_primary = false
})
}
setRows(next)
}
const addRow = () => setRows([...rows, { [idKey]: '', is_primary: rows.length === 0 }])
const removeRow = (idx) => {
const next = rows.filter((_, i) => i !== idx)
if (next.length && showPrimary && !next.some((r) => r.is_primary)) next[0].is_primary = true
setRows(next)
}
const optionLabel = (o) => {
const parts = []
if (o.icon) parts.push(o.icon)
parts.push(o.name)
if (o.abbreviation) parts.push(`(${o.abbreviation})`)
return parts.join(' ')
}
return (
<div className="exercise-meta-block">
<div className="exercise-meta-block__head">
<h4 className="exercise-meta-block__title">{title}</h4>
<button type="button" className="btn btn-secondary exercise-meta-block__add" onClick={addRow}>
+ Eintrag
</button>
</div>
{rows.length === 0 ? (
<p className="exercise-meta-block__empty">{emptyLabel}</p>
) : (
<div className="exercise-meta-block__chips" role="list">
{rows.map((row, idx) => (
<div key={idx} className="exercise-catalog-chip" role="listitem">
<select
className="exercise-catalog-chip__select"
value={row[idKey] || ''}
aria-label={`${title} wählen`}
onChange={(e) =>
updateRow(idx, { [idKey]: e.target.value ? parseInt(e.target.value, 10) : '' })
}
>
<option value=""> wählen </option>
{options.map((o) => (
<option key={o.id} value={o.id}>
{optionLabel(o)}
</option>
))}
</select>
{showPrimary ? (
<button
type="button"
className={`exercise-catalog-chip__primary${row.is_primary ? ' exercise-catalog-chip__primary--on' : ''}`}
title={row.is_primary ? 'Primär' : 'Als primär markieren'}
aria-pressed={!!row.is_primary}
onClick={() => setPrimary(idx)}
>
</button>
) : null}
<button
type="button"
className="exercise-catalog-chip__remove"
aria-label="Entfernen"
title="Entfernen"
onClick={() => removeRow(idx)}
>
</button>
</div>
))}
</div>
)}
</div>
)
}