import React, { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { FormGeneratorTable, type ColumnConfig } from '../../components/FormGenerator/FormGeneratorTable'; import { useApiRequest } from '../../hooks/useApi'; import { useConfirm } from '../../hooks/useConfirm'; import api from '../../api'; import styles from './Billing.module.css'; const _TERMINAL_STATUSES = new Set(['EXPIRED']); const _STATUS_LABELS: Record = { PENDING: 'Ausstehend', SCHEDULED: 'Geplant', TRIALING: 'Testphase', ACTIVE: 'Aktiv', PAST_DUE: 'Überfällig', EXPIRED: 'Abgelaufen', }; const _COLUMNS: ColumnConfig[] = [ { key: 'mandateName', label: 'Mandant', type: 'text' as any, sortable: true, filterable: true, width: 180 }, { key: 'planTitle', label: 'Plan', type: 'text' as any, sortable: true, filterable: true, width: 180 }, { key: 'status', label: 'Status', type: 'text' as any, sortable: true, filterable: true, width: 110 }, { key: 'recurring', label: 'Wiederkehrend', type: 'boolean' as any, sortable: true, filterable: true, width: 120 }, { key: 'activeUsers', label: 'User', type: 'number' as any, sortable: true, width: 70 }, { key: 'activeInstances', label: 'Instanzen', type: 'number' as any, sortable: true, width: 90 }, { key: 'monthlyRevenueCHF', label: 'Revenue/Mt (CHF)', type: 'number' as any, sortable: true, width: 140 }, { key: 'startedAt', label: 'Gestartet', type: 'date' as any, sortable: true, width: 130 }, { key: 'currentPeriodEnd', label: 'Periodenende', type: 'date' as any, sortable: true, width: 130 }, { key: 'snapshotPricePerUserCHF', label: 'Preis/User', type: 'number' as any, sortable: true, width: 100 }, { key: 'snapshotPricePerInstanceCHF', label: 'Preis/Instanz', type: 'number' as any, sortable: true, width: 110 }, ]; const AdminSubscriptionsPage: React.FC = () => { const navigate = useNavigate(); const { request } = useApiRequest(); const { confirm, ConfirmDialog } = useConfirm(); const [subscriptions, setSubscriptions] = useState([]); const [loading, setLoading] = useState(true); const _loadSubscriptions = useCallback(async () => { setLoading(true); try { const data = await request({ url: '/api/subscription/admin/all', method: 'get' }); const rows = (Array.isArray(data) ? data : []).map((row: any) => ({ ...row, status: _STATUS_LABELS[row.status] || row.status, _rawStatus: row.status, })); setSubscriptions(rows); } catch (err) { console.error('Failed to load subscriptions:', err); setSubscriptions([]); } finally { setLoading(false); } }, [request]); useEffect(() => { _loadSubscriptions(); }, [_loadSubscriptions]); const _handleForceCancel = useCallback(async (row: any) => { const ok = await confirm( `Subscription «${row.planTitle}» für Mandant «${row.mandateName}» sofort kündigen? Dies wird auch auf Stripe sofort storniert.`, { confirmLabel: 'Sofort kündigen', cancelLabel: 'Abbrechen', variant: 'danger' }, ); if (!ok) return; try { await api.post('/api/subscription/force-cancel', { subscriptionId: row.id }); await _loadSubscriptions(); } catch (err) { console.error('Force cancel failed:', err); } }, [confirm, _loadSubscriptions]); return (

Subscription-Übersicht

Alle Abonnements aller Mandanten

{loading ? (
Lade Subscriptions…
) : ( _handleForceCancel(row), isVisible: (row: any) => !_TERMINAL_STATUSES.has(row._rawStatus), }, ]} emptyMessage="Keine Subscriptions vorhanden." /> )}
); }; export default AdminSubscriptionsPage;