Membership-System und Bug Fixing (inkl. Nutrition) #8

Merged
Lars merged 56 commits from develop into main 2026-03-21 08:48:57 +01:00
Showing only changes of commit 5ef6a80a1f - Show all commits

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>