mitai-jinkendo/frontend/src/components/FeatureUsageOverview.jsx
Lars 405abc1973
All checks were successful
Deploy Development / deploy (push) Successful in 35s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 12s
feat: add feature usage UI components (Phase 3)
- Add api.getFeatureUsage() endpoint call
- Create UsageBadge component (inline indicators)
- Create FeatureUsageOverview component (Settings table)
- Add "Kontingente" section to Settings page
- Color-coded status (green/yellow/red)
- Grouped by category
- Shows reset period and next reset date

Phase 3: Frontend Display

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 06:39:52 +01:00

143 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* FeatureUsageOverview - Full feature usage table for Settings page
*
* Shows all features with usage, limits, reset period, and next reset date
* Phase 3: Frontend Display
*/
import { useState, useEffect } from 'react'
import { api } from '../utils/api'
import './FeatureUsageOverview.css'
const RESET_PERIOD_LABELS = {
'never': 'Niemals',
'daily': 'Täglich',
'monthly': 'Monatlich'
}
const CATEGORY_LABELS = {
'data': 'Daten',
'ai': 'KI',
'export': 'Export',
'import': 'Import',
'integration': 'Integration'
}
export default function FeatureUsageOverview() {
const [features, setFeatures] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
loadFeatures()
}, [])
const loadFeatures = async () => {
try {
setLoading(true)
const data = await api.getFeatureUsage()
setFeatures(data)
setError(null)
} catch (err) {
console.error('Failed to load feature usage:', err)
setError('Fehler beim Laden der Kontingente')
} finally {
setLoading(false)
}
}
const formatResetDate = (resetAt) => {
if (!resetAt) return '—'
try {
const date = new Date(resetAt)
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
} catch {
return '—'
}
}
const getStatusClass = (feature) => {
if (!feature.allowed || feature.remaining < 0) return 'exceeded'
if (feature.limit && feature.remaining <= Math.ceil(feature.limit * 0.2)) return 'warning'
return 'ok'
}
// Group by category
const byCategory = features.reduce((acc, f) => {
const cat = f.category || 'other'
if (!acc[cat]) acc[cat] = []
acc[cat].push(f)
return acc
}, {})
if (loading) {
return (
<div className="feature-usage-loading">
<div className="spinner" />
Lade Kontingente...
</div>
)
}
if (error) {
return (
<div className="feature-usage-error">
{error}
</div>
)
}
if (features.length === 0) {
return (
<div className="feature-usage-empty">
Keine Features gefunden
</div>
)
}
return (
<div className="feature-usage-overview">
{Object.entries(byCategory).map(([category, items]) => (
<div key={category} className="feature-category">
<div className="feature-category-label">
{CATEGORY_LABELS[category] || category}
</div>
<div className="feature-list">
{items.map(feature => (
<div key={feature.feature_id} className={`feature-item feature-item--${getStatusClass(feature)}`}>
<div className="feature-main">
<div className="feature-name">{feature.name}</div>
<div className="feature-usage">
{feature.limit === null ? (
<span className="usage-unlimited">Unbegrenzt</span>
) : feature.limit_type === 'boolean' ? (
<span className={`usage-boolean ${feature.allowed ? 'enabled' : 'disabled'}`}>
{feature.allowed ? '✓ Aktiviert' : '✗ Deaktiviert'}
</span>
) : (
<span className="usage-count">
<strong>{feature.used}</strong> / {feature.limit}
</span>
)}
</div>
</div>
{feature.limit_type === 'count' && feature.limit !== null && (
<div className="feature-meta">
<span className="meta-reset">
Reset: {RESET_PERIOD_LABELS[feature.reset_period] || feature.reset_period}
</span>
{feature.reset_at && (
<span className="meta-next-reset">
Nächster Reset: {formatResetDate(feature.reset_at)}
</span>
)}
</div>
)}
</div>
))}
</div>
</div>
))}
</div>
)
}