All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m1s
- Bumped APP_VERSION to 0.8.121 and updated the changelog to reflect new features. - Introduced the ExerciseListFilterModal and ExerciseListBulkModal components, enhancing the exercise list functionality. - Modularized the ExerciseListPage to improve code organization and maintainability. - Added Playwright tests for the filter dialog functionality, ensuring proper user interaction and visibility.
241 lines
8.9 KiB
JavaScript
241 lines
8.9 KiB
JavaScript
import React from 'react'
|
|
import { activeClubMemberships } from '../../utils/activeClub'
|
|
import MultiSelectCombo from '../MultiSelectCombo'
|
|
|
|
/**
|
|
* Massenänderung für ausgewählte Übungen in der Liste.
|
|
*/
|
|
export default function ExerciseListBulkModal({
|
|
open,
|
|
onClose,
|
|
onSubmit,
|
|
bulkSubmitting,
|
|
selectedCount,
|
|
bulkMaxIds,
|
|
user,
|
|
isPlatformAdmin,
|
|
statusOptions,
|
|
bulkVisibilityOptions,
|
|
focusOptions,
|
|
styleOptions,
|
|
trainingTypeOptions,
|
|
targetGroupOptions,
|
|
bulkVisibility,
|
|
setBulkVisibility,
|
|
bulkStatus,
|
|
setBulkStatus,
|
|
bulkClubSelect,
|
|
setBulkClubSelect,
|
|
bulkClubManual,
|
|
setBulkClubManual,
|
|
bulkPatchFocusAreas,
|
|
setBulkPatchFocusAreas,
|
|
bulkFocusAreaIds,
|
|
setBulkFocusAreaIds,
|
|
bulkPatchStyleDirections,
|
|
setBulkPatchStyleDirections,
|
|
bulkStyleDirectionIds,
|
|
setBulkStyleDirectionIds,
|
|
bulkPatchTrainingTypes,
|
|
setBulkPatchTrainingTypes,
|
|
bulkTrainingTypeIds,
|
|
setBulkTrainingTypeIds,
|
|
bulkPatchTargetGroups,
|
|
setBulkPatchTargetGroups,
|
|
bulkTargetGroupIds,
|
|
setBulkTargetGroupIds,
|
|
}) {
|
|
if (!open) return null
|
|
|
|
return (
|
|
<div
|
|
className="admin-modal-backdrop"
|
|
role="presentation"
|
|
onClick={(e) => {
|
|
if (e.target === e.currentTarget) onClose()
|
|
}}
|
|
>
|
|
<div
|
|
className="admin-modal-sheet exercise-filter-modal"
|
|
data-testid="exercise-list-bulk-modal"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="exercise-bulk-modal-title"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="admin-modal-sheet__header">
|
|
<h3 id="exercise-bulk-modal-title" className="admin-modal-sheet__title">
|
|
Massenänderung
|
|
</h3>
|
|
<button type="button" className="btn btn-secondary admin-modal-sheet__close" onClick={onClose}>
|
|
Schließen
|
|
</button>
|
|
</div>
|
|
<div className="admin-modal-sheet__body exercise-filter-modal__scroll">
|
|
<p className="muted" style={{ marginTop: 0 }}>
|
|
Es werden <strong>{selectedCount}</strong> Übung(en) aus der aktuellen Auswahl bearbeitet. Pro Durchlauf
|
|
höchstens {bulkMaxIds}. Ohne Berechtigung bleiben Einzelübungen unverändert (siehe Hinweis nach dem
|
|
Speichern).
|
|
</p>
|
|
<p className="form-sub" style={{ marginTop: 0, marginBottom: '14px' }}>
|
|
Unter „Zuordnung ersetzen“: die gewählte Liste ersetzt die bisherige Zuordnung bei allen betroffenen Übungen
|
|
vollständig (leere Auswahl = alle Zuordnungen dieser Kategorie entfernen). Die erste Auswahl gilt als
|
|
Primärzuordnung.
|
|
</p>
|
|
<div className="form-row">
|
|
<label className="form-label">Sichtbarkeit</label>
|
|
<select className="form-input" value={bulkVisibility} onChange={(e) => setBulkVisibility(e.target.value)}>
|
|
{bulkVisibilityOptions.map((o) => (
|
|
<option key={o.id === '' ? '_unchanged' : o.id} value={o.id}>
|
|
{o.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
{bulkVisibility === 'club' ? (
|
|
<div className="form-row">
|
|
<label className="form-label">Verein zuordnen</label>
|
|
<select className="form-input" value={bulkClubSelect} onChange={(e) => setBulkClubSelect(e.target.value)}>
|
|
<option value="">Aktiver Verein (Vereins-Umschalter / Header)</option>
|
|
{activeClubMemberships(user?.clubs).map((c) => (
|
|
<option key={c.id} value={String(c.id)}>
|
|
{c.name || `#${c.id}`}
|
|
</option>
|
|
))}
|
|
</select>
|
|
{isPlatformAdmin ? (
|
|
<>
|
|
<label className="form-label" style={{ marginTop: '10px' }}>
|
|
Oder Vereins-ID (Plattform-Admin)
|
|
</label>
|
|
<input
|
|
type="number"
|
|
min={1}
|
|
className="form-input"
|
|
placeholder="Leer = wie Dropdown / aktiver Verein"
|
|
value={bulkClubManual}
|
|
onChange={(e) => setBulkClubManual(e.target.value)}
|
|
/>
|
|
</>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
<div className="form-row">
|
|
<label className="form-label">Status</label>
|
|
<select className="form-input" value={bulkStatus} onChange={(e) => setBulkStatus(e.target.value)}>
|
|
<option value="">— nicht ändern —</option>
|
|
{statusOptions.map((o) => (
|
|
<option key={o.id} value={o.id}>
|
|
{o.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<section className="exercise-filter-section" style={{ marginTop: '8px', paddingTop: '12px' }}>
|
|
<h4 className="exercise-filter-section-title">Zuordnung (optional)</h4>
|
|
<div className="exercise-filters-modal-grid">
|
|
<div>
|
|
<label className="form-label" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
<input
|
|
type="checkbox"
|
|
checked={bulkPatchFocusAreas}
|
|
onChange={(e) => {
|
|
const on = e.target.checked
|
|
setBulkPatchFocusAreas(on)
|
|
if (!on) setBulkFocusAreaIds([])
|
|
}}
|
|
/>
|
|
Fokusbereiche ersetzen
|
|
</label>
|
|
{bulkPatchFocusAreas ? (
|
|
<MultiSelectCombo
|
|
value={bulkFocusAreaIds}
|
|
onChange={setBulkFocusAreaIds}
|
|
options={focusOptions}
|
|
placeholder="Fokusbereiche wählen …"
|
|
/>
|
|
) : null}
|
|
</div>
|
|
<div>
|
|
<label className="form-label" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
<input
|
|
type="checkbox"
|
|
checked={bulkPatchStyleDirections}
|
|
onChange={(e) => {
|
|
const on = e.target.checked
|
|
setBulkPatchStyleDirections(on)
|
|
if (!on) setBulkStyleDirectionIds([])
|
|
}}
|
|
/>
|
|
Stilrichtungen ersetzen
|
|
</label>
|
|
{bulkPatchStyleDirections ? (
|
|
<MultiSelectCombo
|
|
value={bulkStyleDirectionIds}
|
|
onChange={setBulkStyleDirectionIds}
|
|
options={styleOptions}
|
|
placeholder="Stilrichtungen wählen …"
|
|
/>
|
|
) : null}
|
|
</div>
|
|
<div>
|
|
<label className="form-label" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
<input
|
|
type="checkbox"
|
|
checked={bulkPatchTrainingTypes}
|
|
onChange={(e) => {
|
|
const on = e.target.checked
|
|
setBulkPatchTrainingTypes(on)
|
|
if (!on) setBulkTrainingTypeIds([])
|
|
}}
|
|
/>
|
|
Trainingsstile ersetzen
|
|
</label>
|
|
{bulkPatchTrainingTypes ? (
|
|
<MultiSelectCombo
|
|
value={bulkTrainingTypeIds}
|
|
onChange={setBulkTrainingTypeIds}
|
|
options={trainingTypeOptions}
|
|
placeholder="Trainingsstile wählen …"
|
|
/>
|
|
) : null}
|
|
</div>
|
|
<div>
|
|
<label className="form-label" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
<input
|
|
type="checkbox"
|
|
checked={bulkPatchTargetGroups}
|
|
onChange={(e) => {
|
|
const on = e.target.checked
|
|
setBulkPatchTargetGroups(on)
|
|
if (!on) setBulkTargetGroupIds([])
|
|
}}
|
|
/>
|
|
Zielgruppen ersetzen
|
|
</label>
|
|
{bulkPatchTargetGroups ? (
|
|
<MultiSelectCombo
|
|
value={bulkTargetGroupIds}
|
|
onChange={setBulkTargetGroupIds}
|
|
options={targetGroupOptions}
|
|
placeholder="Zielgruppen wählen …"
|
|
/>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
<div className="exercise-filter-modal__footer">
|
|
<button type="button" className="btn" disabled={bulkSubmitting} onClick={onClose}>
|
|
Abbrechen
|
|
</button>
|
|
<button type="button" className="btn btn-primary" disabled={bulkSubmitting} onClick={onSubmit}>
|
|
{bulkSubmitting ? 'Speichern…' : 'Anwenden'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|