fix: add tier limits display and improve buttons in AdminUserRestrictionsPage
All checks were successful
Deploy Development / deploy (push) Successful in 58s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 12s

Added tier limits column:
- Shows current tier-limit value for each feature
- Loads from tier-limits matrix based on user's tier
- Visual display for boolean (✓ AN / ✗ AUS) and count features
- Clear comparison: Tier-Limit vs Override-Wert

Added per-feature reset button:
- "↺ Zurück zu Standard" button per feature
- Only shown when override exists
- Removes override with single click

Improved bottom bar buttons:
- Renamed "Zurücksetzen" to "Abbrechen" (clearer)
- Always visible (not hidden when no changes)
- Disabled state when no changes
- Shows "Keine Änderungen" when nothing to save

Better UX:
- Tier-Limit column shows what user gets without override
- Override input highlighted when active (accent-light background)
- Clear action buttons per row
- Global save/cancel at bottom

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-20 08:13:11 +01:00
parent 365fe3d068
commit 5ef6a80a1f

View File

@ -11,6 +11,7 @@ export default function AdminUserRestrictionsPage() {
const [selectedUserId, setSelectedUserId] = useState('')
const [selectedUser, setSelectedUser] = useState(null)
const [restrictions, setRestrictions] = useState([])
const [tierLimits, setTierLimits] = useState({})
const [changes, setChanges] = useState({})
const [saving, setSaving] = useState(false)
@ -47,12 +48,24 @@ export default function AdminUserRestrictionsPage() {
async function loadUserData(userId) {
try {
const [user, restrictionsData] = await Promise.all([
const [user, restrictionsData, limitsMatrix] = await Promise.all([
api.adminListProfiles().then(users => users.find(u => u.id === userId)),
api.listUserRestrictions(userId)
api.listUserRestrictions(userId),
api.getTierLimitsMatrix()
])
setSelectedUser(user)
setRestrictions(restrictionsData)
// Build tier limits lookup for this user's tier
const userTier = user.tier || 'free'
const limits = {}
features.forEach(feature => {
const key = `${userTier}:${feature.id}`
limits[feature.id] = limitsMatrix.limits[key] ?? feature.default_limit
})
setTierLimits(limits)
setChanges({})
setError('')
setSuccess('')
@ -312,14 +325,17 @@ export default function AdminUserRestrictionsPage() {
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
<thead>
<tr style={{ background: 'var(--surface2)' }}>
<th style={{ textAlign: 'left', padding: '12px 16px', fontWeight: 600, width: '40%' }}>
<th style={{ textAlign: 'left', padding: '12px 16px', fontWeight: 600 }}>
Feature
</th>
<th style={{ textAlign: 'center', padding: '12px 16px', fontWeight: 600, width: '30%' }}>
<th style={{ textAlign: 'center', padding: '12px 16px', fontWeight: 600 }}>
Tier-Limit
</th>
<th style={{ textAlign: 'center', padding: '12px 16px', fontWeight: 600 }}>
Override-Wert
</th>
<th style={{ textAlign: 'right', padding: '12px 16px', fontWeight: 600, width: '30%' }}>
Status
<th style={{ textAlign: 'right', padding: '12px 16px', fontWeight: 600 }}>
Aktion
</th>
</tr>
</thead>
@ -328,7 +344,7 @@ export default function AdminUserRestrictionsPage() {
<>
{/* Category Header */}
<tr key={`cat-${category}`} style={{ background: 'var(--accent-light)' }}>
<td colSpan={3} style={{
<td colSpan={4} style={{
padding: '8px 16px', fontWeight: 600, fontSize: 11,
textTransform: 'uppercase', letterSpacing: '0.5px',
color: 'var(--accent-dark)'
@ -357,6 +373,24 @@ export default function AdminUserRestrictionsPage() {
</div>
</td>
{/* Tier-Limit */}
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
{feature.limit_type === 'boolean' ? (
<span style={{
padding: '6px 12px', borderRadius: 20,
background: tierLimits[feature.id] !== 0 ? 'var(--accent-light)' : 'var(--surface2)',
color: tierLimits[feature.id] !== 0 ? 'var(--accent-dark)' : 'var(--text3)',
fontSize: 12, fontWeight: 600
}}>
{tierLimits[feature.id] !== 0 ? '✓ AN' : '✗ AUS'}
</span>
) : (
<span style={{ fontWeight: 500, color: 'var(--text2)' }}>
{tierLimits[feature.id] === null ? '∞' : tierLimits[feature.id]}
</span>
)}
</td>
{/* Override Input */}
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
{feature.limit_type === 'boolean' ? (
@ -382,36 +416,32 @@ export default function AdminUserRestrictionsPage() {
type="text"
value={displayValue}
onChange={(e) => handleChange(feature.id, e.target.value)}
placeholder="leer = Tier-Standard"
placeholder={override ? "Wert..." : ""}
style={{
width: '140px',
width: '100px',
padding: '6px 8px',
border: `1.5px solid ${changed ? 'var(--accent)' : 'var(--border)'}`,
border: `1.5px solid ${changed ? 'var(--accent)' : override ? 'var(--accent)' : 'var(--border)'}`,
borderRadius: 6,
textAlign: 'center',
fontSize: 13,
fontWeight: changed ? 600 : 400,
background: 'var(--bg)',
fontWeight: override || changed ? 600 : 400,
background: override ? 'var(--accent-light)' : 'var(--bg)',
color: displayValue === 0 || displayValue === '0' ? 'var(--danger)' : 'var(--text1)'
}}
/>
)}
</td>
{/* Status */}
{/* Action */}
<td style={{ padding: '12px 16px', textAlign: 'right' }}>
{override ? (
<span style={{
padding: '4px 8px', borderRadius: 4, fontSize: 11,
background: 'var(--accent-light)', color: 'var(--accent-dark)',
fontWeight: 600
}}>
Override aktiv
</span>
) : (
<span style={{ fontSize: 11, color: 'var(--text3)' }}>
Tier-Standard
</span>
{override && (
<button
className="btn btn-secondary"
onClick={() => handleChange(feature.id, '')}
style={{ padding: '4px 8px', fontSize: 11 }}
>
Zurück zu Standard
</button>
)}
</td>
</tr>
@ -436,7 +466,7 @@ export default function AdminUserRestrictionsPage() {
disabled={!hasChanges || saving}
style={{ flex: 1 }}
>
<RotateCcw size={14} /> Zurücksetzen
<X size={14} /> Abbrechen
</button>
<button
className="btn btn-primary"
@ -444,7 +474,7 @@ export default function AdminUserRestrictionsPage() {
disabled={!hasChanges || saving}
style={{ flex: 2 }}
>
{saving ? 'Speichern...' : hasChanges ? `${Object.keys(changes).length} Änderung(en) speichern` : 'Speichern'}
{saving ? 'Speichern...' : hasChanges ? `${Object.keys(changes).length} Änderung(en) speichern` : 'Keine Änderungen'}
</button>
</div>