/** * Billing Dashboard Page * * Zeigt Guthaben, Statistiken und Transaktionen für den Benutzer. */ import React, { useState, useEffect, useMemo } from 'react'; import { useBilling, type BillingBalance, type UsageReport, type BillingBucketSize } from '../../hooks/useBilling'; import { BillingNav } from './BillingNav'; import styles from './Billing.module.css'; import { useLanguage } from '../../providers/language/LanguageContext'; import { PeriodPicker, resolvePeriod, daysInRange, type PeriodValue, } from '../../components/PeriodPicker'; const _DEFAULT_BILLING_PRESET = { kind: 'thisMonth' as const }; function _suggestBucketSize(value: PeriodValue): BillingBucketSize { const days = daysInRange(value.fromDate, value.toDate); if (days <= 62) return 'day'; if (days <= 24 * 31) return 'month'; return 'year'; } // ============================================================================ // BALANCE CARD COMPONENT // ============================================================================ interface BalanceCardProps { balance: BillingBalance; onClick?: () => void; } const BalanceCard: React.FC = ({ balance, onClick }) => { const { t } = useLanguage(); const formatCurrency = (amount: number) => { return new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF' }).format(amount); }; return (

{balance.mandateName}

{formatCurrency(balance.balance)}
{balance.isWarning && (
{t('Niedriges Guthaben')}
)}
); }; // ============================================================================ // STATISTICS CHART COMPONENT // ============================================================================ interface StatisticsChartProps { statistics: UsageReport | null; loading?: boolean; } const StatisticsChart: React.FC = ({ statistics, loading }) => { const { t } = useLanguage(); const formatCurrency = (amount: number) => { return new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF' }).format(amount); }; if (loading) { return
{t('Statistiken laden')}
; } if (!statistics) { return
{t('Keine Statistiken verfügbar')}
; } // Calculate max cost for bar scaling const maxProviderCost = Math.max(...Object.values(statistics.costByProvider), 1); return (
{t('Gesamtkosten')} {formatCurrency(statistics.totalCost)}

{t('Kosten nach Anbieter')}

{Object.entries(statistics.costByProvider).length === 0 ? (
{t('Keine Daten')}
) : (
{Object.entries(statistics.costByProvider).map(([provider, cost]) => (
{provider}
{formatCurrency(cost)}
))}
)}

{t('Kosten nach Modell')}

{Object.entries(statistics.costByModel || {}).length === 0 ? (
{t('Keine Daten')}
) : (
{Object.entries(statistics.costByModel || {}).map(([model, cost]) => (
{model}
{formatCurrency(cost)}
))}
)}

{t('Kosten nach Feature')}

{Object.entries(statistics.costByFeature).length === 0 ? (
{t('Keine Daten')}
) : (
{Object.entries(statistics.costByFeature).map(([feature, cost]) => (
{feature} {formatCurrency(cost)}
))}
)}
); }; // ============================================================================ // MAIN COMPONENT // ============================================================================ export const BillingDashboard: React.FC = () => { const { t } = useLanguage(); const { balances, statistics, loading, loadStatistics } = useBilling(); const [period, setPeriod] = useState(() => { const r = resolvePeriod(_DEFAULT_BILLING_PRESET); return { preset: _DEFAULT_BILLING_PRESET, fromDate: r.fromDate, toDate: r.toDate }; }); // Frontend-Heuristik fuer Default; user kann uebersteuern. const [bucketSize, setBucketSize] = useState(() => _suggestBucketSize(period)); const [bucketUserOverridden, setBucketUserOverridden] = useState(false); const _handlePeriodChange = (next: PeriodValue) => { setPeriod(next); if (!bucketUserOverridden) { setBucketSize(_suggestBucketSize(next)); } }; useEffect(() => { void loadStatistics({ dateFrom: period.fromDate, dateTo: period.toDate, bucketSize, }); }, [period.fromDate, period.toDate, bucketSize, loadStatistics]); const _bucketLabel = useMemo(() => ({ day: t('Tag'), month: t('Monat'), year: t('Jahr'), } as Record), [t]); return (

{t('Abrechnung')}

{t('Übersicht über Guthaben und Nutzung')}

{/* Balance Cards */}

{t('Guthaben')}

{loading ? (
{t('Guthaben laden')}
) : balances.length === 0 ? (
{t('Keine Abrechnungskonten vorhanden')}
) : (
{balances.map((balance) => ( ))}
)}
{/* Statistics */}

{t('Nutzungsstatistik')}

); }; export default BillingDashboard;