feat: Enhance Admin Activity Attribute Profiles UI and styling
All checks were successful
Deploy Development / deploy (push) Successful in 55s
Build Test / pytest-backend (push) Successful in 4s
Build Test / lint-backend (push) Successful in 1s
Build Test / build-frontend (push) Successful in 16s

- Introduced a new layout for the Admin Activity Attribute Profiles page, improving the user interface with a dedicated class for styling.
- Added new CSS styles for input fields, labels, and layout structures to enhance the visual presentation and usability of the attribute profiles.
- Updated the form structure to include clearer labels and organization for input fields, ensuring better accessibility and user experience.
- Improved responsiveness of the layout for mobile devices, ensuring a consistent experience across different screen sizes.
This commit is contained in:
Lars 2026-04-17 20:57:31 +02:00
parent bc8e9fb7fa
commit 92e334dcd2
2 changed files with 461 additions and 181 deletions

View File

@ -440,6 +440,141 @@ a.analysis-split__nav-item {
}
}
/* Admin: Session-Metriken / Attributprofile — volle Breite, linksbündig (nicht globale 90px-Zahlfelder) */
.activity-attribute-profiles .aaf-stack {
max-width: 42rem;
}
.activity-attribute-profiles .aaf-field {
margin-bottom: 1rem;
}
.activity-attribute-profiles .aaf-label {
display: block;
font-size: 14px;
font-weight: 600;
color: var(--text1);
text-align: left;
margin-bottom: 6px;
line-height: 1.35;
}
.activity-attribute-profiles .aaf-sublabel {
display: block;
font-size: 12px;
font-weight: 600;
color: var(--text2);
text-align: left;
margin-bottom: 4px;
}
.activity-attribute-profiles .aaf-hint {
font-size: 12px;
color: var(--text3);
text-align: left;
margin: 6px 0 0;
line-height: 1.45;
}
.activity-attribute-profiles .aaf-input,
.activity-attribute-profiles textarea.aaf-input {
display: block;
width: 100%;
box-sizing: border-box;
padding: 10px 12px;
text-align: left;
font-family: var(--font);
font-size: 15px;
font-weight: 500;
color: var(--text1);
background: var(--surface2);
border: 1.5px solid var(--border2);
border-radius: 8px;
transition: border-color 0.15s;
}
.activity-attribute-profiles textarea.aaf-input {
resize: vertical;
min-height: 4.5rem;
font-weight: 400;
}
.activity-attribute-profiles .aaf-input:focus {
outline: none;
border-color: var(--accent);
}
.activity-attribute-profiles .aaf-split {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
@media (max-width: 560px) {
.activity-attribute-profiles .aaf-split {
grid-template-columns: 1fr;
}
}
.activity-attribute-profiles .aaf-field-select {
padding: 12px 0;
border-bottom: 1px solid var(--border);
}
.activity-attribute-profiles .aaf-field-select:last-child {
border-bottom: none;
}
.activity-attribute-profiles .aaf-field-select .form-label {
display: block;
text-align: left;
margin-bottom: 6px;
font-weight: 600;
flex: unset;
}
.activity-attribute-profiles .aaf-field-select .form-input,
.activity-attribute-profiles .aaf-field-select select.form-input {
width: 100%;
max-width: none;
min-width: 0;
text-align: left;
box-sizing: border-box;
padding: 10px 12px;
}
.activity-attribute-profiles .aaf-toolbar {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid var(--border);
}
.activity-attribute-profiles .aaf-toolbar .form-label {
display: block;
text-align: left;
margin-bottom: 6px;
font-weight: 600;
flex: unset;
}
.activity-attribute-profiles .aaf-toolbar__grow {
flex: 1 1 240px;
min-width: 0;
}
.activity-attribute-profiles .aaf-toolbar .form-input,
.activity-attribute-profiles .aaf-toolbar select.form-input {
width: 100%;
min-width: 140px;
max-width: none;
text-align: left;
box-sizing: border-box;
padding: 10px 12px;
}
.activity-attribute-profiles .aaf-toolbar__compact {
flex: 0 0 auto;
}
.activity-attribute-profiles .aaf-toolbar__compact .form-input,
.activity-attribute-profiles .aaf-toolbar__compact select.form-input {
width: 100%;
min-width: 5rem;
}
.activity-attribute-profiles .aaf-inline-edit .form-input,
.activity-attribute-profiles .aaf-inline-edit select.form-input {
text-align: left;
min-width: 4.5rem;
width: auto;
max-width: none;
box-sizing: border-box;
padding: 8px 10px;
}
/* Erfassung: Sub-Navigation (Mobil = Chips, Desktop = linke Spalte) */
.capture-shell {
width: 100%;

View File

@ -279,7 +279,7 @@ export default function AdminActivityAttributeProfilesPage() {
}
return (
<div className="capture-page">
<div className="capture-page activity-attribute-profiles">
<div style={{ marginBottom: 12 }}>
<Link to="/admin/g/training" className="text-link" style={{ fontSize: 13 }}>
Training (Hub)
@ -378,87 +378,151 @@ export default function AdminActivityAttributeProfilesPage() {
style={{
border: '1px solid var(--border)',
borderRadius: 8,
padding: 12,
padding: 16,
marginBottom: 12,
background: 'var(--surface2)',
}}
>
<div className="form-row">
<label className="form-label">key</label>
<input
className="form-input"
placeholder="z. B. avg_power"
value={paramForm.key}
onChange={(e) => setParamForm((f) => ({ ...f, key: e.target.value }))}
/>
</div>
<div className="form-row">
<label className="form-label">name_de / name_en</label>
<input
className="form-input"
value={paramForm.name_de}
onChange={(e) => setParamForm((f) => ({ ...f, name_de: e.target.value }))}
/>
<input
className="form-input"
value={paramForm.name_en}
onChange={(e) => setParamForm((f) => ({ ...f, name_en: e.target.value }))}
/>
</div>
<div className="form-row">
<label className="form-label">Beschreibung DE / EN (optional, für KI)</label>
<textarea
className="form-input"
rows={2}
placeholder="Was bedeutet der Wert? Einheit/Skala?"
value={paramForm.description_de}
onChange={(e) => setParamForm((f) => ({ ...f, description_de: e.target.value }))}
/>
<textarea
className="form-input"
rows={2}
placeholder="Short meaning for prompts / EN users"
value={paramForm.description_en}
onChange={(e) => setParamForm((f) => ({ ...f, description_en: e.target.value }))}
/>
</div>
<div className="form-row">
<label className="form-label">Gruppe / Datentyp</label>
<select
className="form-input"
value={paramForm.category}
onChange={(e) => setParamForm((f) => ({ ...f, category: e.target.value }))}
>
{PARAM_GROUP.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
<select
className="form-input"
value={paramForm.data_type}
onChange={(e) => setParamForm((f) => ({ ...f, data_type: e.target.value }))}
>
{DATA_TYPES.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
</div>
<div className="form-row">
<label className="form-label">Einheit / source_field</label>
<input
className="form-input"
value={paramForm.unit}
onChange={(e) => setParamForm((f) => ({ ...f, unit: e.target.value }))}
/>
<input
className="form-input"
value={paramForm.source_field}
onChange={(e) => setParamForm((f) => ({ ...f, source_field: e.target.value }))}
/>
<div className="aaf-stack">
<div className="aaf-field">
<label className="aaf-label" htmlFor="aaf-new-key">
Technischer Schlüssel (key)
</label>
<input
id="aaf-new-key"
className="aaf-input"
placeholder="z. B. avg_power"
value={paramForm.key}
onChange={(e) => setParamForm((f) => ({ ...f, key: e.target.value }))}
/>
</div>
<div className="aaf-field">
<span className="aaf-label">Bezeichnung</span>
<div className="aaf-split">
<div>
<label className="aaf-sublabel" htmlFor="aaf-new-name-de">
Deutsch
</label>
<input
id="aaf-new-name-de"
className="aaf-input"
value={paramForm.name_de}
onChange={(e) => setParamForm((f) => ({ ...f, name_de: e.target.value }))}
/>
</div>
<div>
<label className="aaf-sublabel" htmlFor="aaf-new-name-en">
English
</label>
<input
id="aaf-new-name-en"
className="aaf-input"
value={paramForm.name_en}
onChange={(e) => setParamForm((f) => ({ ...f, name_en: e.target.value }))}
/>
</div>
</div>
</div>
<div className="aaf-field">
<span className="aaf-label">Beschreibung (optional, für KI / Export)</span>
<div className="aaf-split">
<div>
<label className="aaf-sublabel" htmlFor="aaf-new-desc-de">
Deutsch
</label>
<textarea
id="aaf-new-desc-de"
className="aaf-input"
rows={3}
placeholder="Was bedeutet der Wert? Einheit, Skala, Herkunft …"
value={paramForm.description_de}
onChange={(e) => setParamForm((f) => ({ ...f, description_de: e.target.value }))}
/>
</div>
<div>
<label className="aaf-sublabel" htmlFor="aaf-new-desc-en">
English
</label>
<textarea
id="aaf-new-desc-en"
className="aaf-input"
rows={3}
placeholder="Short meaning for prompts and EN contexts"
value={paramForm.description_en}
onChange={(e) => setParamForm((f) => ({ ...f, description_en: e.target.value }))}
/>
</div>
</div>
</div>
<div className="aaf-field">
<span className="aaf-label">Gruppe und Datentyp</span>
<div className="aaf-split">
<div>
<label className="aaf-sublabel" htmlFor="aaf-new-cat">
Parameter-Gruppe
</label>
<select
id="aaf-new-cat"
className="aaf-input"
value={paramForm.category}
onChange={(e) => setParamForm((f) => ({ ...f, category: e.target.value }))}
>
{PARAM_GROUP.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
</div>
<div>
<label className="aaf-sublabel" htmlFor="aaf-new-dtype">
Datentyp
</label>
<select
id="aaf-new-dtype"
className="aaf-input"
value={paramForm.data_type}
onChange={(e) => setParamForm((f) => ({ ...f, data_type: e.target.value }))}
>
{DATA_TYPES.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
</div>
</div>
</div>
<div className="aaf-field">
<label className="aaf-label" htmlFor="aaf-new-unit">
Einheit (optional)
</label>
<input
id="aaf-new-unit"
className="aaf-input"
placeholder="z. B. W, bpm, min"
value={paramForm.unit}
onChange={(e) => setParamForm((f) => ({ ...f, unit: e.target.value }))}
/>
</div>
<div className="aaf-field">
<label className="aaf-label" htmlFor="aaf-new-source-field">
Quell-Spalte in activity_log (source_field, optional)
</label>
<input
id="aaf-new-source-field"
className="aaf-input"
placeholder="z. B. hr_avg — Spaltenname der Trainingseinheit"
autoComplete="off"
value={paramForm.source_field}
onChange={(e) => setParamForm((f) => ({ ...f, source_field: e.target.value }))}
/>
<p className="aaf-hint">
Wenn gesetzt, wird der Messwert beim Anzeigen und Zusammenführen mit EAV primär aus dieser
Spalte der Einheit gelesen (nicht aus der EAV-Tabelle). Leer lassen, wenn der Wert nur über
EAV oder Standard-Spalten kommt.
</p>
</div>
</div>
<div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
<button type="button" className="btn btn-primary" onClick={saveNewParameter}>
@ -476,78 +540,138 @@ export default function AdminActivityAttributeProfilesPage() {
style={{
border: '1px solid var(--accent)',
borderRadius: 8,
padding: 12,
padding: 16,
marginBottom: 12,
}}
>
<div className="card-title" style={{ fontSize: 14 }}>
Bearbeiten: <code>{editParam.key}</code>
</div>
<div className="form-row">
<label className="form-label">name_de / name_en</label>
<input
className="form-input"
value={editParam.name_de || ''}
onChange={(e) => setEditParam((p) => ({ ...p, name_de: e.target.value }))}
/>
<input
className="form-input"
value={editParam.name_en || ''}
onChange={(e) => setEditParam((p) => ({ ...p, name_en: e.target.value }))}
/>
</div>
<div className="form-row">
<label className="form-label">Beschreibung DE / EN (optional, für KI)</label>
<textarea
className="form-input"
rows={2}
value={editParam.description_de || ''}
onChange={(e) => setEditParam((p) => ({ ...p, description_de: e.target.value }))}
/>
<textarea
className="form-input"
rows={2}
value={editParam.description_en || ''}
onChange={(e) => setEditParam((p) => ({ ...p, description_en: e.target.value }))}
/>
</div>
<div className="form-row">
<label className="form-label">Gruppe / Typ</label>
<select
className="form-input"
value={editParam.category}
onChange={(e) => setEditParam((p) => ({ ...p, category: e.target.value }))}
>
{PARAM_GROUP.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
<select
className="form-input"
value={editParam.data_type}
onChange={(e) => setEditParam((p) => ({ ...p, data_type: e.target.value }))}
>
{DATA_TYPES.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
</div>
<div className="form-row">
<label className="form-label">Einheit / source_field</label>
<input
className="form-input"
value={editParam.unit || ''}
onChange={(e) => setEditParam((p) => ({ ...p, unit: e.target.value }))}
/>
<input
className="form-input"
value={editParam.source_field || ''}
onChange={(e) => setEditParam((p) => ({ ...p, source_field: e.target.value }))}
/>
<div className="aaf-stack">
<div className="aaf-field">
<span className="aaf-label">Bezeichnung</span>
<div className="aaf-split">
<div>
<label className="aaf-sublabel" htmlFor="aaf-edit-name-de">
Deutsch
</label>
<input
id="aaf-edit-name-de"
className="aaf-input"
value={editParam.name_de || ''}
onChange={(e) => setEditParam((p) => ({ ...p, name_de: e.target.value }))}
/>
</div>
<div>
<label className="aaf-sublabel" htmlFor="aaf-edit-name-en">
English
</label>
<input
id="aaf-edit-name-en"
className="aaf-input"
value={editParam.name_en || ''}
onChange={(e) => setEditParam((p) => ({ ...p, name_en: e.target.value }))}
/>
</div>
</div>
</div>
<div className="aaf-field">
<span className="aaf-label">Beschreibung (optional, für KI / Export)</span>
<div className="aaf-split">
<div>
<label className="aaf-sublabel" htmlFor="aaf-edit-desc-de">
Deutsch
</label>
<textarea
id="aaf-edit-desc-de"
className="aaf-input"
rows={3}
value={editParam.description_de || ''}
onChange={(e) => setEditParam((p) => ({ ...p, description_de: e.target.value }))}
/>
</div>
<div>
<label className="aaf-sublabel" htmlFor="aaf-edit-desc-en">
English
</label>
<textarea
id="aaf-edit-desc-en"
className="aaf-input"
rows={3}
value={editParam.description_en || ''}
onChange={(e) => setEditParam((p) => ({ ...p, description_en: e.target.value }))}
/>
</div>
</div>
</div>
<div className="aaf-field">
<span className="aaf-label">Gruppe und Datentyp</span>
<div className="aaf-split">
<div>
<label className="aaf-sublabel" htmlFor="aaf-edit-cat">
Parameter-Gruppe
</label>
<select
id="aaf-edit-cat"
className="aaf-input"
value={editParam.category}
onChange={(e) => setEditParam((p) => ({ ...p, category: e.target.value }))}
>
{PARAM_GROUP.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
</div>
<div>
<label className="aaf-sublabel" htmlFor="aaf-edit-dtype">
Datentyp
</label>
<select
id="aaf-edit-dtype"
className="aaf-input"
value={editParam.data_type}
onChange={(e) => setEditParam((p) => ({ ...p, data_type: e.target.value }))}
>
{DATA_TYPES.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
</div>
</div>
</div>
<div className="aaf-field">
<label className="aaf-label" htmlFor="aaf-edit-unit">
Einheit (optional)
</label>
<input
id="aaf-edit-unit"
className="aaf-input"
placeholder="z. B. W, bpm, min"
value={editParam.unit || ''}
onChange={(e) => setEditParam((p) => ({ ...p, unit: e.target.value }))}
/>
</div>
<div className="aaf-field">
<label className="aaf-label" htmlFor="aaf-edit-source-field">
Quell-Spalte in activity_log (source_field, optional)
</label>
<input
id="aaf-edit-source-field"
className="aaf-input"
placeholder="z. B. hr_avg"
autoComplete="off"
value={editParam.source_field || ''}
onChange={(e) => setEditParam((p) => ({ ...p, source_field: e.target.value }))}
/>
<p className="aaf-hint">
Optional: Name der <code>activity_log</code>-Spalte, aus der dieser Parameter beim Lesen zuerst
befüllt wird (kanonisch vor EAV). Leer, wenn nur EAV oder implizites Spalten-Mapping.
</p>
</div>
</div>
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, marginBottom: 8 }}>
<input
@ -625,13 +749,15 @@ export default function AdminActivityAttributeProfilesPage() {
{tab === 'category' && (
<div className="card section-gap">
<div className="card-title">Zuordnung: Trainings-Kategorie</div>
<div className="form-row">
<label className="form-label">Kategorie</label>
<div className="aaf-field-select">
<label className="form-label" htmlFor="aaf-cat-pick">
Kategorie
</label>
<select
id="aaf-cat-pick"
className="form-input"
value={selCategory}
onChange={(e) => setSelCategory(e.target.value)}
style={{ maxWidth: 280 }}
>
{categoryKeys.map((k) => (
<option key={k} value={k}>
@ -640,10 +766,13 @@ export default function AdminActivityAttributeProfilesPage() {
))}
</select>
</div>
<div className="form-row" style={{ alignItems: 'flex-end', flexWrap: 'wrap', gap: 8 }}>
<div style={{ flex: 1, minWidth: 200 }}>
<label className="form-label">Parameter</label>
<div className="aaf-toolbar">
<div className="aaf-toolbar__grow">
<label className="form-label" htmlFor="aaf-cat-param">
Parameter
</label>
<select
id="aaf-cat-param"
className="form-input"
value={catAdd.training_parameter_id}
onChange={(e) => setCatAdd((a) => ({ ...a, training_parameter_id: e.target.value }))}
@ -656,17 +785,22 @@ export default function AdminActivityAttributeProfilesPage() {
))}
</select>
</div>
<div>
<label className="form-label">sort</label>
<div className="aaf-toolbar__compact">
<label className="form-label" htmlFor="aaf-cat-sort">
Sortierung
</label>
<input
id="aaf-cat-sort"
type="number"
className="form-input"
style={{ width: 80 }}
value={catAdd.sort_order}
onChange={(e) => setCatAdd((a) => ({ ...a, sort_order: e.target.value }))}
/>
</div>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13 }}>
<label
className="aaf-toolbar__compact"
style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 14, paddingBottom: 4 }}
>
<input
type="checkbox"
checked={catAdd.required}
@ -674,11 +808,14 @@ export default function AdminActivityAttributeProfilesPage() {
/>
Pflicht
</label>
<div>
<label className="form-label">ui_group</label>
<div className="aaf-toolbar__compact">
<label className="form-label" htmlFor="aaf-cat-uigroup">
ui_group
</label>
<input
id="aaf-cat-uigroup"
className="form-input"
style={{ width: 120 }}
placeholder="optional"
value={catAdd.ui_group}
onChange={(e) => setCatAdd((a) => ({ ...a, ui_group: e.target.value }))}
/>
@ -698,21 +835,20 @@ export default function AdminActivityAttributeProfilesPage() {
}}
>
{editingCatId === l.id ? (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'flex-end' }}>
<div className="aaf-inline-edit" style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'flex-end' }}>
<span style={{ flex: '1 1 200px' }}>
<strong>{l.parameter_key}</strong> · {l.parameter_name_de}
</span>
<div>
<label className="form-label">sort</label>
<label className="form-label">Sortierung</label>
<input
type="number"
className="form-input"
style={{ width: 72 }}
value={catDraft.sort_order}
onChange={(e) => setCatDraft((d) => ({ ...d, sort_order: e.target.value }))}
/>
</div>
<label style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 14, paddingBottom: 4 }}>
<input
type="checkbox"
checked={!!catDraft.required}
@ -722,7 +858,6 @@ export default function AdminActivityAttributeProfilesPage() {
</label>
<input
className="form-input"
style={{ width: 100 }}
placeholder="ui_group"
value={catDraft.ui_group}
onChange={(e) => setCatDraft((d) => ({ ...d, ui_group: e.target.value }))}
@ -782,13 +917,15 @@ export default function AdminActivityAttributeProfilesPage() {
{tab === 'type' && (
<div className="card section-gap">
<div className="card-title">Zuordnung: Trainingstyp (Zusatz / Override)</div>
<div className="form-row">
<label className="form-label">Trainingstyp</label>
<div className="aaf-field-select">
<label className="form-label" htmlFor="aaf-type-pick">
Trainingstyp
</label>
<select
id="aaf-type-pick"
className="form-input"
value={selTypeId}
onChange={(e) => setSelTypeId(e.target.value)}
style={{ maxWidth: 420 }}
>
{flatTypes.map((t) => (
<option key={t.id} value={t.id}>
@ -797,10 +934,13 @@ export default function AdminActivityAttributeProfilesPage() {
))}
</select>
</div>
<div className="form-row" style={{ alignItems: 'flex-end', flexWrap: 'wrap', gap: 8 }}>
<div style={{ flex: 1, minWidth: 200 }}>
<label className="form-label">Parameter</label>
<div className="aaf-toolbar">
<div className="aaf-toolbar__grow">
<label className="form-label" htmlFor="aaf-type-param">
Parameter
</label>
<select
id="aaf-type-param"
className="form-input"
value={typeAdd.training_parameter_id}
onChange={(e) => setTypeAdd((a) => ({ ...a, training_parameter_id: e.target.value }))}
@ -813,21 +953,25 @@ export default function AdminActivityAttributeProfilesPage() {
))}
</select>
</div>
<div>
<label className="form-label">sort (leer=Erben)</label>
<div className="aaf-toolbar__compact">
<label className="form-label" htmlFor="aaf-type-sort">
Sortierung (leer = erben)
</label>
<input
id="aaf-type-sort"
type="number"
className="form-input"
style={{ width: 80 }}
value={typeAdd.sort_order}
onChange={(e) => setTypeAdd((a) => ({ ...a, sort_order: e.target.value }))}
/>
</div>
<div>
<label className="form-label">Pflicht (leer=Erben)</label>
<div className="aaf-toolbar__compact">
<label className="form-label" htmlFor="aaf-type-req">
Pflicht (leer = erben)
</label>
<select
id="aaf-type-req"
className="form-input"
style={{ width: 100 }}
value={typeAdd.required}
onChange={(e) => setTypeAdd((a) => ({ ...a, required: e.target.value }))}
>
@ -836,11 +980,14 @@ export default function AdminActivityAttributeProfilesPage() {
<option value="false">nein</option>
</select>
</div>
<div>
<label className="form-label">ui_group</label>
<div className="aaf-toolbar__compact">
<label className="form-label" htmlFor="aaf-type-uigroup">
ui_group
</label>
<input
id="aaf-type-uigroup"
className="form-input"
style={{ width: 120 }}
placeholder="optional"
value={typeAdd.ui_group}
onChange={(e) => setTypeAdd((a) => ({ ...a, ui_group: e.target.value }))}
/>
@ -860,23 +1007,21 @@ export default function AdminActivityAttributeProfilesPage() {
}}
>
{editingTypeId === l.id ? (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'flex-end' }}>
<div className="aaf-inline-edit" style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'flex-end' }}>
<span style={{ flex: '1 1 200px' }}>
<strong>{l.parameter_key}</strong>
</span>
<div>
<label className="form-label">sort</label>
<label className="form-label">Sortierung</label>
<input
type="number"
className="form-input"
style={{ width: 72 }}
value={typeDraft.sort_order}
onChange={(e) => setTypeDraft((d) => ({ ...d, sort_order: e.target.value }))}
/>
</div>
<select
className="form-input"
style={{ width: 100 }}
value={typeDraft.required}
onChange={(e) => setTypeDraft((d) => ({ ...d, required: e.target.value }))}
>