fix: AdminUserRestrictionsPage - show effective values, auto-remove redundant overrides
All checks were successful
Deploy Development / deploy (push) Successful in 54s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 12s

Major UX improvements:
- Display effective value in input (override if set, otherwise tier limit)
- Format NULL as "unlimited" (easy to type, no special char needed)
- Auto-remove override when value equals tier default
- "Zurück" button resets to tier default value
- Wider input field (120px) for "unlimited" text

This solves:
- User can now see and edit current effective values
- "unlimited" can be typed and saved
- Redundant overrides (value = tier default) are prevented
- No more confusion with empty fields vs actual values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-20 12:08:29 +01:00
parent adfa9ec139
commit 4e592dddc5

View File

@ -77,13 +77,7 @@ export default function AdminUserRestrictionsPage() {
function handleChange(featureId, value) { function handleChange(featureId, value) {
const newChanges = { ...changes } const newChanges = { ...changes }
const tierLimit = tierLimits[featureId]
// Empty string means: remove override
if (value === '') {
newChanges[featureId] = { action: 'remove', tempValue: '' }
setChanges(newChanges)
return
}
// Parse value (EXACTLY like TierLimitsPage) // Parse value (EXACTLY like TierLimitsPage)
let parsedValue = null let parsedValue = null
@ -91,6 +85,8 @@ export default function AdminUserRestrictionsPage() {
parsedValue = null // unlimited parsedValue = null // unlimited
} else if (value === '0' || value === 'disabled') { } else if (value === '0' || value === 'disabled') {
parsedValue = 0 // disabled parsedValue = 0 // disabled
} else if (value === '') {
parsedValue = null // empty unlimited
} else { } else {
const num = parseInt(value) const num = parseInt(value)
if (!isNaN(num) && num >= 0) { if (!isNaN(num) && num >= 0) {
@ -100,7 +96,14 @@ export default function AdminUserRestrictionsPage() {
} }
} }
newChanges[featureId] = { action: 'set', value: parsedValue, tempValue: value } // Check if value equals tier limit remove override
if (parsedValue === tierLimit) {
newChanges[featureId] = { action: 'remove', tempValue: value }
} else {
// Different from tier default set override
newChanges[featureId] = { action: 'set', value: parsedValue, tempValue: value }
}
setChanges(newChanges) setChanges(newChanges)
} }
@ -175,22 +178,30 @@ export default function AdminUserRestrictionsPage() {
// Check pending changes first // Check pending changes first
if (featureId in changes) { if (featureId in changes) {
const change = changes[featureId] const change = changes[featureId]
if (change.action === 'remove') return '' if (change.action === 'remove') {
// Returning to tier default
return formatValue(tierLimits[featureId])
}
if (change.action === 'set') { if (change.action === 'set') {
// Use tempValue for display if available, otherwise format the value // Use tempValue for display if available, otherwise format the value
return change.tempValue !== undefined ? change.tempValue : formatValue(change.value) return change.tempValue !== undefined ? change.tempValue : formatValue(change.value)
} }
} }
// Show existing restriction value (or empty if no restriction) // Show override if exists, otherwise tier limit (= effective value)
const restriction = restrictions.find(r => r.feature_id === featureId) const restriction = restrictions.find(r => r.feature_id === featureId)
if (!restriction) return '' // No override = empty input if (restriction) {
return formatValue(restriction.limit_value) return formatValue(restriction.limit_value)
}
// No override: show tier limit as default
return formatValue(tierLimits[featureId])
} }
function formatValue(val) { function formatValue(val) {
if (val === '' || val === null || val === undefined) return '' if (val === null || val === undefined) return 'unlimited'
if (val === '∞' || val === 'unlimited') return '∞' if (val === '' ) return ''
if (val === '∞' || val === 'unlimited') return 'unlimited'
if (val === 0 || val === '0') return '0' if (val === 0 || val === '0') return '0'
return val.toString() return val.toString()
} }
@ -258,9 +269,8 @@ export default function AdminUserRestrictionsPage() {
}}> }}>
<AlertCircle size={16} style={{ marginTop: 2, flexShrink: 0 }} /> <AlertCircle size={16} style={{ marginTop: 2, flexShrink: 0 }} />
<div> <div>
<strong>Hinweis:</strong> User-Overrides überschreiben Tier-Limits. <strong>Hinweis:</strong> Felder zeigen effektive Werte (Override falls gesetzt, sonst Tier-Standard).
Leere Felder = kein Override (User nutzt Tier-Standard). Wert ändern Override wird gesetzt. Wert = Tier-Standard Override wird entfernt.
Wert eingeben = Override setzen.
</div> </div>
</div> </div>
@ -456,9 +466,9 @@ export default function AdminUserRestrictionsPage() {
type="text" type="text"
value={displayValue} value={displayValue}
onChange={(e) => handleChange(feature.id, e.target.value)} onChange={(e) => handleChange(feature.id, e.target.value)}
placeholder="" placeholder=""
style={{ style={{
width: '100px', width: '120px',
padding: '6px 8px', padding: '6px 8px',
border: `1.5px solid ${changed ? 'var(--accent)' : override ? 'var(--accent)' : 'var(--border)'}`, border: `1.5px solid ${changed ? 'var(--accent)' : override ? 'var(--accent)' : 'var(--border)'}`,
borderRadius: 6, borderRadius: 6,
@ -467,7 +477,7 @@ export default function AdminUserRestrictionsPage() {
fontWeight: override || changed ? 600 : 400, fontWeight: override || changed ? 600 : 400,
background: override || changed ? 'var(--accent-light)' : 'var(--bg)', background: override || changed ? 'var(--accent-light)' : 'var(--bg)',
color: displayValue === '0' ? 'var(--danger)' : color: displayValue === '0' ? 'var(--danger)' :
displayValue === '' ? 'var(--accent)' : 'var(--text1)' displayValue === 'unlimited' ? 'var(--accent)' : 'var(--text1)'
}} }}
/> />
)} )}
@ -477,7 +487,11 @@ export default function AdminUserRestrictionsPage() {
<td style={{ padding: '12px 16px', textAlign: 'right' }}> <td style={{ padding: '12px 16px', textAlign: 'right' }}>
<button <button
className="btn btn-secondary" className="btn btn-secondary"
onClick={() => handleChange(feature.id, '')} onClick={() => {
// Reset to tier default
const tierValue = tierLimits[feature.id]
handleChange(feature.id, formatValue(tierValue))
}}
disabled={!override} disabled={!override}
style={{ style={{
padding: '4px 8px', padding: '4px 8px',
@ -504,12 +518,16 @@ export default function AdminUserRestrictionsPage() {
marginTop: 16, padding: 12, background: 'var(--surface2)', marginTop: 16, padding: 12, background: 'var(--surface2)',
borderRadius: 8, fontSize: 12, color: 'var(--text3)' borderRadius: 8, fontSize: 12, color: 'var(--text3)'
}}> }}>
<strong>Eingabe (Count-Features):</strong> <strong>Eingabe:</strong>
<div style={{ marginTop: 8, display: 'flex', gap: 16, flexWrap: 'wrap' }}> <div style={{ marginTop: 8, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
<span><strong style={{ color: 'var(--accent)' }}></strong> oder <strong>unlimited</strong> = Unbegrenzt</span> <span><strong>unlimited</strong> = Unbegrenzt</span>
<span><strong style={{ color: 'var(--danger)' }}>0</strong> = Feature deaktiviert</span> <span><strong style={{ color: 'var(--danger)' }}>0</strong> = Feature deaktiviert</span>
<span><strong>1+</strong> = Limit-Wert</span> <span><strong>1+</strong> = Limit-Wert</span>
<span><strong>Leer</strong> = kein Override (Tier-Standard)</span> </div>
<div style={{ marginTop: 8, fontSize: 11, opacity: 0.8 }}>
Feld zeigt effektiven Wert (Override falls gesetzt, sonst Tier-Standard)<br />
Wert ändern Override wird gesetzt<br />
Wert = Tier-Standard Override wird entfernt
</div> </div>
</div> </div>
</> </>