import React from 'react'; import { CreditCard, Download, Loader2, RefreshCw, Sparkles } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; import { AdminLayout } from '../components/AdminLayout'; import { CreditLedgerEntry, getCreditBalance, getCreditLedger, getTenantPackagesOverview, PaginationMeta, TenantPackageSummary, } from '../api'; import { isAuthError } from '../auth/tokens'; type LedgerState = { entries: CreditLedgerEntry[]; meta: PaginationMeta | null; }; export default function BillingPage() { const [balance, setBalance] = React.useState(0); const [packages, setPackages] = React.useState([]); const [activePackage, setActivePackage] = React.useState(null); const [ledger, setLedger] = React.useState({ entries: [], meta: null }); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [loadingMore, setLoadingMore] = React.useState(false); React.useEffect(() => { void loadAll(); }, []); async function loadAll() { setLoading(true); setError(null); try { const [balanceResult, packagesResult, ledgerResult] = await Promise.all([ safeCall(() => getCreditBalance()), safeCall(() => getTenantPackagesOverview()), safeCall(() => getCreditLedger(1)), ]); if (balanceResult?.balance !== undefined) { setBalance(balanceResult.balance); } if (packagesResult) { setPackages(packagesResult.packages); setActivePackage(packagesResult.activePackage); } if (ledgerResult) { setLedger({ entries: ledgerResult.data, meta: ledgerResult.meta }); } else { setLedger({ entries: [], meta: null }); } } catch (err) { if (!isAuthError(err)) { setError('Billing Daten konnten nicht geladen werden.'); } } finally { setLoading(false); } } async function loadMore() { if (!ledger.meta || loadingMore) { return; } const { current_page, last_page } = ledger.meta; if (current_page >= last_page) { return; } setLoadingMore(true); try { const next = await getCreditLedger(current_page + 1); setLedger({ entries: [...ledger.entries, ...next.data], meta: next.meta, }); } catch (err) { if (!isAuthError(err)) { setError('Weitere Ledger Eintraege konnten nicht geladen werden.'); } } finally { setLoadingMore(false); } } const actions = ( ); return ( {error && ( Fehler {error} )} {loading ? ( ) : ( <>
Credits und Status Dein aktuelles Guthaben und das aktive Reseller Paket.
{activePackage ? activePackage.package_name : 'Kein aktives Paket'}
Paket Historie Uebersicht ueber aktive und vergangene Reseller Pakete. {packages.length === 0 ? ( ) : ( packages.map((pkg) => ( )) )}
Credit Ledger Alle Zu- und Abbuchungen deines Credits Kontos.
{ledger.entries.length === 0 ? ( ) : ( <> {ledger.entries.map((entry) => ( ))} {ledger.meta && ledger.meta.current_page < ledger.meta.last_page && ( )} )}
)}
); } async function safeCall(callback: () => Promise): Promise { try { return await callback(); } catch (error) { if (!isAuthError(error)) { console.warn('[Tenant Billing] optional endpoint fehlgeschlagen', error); } return null; } } function InfoCard({ label, value, helper, tone, }: { label: string; value: string | number | null | undefined; helper?: string; tone: 'pink' | 'amber' | 'sky' | 'emerald'; }) { const toneClass = { pink: 'from-pink-50 to-rose-100 text-pink-700', amber: 'from-amber-50 to-yellow-100 text-amber-700', sky: 'from-sky-50 to-blue-100 text-sky-700', emerald: 'from-emerald-50 to-green-100 text-emerald-700', }[tone]; return (
{label}
{value ?? '--'}
{helper &&

{helper}

}
); } function PackageCard({ pkg, isActive }: { pkg: TenantPackageSummary; isActive: boolean }) { return (

{pkg.package_name}

{formatDate(pkg.purchased_at)} - {formatCurrency(pkg.price)} {pkg.currency ?? 'EUR'}

{isActive ? 'Aktiv' : 'Inaktiv'}
Genutzte Events: {pkg.used_events} Verfuegbar: {pkg.remaining_events ?? '--'} Ablauf: {formatDate(pkg.expires_at)}
); } function LedgerRow({ entry }: { entry: CreditLedgerEntry }) { const positive = entry.delta >= 0; return (

{mapReason(entry.reason)}

{entry.note &&

{entry.note}

}
{positive ? '+' : ''} {entry.delta} {formatDate(entry.created_at)}
); } function EmptyState({ message }: { message: string }) { return (

{message}

); } function BillingSkeleton() { return (
{Array.from({ length: 3 }).map((_, index) => (
{Array.from({ length: 4 }).map((__, placeholderIndex) => (
))}
))}
); } function mapReason(reason: string): string { switch (reason) { case 'purchase': return 'Credit Kauf'; case 'usage': return 'Verbrauch'; case 'manual': return 'Manuelle Anpassung'; default: return reason; } } function formatDate(value: string | null | undefined): string { if (!value) return '--'; const date = new Date(value); if (Number.isNaN(date.getTime())) return '--'; return date.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', year: 'numeric' }); } function formatCurrency(value: number | null | undefined): string { if (value === null || value === undefined) return '--'; return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(value); }