refactor: improve AdminFeaturesPage form layout and UX
All checks were successful
Deploy Development / deploy (push) Successful in 1m0s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s

Layout improvements:
- Labels now above inputs (not beside)
- Inputs use full width for better readability
- Better spacing and visual hierarchy

Field changes:
- Removed "Einheit" field (unused, confusing)
- "Sortierung" renamed to "Anzeigereihenfolge" with help text
- Added help text under inputs for clarity

Conditional rendering:
- Boolean features: hide Reset-Periode and Standard-Limit
- Show info box explaining Boolean features
- Count features: show all relevant fields

Better UX:
- Clear explanations what each field does
- Visual feedback for different limit types
- Cleaner, more focused interface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-20 07:47:00 +01:00
parent 69b6f38c89
commit bc4db19190

View File

@ -12,7 +12,6 @@ export default function AdminFeaturesPage() {
name: '',
category: 'data',
description: '',
unit: 'count',
limit_type: 'count',
default_limit: '',
reset_period: 'never',
@ -43,7 +42,6 @@ export default function AdminFeaturesPage() {
name: '',
category: 'data',
description: '',
unit: 'count',
limit_type: 'count',
default_limit: '',
reset_period: 'never',
@ -59,7 +57,6 @@ export default function AdminFeaturesPage() {
name: feature.name,
category: feature.category,
description: feature.description || '',
unit: feature.unit || 'count',
limit_type: feature.limit_type,
default_limit: feature.default_limit === null ? '' : feature.default_limit,
reset_period: feature.reset_period,
@ -84,7 +81,6 @@ export default function AdminFeaturesPage() {
name: formData.name.trim(),
category: formData.category,
description: formData.description.trim(),
unit: formData.unit,
limit_type: formData.limit_type,
default_limit: formData.default_limit === '' ? null : parseInt(formData.default_limit),
reset_period: formData.reset_period,
@ -189,35 +185,63 @@ export default function AdminFeaturesPage() {
</button>
</div>
<div style={{ display: 'grid', gap: 12 }}>
<div style={{ display: 'grid', gap: 16 }}>
{/* Feature ID (read-only) */}
<div className="form-row">
<label className="form-label">Feature ID</label>
<div>
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
Feature ID
</label>
<input
className="form-input"
value={editingId}
disabled
style={{ background: 'var(--surface2)', color: 'var(--text3)', cursor: 'not-allowed' }}
style={{
width: '100%',
background: 'var(--surface2)',
color: 'var(--text3)',
cursor: 'not-allowed',
fontFamily: 'monospace'
}}
/>
</div>
{/* Name */}
<div className="form-row">
<label className="form-label">Name *</label>
<div>
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
Name *
</label>
<input
className="form-input"
style={{ width: '100%' }}
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="z.B. Gewichtseinträge"
/>
</div>
{/* Description */}
<div>
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
Beschreibung (optional)
</label>
<input
className="form-input"
style={{ width: '100%' }}
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder="Kurze Erklärung was dieses Feature limitiert"
/>
</div>
{/* Category + Limit Type */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div className="form-row">
<label className="form-label">Kategorie</label>
<div>
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
Kategorie
</label>
<select
className="form-input"
style={{ width: '100%' }}
value={formData.category}
onChange={(e) => setFormData({ ...formData, category: e.target.value })}
>
@ -227,10 +251,13 @@ export default function AdminFeaturesPage() {
</select>
</div>
<div className="form-row">
<label className="form-label">Limit-Typ</label>
<div>
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
Limit-Typ
</label>
<select
className="form-input"
style={{ width: '100%' }}
value={formData.limit_type}
onChange={(e) => setFormData({ ...formData, limit_type: e.target.value })}
>
@ -241,72 +268,79 @@ export default function AdminFeaturesPage() {
</div>
</div>
{/* Description */}
<div className="form-row">
<label className="form-label">Beschreibung</label>
{/* Count-specific fields (only for limit_type='count') */}
{formData.limit_type === 'count' && (
<>
{/* Reset Period */}
<div>
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
Reset-Periode
</label>
<select
className="form-input"
style={{ width: '100%' }}
value={formData.reset_period}
onChange={(e) => setFormData({ ...formData, reset_period: e.target.value })}
>
{resetPeriodOptions.map(o => (
<option key={o.value} value={o.value}>{o.label}</option>
))}
</select>
<div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 4 }}>
Wann wird der Nutzungszähler zurückgesetzt?
</div>
</div>
{/* Default Limit */}
<div>
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
Standard-Limit
</label>
<input
className="form-input"
style={{ width: '100%' }}
type="number"
value={formData.default_limit}
onChange={(e) => setFormData({ ...formData, default_limit: e.target.value })}
placeholder="Leer = unbegrenzt"
/>
<div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 4 }}>
Fallback-Wert wenn kein Tier-spezifisches Limit gesetzt ist
</div>
</div>
</>
)}
{/* Boolean info */}
{formData.limit_type === 'boolean' && (
<div style={{
padding: 12, background: 'var(--accent-light)', borderRadius: 8,
fontSize: 12, color: 'var(--accent-dark)'
}}>
<strong>Boolean-Feature:</strong> Ist entweder verfügbar (AN) oder nicht verfügbar (AUS).
Keine Zähler oder Reset-Perioden notwendig.
</div>
)}
{/* Sort Order */}
<div>
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
Anzeigereihenfolge
</label>
<input
className="form-input"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder="Optionale Beschreibung"
style={{ width: '100%' }}
type="number"
value={formData.sort_order}
onChange={(e) => setFormData({ ...formData, sort_order: parseInt(e.target.value) || 50 })}
/>
</div>
{/* Unit + Reset Period */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div className="form-row">
<label className="form-label">Einheit</label>
<input
className="form-input"
value={formData.unit}
onChange={(e) => setFormData({ ...formData, unit: e.target.value })}
placeholder="z.B. count, calls"
/>
</div>
<div className="form-row">
<label className="form-label">Reset-Periode</label>
<select
className="form-input"
value={formData.reset_period}
onChange={(e) => setFormData({ ...formData, reset_period: e.target.value })}
>
{resetPeriodOptions.map(o => (
<option key={o.value} value={o.value}>{o.label}</option>
))}
</select>
</div>
</div>
{/* Default Limit + Sort Order */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div className="form-row">
<label className="form-label">Standard-Limit</label>
<input
className="form-input"
type="number"
value={formData.default_limit}
onChange={(e) => setFormData({ ...formData, default_limit: e.target.value })}
placeholder="Leer = unbegrenzt"
/>
<span className="form-unit" style={{ fontSize: 11, color: 'var(--text3)' }}>
Fallback wenn kein Tier-Limit gesetzt
</span>
</div>
<div className="form-row">
<label className="form-label">Sortierung</label>
<input
className="form-input"
type="number"
value={formData.sort_order}
onChange={(e) => setFormData({ ...formData, sort_order: parseInt(e.target.value) || 50 })}
/>
<div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 4 }}>
Niedrigere Werte erscheinen weiter oben in Listen (Standard: 50)
</div>
</div>
{/* Checkboxes */}
<div style={{ display: 'flex', gap: 16 }}>
<div style={{ display: 'flex', gap: 16, paddingTop: 8 }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13 }}>
<input
type="checkbox"