Some checks failed
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 7s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 28s
Test Suite / pytest-backend (pull_request) Successful in 5s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 7s
Test Suite / playwright-tests (pull_request) Failing after 28s
- Introduced new filtering options for style directions, training types, and target groups in the exercise list. - Implemented catalog rule picker components to manage inclusion and exclusion of exercise attributes. - Updated utility functions to handle new catalog rules for improved filtering logic. - Enhanced the ExercisesListPage and ExercisePickerModal to support the new filtering features, improving user experience.
110 lines
3.3 KiB
JavaScript
110 lines
3.3 KiB
JavaScript
import React, { useState } from 'react'
|
||
import { newCatalogRuleKey } from '../constants/exerciseListFilters'
|
||
|
||
/**
|
||
* Kompakte +/- Regeln für Katalogwerte (numerische IDs oder Slugs).
|
||
* Chips oben, schmales Dropdown, Schalter nur „+“ und „−“.
|
||
*/
|
||
export default function CatalogRulePicker({
|
||
label,
|
||
hint,
|
||
options = [],
|
||
rules = [],
|
||
rulesFieldName,
|
||
disabled = false,
|
||
placeholder = 'Auswählen …',
|
||
idKind = 'numeric',
|
||
onPatch,
|
||
}) {
|
||
const [pendingId, setPendingId] = useState('')
|
||
|
||
const labelFor = (id) => options.find((o) => String(o.id) === String(id))?.label ?? id
|
||
|
||
const addRule = (mode) => {
|
||
const raw = String(pendingId || '').trim()
|
||
if (!raw || disabled) return
|
||
if (idKind === 'numeric') {
|
||
const n = Number(raw)
|
||
if (!Number.isFinite(n) || n < 1) return
|
||
}
|
||
const dup = (rules || []).some((r) => String(r.id) === raw && r.mode === mode)
|
||
if (dup) return
|
||
onPatch({
|
||
[rulesFieldName]: [
|
||
...(rules || []),
|
||
{ key: newCatalogRuleKey(rulesFieldName), id: raw, mode },
|
||
],
|
||
})
|
||
setPendingId('')
|
||
}
|
||
|
||
const removeRule = (key) => {
|
||
onPatch({
|
||
[rulesFieldName]: (rules || []).filter((r) => r.key !== key),
|
||
})
|
||
}
|
||
|
||
return (
|
||
<div className={`catalog-rule-picker${disabled ? ' catalog-rule-picker--disabled' : ''}`}>
|
||
<label className="form-label catalog-rule-picker__label">{label}</label>
|
||
{hint ? (
|
||
<p className="muted catalog-rule-picker__hint" style={{ fontSize: '11px', marginTop: '2px', marginBottom: '6px' }}>
|
||
{hint}
|
||
</p>
|
||
) : null}
|
||
<div className="catalog-rule-picker__chips" aria-live="polite">
|
||
{(rules || []).map((r) => (
|
||
<span key={r.key} className="catalog-rule-chip">
|
||
<span className={`catalog-rule-chip__sign catalog-rule-chip__sign--${r.mode}`}>
|
||
{r.mode === 'forbid' ? '−' : '+'}
|
||
</span>
|
||
<span className="catalog-rule-chip__text">{labelFor(r.id)}</span>
|
||
<button
|
||
type="button"
|
||
className="catalog-rule-chip__x"
|
||
aria-label={`${label}: Regel entfernen`}
|
||
onClick={() => removeRule(r.key)}
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
<div className="catalog-rule-picker__row">
|
||
<select
|
||
className="form-input catalog-rule-picker__select"
|
||
value={pendingId}
|
||
disabled={disabled}
|
||
onChange={(e) => setPendingId(e.target.value)}
|
||
aria-label={label}
|
||
>
|
||
<option value="">{placeholder}</option>
|
||
{options.map((o) => (
|
||
<option key={o.id} value={String(o.id)}>
|
||
{o.label || o.id}
|
||
</option>
|
||
))}
|
||
</select>
|
||
<button
|
||
type="button"
|
||
className="btn btn-secondary btn-small catalog-rule-picker__sign-btn"
|
||
disabled={disabled || !pendingId}
|
||
title="Muss zutreffen"
|
||
onClick={() => addRule('require')}
|
||
>
|
||
+
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="btn btn-secondary btn-small catalog-rule-picker__sign-btn"
|
||
disabled={disabled || !pendingId}
|
||
title="Darf nicht zutreffen"
|
||
onClick={() => addRule('forbid')}
|
||
>
|
||
−
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|