import { useState, useEffect } from 'react' import { Save, RotateCcw, ChevronDown, ChevronUp } from 'lucide-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) const [isMobile, setIsMobile] = useState(window.innerWidth < 768) useEffect(() => { loadMatrix() const handleResize = () => setIsMobile(window.innerWidth < 768) window.addEventListener('resize', handleResize) return () => window.removeEventListener('resize', handleResize) }, []) 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 } // Allow temporary empty input for better UX if (value === '') { newChanges[key] = { tierId, featureId, value: '', tempValue: '' } setChanges(newChanges) return } // Parse value let parsedValue = null if (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, ignore } } newChanges[key] = { tierId, featureId, value: parsedValue, tempValue: value } setChanges(newChanges) } async function saveChanges() { // Filter out empty temporary values const validChanges = Object.values(changes).filter(c => c.value !== '') if (validChanges.length === 0) { setSuccess('Keine Änderungen') return } try { setSaving(true) setError('') setSuccess('') const updates = validChanges.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 temp value for display return changes[key].tempValue !== undefined ? changes[key].tempValue : changes[key].value } return matrix.limits[key] ?? null } function formatValue(val) { if (val === '' || val === null || val === undefined) return '' if (val === '∞' || val === 'unlimited') return '∞' if (val === 0 || val === '0') return '0' 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).filter(k => changes[k].value !== '').length > 0 const categoryGroups = groupFeaturesByCategory() const categoryIcons = { data: '📊', ai: '🤖', export: '📤', integration: '🔗' } const categoryNames = { data: 'DATEN', ai: 'KI', export: 'EXPORT', integration: 'INTEGRATIONEN' } // Mobile: Card-based view if (isMobile) { return (
{/* Header */}
Tier Limits
Limits pro Tier konfigurieren
{/* Messages */} {error && (
{error}
)} {success && (
{success}
)} {/* Mobile: Feature Cards */} {Object.entries(categoryGroups).map(([category, features]) => (
{/* Category Header */}
{categoryIcons[category]} {categoryNames[category] || category}
{/* Features */} {features.map(feature => ( ))}
))} {/* Fixed Bottom Bar */}
{hasChanges && ( )}
) } // Desktop: Table view return (
{/* Header */}
Tier Limits Matrix
Feature-Limits pro Tier (leer = unbegrenzt, 0 = 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 && changes[`${tier.id}:${feature.id}`].value !== '' // Boolean features: Toggle button if (feature.limit_type === 'boolean') { const isEnabled = currentValue !== 0 && currentValue !== '0' return ( ) } // Count features: Text input return ( ) })} ))} ))}
Feature
{tier.name}
{tier.id}
{categoryIcons[category]} {categoryNames[category] || category}
{feature.name}
{feature.limit_type === 'boolean' ? '(ja/nein)' : `(count, reset: ${feature.reset_period})`}
handleChange(tier.id, feature.id, e.target.value)} placeholder="∞" style={{ width: '80px', padding: '6px 8px', border: `1.5px solid ${isChanged ? 'var(--accent)' : 'var(--border)'}`, borderRadius: 6, textAlign: 'center', fontSize: 13, fontWeight: isChanged ? 600 : 400, background: 'var(--bg)', color: currentValue === 0 || currentValue === '0' ? 'var(--danger)' : currentValue === null || currentValue === '' || currentValue === '∞' ? 'var(--accent)' : 'var(--text1)' }} />
{/* Legend */}
Eingabe:
leer oder ∞ = Unbegrenzt 0 = Deaktiviert 1-999999 = Limit-Wert
) } // Mobile Card Component function FeatureMobileCard({ feature, tiers, getCurrentValue, handleChange, changes }) { const [expanded, setExpanded] = useState(false) return (
{/* Feature Header */}
setExpanded(!expanded)} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: 'pointer', padding: '4px 0' }} >
{feature.name}
{feature.limit_type === 'boolean' ? '(ja/nein)' : `(${feature.reset_period})`}
{expanded ? : }
{/* Tier Inputs (Expanded) */} {expanded && (
{tiers.map(tier => { const currentValue = getCurrentValue(tier.id, feature.id) const isChanged = `${tier.id}:${feature.id}` in changes && changes[`${tier.id}:${feature.id}`].value !== '' // Boolean features: Toggle button if (feature.limit_type === 'boolean') { const isEnabled = currentValue !== 0 && currentValue !== '0' return (
) } // Count features: Text input return (
handleChange(tier.id, feature.id, e.target.value)} placeholder="∞" style={{ border: `1.5px solid ${isChanged ? 'var(--accent)' : 'var(--border)'}`, background: isChanged ? 'var(--accent-light)' : 'var(--bg)', color: currentValue === 0 ? 'var(--danger)' : currentValue === null || currentValue === '' ? 'var(--accent)' : 'var(--text1)', fontWeight: isChanged ? 600 : 400 }} /> {currentValue === null || currentValue === '' ? '∞' : currentValue === 0 ? '❌' : '✓'}
) })}
)}
) }