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 [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>