feat: enhance RichTextEditor and exercise variant management
Some checks failed
Deploy Development / deploy (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 1m55s

- Added support for saving and restoring text selection in the RichTextEditor, improving user experience during formatting.
- Updated CSS styles for the RichTextEditor to enhance list formatting and overall appearance.
- Introduced new ExerciseVariantFields component to streamline the editing of exercise variants, including fields for variant name, description, execution changes, and more.
- Enhanced ExerciseFormPage to manage exercise variants more effectively, including improved state handling and UI updates for variant selection.
This commit is contained in:
Lars 2026-04-28 13:09:42 +02:00
parent 1ee1a2f2d9
commit 91b3fec5cc
3 changed files with 393 additions and 293 deletions

View File

@ -2406,6 +2406,26 @@ a.analysis-split__nav-item {
overflow-y: auto;
resize: vertical;
}
/* Listen im Editor (nicht nur in .rich-text-content) sonst „unsichtbare“ Bullets */
.rich-text-editor ul,
.rich-text-editor ol {
margin: 0.35rem 0;
padding-left: 1.35rem;
list-style-position: outside;
}
.rich-text-editor ul {
list-style-type: disc;
}
.rich-text-editor ol {
list-style-type: decimal;
}
.rich-text-editor li {
margin: 0.15rem 0;
display: list-item;
}
.rich-text-editor p {
margin: 0.35rem 0;
}
.rich-text-editor:empty:before {
content: attr(data-placeholder);
color: var(--text3);
@ -2441,6 +2461,45 @@ a.analysis-split__nav-item {
.exercise-card__body {
flex: 1 1 auto;
}
.exercise-variants-details summary {
list-style: none;
}
.exercise-variants-details summary::-webkit-details-marker {
display: none;
}
.exercise-variants-summary {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 16px;
cursor: pointer;
user-select: none;
}
.exercise-variants-summary__title {
font-size: 1.1rem;
font-weight: 600;
}
.exercise-variants-summary__badge {
font-size: 12px;
font-weight: 600;
color: var(--text2);
background: var(--surface2);
padding: 4px 10px;
border-radius: 999px;
border: 1px solid var(--border);
}
.exercise-variants-details__body {
padding: 0 16px 16px;
border-top: 1px solid var(--border);
}
.exercise-variants-hint {
font-size: 13px;
color: var(--text2);
margin: 12px 0;
line-height: 1.45;
}
.exercise-card__actions {
flex-shrink: 0;
display: flex;

View File

@ -8,6 +8,30 @@ function exec(cmd, value = null) {
}
}
/** Selection speichern, bevor Toolbar-Klicks sie zerstört (mousedown + preventDefault allein reicht nicht überall). */
function saveSelectionInside(editorEl) {
const sel = window.getSelection()
if (!sel || sel.rangeCount === 0 || !editorEl) return null
try {
const range = sel.getRangeAt(0)
if (!editorEl.contains(range.commonAncestorContainer)) return null
return range.cloneRange()
} catch {
return null
}
}
function restoreSelection(range) {
if (!range) return
try {
const sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
} catch {
/* noop */
}
}
/** Browser: formatBlock erwartet oft Tag in Großschreibung. */
function formatBlock(tag) {
const t = String(tag).toUpperCase()
@ -44,14 +68,29 @@ export default function RichTextEditor({ value, onChange, placeholder, minHeight
const run = (fn) => (e) => {
e.preventDefault()
ref.current?.focus()
e.stopPropagation()
const el = ref.current
if (!el) return
const saved = saveSelectionInside(el)
el.focus()
restoreSelection(saved)
try {
document.execCommand('styleWithCSS', false, false)
} catch {
/* optional */
}
fn()
sync()
}
const onLink = (e) => {
e.preventDefault()
ref.current?.focus()
e.stopPropagation()
const el = ref.current
if (!el) return
const saved = saveSelectionInside(el)
el.focus()
restoreSelection(saved)
const url = window.prompt('Link-URL (https://…)')
if (url) {
exec('createLink', url)
@ -79,7 +118,12 @@ export default function RichTextEditor({ value, onChange, placeholder, minHeight
Ü3
</button>
<span className="rte-sep" />
<button type="button" className="rte-btn" title="Aufzählung" onMouseDown={run(() => exec('insertUnorderedList'))}>
<button
type="button"
className="rte-btn"
title="Aufzählung (Mehrzeilen-Markierung möglich)"
onMouseDown={run(() => exec('insertUnorderedList'))}
>
Liste
</button>
<button type="button" className="rte-btn" title="Nummerierte Liste" onMouseDown={run(() => exec('insertOrderedList'))}>

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useState, useRef } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import api, { buildExerciseApiPayload } from '../utils/api'
import RichTextEditor from '../components/RichTextEditor'
@ -87,6 +87,126 @@ function buildVariantPayloadFromRow(row) {
}
}
/** Gemeinsame Felder für „Variante bearbeiten“ und „Neue Variante“. */
function ExerciseVariantFields({ row, onPatch, prerequisiteOthers, rteMinHeight = '110px' }) {
return (
<>
<div className="form-row">
<label className="form-label">Variantenname *</label>
<input
type="text"
className="form-input"
value={row.variant_name || ''}
onChange={(e) => onPatch({ variant_name: e.target.value })}
minLength={3}
/>
</div>
<div className="form-row">
<label className="form-label">Kurzbeschreibung</label>
<textarea
className="form-input"
rows={2}
value={row.description || ''}
onChange={(e) => onPatch({ description: e.target.value })}
/>
</div>
<div className="form-row">
<label className="form-label">Abweichungen zur Durchführung</label>
<RichTextEditor
value={row.execution_changes || ''}
onChange={(html) => onPatch({ execution_changes: html })}
placeholder="Was unterscheidet diese Variante? (Listen über Symbolleiste)"
minHeight={rteMinHeight}
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
<div className="form-row">
<label className="form-label">Dauer Min</label>
<input
type="number"
className="form-input"
value={row.duration_min}
onChange={(e) => onPatch({ duration_min: e.target.value })}
/>
</div>
<div className="form-row">
<label className="form-label">Dauer Max</label>
<input
type="number"
className="form-input"
value={row.duration_max}
onChange={(e) => onPatch({ duration_max: e.target.value })}
/>
</div>
</div>
<div className="form-row">
<label className="form-label">Materialänderungen (eine Zeile pro Eintrag)</label>
<textarea
className="form-input"
rows={2}
value={row.equipment_lines || ''}
onChange={(e) => onPatch({ equipment_lines: e.target.value })}
placeholder="+ Pratzen"
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: '10px' }}>
<div className="form-row">
<label className="form-label">Schwere relativ</label>
<select
className="form-input"
value={row.difficulty_adjustment || ''}
onChange={(e) => onPatch({ difficulty_adjustment: e.target.value })}
>
{VARIANT_DIFFICULTY.map((o) => (
<option key={o.label} value={o.value}>
{o.label}
</option>
))}
</select>
</div>
<div className="form-row">
<label className="form-label">Progressions-Stufe (110)</label>
<input
type="number"
min={1}
max={10}
className="form-input"
value={row.progression_level}
onChange={(e) =>
onPatch({
progression_level: e.target.value === '' ? '' : parseInt(e.target.value, 10),
})
}
/>
</div>
<div className="form-row">
<label className="form-label">Voraussetzungs-Variante</label>
<select
className="form-input"
value={
row.prerequisite_variant_id === '' || row.prerequisite_variant_id == null
? ''
: String(row.prerequisite_variant_id)
}
onChange={(e) =>
onPatch({
prerequisite_variant_id: e.target.value === '' ? '' : parseInt(e.target.value, 10),
})
}
>
<option value=""> keine </option>
{prerequisiteOthers.map((o) => (
<option key={o.id} value={o.id}>
{o.variant_name || `Variante #${o.id}`}
</option>
))}
</select>
</div>
</div>
</>
)
}
function emptyForm() {
return {
title: '',
@ -237,6 +357,8 @@ function ExerciseFormPage() {
const [variantDraft, setVariantDraft] = useState(() => emptyVariantDraft())
const [variantSavingId, setVariantSavingId] = useState(null)
const [variantBusy, setVariantBusy] = useState(false)
const [variantEditSelection, setVariantEditSelection] = useState(null)
const variantsDetailsRef = useRef(null)
const [mediaFile, setMediaFile] = useState(null)
const [mediaType, setMediaType] = useState('image')
@ -284,6 +406,7 @@ function ExerciseFormPage() {
setMediaList([])
setVariants([])
setVariantDraft(emptyVariantDraft())
setVariantEditSelection(null)
setLoading(false)
return
}
@ -297,6 +420,7 @@ function ExerciseFormPage() {
setMediaList(exercise.media || [])
setVariants((exercise.variants || []).map(apiVariantToRow))
setVariantDraft(emptyVariantDraft())
setVariantEditSelection(null)
} catch (err) {
if (!cancelled) {
alert(err.message || 'Übung nicht ladbar')
@ -312,6 +436,19 @@ function ExerciseFormPage() {
}
}, [isEdit, exerciseId, navigate])
useEffect(() => {
if (variantEditSelection == null || variantEditSelection === 'new') return
if (!variants.some((v) => v.id === variantEditSelection)) {
setVariantEditSelection(null)
}
}, [variants, variantEditSelection])
useEffect(() => {
if (variantEditSelection != null && variantsDetailsRef.current) {
variantsDetailsRef.current.open = true
}
}, [variantEditSelection])
const updateFormField = (field, value) => {
setFormData((prev) => ({ ...prev, [field]: value }))
}
@ -493,6 +630,7 @@ function ExerciseFormPage() {
setVariantBusy(true)
try {
await api.deleteExerciseVariant(exerciseId, id)
if (variantEditSelection === id) setVariantEditSelection(null)
await refreshVariants()
} catch (e) {
alert(e.message || String(e))
@ -530,9 +668,11 @@ function ExerciseFormPage() {
}
setVariantBusy(true)
try {
await api.createExerciseVariant(exerciseId, payload)
const created = await api.createExerciseVariant(exerciseId, payload)
setVariantDraft(emptyVariantDraft())
await refreshVariants()
if (created?.id != null) setVariantEditSelection(created.id)
else setVariantEditSelection(null)
} catch (err) {
alert(err.message || String(err))
} finally {
@ -542,6 +682,12 @@ function ExerciseFormPage() {
const availableSkills = skillsCatalog.filter((s) => !formData.skills.some((x) => x.skill_id === s.id))
const selectedVariantForEdit =
typeof variantEditSelection === 'number' ? variants.find((v) => v.id === variantEditSelection) : null
const selectedVariantIdx = selectedVariantForEdit
? variants.findIndex((v) => v.id === selectedVariantForEdit.id)
: -1
if (loading) {
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
@ -854,304 +1000,155 @@ function ExerciseFormPage() {
</div>
{isEdit && (
<div className="card" style={{ marginTop: '16px' }}>
<h2 style={{ marginTop: 0, fontSize: '1.1rem' }}>Übungsvarianten</h2>
<p style={{ color: 'var(--text2)', fontSize: '13px', marginBottom: '12px' }}>
Alternative Ausführungen zur Stammübung. Reihenfolge (Nach oben/unten) steuert Darstellung und Auswahl in der
Trainingsplanung. Voraussetzung verknüpft Varianten für spätere Progressions-Serien als Blöcke.
</p>
{variants.length === 0 && (
<p style={{ fontSize: '13px', color: 'var(--text3)', marginBottom: '12px' }}>Noch keine Varianten angelegt.</p>
)}
{variants.map((v, idx) => (
<div
key={v.id}
style={{
border: '1px solid var(--border)',
borderRadius: '10px',
padding: '12px',
marginBottom: '12px',
background: 'var(--surface2)',
}}
>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center', marginBottom: '10px' }}>
<span style={{ fontSize: '12px', color: 'var(--text3)' }}>#{idx + 1}</span>
<button
type="button"
className="btn btn-secondary"
style={{ fontSize: '11px', padding: '4px 8px' }}
disabled={variantBusy || idx === 0}
onClick={() => moveVariantRow(idx, -1)}
>
Nach oben
</button>
<button
type="button"
className="btn btn-secondary"
style={{ fontSize: '11px', padding: '4px 8px' }}
disabled={variantBusy || idx === variants.length - 1}
onClick={() => moveVariantRow(idx, 1)}
>
Nach unten
</button>
<button
type="button"
className="btn btn-primary"
style={{ fontSize: '12px', marginLeft: 'auto' }}
disabled={variantSavingId === v.id || variantBusy}
onClick={() => saveVariantRow(v)}
>
{variantSavingId === v.id ? 'Speichern…' : 'Speichern'}
</button>
<button
type="button"
className="btn"
style={{ fontSize: '12px', background: 'var(--danger)', color: '#fff', border: 'none' }}
disabled={variantBusy}
onClick={() => deleteVariantRow(v.id)}
>
Löschen
</button>
</div>
<details ref={variantsDetailsRef} className="card exercise-variants-details" style={{ marginTop: '16px' }}>
<summary className="exercise-variants-summary">
<span className="exercise-variants-summary__title">Übungsvarianten</span>
<span className="exercise-variants-summary__badge">
{variants.length === 0
? 'keine'
: `${variants.length} ${variants.length === 1 ? 'Variante' : 'Varianten'}`}
</span>
</summary>
<div className="exercise-variants-details__body">
<p className="exercise-variants-hint">
Pro Durchgang nur eine Variante bearbeiten weniger Scrollen. Reihenfolge entspricht Planung und Auswahl im
Training; Voraussetzung nutzt ihr später für Progressions-Serien.
</p>
{variants.length > 0 && (
<div className="form-row">
<label className="form-label">Variantenname *</label>
<input
type="text"
className="form-input"
value={v.variant_name || ''}
onChange={(e) => updateVariantField(v.id, { variant_name: e.target.value })}
minLength={3}
/>
</div>
<div className="form-row">
<label className="form-label">Kurzbeschreibung</label>
<textarea
className="form-input"
rows={2}
value={v.description || ''}
onChange={(e) => updateVariantField(v.id, { description: e.target.value })}
/>
</div>
<div className="form-row">
<label className="form-label">Abweichungen zur Durchführung</label>
<RichTextEditor
value={v.execution_changes || ''}
onChange={(html) => updateVariantField(v.id, { execution_changes: html })}
placeholder="Was unterscheidet diese Variante?"
minHeight="100px"
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
<div className="form-row">
<label className="form-label">Dauer Min</label>
<input
type="number"
className="form-input"
value={v.duration_min}
onChange={(e) => updateVariantField(v.id, { duration_min: e.target.value })}
/>
</div>
<div className="form-row">
<label className="form-label">Dauer Max</label>
<input
type="number"
className="form-input"
value={v.duration_max}
onChange={(e) => updateVariantField(v.id, { duration_max: e.target.value })}
/>
</div>
</div>
<div className="form-row">
<label className="form-label">Materialänderungen (eine Zeile pro Eintrag)</label>
<textarea
className="form-input"
rows={2}
value={v.equipment_lines || ''}
onChange={(e) => updateVariantField(v.id, { equipment_lines: e.target.value })}
placeholder="+ Pratzen"
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: '10px' }}>
<div className="form-row">
<label className="form-label">Schwere relativ</label>
<select
className="form-input"
value={v.difficulty_adjustment || ''}
onChange={(e) => updateVariantField(v.id, { difficulty_adjustment: e.target.value })}
>
{VARIANT_DIFFICULTY.map((o) => (
<option key={o.label} value={o.value}>
{o.label}
</option>
))}
</select>
</div>
<div className="form-row">
<label className="form-label">Progressions-Stufe (110)</label>
<input
type="number"
min={1}
max={10}
className="form-input"
value={v.progression_level}
onChange={(e) =>
updateVariantField(v.id, {
progression_level: e.target.value === '' ? '' : parseInt(e.target.value, 10),
})
}
/>
</div>
<div className="form-row">
<label className="form-label">Voraussetzungs-Variante</label>
<select
className="form-input"
value={
v.prerequisite_variant_id === '' || v.prerequisite_variant_id == null
? ''
: String(v.prerequisite_variant_id)
}
onChange={(e) =>
updateVariantField(v.id, {
prerequisite_variant_id: e.target.value === '' ? '' : parseInt(e.target.value, 10),
})
}
>
<option value=""> keine </option>
{variants
.filter((o) => o.id !== v.id)
.map((o) => (
<option key={o.id} value={o.id}>
{o.variant_name || `Variante #${o.id}`}
</option>
))}
</select>
</div>
</div>
</div>
))}
<form
onSubmit={createVariantSubmit}
style={{ marginTop: '16px', paddingTop: '16px', borderTop: '1px solid var(--border)' }}
>
<h3 style={{ marginTop: 0, fontSize: '1rem' }}>Neue Variante anlegen</h3>
<div className="form-row">
<label className="form-label">Variantenname *</label>
<input
type="text"
className="form-input"
value={variantDraft.variant_name}
onChange={(e) => setVariantDraft((d) => ({ ...d, variant_name: e.target.value }))}
minLength={3}
/>
</div>
<div className="form-row">
<label className="form-label">Kurzbeschreibung</label>
<textarea
className="form-input"
rows={2}
value={variantDraft.description}
onChange={(e) => setVariantDraft((d) => ({ ...d, description: e.target.value }))}
/>
</div>
<div className="form-row">
<label className="form-label">Abweichungen zur Durchführung</label>
<RichTextEditor
value={variantDraft.execution_changes}
onChange={(html) => setVariantDraft((d) => ({ ...d, execution_changes: html }))}
placeholder="Optional: abweichende Schritte"
minHeight="100px"
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
<div className="form-row">
<label className="form-label">Dauer Min</label>
<input
type="number"
className="form-input"
value={variantDraft.duration_min}
onChange={(e) => setVariantDraft((d) => ({ ...d, duration_min: e.target.value }))}
/>
</div>
<div className="form-row">
<label className="form-label">Dauer Max</label>
<input
type="number"
className="form-input"
value={variantDraft.duration_max}
onChange={(e) => setVariantDraft((d) => ({ ...d, duration_max: e.target.value }))}
/>
</div>
</div>
<div className="form-row">
<label className="form-label">Materialänderungen</label>
<textarea
className="form-input"
rows={2}
value={variantDraft.equipment_lines}
onChange={(e) => setVariantDraft((d) => ({ ...d, equipment_lines: e.target.value }))}
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: '10px' }}>
<div className="form-row">
<label className="form-label">Schwere relativ</label>
<select
className="form-input"
value={variantDraft.difficulty_adjustment}
onChange={(e) => setVariantDraft((d) => ({ ...d, difficulty_adjustment: e.target.value }))}
>
{VARIANT_DIFFICULTY.map((o) => (
<option key={o.label} value={o.value}>
{o.label}
</option>
))}
</select>
</div>
<div className="form-row">
<label className="form-label">Progressions-Stufe (110)</label>
<input
type="number"
min={1}
max={10}
className="form-input"
value={variantDraft.progression_level}
onChange={(e) =>
setVariantDraft((d) => ({
...d,
progression_level: e.target.value === '' ? 1 : parseInt(e.target.value, 10),
}))
}
/>
</div>
<div className="form-row">
<label className="form-label">Voraussetzungs-Variante</label>
<label className="form-label" htmlFor="variant-edit-select">
Variante auswählen
</label>
<select
id="variant-edit-select"
className="form-input"
value={
variantDraft.prerequisite_variant_id === '' || variantDraft.prerequisite_variant_id == null
? ''
: String(variantDraft.prerequisite_variant_id)
}
onChange={(e) =>
setVariantDraft((d) => ({
...d,
prerequisite_variant_id: e.target.value === '' ? '' : parseInt(e.target.value, 10),
}))
variantEditSelection === 'new'
? 'new'
: variantEditSelection == null
? ''
: String(variantEditSelection)
}
onChange={(e) => {
const val = e.target.value
if (val === '') setVariantEditSelection(null)
else if (val === 'new') setVariantEditSelection('new')
else setVariantEditSelection(parseInt(val, 10))
}}
>
<option value=""> keine </option>
{variants.map((o) => (
<option key={o.id} value={o.id}>
{o.variant_name || `Variante #${o.id}`}
<option value=""> nicht bearbeiten </option>
{variants.map((v) => (
<option key={v.id} value={v.id}>
{(v.variant_name && String(v.variant_name).trim()) || `Variante #${v.id}`}
</option>
))}
<option value="new">+ Neue Variante anlegen</option>
</select>
</div>
</div>
<button type="submit" className="btn btn-primary" disabled={variantBusy}>
{variantBusy ? 'Anlegen…' : 'Variante anlegen'}
</button>
</form>
</div>
)}
{variants.length === 0 && (
<p style={{ fontSize: '13px', color: 'var(--text3)', marginBottom: '10px' }}>
Noch keine Varianten optional für andere Ausführung, Dauer oder Material in Planung und Training.
</p>
)}
{variants.length === 0 && variantEditSelection !== 'new' && (
<button type="button" className="btn btn-secondary" onClick={() => setVariantEditSelection('new')}>
Erste Variante anlegen
</button>
)}
{variantEditSelection === 'new' && (
<form
className="exercise-variant-single-form"
onSubmit={createVariantSubmit}
style={{ marginTop: '14px', paddingTop: '14px', borderTop: '1px solid var(--border)' }}
>
<h3 style={{ marginTop: 0, fontSize: '1rem' }}>Neue Variante</h3>
<ExerciseVariantFields
row={variantDraft}
onPatch={(patch) => setVariantDraft((d) => ({ ...d, ...patch }))}
prerequisiteOthers={variants}
rteMinHeight="110px"
/>
<button type="submit" className="btn btn-primary" style={{ marginTop: '10px' }} disabled={variantBusy}>
{variantBusy ? 'Anlegen…' : 'Variante anlegen'}
</button>
</form>
)}
{selectedVariantForEdit && (
<div
className="exercise-variant-single-form"
style={{ marginTop: '14px', paddingTop: '14px', borderTop: '1px solid var(--border)' }}
>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: '8px',
alignItems: 'center',
marginBottom: '12px',
}}
>
<span style={{ fontSize: '12px', color: 'var(--text3)' }}>
Pos. {selectedVariantIdx + 1} von {variants.length}
</span>
<button
type="button"
className="btn btn-secondary"
style={{ fontSize: '11px', padding: '4px 8px' }}
disabled={variantBusy || selectedVariantIdx <= 0}
onClick={() => moveVariantRow(selectedVariantIdx, -1)}
>
Nach oben
</button>
<button
type="button"
className="btn btn-secondary"
style={{ fontSize: '11px', padding: '4px 8px' }}
disabled={variantBusy || selectedVariantIdx >= variants.length - 1}
onClick={() => moveVariantRow(selectedVariantIdx, 1)}
>
Nach unten
</button>
<button
type="button"
className="btn btn-primary"
style={{ marginLeft: 'auto', fontSize: '12px' }}
disabled={variantSavingId === selectedVariantForEdit.id || variantBusy}
onClick={() => saveVariantRow(selectedVariantForEdit)}
>
{variantSavingId === selectedVariantForEdit.id ? 'Speichern…' : 'Speichern'}
</button>
<button
type="button"
className="btn"
style={{ fontSize: '12px', background: 'var(--danger)', color: '#fff', border: 'none' }}
disabled={variantBusy}
onClick={() => deleteVariantRow(selectedVariantForEdit.id)}
>
Löschen
</button>
</div>
<ExerciseVariantFields
row={selectedVariantForEdit}
onPatch={(patch) => updateVariantField(selectedVariantForEdit.id, patch)}
prerequisiteOthers={variants.filter((o) => o.id !== selectedVariantForEdit.id)}
rteMinHeight="110px"
/>
</div>
)}
{variants.length > 0 && variantEditSelection == null && (
<p style={{ fontSize: '13px', color: 'var(--text3)', marginTop: '12px', marginBottom: 0 }}>
Wähle eine Variante zum Bearbeiten oder Neue Variante anlegen.
</p>
)}
</div>
</details>
)}
{isEdit && (