import { useState, useEffect } from 'react' import { api } from '../utils/api' export default function AdminTierLimitsPage() { const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [success, setSuccess] = useState('') const [matrix, setMatrix] = useState({ tiers: [], features: [], limits: {} }) const [changes, setChanges] = useState({}) const [saving, setSaving] = useState(false) useEffect(() => { loadMatrix() }, []) async function loadMatrix() { try { setLoading(true) const data = await api.getTierLimitsMatrix() setMatrix(data) setChanges({}) setError('') } catch (e) { setError(e.message) } finally { setLoading(false) } } function handleChange(tierId, featureId, value) { const key = `${tierId}:${featureId}` const newChanges = { ...changes } // Parse value let parsedValue = null if (value === '' || value === 'unlimited' || value === '∞') { parsedValue = null // unlimited } else if (value === '0' || value === 'disabled') { parsedValue = 0 // disabled } else { const num = parseInt(value) if (!isNaN(num) && num >= 0) { parsedValue = num } else { return // invalid input } } newChanges[key] = { tierId, featureId, value: parsedValue } setChanges(newChanges) } async function saveChanges() { if (Object.keys(changes).length === 0) { setSuccess('Keine Änderungen') return } try { setSaving(true) setError('') setSuccess('') const updates = Object.values(changes).map(c => ({ tier_id: c.tierId, feature_id: c.featureId, limit_value: c.value })) await api.updateTierLimitsBatch(updates) setSuccess(`${updates.length} Limits gespeichert`) await loadMatrix() } catch (e) { setError(e.message) } finally { setSaving(false) } } function getCurrentValue(tierId, featureId) { const key = `${tierId}:${featureId}` if (key in changes) { return changes[key].value } return matrix.limits[key] ?? null } function formatValue(val) { if (val === null) return '∞' if (val === 0) return '❌' return val.toString() } function groupFeaturesByCategory() { const groups = {} matrix.features.forEach(f => { if (!groups[f.category]) groups[f.category] = [] groups[f.category].push(f) }) return groups } if (loading) return (
) const hasChanges = Object.keys(changes).length > 0 const categoryGroups = groupFeaturesByCategory() const categoryIcons = { data: '📊', ai: '🤖', export: '📤', integration: '🔗' } const categoryNames = { data: 'DATEN', ai: 'KI', export: 'EXPORT', integration: 'INTEGRATIONEN' } return (
{/* Header */}
Tier Limits Matrix
Konfiguriere Feature-Limits pro Tier (∞ = unbegrenzt, ❌ = deaktiviert)
{hasChanges && ( )}
{/* Messages */} {error && (
{error}
)} {success && (
{success}
)} {/* Matrix Table */}
{matrix.tiers.map(tier => ( ))} {Object.entries(categoryGroups).map(([category, features]) => ( <> {/* Category Header */} {/* Feature Rows */} {features.map((feature, idx) => ( {matrix.tiers.map(tier => { const currentValue = getCurrentValue(tier.id, feature.id) const isChanged = `${tier.id}:${feature.id}` in changes return ( ) })} ))} ))}
Feature {tier.name}
{categoryIcons[category]} {categoryNames[category] || category}
{feature.name}
{feature.limit_type === 'boolean' ? '(boolean)' : `(count, reset: ${feature.reset_period})`}
handleChange(tier.id, feature.id, e.target.value)} style={{ width: '80px', padding: '4px 8px', border: `1px solid ${isChanged ? 'var(--accent)' : 'var(--border)'}`, borderRadius: 4, textAlign: 'center', fontSize: 13, fontWeight: isChanged ? 600 : 400, background: 'var(--bg)', color: currentValue === 0 ? 'var(--danger)' : currentValue === null ? 'var(--accent)' : 'var(--text1)' }} placeholder={feature.limit_type === 'boolean' ? '0/1' : '0-999'} />
{/* Legend */}
Legende:
∞ = Unbegrenzt (NULL) ❌ = Deaktiviert (0) 1-999 = Limit-Wert Boolean: 0 = deaktiviert, 1 = aktiviert
) }