From 365fe3d06828ec3c10e6c58ec2e218614ea65077 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 20 Mar 2026 08:08:02 +0100 Subject: [PATCH] fix: complete rewrite of AdminUserRestrictionsPage Fixed all reported bugs: 1. Initial values now correct (empty = no override, not defaults) 2. Save/Reset buttons always visible (fixed bottom bar) 3. Toggle buttons work correctly (can be toggled multiple times) 4. Simplified table columns (removed confusing Tier-Limit/Aktiv/Aktion) New logic: - Empty input = no override (user uses tier standard) - Value entered = override set - Change tracking with 3 actions: set, remove, toggle - Clear status display: "Override aktiv" vs "Tier-Standard" Simplified table structure: - Feature (name + type) - Override-Wert (input/toggle) - Status (has override yes/no) Better UX: - Placeholder text explains empty = tier standard - Status badge shows if override is active - Fixed bottom bar always present - Buttons disabled only when no changes - Legend explains all input options Co-Authored-By: Claude Opus 4.6 --- .../src/pages/AdminUserRestrictionsPage.jsx | 329 ++++++++++-------- 1 file changed, 175 insertions(+), 154 deletions(-) diff --git a/frontend/src/pages/AdminUserRestrictionsPage.jsx b/frontend/src/pages/AdminUserRestrictionsPage.jsx index 4f7a57d..a27be09 100644 --- a/frontend/src/pages/AdminUserRestrictionsPage.jsx +++ b/frontend/src/pages/AdminUserRestrictionsPage.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { Save, User, AlertCircle, X } from 'lucide-react' +import { Save, AlertCircle, X, RotateCcw } from 'lucide-react' import { api } from '../utils/api' export default function AdminUserRestrictionsPage() { @@ -21,6 +21,10 @@ export default function AdminUserRestrictionsPage() { useEffect(() => { if (selectedUserId) { loadUserData(selectedUserId) + } else { + setSelectedUser(null) + setRestrictions([]) + setChanges({}) } }, [selectedUserId]) @@ -51,40 +55,43 @@ export default function AdminUserRestrictionsPage() { setRestrictions(restrictionsData) setChanges({}) setError('') + setSuccess('') } catch (e) { setError(e.message) } } - function handleChange(featureId, value, enabled) { - const key = featureId + function handleChange(featureId, value) { const newChanges = { ...changes } - // Parse value - let parsedValue = null - if (value === '' || value === 'unlimited' || value === '∞') { - parsedValue = null // unlimited - } else if (value === '0' || value === 'disabled') { - parsedValue = 0 // disabled + // Empty string means: remove override (if exists) or do nothing + if (value === '') { + newChanges[featureId] = { action: 'remove' } } else { - const num = parseInt(value) - if (!isNaN(num) && num >= 0) { - parsedValue = num + // Parse value + let parsedValue = null + if (value === 'unlimited' || value === '∞') { + parsedValue = null + } else if (value === '0') { + parsedValue = 0 } else { - return // invalid input + const num = parseInt(value) + if (!isNaN(num) && num >= 0) { + parsedValue = num + } else { + return // invalid + } } + newChanges[featureId] = { action: 'set', value: parsedValue } } - newChanges[key] = { featureId, value: parsedValue, enabled } setChanges(newChanges) } - function toggleEnabled(featureId) { - const restriction = restrictions.find(r => r.feature_id === featureId) - const currentEnabled = restriction ? restriction.enabled : true - const currentValue = restriction ? restriction.limit_value : null - - handleChange(featureId, currentValue === null ? '' : currentValue, !currentEnabled) + function handleToggle(featureId, currentEnabled) { + const newChanges = { ...changes } + newChanges[featureId] = { action: 'toggle', enabled: !currentEnabled } + setChanges(newChanges) } async function handleSave() { @@ -95,29 +102,56 @@ export default function AdminUserRestrictionsPage() { setError('') setSuccess('') - // Process changes + let changeCount = 0 + for (const [featureId, change] of Object.entries(changes)) { const existingRestriction = restrictions.find(r => r.feature_id === featureId) - if (existingRestriction) { - // Update existing restriction - await api.updateUserRestriction(existingRestriction.id, { - limit_value: change.value, - enabled: change.enabled - }) - } else { - // Create new restriction - await api.createUserRestriction({ - profile_id: selectedUserId, - feature_id: featureId, - limit_value: change.value, - enabled: change.enabled, - reason: 'Admin override' - }) + if (change.action === 'remove') { + // Remove restriction if exists + if (existingRestriction) { + await api.deleteUserRestriction(existingRestriction.id) + changeCount++ + } + } else if (change.action === 'set') { + // Create or update + if (existingRestriction) { + await api.updateUserRestriction(existingRestriction.id, { + limit_value: change.value, + enabled: true + }) + } else { + await api.createUserRestriction({ + profile_id: selectedUserId, + feature_id: featureId, + limit_value: change.value, + enabled: true, + reason: 'Admin override' + }) + } + changeCount++ + } else if (change.action === 'toggle') { + // Toggle enabled state + if (existingRestriction) { + await api.updateUserRestriction(existingRestriction.id, { + enabled: change.enabled + }) + changeCount++ + } else { + // Create new restriction with toggle state + await api.createUserRestriction({ + profile_id: selectedUserId, + feature_id: featureId, + limit_value: change.enabled ? 1 : 0, + enabled: true, + reason: 'Admin override' + }) + changeCount++ + } } } - setSuccess(`${Object.keys(changes).length} Overrides gespeichert`) + setSuccess(`${changeCount} Änderung(en) gespeichert`) await loadUserData(selectedUserId) } catch (e) { setError(e.message) @@ -126,52 +160,41 @@ export default function AdminUserRestrictionsPage() { } } - async function handleRemoveRestriction(featureId) { - if (!confirm('Override wirklich entfernen? User erhält dann das Standard-Tier-Limit.')) return - - try { - const restriction = restrictions.find(r => r.feature_id === featureId) - if (restriction) { - await api.deleteUserRestriction(restriction.id) - setSuccess('Override entfernt') - await loadUserData(selectedUserId) - // Remove from changes if exists - const newChanges = { ...changes } - delete newChanges[featureId] - setChanges(newChanges) - } - } catch (e) { - setError(e.message) + function getDisplayValue(featureId) { + // Check pending changes first + if (featureId in changes) { + const change = changes[featureId] + if (change.action === 'remove') return '' + if (change.action === 'set') return change.value === null ? '' : change.value + if (change.action === 'toggle') return change.enabled ? 1 : 0 } + + // Show existing restriction value (or empty if no restriction) + const restriction = restrictions.find(r => r.feature_id === featureId) + if (!restriction) return '' // No override = empty input + return restriction.limit_value === null ? '' : restriction.limit_value } - function getCurrentValue(featureId) { - // Check if there's a pending change - if (featureId in changes) { - return changes[featureId].value + function getToggleState(featureId) { + // Check pending changes first + if (featureId in changes && changes[featureId].action === 'toggle') { + return changes[featureId].enabled } // Check existing restriction const restriction = restrictions.find(r => r.feature_id === featureId) - return restriction ? restriction.limit_value : null + if (!restriction) return true // Default: enabled + + // For boolean features: limit_value determines state + return restriction.limit_value !== 0 } - function isEnabled(featureId) { - if (featureId in changes) { - return changes[featureId].enabled - } - - const restriction = restrictions.find(r => r.feature_id === featureId) - return restriction ? restriction.enabled : true - } - - function hasRestriction(featureId) { + function hasOverride(featureId) { return restrictions.some(r => r.feature_id === featureId) } - function formatValue(val) { - if (val === null || val === undefined) return '' - return val.toString() + function isChanged(featureId) { + return featureId in changes } if (loading) return ( @@ -210,8 +233,9 @@ export default function AdminUserRestrictionsPage() { }}>
- Hinweis: User-Overrides haben höchste Priorität und überschreiben Tier-Limits. - Nutze dies sparsam für Sonderfälle (z.B. Beta-Tester, Support-Anfragen). + Hinweis: User-Overrides überschreiben Tier-Limits. + Leere Felder = kein Override (User nutzt Tier-Standard). + Wert eingeben = Override setzen.
@@ -283,16 +307,20 @@ export default function AdminUserRestrictionsPage() { - {/* Features List */} -
+ {/* Features Table */} +
- - - - - + + + @@ -300,7 +328,7 @@ export default function AdminUserRestrictionsPage() { <> {/* Category Header */} - - - - - - @@ -398,47 +423,43 @@ export default function AdminUserRestrictionsPage() {
FeatureTier-LimitOverrideAktivAktion + Feature + + Override-Wert + + Status +
{ - const currentValue = getCurrentValue(feature.id) - const enabled = isEnabled(feature.id) - const hasOverride = hasRestriction(feature.id) - const isChanged = feature.id in changes + const displayValue = getDisplayValue(feature.id) + const toggleState = getToggleState(feature.id) + const override = hasOverride(feature.id) + const changed = isChanged(feature.id) return (
+ {/* Feature Name */} +
{feature.name}
{feature.limit_type === 'boolean' ? '(ja/nein)' : `(${feature.reset_period})`}
- {/* TODO: Show actual tier limit - for now just show "Standard" */} - Standard - + + {/* Override Input */} + {feature.limit_type === 'boolean' ? ( ) : ( handleChange(feature.id, e.target.value, enabled)} - placeholder="∞" + value={displayValue} + onChange={(e) => handleChange(feature.id, e.target.value)} + placeholder="leer = Tier-Standard" style={{ - width: '80px', + width: '140px', padding: '6px 8px', - border: `1.5px solid ${isChanged ? 'var(--accent)' : hasOverride ? 'var(--border)' : 'var(--border)'}`, + border: `1.5px solid ${changed ? 'var(--accent)' : 'var(--border)'}`, borderRadius: 6, textAlign: 'center', fontSize: 13, - fontWeight: isChanged ? 600 : 400, - background: hasOverride ? 'var(--surface2)' : 'var(--bg)', - color: currentValue === 0 ? 'var(--danger)' : - currentValue === null ? 'var(--accent)' : 'var(--text1)' + fontWeight: changed ? 600 : 400, + background: 'var(--bg)', + color: displayValue === 0 || displayValue === '0' ? 'var(--danger)' : 'var(--text1)' }} /> )} - {hasOverride ? ( - + + {/* Status */} + + {override ? ( + + ✓ Override aktiv + ) : ( - - - )} - - {hasOverride && ( - + + Tier-Standard + )}
- {/* Save Button */} - {hasChanges && ( -
- - -
- )} + {/* Fixed Bottom Bar */} +
+ + +
{/* Legend */}
Eingabe:
- leer oder ∞ = Unbegrenzt - 0 = Deaktiviert + Leer = Tier-Standard nutzen (kein Override) + 0 = Feature deaktiviert + ∞ oder leer = Unbegrenzt (bei Count-Features) 1-999999 = Limit-Wert
-
- ✓ Aktiv = Override gesetzt, überschreibt Tier-Limit -
)}