refactor: enhance filtering logic and UI for ExercisesListPage
- Removed the countActiveFilterGroups function and replaced it with a more comprehensive filterChips implementation to manage active filters. - Improved the rendering of active filters with dynamic chips that allow users to remove individual filters. - Updated the UI to reflect the new filtering logic, enhancing user experience and interaction with the filter options. - Adjusted the layout and styling for better visibility and usability of the filter components.
This commit is contained in:
parent
0d4ad9a2c8
commit
2c831d6cea
|
|
@ -1586,16 +1586,98 @@ a.analysis-split__nav-item {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
.exercise-filter-level-row {
|
.exercise-filters-modal-grid--two {
|
||||||
grid-column: 1 / -1;
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
}
|
||||||
@media (max-width: 480px) {
|
.exercise-filter-chips-row {
|
||||||
.exercise-filter-level-row {
|
display: flex;
|
||||||
grid-template-columns: 1fr;
|
flex-wrap: wrap;
|
||||||
}
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.exercise-filter-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--surface2);
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
color: var(--text1);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.exercise-filter-chip__text {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: min(260px, 88vw);
|
||||||
|
}
|
||||||
|
.exercise-filter-chip__x {
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 0.65;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.exercise-filter-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.exercise-filter-section--last {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.exercise-filter-section-title {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: var(--text3);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.exercise-filter-skill-block {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--surface2);
|
||||||
|
}
|
||||||
|
.exercise-filter-skill-block > .form-label {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.exercise-filter-skill-hint {
|
||||||
|
margin: 10px 0 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text3);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
.exercise-filter-skill-levels-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.exercise-filter-skill-level-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.exercise-filter-skill-level-caption {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text3);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.exercise-filter-level-select {
|
||||||
|
width: 72px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.exercise-filter-skill-dash {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
color: var(--text3);
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reifegradmodell-Admin: klare Schritte, responsives Raster */
|
/* Reifegradmodell-Admin: klare Schritte, responsives Raster */
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,9 @@ const INITIAL_FILTERS = {
|
||||||
status_any: [],
|
status_any: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
function countActiveFilterGroups(f) {
|
function levelOptionShort(levelStr) {
|
||||||
let n = 0
|
const o = LEVEL_FILTER_OPTS.find((x) => String(x.level) === String(levelStr))
|
||||||
if (f.focus_area_ids?.length) n++
|
return o ? String(o.level) : String(levelStr)
|
||||||
if (f.style_direction_ids?.length) n++
|
|
||||||
if (f.training_type_ids?.length) n++
|
|
||||||
if (f.target_group_ids?.length) n++
|
|
||||||
if (f.skill_ids?.length) n++
|
|
||||||
if (f.skill_min_level || f.skill_max_level) n++
|
|
||||||
if (f.visibility_any?.length) n++
|
|
||||||
if (f.status_any?.length) n++
|
|
||||||
return n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExercisesListPage() {
|
function ExercisesListPage() {
|
||||||
|
|
@ -53,8 +45,6 @@ function ExercisesListPage() {
|
||||||
const [filters, setFilters] = useState(() => ({ ...INITIAL_FILTERS }))
|
const [filters, setFilters] = useState(() => ({ ...INITIAL_FILTERS }))
|
||||||
const [filterModalOpen, setFilterModalOpen] = useState(false)
|
const [filterModalOpen, setFilterModalOpen] = useState(false)
|
||||||
|
|
||||||
const activeFilterGroups = useMemo(() => countActiveFilterGroups(filters), [filters])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const t = setTimeout(() => setDebouncedSearch(searchInput.trim()), 400)
|
const t = setTimeout(() => setDebouncedSearch(searchInput.trim()), 400)
|
||||||
return () => clearTimeout(t)
|
return () => clearTimeout(t)
|
||||||
|
|
@ -116,6 +106,122 @@ function ExercisesListPage() {
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const filterChips = useMemo(() => {
|
||||||
|
const chips = []
|
||||||
|
|
||||||
|
;(filters.focus_area_ids || []).forEach((id) => {
|
||||||
|
const opt = focusOptions.find((o) => String(o.id) === String(id))
|
||||||
|
chips.push({
|
||||||
|
key: `fa-${id}`,
|
||||||
|
label: `Fokus: ${opt?.label ?? id}`,
|
||||||
|
onRemove: () =>
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
focus_area_ids: prev.focus_area_ids.filter((x) => String(x) !== String(id)),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
;(filters.style_direction_ids || []).forEach((id) => {
|
||||||
|
const opt = styleOptions.find((o) => String(o.id) === String(id))
|
||||||
|
chips.push({
|
||||||
|
key: `sd-${id}`,
|
||||||
|
label: `Stil: ${opt?.label ?? id}`,
|
||||||
|
onRemove: () =>
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
style_direction_ids: prev.style_direction_ids.filter((x) => String(x) !== String(id)),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
;(filters.training_type_ids || []).forEach((id) => {
|
||||||
|
const opt = trainingTypeOptions.find((o) => String(o.id) === String(id))
|
||||||
|
chips.push({
|
||||||
|
key: `tt-${id}`,
|
||||||
|
label: `Trainingsstil: ${opt?.label ?? id}`,
|
||||||
|
onRemove: () =>
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
training_type_ids: prev.training_type_ids.filter((x) => String(x) !== String(id)),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
;(filters.target_group_ids || []).forEach((id) => {
|
||||||
|
const opt = targetGroupOptions.find((o) => String(o.id) === String(id))
|
||||||
|
chips.push({
|
||||||
|
key: `tg-${id}`,
|
||||||
|
label: `Zielgruppe: ${opt?.label ?? id}`,
|
||||||
|
onRemove: () =>
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
target_group_ids: prev.target_group_ids.filter((x) => String(x) !== String(id)),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
;(filters.skill_ids || []).forEach((id) => {
|
||||||
|
const opt = skillOptions.find((o) => String(o.id) === String(id))
|
||||||
|
chips.push({
|
||||||
|
key: `sk-${id}`,
|
||||||
|
label: `Fähigkeit: ${opt?.label ?? id}`,
|
||||||
|
onRemove: () =>
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
skill_ids: prev.skill_ids.filter((x) => String(x) !== String(id)),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (filters.skill_min_level || filters.skill_max_level) {
|
||||||
|
const a = filters.skill_min_level ? levelOptionShort(filters.skill_min_level) : '…'
|
||||||
|
const b = filters.skill_max_level ? levelOptionShort(filters.skill_max_level) : '…'
|
||||||
|
chips.push({
|
||||||
|
key: 'skill-levels',
|
||||||
|
label: `Stufe ${a}–${b}`,
|
||||||
|
onRemove: () =>
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
skill_min_level: '',
|
||||||
|
skill_max_level: '',
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
;(filters.visibility_any || []).forEach((id) => {
|
||||||
|
const opt = visibilityOptions.find((o) => String(o.id) === String(id))
|
||||||
|
chips.push({
|
||||||
|
key: `vis-${id}`,
|
||||||
|
label: `Sichtbarkeit: ${opt?.label ?? id}`,
|
||||||
|
onRemove: () =>
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
visibility_any: prev.visibility_any.filter((x) => String(x) !== String(id)),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
;(filters.status_any || []).forEach((id) => {
|
||||||
|
const opt = statusOptions.find((o) => String(o.id) === String(id))
|
||||||
|
chips.push({
|
||||||
|
key: `st-${id}`,
|
||||||
|
label: `Status: ${opt?.label ?? id}`,
|
||||||
|
onRemove: () =>
|
||||||
|
setFilters((prev) => ({
|
||||||
|
...prev,
|
||||||
|
status_any: prev.status_any.filter((x) => String(x) !== String(id)),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return chips
|
||||||
|
}, [
|
||||||
|
filters,
|
||||||
|
focusOptions,
|
||||||
|
styleOptions,
|
||||||
|
trainingTypeOptions,
|
||||||
|
targetGroupOptions,
|
||||||
|
skillOptions,
|
||||||
|
visibilityOptions,
|
||||||
|
statusOptions,
|
||||||
|
])
|
||||||
|
|
||||||
const queryBase = useMemo(() => {
|
const queryBase = useMemo(() => {
|
||||||
const q = {}
|
const q = {}
|
||||||
const n = (v) => (v === '' || v == null ? undefined : Number(v))
|
const n = (v) => (v === '' || v == null ? undefined : Number(v))
|
||||||
|
|
@ -278,18 +384,37 @@ function ExercisesListPage() {
|
||||||
<div className="exercise-search-bar__actions">
|
<div className="exercise-search-bar__actions">
|
||||||
<button type="button" className="btn btn-secondary exercise-filter-trigger" onClick={() => setFilterModalOpen(true)}>
|
<button type="button" className="btn btn-secondary exercise-filter-trigger" onClick={() => setFilterModalOpen(true)}>
|
||||||
Filter
|
Filter
|
||||||
{activeFilterGroups > 0 ? (
|
{filterChips.length > 0 ? (
|
||||||
<span className="exercise-filter-badge" aria-hidden>
|
<span className="exercise-filter-badge" aria-hidden>
|
||||||
{activeFilterGroups}
|
{filterChips.length}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</button>
|
</button>
|
||||||
{activeFilterGroups > 0 ? (
|
{filterChips.length > 0 ? (
|
||||||
<button type="button" className="btn" onClick={resetAllFilters}>
|
<button type="button" className="btn" onClick={resetAllFilters}>
|
||||||
Filter löschen
|
Alle entfernen
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
{filterChips.length > 0 ? (
|
||||||
|
<div className="exercise-filter-chips-row" role="list" aria-label="Aktive Filter">
|
||||||
|
{filterChips.map((c) => (
|
||||||
|
<button
|
||||||
|
key={c.key}
|
||||||
|
type="button"
|
||||||
|
role="listitem"
|
||||||
|
className="exercise-filter-chip"
|
||||||
|
title="Filter entfernen"
|
||||||
|
onClick={() => c.onRemove()}
|
||||||
|
>
|
||||||
|
<span className="exercise-filter-chip__text">{c.label}</span>
|
||||||
|
<span className="exercise-filter-chip__x" aria-hidden>
|
||||||
|
×
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: '10px', marginBottom: 0 }}>
|
<p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: '10px', marginBottom: 0 }}>
|
||||||
Vereins-/Trainerfilter folgen später. Fachliche Filter über „Filter“ – zwischen Feldern UND, Auswahl mehrerer Werte je Feld mit ODER.
|
Vereins-/Trainerfilter folgen später. Fachliche Filter über „Filter“ – zwischen Feldern UND, Auswahl mehrerer Werte je Feld mit ODER.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -324,47 +449,55 @@ function ExercisesListPage() {
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-modal-sheet__body exercise-filter-modal__scroll">
|
<div className="admin-modal-sheet__body exercise-filter-modal__scroll">
|
||||||
<p style={{ fontSize: '13px', color: 'var(--text2)', marginTop: 0, marginBottom: '14px' }}>
|
<p style={{ fontSize: '13px', color: 'var(--text2)', marginTop: 0, marginBottom: '14px' }}>
|
||||||
Zwischen den Bereichen gilt <strong>UND</strong>. Innerhalb eines Bereichs werden mehrere Einträge mit{' '}
|
Zwischen den Bereichen gilt <strong>UND</strong>. Innerhalb eines Feldes werden mehrere Einträge mit{' '}
|
||||||
<strong>ODER</strong> verknüpft (die Übung muss mindestens eine gewählte Zuordnung erfüllen).
|
<strong>ODER</strong> verknüpft.
|
||||||
</p>
|
</p>
|
||||||
<div className="exercise-filters-modal-grid">
|
|
||||||
<div>
|
<section className="exercise-filter-section">
|
||||||
<label className="form-label">Fokus</label>
|
<h4 className="exercise-filter-section-title">Zuordnung</h4>
|
||||||
<MultiSelectCombo
|
<div className="exercise-filters-modal-grid">
|
||||||
value={filters.focus_area_ids}
|
<div>
|
||||||
onChange={(v) => setFilters({ ...filters, focus_area_ids: v })}
|
<label className="form-label">Fokus</label>
|
||||||
options={focusOptions}
|
<MultiSelectCombo
|
||||||
placeholder="Fokus suchen oder „▼ Alle“ …"
|
value={filters.focus_area_ids}
|
||||||
/>
|
onChange={(v) => setFilters({ ...filters, focus_area_ids: v })}
|
||||||
|
options={focusOptions}
|
||||||
|
placeholder="Fokus suchen oder „▼ Alle“ …"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="form-label">Stilrichtung</label>
|
||||||
|
<MultiSelectCombo
|
||||||
|
value={filters.style_direction_ids}
|
||||||
|
onChange={(v) => setFilters({ ...filters, style_direction_ids: v })}
|
||||||
|
options={styleOptions}
|
||||||
|
placeholder="Stilrichtung suchen …"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="form-label">Trainingsstil</label>
|
||||||
|
<MultiSelectCombo
|
||||||
|
value={filters.training_type_ids}
|
||||||
|
onChange={(v) => setFilters({ ...filters, training_type_ids: v })}
|
||||||
|
options={trainingTypeOptions}
|
||||||
|
placeholder="Trainingsstil suchen …"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="form-label">Zielgruppe</label>
|
||||||
|
<MultiSelectCombo
|
||||||
|
value={filters.target_group_ids}
|
||||||
|
onChange={(v) => setFilters({ ...filters, target_group_ids: v })}
|
||||||
|
options={targetGroupOptions}
|
||||||
|
placeholder="Zielgruppe suchen …"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</section>
|
||||||
<label className="form-label">Stilrichtung</label>
|
|
||||||
<MultiSelectCombo
|
<section className="exercise-filter-section">
|
||||||
value={filters.style_direction_ids}
|
<h4 className="exercise-filter-section-title">Fähigkeit und zugehörige Stufe</h4>
|
||||||
onChange={(v) => setFilters({ ...filters, style_direction_ids: v })}
|
<div className="exercise-filter-skill-block">
|
||||||
options={styleOptions}
|
|
||||||
placeholder="Stilrichtung suchen …"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="form-label">Trainingsstil</label>
|
|
||||||
<MultiSelectCombo
|
|
||||||
value={filters.training_type_ids}
|
|
||||||
onChange={(v) => setFilters({ ...filters, training_type_ids: v })}
|
|
||||||
options={trainingTypeOptions}
|
|
||||||
placeholder="Trainingsstil suchen …"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="form-label">Zielgruppe</label>
|
|
||||||
<MultiSelectCombo
|
|
||||||
value={filters.target_group_ids}
|
|
||||||
onChange={(v) => setFilters({ ...filters, target_group_ids: v })}
|
|
||||||
options={targetGroupOptions}
|
|
||||||
placeholder="Zielgruppe suchen …"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="form-label">Fähigkeit</label>
|
<label className="form-label">Fähigkeit</label>
|
||||||
<MultiSelectCombo
|
<MultiSelectCombo
|
||||||
value={filters.skill_ids}
|
value={filters.skill_ids}
|
||||||
|
|
@ -372,58 +505,72 @@ function ExercisesListPage() {
|
||||||
options={skillOptions}
|
options={skillOptions}
|
||||||
placeholder="Fähigkeit suchen …"
|
placeholder="Fähigkeit suchen …"
|
||||||
/>
|
/>
|
||||||
</div>
|
<p className="exercise-filter-skill-hint">
|
||||||
<div className="exercise-filter-level-row">
|
Die Stufen filtern nach dem Niveau der Zuordnung Übung ↔ Fähigkeit (von–bis).
|
||||||
<div>
|
</p>
|
||||||
<label className="form-label">Fähigkeit Stufe von</label>
|
<div className="exercise-filter-skill-levels-row">
|
||||||
<select
|
<label className="exercise-filter-skill-level-field">
|
||||||
className="form-input"
|
<span className="exercise-filter-skill-level-caption">von</span>
|
||||||
value={filters.skill_min_level}
|
<select
|
||||||
onChange={(e) => setFilters({ ...filters, skill_min_level: e.target.value })}
|
className="form-input exercise-filter-level-select"
|
||||||
>
|
title="Mindest-Stufe"
|
||||||
<option value="">egal</option>
|
value={filters.skill_min_level}
|
||||||
{LEVEL_FILTER_OPTS.map((o) => (
|
onChange={(e) => setFilters({ ...filters, skill_min_level: e.target.value })}
|
||||||
<option key={o.value} value={String(o.level)}>
|
>
|
||||||
{o.label}
|
<option value="">–</option>
|
||||||
</option>
|
{LEVEL_FILTER_OPTS.map((o) => (
|
||||||
))}
|
<option key={o.value} value={String(o.level)} title={o.label}>
|
||||||
</select>
|
{o.level}
|
||||||
</div>
|
</option>
|
||||||
<div>
|
))}
|
||||||
<label className="form-label">bis</label>
|
</select>
|
||||||
<select
|
</label>
|
||||||
className="form-input"
|
<span className="exercise-filter-skill-dash" aria-hidden>
|
||||||
value={filters.skill_max_level}
|
–
|
||||||
onChange={(e) => setFilters({ ...filters, skill_max_level: e.target.value })}
|
</span>
|
||||||
>
|
<label className="exercise-filter-skill-level-field">
|
||||||
<option value="">egal</option>
|
<span className="exercise-filter-skill-level-caption">bis</span>
|
||||||
{LEVEL_FILTER_OPTS.map((o) => (
|
<select
|
||||||
<option key={`m-${o.value}`} value={String(o.level)}>
|
className="form-input exercise-filter-level-select"
|
||||||
{o.label}
|
title="Höchst-Stufe"
|
||||||
</option>
|
value={filters.skill_max_level}
|
||||||
))}
|
onChange={(e) => setFilters({ ...filters, skill_max_level: e.target.value })}
|
||||||
</select>
|
>
|
||||||
|
<option value="">–</option>
|
||||||
|
{LEVEL_FILTER_OPTS.map((o) => (
|
||||||
|
<option key={`m-${o.value}`} value={String(o.level)} title={o.label}>
|
||||||
|
{o.level}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</section>
|
||||||
<label className="form-label">Sichtbarkeit</label>
|
|
||||||
<MultiSelectCombo
|
<section className="exercise-filter-section exercise-filter-section--last">
|
||||||
value={filters.visibility_any}
|
<h4 className="exercise-filter-section-title">Freigabe</h4>
|
||||||
onChange={(v) => setFilters({ ...filters, visibility_any: v })}
|
<div className="exercise-filters-modal-grid exercise-filters-modal-grid--two">
|
||||||
options={visibilityOptions}
|
<div>
|
||||||
placeholder="Sichtbarkeit wählen …"
|
<label className="form-label">Sichtbarkeit</label>
|
||||||
/>
|
<MultiSelectCombo
|
||||||
|
value={filters.visibility_any}
|
||||||
|
onChange={(v) => setFilters({ ...filters, visibility_any: v })}
|
||||||
|
options={visibilityOptions}
|
||||||
|
placeholder="Sichtbarkeit wählen …"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="form-label">Status</label>
|
||||||
|
<MultiSelectCombo
|
||||||
|
value={filters.status_any}
|
||||||
|
onChange={(v) => setFilters({ ...filters, status_any: v })}
|
||||||
|
options={statusOptions}
|
||||||
|
placeholder="Status wählen …"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</section>
|
||||||
<label className="form-label">Status</label>
|
|
||||||
<MultiSelectCombo
|
|
||||||
value={filters.status_any}
|
|
||||||
onChange={(v) => setFilters({ ...filters, status_any: v })}
|
|
||||||
options={statusOptions}
|
|
||||||
placeholder="Status wählen …"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="exercise-filter-modal__footer">
|
<div className="exercise-filter-modal__footer">
|
||||||
<button type="button" className="btn" onClick={resetAllFilters}>
|
<button type="button" className="btn" onClick={resetAllFilters}>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user