Bug_fixes #45
|
|
@ -7,6 +7,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'
|
||||||
import api from '../utils/api'
|
import api from '../utils/api'
|
||||||
import { useAuth } from '../context/AuthContext'
|
import { useAuth } from '../context/AuthContext'
|
||||||
import { SKILL_LEVEL_OPTIONS } from '../constants/skillLevels'
|
import { SKILL_LEVEL_OPTIONS } from '../constants/skillLevels'
|
||||||
|
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../constants/exerciseGovernanceLabels'
|
||||||
import {
|
import {
|
||||||
INITIAL_EXERCISE_LIST_FILTERS,
|
INITIAL_EXERCISE_LIST_FILTERS,
|
||||||
mergeExerciseListPrefsFromApi,
|
mergeExerciseListPrefsFromApi,
|
||||||
|
|
@ -373,7 +374,7 @@ export default function ExercisePickerModal({
|
||||||
{quickOpen ? (
|
{quickOpen ? (
|
||||||
<div style={{ marginTop: '12px', display: 'grid', gap: '10px' }}>
|
<div style={{ marginTop: '12px', display: 'grid', gap: '10px' }}>
|
||||||
<p style={{ margin: 0, fontSize: '13px', color: 'var(--text2)', lineHeight: 1.45 }}>
|
<p style={{ margin: 0, fontSize: '13px', color: 'var(--text2)', lineHeight: 1.45 }}>
|
||||||
Wird mit Sichtbarkeit <strong>privat</strong> und Status <strong>Entwurf</strong> gespeichert und
|
Wird mit Freigabelevel <strong>privat</strong> und Status <strong>Entwurf</strong> gespeichert und
|
||||||
erscheint auf dem Dashboard zum Weiterbearbeiten. Nach dem Speichern wird die Übung direkt in den
|
erscheint auf dem Dashboard zum Weiterbearbeiten. Nach dem Speichern wird die Übung direkt in den
|
||||||
Ablauf übernommen.
|
Ablauf übernommen.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -462,7 +463,7 @@ export default function ExercisePickerModal({
|
||||||
<div style={{ marginTop: '0.35rem', fontSize: '13px', color: 'var(--text2)' }}>
|
<div style={{ marginTop: '0.35rem', fontSize: '13px', color: 'var(--text2)' }}>
|
||||||
<p style={{ margin: '0 0 12px 0' }}>
|
<p style={{ margin: '0 0 12px 0' }}>
|
||||||
Felder gelten mit <strong>UND</strong>. Kataloge: mehrere „+“ = alle zutreffend; „−“ schließt aus.
|
Felder gelten mit <strong>UND</strong>. Kataloge: mehrere „+“ = alle zutreffend; „−“ schließt aus.
|
||||||
Sichtbarkeit/Status: mehrere „+“ = eine davon (ODER); „−“ blendet aus.
|
Freigabelevel/Status: mehrere „+“ = eine davon (ODER); „−“ blendet aus.
|
||||||
</p>
|
</p>
|
||||||
<ExerciseFocusRulePicker
|
<ExerciseFocusRulePicker
|
||||||
focusOptions={focusOptions}
|
focusOptions={focusOptions}
|
||||||
|
|
@ -539,12 +540,12 @@ export default function ExercisePickerModal({
|
||||||
</div>
|
</div>
|
||||||
<div className="exercise-filters-modal-grid exercise-filters-modal-grid--two exercise-filters-modal-grid--catalog" style={{ marginTop: 12 }}>
|
<div className="exercise-filters-modal-grid exercise-filters-modal-grid--two exercise-filters-modal-grid--catalog" style={{ marginTop: 12 }}>
|
||||||
<CatalogRulePicker
|
<CatalogRulePicker
|
||||||
label="Sichtbarkeit"
|
label={EXERCISE_VISIBILITY_FIELD_LABEL}
|
||||||
options={visibilityOptions}
|
options={visibilityOptions}
|
||||||
rules={filters.visibility_rules}
|
rules={filters.visibility_rules}
|
||||||
rulesFieldName="visibility_rules"
|
rulesFieldName="visibility_rules"
|
||||||
idKind="string"
|
idKind="string"
|
||||||
placeholder="Sichtbarkeit …"
|
placeholder="Freigabelevel …"
|
||||||
onPatch={(patch) => setFilters((f) => ({ ...f, ...patch }))}
|
onPatch={(patch) => setFilters((f) => ({ ...f, ...patch }))}
|
||||||
/>
|
/>
|
||||||
<CatalogRulePicker
|
<CatalogRulePicker
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import SkillProfilePanel from './skills/SkillProfilePanel'
|
||||||
import { useAuth } from '../context/AuthContext'
|
import { useAuth } from '../context/AuthContext'
|
||||||
import { getTenantClubDependencyKey } from '../utils/activeClub'
|
import { getTenantClubDependencyKey } from '../utils/activeClub'
|
||||||
import ExercisePickerModal from './ExercisePickerModal'
|
import ExercisePickerModal from './ExercisePickerModal'
|
||||||
|
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../constants/exerciseGovernanceLabels'
|
||||||
|
|
||||||
const VIS_OPTIONS = [
|
const VIS_OPTIONS = [
|
||||||
{ value: 'private', label: 'Privat' },
|
{ value: 'private', label: 'Privat' },
|
||||||
|
|
@ -605,7 +606,7 @@ export default function ExerciseProgressionGraphPanel({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row" style={{ flex: '1 1 140px', marginBottom: 0 }}>
|
<div className="form-row" style={{ flex: '1 1 140px', marginBottom: 0 }}>
|
||||||
<label className="form-label">Sichtbarkeit</label>
|
<label className="form-label">{EXERCISE_VISIBILITY_FIELD_LABEL}</label>
|
||||||
<select
|
<select
|
||||||
className="form-input"
|
className="form-input"
|
||||||
value={newGraphVisibility}
|
value={newGraphVisibility}
|
||||||
|
|
@ -642,7 +643,7 @@ export default function ExerciseProgressionGraphPanel({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Sichtbarkeit</label>
|
<label className="form-label">{EXERCISE_VISIBILITY_FIELD_LABEL}</label>
|
||||||
<select className="form-input" value={metaVisibility} onChange={(e) => setMetaVisibility(e.target.value)}>
|
<select className="form-input" value={metaVisibility} onChange={(e) => setMetaVisibility(e.target.value)}>
|
||||||
{filteredGraphVisOptions.map((o) => (
|
{filteredGraphVisOptions.map((o) => (
|
||||||
<option key={o.value} value={o.value}>
|
<option key={o.value} value={o.value}>
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,10 @@ import {
|
||||||
EXERCISE_SKILL_INTENSITY_DEFAULT,
|
EXERCISE_SKILL_INTENSITY_DEFAULT,
|
||||||
normalizeExerciseSkillIntensity,
|
normalizeExerciseSkillIntensity,
|
||||||
} from '../../constants/exerciseSkillIntensity'
|
} from '../../constants/exerciseSkillIntensity'
|
||||||
|
import {
|
||||||
|
EXERCISE_VISIBILITY_CLUB_FIELD_LABEL,
|
||||||
|
EXERCISE_VISIBILITY_FIELD_LABEL,
|
||||||
|
} from '../../constants/exerciseGovernanceLabels'
|
||||||
|
|
||||||
const VARIANT_DIFFICULTY = [
|
const VARIANT_DIFFICULTY = [
|
||||||
{ value: '', label: '—' },
|
{ value: '', label: '—' },
|
||||||
|
|
@ -859,6 +863,33 @@ function ExerciseFormPageRoot() {
|
||||||
setVariants(rows)
|
setVariants(rows)
|
||||||
}, [exerciseId, syncVariantsSavedSnapshot])
|
}, [exerciseId, syncVariantsSavedSnapshot])
|
||||||
|
|
||||||
|
const createVariantFromDraft = useCallback(
|
||||||
|
async ({ showSuccessToast = false } = {}) => {
|
||||||
|
if (!exerciseId) return false
|
||||||
|
if (!variantDraftHasContent(variantDraft)) return true
|
||||||
|
const payload = buildVariantPayloadFromRow(variantDraft)
|
||||||
|
if (payload.variant_name.length < 3) {
|
||||||
|
toast.error('Variantenname mindestens 3 Zeichen')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
setVariantBusy(true)
|
||||||
|
try {
|
||||||
|
const created = await api.createExerciseVariant(exerciseId, payload)
|
||||||
|
setVariantDraft(emptyVariantDraft())
|
||||||
|
if (created?.id != null) setVariantEditSelection(created.id)
|
||||||
|
await refreshVariants()
|
||||||
|
if (showSuccessToast) toast.success('Variante angelegt.')
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(e.message || String(e))
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
setVariantBusy(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[exerciseId, variantDraft, refreshVariants, toast],
|
||||||
|
)
|
||||||
|
|
||||||
const persistPendingVariantChanges = useCallback(async () => {
|
const persistPendingVariantChanges = useCallback(async () => {
|
||||||
if (!exerciseId) return true
|
if (!exerciseId) return true
|
||||||
|
|
||||||
|
|
@ -885,28 +916,9 @@ function ExerciseFormPageRoot() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variantDraftHasContent(variantDraft)) {
|
const draftOk = await createVariantFromDraft()
|
||||||
const payload = buildVariantPayloadFromRow(variantDraft)
|
return draftOk
|
||||||
if (payload.variant_name.length < 3) {
|
}, [exerciseId, variants, getDirtyVariantRows, refreshVariants, toast, createVariantFromDraft])
|
||||||
toast.error('Variantenentwurf: Name mindestens 3 Zeichen, sonst Felder verwerfen oder ausfüllen.')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
setVariantBusy(true)
|
|
||||||
try {
|
|
||||||
const created = await api.createExerciseVariant(exerciseId, payload)
|
|
||||||
setVariantDraft(emptyVariantDraft())
|
|
||||||
if (created?.id != null) setVariantEditSelection(created.id)
|
|
||||||
await refreshVariants()
|
|
||||||
} catch (e) {
|
|
||||||
toast.error(e.message || String(e))
|
|
||||||
return false
|
|
||||||
} finally {
|
|
||||||
setVariantBusy(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}, [exerciseId, variantDraft, variants, getDirtyVariantRows, refreshVariants, toast])
|
|
||||||
|
|
||||||
const performSaveAttempt = useCallback(
|
const performSaveAttempt = useCallback(
|
||||||
async ({ fromUnsavedDialog = false, closeAfter = false } = {}) => {
|
async ({ fromUnsavedDialog = false, closeAfter = false } = {}) => {
|
||||||
|
|
@ -1223,27 +1235,9 @@ function ExerciseFormPageRoot() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createVariantSubmit = async (e) => {
|
const handleCreateVariantClick = useCallback(async () => {
|
||||||
e.preventDefault()
|
await createVariantFromDraft({ showSuccessToast: true })
|
||||||
if (!exerciseId) return
|
}, [createVariantFromDraft])
|
||||||
const payload = buildVariantPayloadFromRow(variantDraft)
|
|
||||||
if (payload.variant_name.length < 3) {
|
|
||||||
toast.error('Variantenname mindestens 3 Zeichen')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setVariantBusy(true)
|
|
||||||
try {
|
|
||||||
const created = await api.createExerciseVariant(exerciseId, payload)
|
|
||||||
setVariantDraft(emptyVariantDraft())
|
|
||||||
await refreshVariants()
|
|
||||||
if (created?.id != null) setVariantEditSelection(created.id)
|
|
||||||
else setVariantEditSelection(null)
|
|
||||||
} catch (err) {
|
|
||||||
toast.error(err.message || String(err))
|
|
||||||
} finally {
|
|
||||||
setVariantBusy(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedVariantForEdit =
|
const selectedVariantForEdit =
|
||||||
typeof variantEditSelection === 'number' ? variants.find((v) => v.id === variantEditSelection) : null
|
typeof variantEditSelection === 'number' ? variants.find((v) => v.id === variantEditSelection) : null
|
||||||
|
|
@ -1298,7 +1292,7 @@ function ExerciseFormPageRoot() {
|
||||||
title="Stammdaten"
|
title="Stammdaten"
|
||||||
hint={
|
hint={
|
||||||
isEdit
|
isEdit
|
||||||
? 'Titel, Rahmendaten und Sichtbarkeit — Inhalt und Einordnung in den anderen Tabs.'
|
? 'Titel, Rahmendaten und Freigabelevel — Inhalt und Einordnung in den anderen Tabs.'
|
||||||
: 'Titel und Rahmendaten. Varianten, Medien und Progressionsgraph sind nach dem ersten Speichern verfügbar.'
|
: 'Titel und Rahmendaten. Varianten, Medien und Progressionsgraph sind nach dem ersten Speichern verfügbar.'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -1428,7 +1422,7 @@ function ExerciseFormPageRoot() {
|
||||||
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Sichtbarkeit</label>
|
<label className="form-label">{EXERCISE_VISIBILITY_FIELD_LABEL}</label>
|
||||||
<select
|
<select
|
||||||
className="form-input"
|
className="form-input"
|
||||||
value={formData.visibility}
|
value={formData.visibility}
|
||||||
|
|
@ -1456,7 +1450,7 @@ function ExerciseFormPageRoot() {
|
||||||
|
|
||||||
{formData.visibility === 'club' && visibilityClubChoices.length > 0 ? (
|
{formData.visibility === 'club' && visibilityClubChoices.length > 0 ? (
|
||||||
<div className="form-row" style={{ marginTop: '10px' }}>
|
<div className="form-row" style={{ marginTop: '10px' }}>
|
||||||
<label className="form-label">Verein (Sichtbarkeit)</label>
|
<label className="form-label">{EXERCISE_VISIBILITY_CLUB_FIELD_LABEL}</label>
|
||||||
<select
|
<select
|
||||||
className="form-input"
|
className="form-input"
|
||||||
value={formData.club_id != null && formData.club_id !== '' ? String(formData.club_id) : ''}
|
value={formData.club_id != null && formData.club_id !== '' ? String(formData.club_id) : ''}
|
||||||
|
|
@ -2081,9 +2075,8 @@ function ExerciseFormPageRoot() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{variantEditSelection === 'new' && (
|
{variantEditSelection === 'new' && (
|
||||||
<form
|
<div
|
||||||
className="exercise-variant-single-form"
|
className="exercise-variant-single-form"
|
||||||
onSubmit={createVariantSubmit}
|
|
||||||
style={{ marginTop: '14px', paddingTop: '14px', borderTop: '1px solid var(--border)' }}
|
style={{ marginTop: '14px', paddingTop: '14px', borderTop: '1px solid var(--border)' }}
|
||||||
>
|
>
|
||||||
<h3 style={{ marginTop: 0, fontSize: '1rem' }}>Neue Variante</h3>
|
<h3 style={{ marginTop: 0, fontSize: '1rem' }}>Neue Variante</h3>
|
||||||
|
|
@ -2099,10 +2092,19 @@ function ExerciseFormPageRoot() {
|
||||||
linkedExerciseMedia={isEdit ? mediaList : []}
|
linkedExerciseMedia={isEdit ? mediaList : []}
|
||||||
onExerciseMediaListChanged={refreshMedia}
|
onExerciseMediaListChanged={refreshMedia}
|
||||||
/>
|
/>
|
||||||
<button type="submit" className="btn btn-primary" style={{ marginTop: '10px' }} disabled={variantBusy}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
style={{ marginTop: '10px' }}
|
||||||
|
disabled={variantBusy}
|
||||||
|
onClick={handleCreateVariantClick}
|
||||||
|
>
|
||||||
{variantBusy ? 'Anlegen…' : 'Variante anlegen'}
|
{variantBusy ? 'Anlegen…' : 'Variante anlegen'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
<p className="exercise-form-panel__hint" style={{ marginTop: '8px', marginBottom: 0 }}>
|
||||||
|
Alternativ reicht „Speichern“ in der Aktionsleiste — der Entwurf wird dann mitgesichert.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedVariantForEdit && (
|
{selectedVariantForEdit && (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { activeClubMemberships } from '../../utils/activeClub'
|
import { activeClubMemberships } from '../../utils/activeClub'
|
||||||
|
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../../constants/exerciseGovernanceLabels'
|
||||||
import MultiSelectCombo from '../MultiSelectCombo'
|
import MultiSelectCombo from '../MultiSelectCombo'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -83,7 +84,7 @@ export default function ExerciseListBulkModal({
|
||||||
Primärzuordnung.
|
Primärzuordnung.
|
||||||
</p>
|
</p>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Sichtbarkeit</label>
|
<label className="form-label">{EXERCISE_VISIBILITY_FIELD_LABEL}</label>
|
||||||
<select className="form-input" value={bulkVisibility} onChange={(e) => setBulkVisibility(e.target.value)}>
|
<select className="form-input" value={bulkVisibility} onChange={(e) => setBulkVisibility(e.target.value)}>
|
||||||
{bulkVisibilityOptions.map((o) => (
|
{bulkVisibilityOptions.map((o) => (
|
||||||
<option key={o.id === '' ? '_unchanged' : o.id} value={o.id}>
|
<option key={o.id === '' ? '_unchanged' : o.id} value={o.id}>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import ExerciseRichTextBlock from '../ExerciseRichTextBlock'
|
import ExerciseRichTextBlock from '../ExerciseRichTextBlock'
|
||||||
import { coerceApiNameList } from '../../utils/sanitizeHtml'
|
import { coerceApiNameList } from '../../utils/sanitizeHtml'
|
||||||
|
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../../constants/exerciseGovernanceLabels'
|
||||||
import { canUserRequestExerciseDelete } from '../../utils/exercisePermissions'
|
import { canUserRequestExerciseDelete } from '../../utils/exercisePermissions'
|
||||||
|
|
||||||
const VIS_LABELS = { official: 'Global', club: 'Verein', private: 'Privat' }
|
const VIS_LABELS = { official: 'Global', club: 'Verein', private: 'Privat' }
|
||||||
|
|
@ -118,7 +119,7 @@ function ExerciseCardScopeStatus({ exercise }) {
|
||||||
<div
|
<div
|
||||||
className="exercise-card__meta-compact"
|
className="exercise-card__meta-compact"
|
||||||
title={tip}
|
title={tip}
|
||||||
aria-label={`Sichtbarkeit: ${visLabel}. Status: ${stLabel}.`}
|
aria-label={`${EXERCISE_VISIBILITY_FIELD_LABEL}: ${visLabel}. Status: ${stLabel}.`}
|
||||||
>
|
>
|
||||||
<span className="exercise-card__meta-glyph">
|
<span className="exercise-card__meta-glyph">
|
||||||
<VisIcon size={15} strokeWidth={2} aria-hidden />
|
<VisIcon size={15} strokeWidth={2} aria-hidden />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { SKILL_LEVEL_OPTIONS } from '../../constants/skillLevels'
|
import { SKILL_LEVEL_OPTIONS } from '../../constants/skillLevels'
|
||||||
|
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../../constants/exerciseGovernanceLabels'
|
||||||
import SkillTreeMultiSelect from '../SkillTreeMultiSelect'
|
import SkillTreeMultiSelect from '../SkillTreeMultiSelect'
|
||||||
import ExerciseFocusRulePicker from '../ExerciseFocusRulePicker'
|
import ExerciseFocusRulePicker from '../ExerciseFocusRulePicker'
|
||||||
import CatalogRulePicker from '../CatalogRulePicker'
|
import CatalogRulePicker from '../CatalogRulePicker'
|
||||||
|
|
@ -56,7 +57,7 @@ export default function ExerciseListFilterModal({
|
||||||
Zwischen den Bereichen gilt <strong>UND</strong>. Fokusbereiche: mehrere „+ mit“ bedeuten alle müssen
|
Zwischen den Bereichen gilt <strong>UND</strong>. Fokusbereiche: mehrere „+ mit“ bedeuten alle müssen
|
||||||
gesetzt sein; „− ohne“ schließt Übungen aus, die diesen Fokus zusätzlich haben. Stilrichtung /
|
gesetzt sein; „− ohne“ schließt Übungen aus, die diesen Fokus zusätzlich haben. Stilrichtung /
|
||||||
Trainingsstil / Zielgruppe: mehrere „+“ = alle zutreffend (UND); „−“ verbietet die Zuordnung. Unter
|
Trainingsstil / Zielgruppe: mehrere „+“ = alle zutreffend (UND); „−“ verbietet die Zuordnung. Unter
|
||||||
„Freigabe“: Sichtbarkeit / Status mit „+“ = eine davon (ODER); „−“ blendet aus.
|
„Freigabe“: Freigabelevel / Status mit „+“ = eine davon (ODER); „−“ blendet aus.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section className="exercise-filter-section">
|
<section className="exercise-filter-section">
|
||||||
|
|
@ -155,7 +156,7 @@ export default function ExerciseListFilterModal({
|
||||||
<section className="exercise-filter-section">
|
<section className="exercise-filter-section">
|
||||||
<h4 className="exercise-filter-section-title">Ausblenden / Liste</h4>
|
<h4 className="exercise-filter-section-title">Ausblenden / Liste</h4>
|
||||||
<p className="muted" style={{ marginTop: 0, marginBottom: '12px', fontSize: '13px' }}>
|
<p className="muted" style={{ marginTop: 0, marginBottom: '12px', fontSize: '13px' }}>
|
||||||
Sichtbarkeit und Status steuern Sie unter „Freigabe“ mit + und −. Hier nur globale Listen-Optionen.
|
Freigabelevel und Status steuern Sie unter „Freigabe“ mit + und −. Hier nur globale Listen-Optionen.
|
||||||
</p>
|
</p>
|
||||||
<div style={{ marginTop: '6px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
<div style={{ marginTop: '6px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
<label
|
<label
|
||||||
|
|
@ -199,12 +200,12 @@ export default function ExerciseListFilterModal({
|
||||||
</p>
|
</p>
|
||||||
<div className="exercise-filters-modal-grid exercise-filters-modal-grid--two exercise-filters-modal-grid--catalog">
|
<div className="exercise-filters-modal-grid exercise-filters-modal-grid--two exercise-filters-modal-grid--catalog">
|
||||||
<CatalogRulePicker
|
<CatalogRulePicker
|
||||||
label="Sichtbarkeit"
|
label={EXERCISE_VISIBILITY_FIELD_LABEL}
|
||||||
options={visibilityOptions}
|
options={visibilityOptions}
|
||||||
rules={filters.visibility_rules}
|
rules={filters.visibility_rules}
|
||||||
rulesFieldName="visibility_rules"
|
rulesFieldName="visibility_rules"
|
||||||
idKind="string"
|
idKind="string"
|
||||||
placeholder="Sichtbarkeit …"
|
placeholder="Freigabelevel …"
|
||||||
onPatch={(patch) => setFilters((prev) => ({ ...prev, ...patch }))}
|
onPatch={(patch) => setFilters((prev) => ({ ...prev, ...patch }))}
|
||||||
/>
|
/>
|
||||||
<CatalogRulePicker
|
<CatalogRulePicker
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,7 @@ function ExercisesListPageRoot() {
|
||||||
bulkPatchTargetGroups
|
bulkPatchTargetGroups
|
||||||
if (!bulkVisibility && !bulkStatus && !anyRelationPatch) {
|
if (!bulkVisibility && !bulkStatus && !anyRelationPatch) {
|
||||||
alert(
|
alert(
|
||||||
'Bitte mindestens eine Änderung wählen (Sichtbarkeit, Status oder eine der Zuordnungen mit gesetztem Häkchen „ersetzen“).'
|
'Bitte mindestens eine Änderung wählen (Freigabelevel, Status oder eine der Zuordnungen mit gesetztem Häkchen „ersetzen“).'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { activeClubMemberships, getDefaultClubIdForGovernanceForms } from '../..
|
||||||
import { hydrateExercisePlanningRow } from '../../utils/trainingUnitSectionsForm'
|
import { hydrateExercisePlanningRow } from '../../utils/trainingUnitSectionsForm'
|
||||||
import { buildRowsPayload, moduleItemToPayload } from '../../utils/exerciseListSelection'
|
import { buildRowsPayload, moduleItemToPayload } from '../../utils/exerciseListSelection'
|
||||||
import { navigateWithAppReturn } from '../../utils/navReturnContext'
|
import { navigateWithAppReturn } from '../../utils/navReturnContext'
|
||||||
|
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../../constants/exerciseGovernanceLabels'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt ein Trainingsmodul aus per Checkbox ausgewählten Übungen (Reihenfolge = Auswahlreihenfolge).
|
* Erstellt ein Trainingsmodul aus per Checkbox ausgewählten Übungen (Reihenfolge = Auswahlreihenfolge).
|
||||||
|
|
@ -174,7 +175,7 @@ export default function SaveSelectedExercisesAsModuleModal({
|
||||||
if (Number.isFinite(fallback) && fallback > 0) cid = fallback
|
if (Number.isFinite(fallback) && fallback > 0) cid = fallback
|
||||||
}
|
}
|
||||||
if (visibility === 'club' && (!Number.isFinite(cid) || cid < 1)) {
|
if (visibility === 'club' && (!Number.isFinite(cid) || cid < 1)) {
|
||||||
toast.error('Bitte einen Verein wählen (Sichtbarkeit „Verein“).')
|
toast.error('Bitte einen Verein wählen (Freigabelevel „Verein“).')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (visibility !== 'club') cid = null
|
if (visibility !== 'club') cid = null
|
||||||
|
|
@ -275,7 +276,7 @@ export default function SaveSelectedExercisesAsModuleModal({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-row" style={{ marginBottom: '0.85rem' }}>
|
<div className="form-row" style={{ marginBottom: '0.85rem' }}>
|
||||||
<label className="form-label">Sichtbarkeit</label>
|
<label className="form-label">{EXERCISE_VISIBILITY_FIELD_LABEL}</label>
|
||||||
<select
|
<select
|
||||||
className="form-input"
|
className="form-input"
|
||||||
value={visibility}
|
value={visibility}
|
||||||
|
|
|
||||||
4
frontend/src/constants/exerciseGovernanceLabels.js
Normal file
4
frontend/src/constants/exerciseGovernanceLabels.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
/** UI-Bezeichnung für `exercises.visibility` (API/DB-Feldname unverändert). */
|
||||||
|
export const EXERCISE_VISIBILITY_FIELD_LABEL = 'Freigabelevel'
|
||||||
|
|
||||||
|
export const EXERCISE_VISIBILITY_CLUB_FIELD_LABEL = 'Verein (Freigabelevel)'
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { SKILL_LEVEL_OPTIONS } from '../constants/skillLevels'
|
import { SKILL_LEVEL_OPTIONS } from '../constants/skillLevels'
|
||||||
|
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../constants/exerciseGovernanceLabels'
|
||||||
|
|
||||||
const LEVEL_FILTER_OPTS = SKILL_LEVEL_OPTIONS.filter((o) => o.level != null)
|
const LEVEL_FILTER_OPTS = SKILL_LEVEL_OPTIONS.filter((o) => o.level != null)
|
||||||
|
|
||||||
|
|
@ -164,7 +165,7 @@ export function buildExerciseListFilterChips({
|
||||||
'visibility_rules',
|
'visibility_rules',
|
||||||
filters.visibility_rules,
|
filters.visibility_rules,
|
||||||
visibilityOptions,
|
visibilityOptions,
|
||||||
'Sichtbarkeit',
|
EXERCISE_VISIBILITY_FIELD_LABEL,
|
||||||
setFilters
|
setFilters
|
||||||
)
|
)
|
||||||
pushCatalogRuleFilterChips(chips, 'status_rules', filters.status_rules, statusOptions, 'Status', setFilters)
|
pushCatalogRuleFilterChips(chips, 'status_rules', filters.status_rules, statusOptions, 'Status', setFilters)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user