refactor: improve AdminFeaturesPage form layout and UX
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:
parent
69b6f38c89
commit
bc4db19190
|
|
@ -12,7 +12,6 @@ export default function AdminFeaturesPage() {
|
||||||
name: '',
|
name: '',
|
||||||
category: 'data',
|
category: 'data',
|
||||||
description: '',
|
description: '',
|
||||||
unit: 'count',
|
|
||||||
limit_type: 'count',
|
limit_type: 'count',
|
||||||
default_limit: '',
|
default_limit: '',
|
||||||
reset_period: 'never',
|
reset_period: 'never',
|
||||||
|
|
@ -43,7 +42,6 @@ export default function AdminFeaturesPage() {
|
||||||
name: '',
|
name: '',
|
||||||
category: 'data',
|
category: 'data',
|
||||||
description: '',
|
description: '',
|
||||||
unit: 'count',
|
|
||||||
limit_type: 'count',
|
limit_type: 'count',
|
||||||
default_limit: '',
|
default_limit: '',
|
||||||
reset_period: 'never',
|
reset_period: 'never',
|
||||||
|
|
@ -59,7 +57,6 @@ export default function AdminFeaturesPage() {
|
||||||
name: feature.name,
|
name: feature.name,
|
||||||
category: feature.category,
|
category: feature.category,
|
||||||
description: feature.description || '',
|
description: feature.description || '',
|
||||||
unit: feature.unit || 'count',
|
|
||||||
limit_type: feature.limit_type,
|
limit_type: feature.limit_type,
|
||||||
default_limit: feature.default_limit === null ? '' : feature.default_limit,
|
default_limit: feature.default_limit === null ? '' : feature.default_limit,
|
||||||
reset_period: feature.reset_period,
|
reset_period: feature.reset_period,
|
||||||
|
|
@ -84,7 +81,6 @@ export default function AdminFeaturesPage() {
|
||||||
name: formData.name.trim(),
|
name: formData.name.trim(),
|
||||||
category: formData.category,
|
category: formData.category,
|
||||||
description: formData.description.trim(),
|
description: formData.description.trim(),
|
||||||
unit: formData.unit,
|
|
||||||
limit_type: formData.limit_type,
|
limit_type: formData.limit_type,
|
||||||
default_limit: formData.default_limit === '' ? null : parseInt(formData.default_limit),
|
default_limit: formData.default_limit === '' ? null : parseInt(formData.default_limit),
|
||||||
reset_period: formData.reset_period,
|
reset_period: formData.reset_period,
|
||||||
|
|
@ -189,35 +185,63 @@ export default function AdminFeaturesPage() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'grid', gap: 12 }}>
|
<div style={{ display: 'grid', gap: 16 }}>
|
||||||
{/* Feature ID (read-only) */}
|
{/* Feature ID (read-only) */}
|
||||||
<div className="form-row">
|
<div>
|
||||||
<label className="form-label">Feature ID</label>
|
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
|
||||||
|
Feature ID
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className="form-input"
|
className="form-input"
|
||||||
value={editingId}
|
value={editingId}
|
||||||
disabled
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<div className="form-row">
|
<div>
|
||||||
<label className="form-label">Name *</label>
|
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
|
||||||
|
Name *
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className="form-input"
|
className="form-input"
|
||||||
|
style={{ width: '100%' }}
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
placeholder="z.B. Gewichtseinträge"
|
placeholder="z.B. Gewichtseinträge"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 */}
|
{/* Category + Limit Type */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
||||||
<div className="form-row">
|
<div>
|
||||||
<label className="form-label">Kategorie</label>
|
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
|
||||||
|
Kategorie
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
className="form-input"
|
className="form-input"
|
||||||
|
style={{ width: '100%' }}
|
||||||
value={formData.category}
|
value={formData.category}
|
||||||
onChange={(e) => setFormData({ ...formData, category: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, category: e.target.value })}
|
||||||
>
|
>
|
||||||
|
|
@ -227,10 +251,13 @@ export default function AdminFeaturesPage() {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-row">
|
<div>
|
||||||
<label className="form-label">Limit-Typ</label>
|
<label className="form-label" style={{ display: 'block', marginBottom: 6 }}>
|
||||||
|
Limit-Typ
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
className="form-input"
|
className="form-input"
|
||||||
|
style={{ width: '100%' }}
|
||||||
value={formData.limit_type}
|
value={formData.limit_type}
|
||||||
onChange={(e) => setFormData({ ...formData, limit_type: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, limit_type: e.target.value })}
|
||||||
>
|
>
|
||||||
|
|
@ -241,72 +268,79 @@ export default function AdminFeaturesPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Count-specific fields (only for limit_type='count') */}
|
||||||
<div className="form-row">
|
{formData.limit_type === 'count' && (
|
||||||
<label className="form-label">Beschreibung</label>
|
<>
|
||||||
|
{/* 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
|
<input
|
||||||
className="form-input"
|
className="form-input"
|
||||||
value={formData.description}
|
style={{ width: '100%' }}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
type="number"
|
||||||
placeholder="Optionale Beschreibung"
|
value={formData.sort_order}
|
||||||
|
onChange={(e) => setFormData({ ...formData, sort_order: parseInt(e.target.value) || 50 })}
|
||||||
/>
|
/>
|
||||||
</div>
|
<div style={{ fontSize: 11, color: 'var(--text3)', marginTop: 4 }}>
|
||||||
|
Niedrigere Werte erscheinen weiter oben in Listen (Standard: 50)
|
||||||
{/* 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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Checkboxes */}
|
{/* 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 }}>
|
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13 }}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user