/** * Billing Admin Page * * Admin-Seite für Billing-Verwaltung (SysAdmin only). * - Settings verwalten * - Guthaben aufladen * - Konten übersicht */ import React, { useState, useEffect, useCallback } from 'react'; import { useBillingAdmin, type BillingSettings, type AccountSummary, type MandateUserSummary } from '../../hooks/useBilling'; import { useAdminMandates } from '../../hooks/useMandates'; import styles from './Billing.module.css'; // ============================================================================ // MANDATE SELECTOR // ============================================================================ interface MandateSelectorProps { selectedMandateId: string | null; onSelect: (mandateId: string) => void; } const MandateSelector: React.FC = ({ selectedMandateId, onSelect }) => { const { mandates, loading } = useAdminMandates(); return (
); }; // ============================================================================ // SETTINGS EDITOR // ============================================================================ interface SettingsEditorProps { settings: BillingSettings | null; onSave: (settings: Partial) => Promise; loading: boolean; } const SettingsEditor: React.FC = ({ settings, onSave, loading }) => { const [formData, setFormData] = useState({ billingModel: settings?.billingModel || 'UNLIMITED', defaultUserCredit: settings?.defaultUserCredit || 10, warningThresholdPercent: settings?.warningThresholdPercent || 10, blockOnZeroBalance: settings?.blockOnZeroBalance ?? true, notifyOnWarning: settings?.notifyOnWarning ?? true, }); const [saving, setSaving] = useState(false); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); useEffect(() => { if (settings) { setFormData({ billingModel: settings.billingModel, defaultUserCredit: settings.defaultUserCredit, warningThresholdPercent: settings.warningThresholdPercent, blockOnZeroBalance: settings.blockOnZeroBalance, notifyOnWarning: settings.notifyOnWarning, }); } }, [settings]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSaving(true); setMessage(null); try { await onSave(formData); setMessage({ type: 'success', text: 'Einstellungen gespeichert!' }); } catch (err: any) { setMessage({ type: 'error', text: err.message || 'Fehler beim Speichern' }); } finally { setSaving(false); } }; return (

Billing-Einstellungen

{message && (
{message.text}
)}
setFormData(prev => ({ ...prev, defaultUserCredit: Number(e.target.value) }))} min="0" step="0.01" />
setFormData(prev => ({ ...prev, warningThresholdPercent: Number(e.target.value) }))} min="0" max="100" step="1" />
); }; // ============================================================================ // CREDIT ADDER // ============================================================================ interface CreditAdderProps { settings: BillingSettings | null; accounts: AccountSummary[]; users: MandateUserSummary[]; onAddCredit: (userId: string | undefined, amount: number, description: string) => Promise; } const CreditAdder: React.FC = ({ settings, accounts, users, onAddCredit }) => { const [selectedUserId, setSelectedUserId] = useState(''); const [amount, setAmount] = useState(10); const [description, setDescription] = useState('Manuelles Aufladen'); const [saving, setSaving] = useState(false); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const isPrepayUser = settings?.billingModel === 'PREPAY_USER'; // Map accounts by userId for balance lookup const accountsByUserId = accounts .filter(acc => acc.accountType === 'USER') .reduce((map, acc) => { if (acc.userId) map[acc.userId] = acc; return map; }, {} as Record); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (amount <= 0) { setMessage({ type: 'error', text: 'Betrag muss positiv sein' }); return; } setSaving(true); setMessage(null); try { await onAddCredit(isPrepayUser ? selectedUserId : undefined, amount, description); setMessage({ type: 'success', text: `${amount} CHF erfolgreich gutgeschrieben!` }); setAmount(10); setDescription('Manuelles Aufladen'); } catch (err: any) { setMessage({ type: 'error', text: err.message || 'Fehler beim Aufladen' }); } finally { setSaving(false); } }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF' }).format(amount); }; return (

Guthaben aufladen

{message && (
{message.text}
)}
{isPrepayUser && (
)}
setAmount(Number(e.target.value))} min="0.01" step="0.01" required />
setDescription(e.target.value)} placeholder="Grund für Gutschrift" />
); }; // ============================================================================ // ACCOUNTS OVERVIEW // ============================================================================ interface AccountsOverviewProps { accounts: AccountSummary[]; loading: boolean; } const AccountsOverview: React.FC = ({ accounts, loading }) => { const formatCurrency = (amount: number) => { return new Intl.NumberFormat('de-CH', { style: 'currency', currency: 'CHF' }).format(amount); }; if (loading) { return
Lade Konten...
; } if (accounts.length === 0) { return
Keine Konten vorhanden
; } return (

Konten

{accounts.map((account) => (

{account.accountType === 'MANDATE' ? 'Mandanten-Konto' : 'Benutzer-Konto'}

{account.userId && User: {account.userId}} Guthaben: {formatCurrency(account.balance)} {account.creditLimit && Limit: {formatCurrency(account.creditLimit)}} Status: {account.enabled ? 'Aktiv' : 'Deaktiviert'}
))}
); }; // ============================================================================ // MAIN COMPONENT // ============================================================================ export const BillingAdmin: React.FC = () => { const [selectedMandateId, setSelectedMandateId] = useState(null); const { settings, accounts, users, loading, loadSettings, saveSettings, addCredit, loadAccounts } = useBillingAdmin(selectedMandateId || undefined); const handleMandateSelect = (mandateId: string) => { setSelectedMandateId(mandateId || null); }; const handleSaveSettings = useCallback(async (settingsUpdate: Partial) => { if (!selectedMandateId) return; await saveSettings(settingsUpdate); }, [selectedMandateId, saveSettings]); const handleAddCredit = useCallback(async (userId: string | undefined, amount: number, description: string) => { if (!selectedMandateId) return; await addCredit({ userId, amount, description }); await loadAccounts(); }, [selectedMandateId, addCredit, loadAccounts]); return (

Billing Administration

Verwaltung von Abrechnungseinstellungen und Guthaben

{selectedMandateId && ( <> )} {!selectedMandateId && (
Bitte wählen Sie einen Mandanten aus.
)}
); }; export default BillingAdmin;