shinkan-jinkendo/frontend/src/components/exercises/ExerciseSkillsEditor.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

135 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 (vonbis).</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 }