shinkan-jinkendo/frontend/src/components/CatalogRulePicker.jsx
Lars b9d27b59b0
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
feat: enhance exercise filtering capabilities with new catalog rules
- 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.
2026-05-06 21:20:19 +02:00

110 lines
3.3 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, { 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>
)
}