import { useState, useEffect } from 'react' import { Save, AlertCircle, X, RotateCcw } from 'lucide-react' import { api } from '../utils/api' export default function AdminUserRestrictionsPage() { const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [success, setSuccess] = useState('') const [users, setUsers] = useState([]) const [features, setFeatures] = useState([]) const [selectedUserId, setSelectedUserId] = useState('') const [selectedUser, setSelectedUser] = useState(null) const [restrictions, setRestrictions] = useState([]) const [changes, setChanges] = useState({}) const [saving, setSaving] = useState(false) useEffect(() => { loadInitialData() }, []) useEffect(() => { if (selectedUserId) { loadUserData(selectedUserId) } else { setSelectedUser(null) setRestrictions([]) setChanges({}) } }, [selectedUserId]) async function loadInitialData() { try { setLoading(true) const [usersData, featuresData] = await Promise.all([ api.adminListProfiles(), api.listFeatures() ]) setUsers(usersData) setFeatures(featuresData.filter(f => f.active)) setError('') } catch (e) { setError(e.message) } finally { setLoading(false) } } async function loadUserData(userId) { try { const [user, restrictionsData] = await Promise.all([ api.adminListProfiles().then(users => users.find(u => u.id === userId)), api.listUserRestrictions(userId) ]) setSelectedUser(user) setRestrictions(restrictionsData) setChanges({}) setError('') setSuccess('') } catch (e) { setError(e.message) } } function handleChange(featureId, value) { const newChanges = { ...changes } // Empty string means: remove override (if exists) or do nothing if (value === '') { newChanges[featureId] = { action: 'remove' } } else { // Parse value let parsedValue = null if (value === 'unlimited' || value === '∞') { parsedValue = null } else if (value === '0') { parsedValue = 0 } else { const num = parseInt(value) if (!isNaN(num) && num >= 0) { parsedValue = num } else { return // invalid } } newChanges[featureId] = { action: 'set', value: parsedValue } } setChanges(newChanges) } function handleToggle(featureId, currentEnabled) { const newChanges = { ...changes } newChanges[featureId] = { action: 'toggle', enabled: !currentEnabled } setChanges(newChanges) } async function handleSave() { if (!selectedUserId) return try { setSaving(true) setError('') setSuccess('') let changeCount = 0 for (const [featureId, change] of Object.entries(changes)) { const existingRestriction = restrictions.find(r => r.feature_id === featureId) 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(`${changeCount} Änderung(en) gespeichert`) await loadUserData(selectedUserId) } catch (e) { setError(e.message) } finally { setSaving(false) } } 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 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) if (!restriction) return true // Default: enabled // For boolean features: limit_value determines state return restriction.limit_value !== 0 } function hasOverride(featureId) { return restrictions.some(r => r.feature_id === featureId) } function isChanged(featureId) { return featureId in changes } if (loading) return (
) const hasChanges = Object.keys(changes).length > 0 const categoryGroups = {} features.forEach(f => { if (!categoryGroups[f.category]) categoryGroups[f.category] = [] categoryGroups[f.category].push(f) }) const categoryIcons = { data: '📊', ai: '🤖', export: '📤', integration: '🔗' } const categoryNames = { data: 'DATEN', ai: 'KI', export: 'EXPORT', integration: 'INTEGRATIONEN' } return (
{/* Header */}
User Feature-Overrides
Individuelle Feature-Limits für einzelne User setzen
{/* Info Box */}
Hinweis: User-Overrides überschreiben Tier-Limits. Leere Felder = kein Override (User nutzt Tier-Standard). Wert eingeben = Override setzen.
{/* Messages */} {error && (
{error}
)} {success && (
{success}
)} {/* User Selection */}
{/* User Info + Features */} {selectedUser && ( <> {/* User Info Card */}
{selectedUser.name?.charAt(0).toUpperCase()}
{selectedUser.name}
{selectedUser.email || `ID: ${selectedUser.id}`}
Tier: {selectedUser.tier || 'free'}
{/* Features Table */}
{Object.entries(categoryGroups).map(([category, categoryFeatures]) => ( <> {/* Category Header */} {/* Feature Rows */} {categoryFeatures.map(feature => { const displayValue = getDisplayValue(feature.id) const toggleState = getToggleState(feature.id) const override = hasOverride(feature.id) const changed = isChanged(feature.id) return ( {/* Feature Name */} {/* Override Input */} {/* Status */} ) })} ))}
Feature Override-Wert Status
{categoryIcons[category]} {categoryNames[category] || category}
{feature.name}
{feature.limit_type === 'boolean' ? '(ja/nein)' : `(${feature.reset_period})`}
{feature.limit_type === 'boolean' ? ( ) : ( handleChange(feature.id, e.target.value)} placeholder="leer = Tier-Standard" style={{ width: '140px', padding: '6px 8px', border: `1.5px solid ${changed ? 'var(--accent)' : 'var(--border)'}`, borderRadius: 6, textAlign: 'center', fontSize: 13, fontWeight: changed ? 600 : 400, background: 'var(--bg)', color: displayValue === 0 || displayValue === '0' ? 'var(--danger)' : 'var(--text1)' }} /> )} {override ? ( ✓ Override aktiv ) : ( Tier-Standard )}
{/* Fixed Bottom Bar */}
{/* Legend */}
Eingabe:
Leer = Tier-Standard nutzen (kein Override) 0 = Feature deaktiviert ∞ oder leer = Unbegrenzt (bei Count-Features) 1-999999 = Limit-Wert
)}
) }