shinkan-jinkendo/frontend/src/components/exercises/ExerciseListBulkModal.jsx
Lars 57a8957c93
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
chore(version): update version and changelog for release 0.8.121
- 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.
2026-05-14 10:58:41 +02:00

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>
)
}