Refactor Exercise Visibility Labels for Consistency Across Components
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 42s
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 1m17s

- Replaced hardcoded visibility labels with the new constant EXERCISE_VISIBILITY_FIELD_LABEL in multiple components, ensuring consistent terminology throughout the application.
- Updated UI text to reflect the change from "Sichtbarkeit" to "Freigabelevel" in various contexts, enhancing clarity for users.
- Improved accessibility by standardizing the visibility-related labels in the ExercisePickerModal, ExerciseProgressionGraphPanel, and other related components.
This commit is contained in:
Lars 2026-05-21 15:08:35 +02:00
parent 13a1d3a060
commit 5f67c01cef
10 changed files with 34 additions and 19 deletions

View File

@ -7,6 +7,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'
import api from '../utils/api'
import { useAuth } from '../context/AuthContext'
import { SKILL_LEVEL_OPTIONS } from '../constants/skillLevels'
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../constants/exerciseGovernanceLabels'
import {
INITIAL_EXERCISE_LIST_FILTERS,
mergeExerciseListPrefsFromApi,
@ -373,7 +374,7 @@ export default function ExercisePickerModal({
{quickOpen ? (
<div style={{ marginTop: '12px', display: 'grid', gap: '10px' }}>
<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
Ablauf übernommen.
</p>
@ -462,7 +463,7 @@ export default function ExercisePickerModal({
<div style={{ marginTop: '0.35rem', fontSize: '13px', color: 'var(--text2)' }}>
<p style={{ margin: '0 0 12px 0' }}>
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>
<ExerciseFocusRulePicker
focusOptions={focusOptions}
@ -539,12 +540,12 @@ export default function ExercisePickerModal({
</div>
<div className="exercise-filters-modal-grid exercise-filters-modal-grid--two exercise-filters-modal-grid--catalog" style={{ marginTop: 12 }}>
<CatalogRulePicker
label="Sichtbarkeit"
label={EXERCISE_VISIBILITY_FIELD_LABEL}
options={visibilityOptions}
rules={filters.visibility_rules}
rulesFieldName="visibility_rules"
idKind="string"
placeholder="Sichtbarkeit …"
placeholder="Freigabelevel …"
onPatch={(patch) => setFilters((f) => ({ ...f, ...patch }))}
/>
<CatalogRulePicker

View File

@ -9,6 +9,7 @@ import SkillProfilePanel from './skills/SkillProfilePanel'
import { useAuth } from '../context/AuthContext'
import { getTenantClubDependencyKey } from '../utils/activeClub'
import ExercisePickerModal from './ExercisePickerModal'
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../constants/exerciseGovernanceLabels'
const VIS_OPTIONS = [
{ value: 'private', label: 'Privat' },
@ -605,7 +606,7 @@ export default function ExerciseProgressionGraphPanel({
/>
</div>
<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
className="form-input"
value={newGraphVisibility}
@ -642,7 +643,7 @@ export default function ExerciseProgressionGraphPanel({
/>
</div>
<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)}>
{filteredGraphVisOptions.map((o) => (
<option key={o.value} value={o.value}>

View File

@ -44,6 +44,10 @@ import {
EXERCISE_SKILL_INTENSITY_DEFAULT,
normalizeExerciseSkillIntensity,
} from '../../constants/exerciseSkillIntensity'
import {
EXERCISE_VISIBILITY_CLUB_FIELD_LABEL,
EXERCISE_VISIBILITY_FIELD_LABEL,
} from '../../constants/exerciseGovernanceLabels'
const VARIANT_DIFFICULTY = [
{ value: '', label: '—' },
@ -1298,7 +1302,7 @@ function ExerciseFormPageRoot() {
title="Stammdaten"
hint={
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.'
}
>
@ -1428,7 +1432,7 @@ function ExerciseFormPageRoot() {
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
<div className="form-row">
<label className="form-label">Sichtbarkeit</label>
<label className="form-label">{EXERCISE_VISIBILITY_FIELD_LABEL}</label>
<select
className="form-input"
value={formData.visibility}
@ -1456,7 +1460,7 @@ function ExerciseFormPageRoot() {
{formData.visibility === 'club' && visibilityClubChoices.length > 0 ? (
<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
className="form-input"
value={formData.club_id != null && formData.club_id !== '' ? String(formData.club_id) : ''}

View File

@ -1,5 +1,6 @@
import React from 'react'
import { activeClubMemberships } from '../../utils/activeClub'
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../../constants/exerciseGovernanceLabels'
import MultiSelectCombo from '../MultiSelectCombo'
/**
@ -83,7 +84,7 @@ export default function ExerciseListBulkModal({
Primärzuordnung.
</p>
<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)}>
{bulkVisibilityOptions.map((o) => (
<option key={o.id === '' ? '_unchanged' : o.id} value={o.id}>

View File

@ -21,6 +21,7 @@ import {
} from 'lucide-react'
import ExerciseRichTextBlock from '../ExerciseRichTextBlock'
import { coerceApiNameList } from '../../utils/sanitizeHtml'
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../../constants/exerciseGovernanceLabels'
import { canUserRequestExerciseDelete } from '../../utils/exercisePermissions'
const VIS_LABELS = { official: 'Global', club: 'Verein', private: 'Privat' }
@ -118,7 +119,7 @@ function ExerciseCardScopeStatus({ exercise }) {
<div
className="exercise-card__meta-compact"
title={tip}
aria-label={`Sichtbarkeit: ${visLabel}. Status: ${stLabel}.`}
aria-label={`${EXERCISE_VISIBILITY_FIELD_LABEL}: ${visLabel}. Status: ${stLabel}.`}
>
<span className="exercise-card__meta-glyph">
<VisIcon size={15} strokeWidth={2} aria-hidden />

View File

@ -1,5 +1,6 @@
import React from 'react'
import { SKILL_LEVEL_OPTIONS } from '../../constants/skillLevels'
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../../constants/exerciseGovernanceLabels'
import SkillTreeMultiSelect from '../SkillTreeMultiSelect'
import ExerciseFocusRulePicker from '../ExerciseFocusRulePicker'
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
gesetzt sein; ohne schließt Übungen aus, die diesen Fokus zusätzlich haben. Stilrichtung /
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>
<section className="exercise-filter-section">
@ -155,7 +156,7 @@ export default function ExerciseListFilterModal({
<section className="exercise-filter-section">
<h4 className="exercise-filter-section-title">Ausblenden / Liste</h4>
<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>
<div style={{ marginTop: '6px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
<label
@ -199,12 +200,12 @@ export default function ExerciseListFilterModal({
</p>
<div className="exercise-filters-modal-grid exercise-filters-modal-grid--two exercise-filters-modal-grid--catalog">
<CatalogRulePicker
label="Sichtbarkeit"
label={EXERCISE_VISIBILITY_FIELD_LABEL}
options={visibilityOptions}
rules={filters.visibility_rules}
rulesFieldName="visibility_rules"
idKind="string"
placeholder="Sichtbarkeit …"
placeholder="Freigabelevel …"
onPatch={(patch) => setFilters((prev) => ({ ...prev, ...patch }))}
/>
<CatalogRulePicker

View File

@ -373,7 +373,7 @@ function ExercisesListPageRoot() {
bulkPatchTargetGroups
if (!bulkVisibility && !bulkStatus && !anyRelationPatch) {
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
}

View File

@ -9,6 +9,7 @@ import { activeClubMemberships, getDefaultClubIdForGovernanceForms } from '../..
import { hydrateExercisePlanningRow } from '../../utils/trainingUnitSectionsForm'
import { buildRowsPayload, moduleItemToPayload } from '../../utils/exerciseListSelection'
import { navigateWithAppReturn } from '../../utils/navReturnContext'
import { EXERCISE_VISIBILITY_FIELD_LABEL } from '../../constants/exerciseGovernanceLabels'
/**
* 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 (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
}
if (visibility !== 'club') cid = null
@ -275,7 +276,7 @@ export default function SaveSelectedExercisesAsModuleModal({
</div>
<div className="form-row" style={{ marginBottom: '0.85rem' }}>
<label className="form-label">Sichtbarkeit</label>
<label className="form-label">{EXERCISE_VISIBILITY_FIELD_LABEL}</label>
<select
className="form-input"
value={visibility}

View 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)'

View File

@ -1,4 +1,5 @@
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)
@ -164,7 +165,7 @@ export function buildExerciseListFilterChips({
'visibility_rules',
filters.visibility_rules,
visibilityOptions,
'Sichtbarkeit',
EXERCISE_VISIBILITY_FIELD_LABEL,
setFilters
)
pushCatalogRuleFilterChips(chips, 'status_rules', filters.status_rules, statusOptions, 'Status', setFilters)