fix: add tier limits display and improve buttons in AdminUserRestrictionsPage
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:
parent
365fe3d068
commit
5ef6a80a1f
|
|
@ -11,6 +11,7 @@ export default function AdminUserRestrictionsPage() {
|
||||||
const [selectedUserId, setSelectedUserId] = useState('')
|
const [selectedUserId, setSelectedUserId] = useState('')
|
||||||
const [selectedUser, setSelectedUser] = useState(null)
|
const [selectedUser, setSelectedUser] = useState(null)
|
||||||
const [restrictions, setRestrictions] = useState([])
|
const [restrictions, setRestrictions] = useState([])
|
||||||
|
const [tierLimits, setTierLimits] = useState({})
|
||||||
const [changes, setChanges] = useState({})
|
const [changes, setChanges] = useState({})
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
|
|
||||||
|
|
@ -47,12 +48,24 @@ export default function AdminUserRestrictionsPage() {
|
||||||
|
|
||||||
async function loadUserData(userId) {
|
async function loadUserData(userId) {
|
||||||
try {
|
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.adminListProfiles().then(users => users.find(u => u.id === userId)),
|
||||||
api.listUserRestrictions(userId)
|
api.listUserRestrictions(userId),
|
||||||
|
api.getTierLimitsMatrix()
|
||||||
])
|
])
|
||||||
|
|
||||||
setSelectedUser(user)
|
setSelectedUser(user)
|
||||||
setRestrictions(restrictionsData)
|
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({})
|
setChanges({})
|
||||||
setError('')
|
setError('')
|
||||||
setSuccess('')
|
setSuccess('')
|
||||||
|
|
@ -312,14 +325,17 @@ export default function AdminUserRestrictionsPage() {
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: 'var(--surface2)' }}>
|
<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
|
Feature
|
||||||
</th>
|
</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
|
Override-Wert
|
||||||
</th>
|
</th>
|
||||||
<th style={{ textAlign: 'right', padding: '12px 16px', fontWeight: 600, width: '30%' }}>
|
<th style={{ textAlign: 'right', padding: '12px 16px', fontWeight: 600 }}>
|
||||||
Status
|
Aktion
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -328,7 +344,7 @@ export default function AdminUserRestrictionsPage() {
|
||||||
<>
|
<>
|
||||||
{/* Category Header */}
|
{/* Category Header */}
|
||||||
<tr key={`cat-${category}`} style={{ background: 'var(--accent-light)' }}>
|
<tr key={`cat-${category}`} style={{ background: 'var(--accent-light)' }}>
|
||||||
<td colSpan={3} style={{
|
<td colSpan={4} style={{
|
||||||
padding: '8px 16px', fontWeight: 600, fontSize: 11,
|
padding: '8px 16px', fontWeight: 600, fontSize: 11,
|
||||||
textTransform: 'uppercase', letterSpacing: '0.5px',
|
textTransform: 'uppercase', letterSpacing: '0.5px',
|
||||||
color: 'var(--accent-dark)'
|
color: 'var(--accent-dark)'
|
||||||
|
|
@ -357,6 +373,24 @@ export default function AdminUserRestrictionsPage() {
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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 */}
|
{/* Override Input */}
|
||||||
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
|
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
|
||||||
{feature.limit_type === 'boolean' ? (
|
{feature.limit_type === 'boolean' ? (
|
||||||
|
|
@ -382,36 +416,32 @@ 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="leer = Tier-Standard"
|
placeholder={override ? "Wert..." : ""}
|
||||||
style={{
|
style={{
|
||||||
width: '140px',
|
width: '100px',
|
||||||
padding: '6px 8px',
|
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,
|
borderRadius: 6,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: changed ? 600 : 400,
|
fontWeight: override || changed ? 600 : 400,
|
||||||
background: 'var(--bg)',
|
background: override ? 'var(--accent-light)' : 'var(--bg)',
|
||||||
color: displayValue === 0 || displayValue === '0' ? 'var(--danger)' : 'var(--text1)'
|
color: displayValue === 0 || displayValue === '0' ? 'var(--danger)' : 'var(--text1)'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Status */}
|
{/* Action */}
|
||||||
<td style={{ padding: '12px 16px', textAlign: 'right' }}>
|
<td style={{ padding: '12px 16px', textAlign: 'right' }}>
|
||||||
{override ? (
|
{override && (
|
||||||
<span style={{
|
<button
|
||||||
padding: '4px 8px', borderRadius: 4, fontSize: 11,
|
className="btn btn-secondary"
|
||||||
background: 'var(--accent-light)', color: 'var(--accent-dark)',
|
onClick={() => handleChange(feature.id, '')}
|
||||||
fontWeight: 600
|
style={{ padding: '4px 8px', fontSize: 11 }}
|
||||||
}}>
|
>
|
||||||
✓ Override aktiv
|
↺ Zurück zu Standard
|
||||||
</span>
|
</button>
|
||||||
) : (
|
|
||||||
<span style={{ fontSize: 11, color: 'var(--text3)' }}>
|
|
||||||
Tier-Standard
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -436,7 +466,7 @@ export default function AdminUserRestrictionsPage() {
|
||||||
disabled={!hasChanges || saving}
|
disabled={!hasChanges || saving}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
>
|
>
|
||||||
<RotateCcw size={14} /> Zurücksetzen
|
<X size={14} /> Abbrechen
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
|
|
@ -444,7 +474,7 @@ export default function AdminUserRestrictionsPage() {
|
||||||
disabled={!hasChanges || saving}
|
disabled={!hasChanges || saving}
|
||||||
style={{ flex: 2 }}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user