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
- 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.
135 lines
5.4 KiB
JavaScript
135 lines
5.4 KiB
JavaScript
import React from 'react'
|
||
import SkillTreeSelect from '../SkillTreeSelect'
|
||
import { skillCatalogPathLabel } from '../../utils/skillCatalogTree'
|
||
import { SKILL_LEVEL_OPTIONS } from '../../constants/skillLevels'
|
||
import {
|
||
EXERCISE_SKILL_INTENSITY_DEFAULT,
|
||
EXERCISE_SKILL_INTENSITY_OPTIONS,
|
||
normalizeExerciseSkillIntensity,
|
||
} from '../../constants/exerciseSkillIntensity'
|
||
|
||
export default function ExerciseSkillsEditor({
|
||
rows,
|
||
skillsCatalog,
|
||
skillPick,
|
||
onSkillPickChange,
|
||
onAdd,
|
||
onRemove,
|
||
onUpdateField,
|
||
}) {
|
||
return (
|
||
<div className="exercise-meta-block exercise-meta-block--skills">
|
||
<div className="exercise-meta-block__head">
|
||
<h4 className="exercise-meta-block__title">Fähigkeiten</h4>
|
||
</div>
|
||
<p className="exercise-meta-block__hint">Je Übung mehrere Fähigkeiten mit Intensität und Niveau (von–bis).</p>
|
||
|
||
<div className="exercise-skills-add">
|
||
<SkillTreeSelect
|
||
value={skillPick}
|
||
onChange={onSkillPickChange}
|
||
skills={skillsCatalog}
|
||
excludeIds={rows.map((s) => s.skill_id)}
|
||
placeholder="Fähigkeit wählen…"
|
||
/>
|
||
<button type="button" className="btn btn-secondary" onClick={onAdd}>
|
||
Hinzufügen
|
||
</button>
|
||
</div>
|
||
|
||
{rows.length === 0 ? (
|
||
<p className="exercise-meta-block__empty">Noch keine Fähigkeit zugeordnet.</p>
|
||
) : (
|
||
<ul className="exercise-skills-list">
|
||
{rows.map((row, idx) => {
|
||
const sk = skillsCatalog.find((s) => s.id === row.skill_id)
|
||
const intensity = normalizeExerciseSkillIntensity(row.intensity)
|
||
return (
|
||
<li key={`${row.skill_id}-${idx}`} className="exercise-skill-chip">
|
||
<div className="exercise-skill-chip__identity">
|
||
<span className="exercise-skill-chip__name">{sk?.name || `Skill #${row.skill_id}`}</span>
|
||
{sk ? (
|
||
<span className="exercise-skill-chip__path" title={skillCatalogPathLabel(sk)}>
|
||
{skillCatalogPathLabel(sk)}
|
||
</span>
|
||
) : null}
|
||
</div>
|
||
|
||
<div className="exercise-skill-chip__controls">
|
||
<div className="exercise-skill-chip__field">
|
||
<span className="exercise-skill-chip__caption">Intensität</span>
|
||
<div className="exercise-intensity-segment" role="radiogroup" aria-label="Intensität">
|
||
{EXERCISE_SKILL_INTENSITY_OPTIONS.map((o) => (
|
||
<button
|
||
key={o.value}
|
||
type="button"
|
||
role="radio"
|
||
aria-checked={intensity === o.value}
|
||
className={`exercise-intensity-segment__btn${
|
||
intensity === o.value ? ' exercise-intensity-segment__btn--active' : ''
|
||
}`}
|
||
onClick={() => onUpdateField(idx, 'intensity', o.value)}
|
||
>
|
||
{o.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="exercise-skill-chip__levels">
|
||
<label className="exercise-skill-chip__field">
|
||
<span className="exercise-skill-chip__caption">von</span>
|
||
<select
|
||
className="form-input exercise-skill-level-select"
|
||
value={row.required_level || ''}
|
||
title="Mindest-Niveau"
|
||
onChange={(e) => onUpdateField(idx, 'required_level', e.target.value)}
|
||
>
|
||
{SKILL_LEVEL_OPTIONS.map((o) => (
|
||
<option key={`r-${o.value}`} value={o.value} title={o.label}>
|
||
{o.level != null ? o.level : '–'}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
<span className="exercise-skill-chip__dash" aria-hidden>
|
||
→
|
||
</span>
|
||
<label className="exercise-skill-chip__field">
|
||
<span className="exercise-skill-chip__caption">bis</span>
|
||
<select
|
||
className="form-input exercise-skill-level-select"
|
||
value={row.target_level || ''}
|
||
title="Ziel-Niveau"
|
||
onChange={(e) => onUpdateField(idx, 'target_level', e.target.value)}
|
||
>
|
||
{SKILL_LEVEL_OPTIONS.map((o) => (
|
||
<option key={`t-${o.value}`} value={o.value} title={o.label}>
|
||
{o.level != null ? o.level : '–'}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</label>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
className="exercise-skill-chip__remove"
|
||
aria-label="Fähigkeit entfernen"
|
||
title="Entfernen"
|
||
onClick={() => onRemove(idx)}
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
</li>
|
||
)
|
||
})}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export { EXERCISE_SKILL_INTENSITY_DEFAULT }
|