99 lines
4.1 KiB
TypeScript
99 lines
4.1 KiB
TypeScript
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
import { FormGeneratorTable, type ColumnConfig } from '../../components/FormGenerator/FormGeneratorTable';
|
|
import { useAdminSubscriptions } from '../../hooks/useAdminSubscriptions';
|
|
import { useConfirm } from '../../hooks/useConfirm';
|
|
import { useApiRequest } from '../../hooks/useApi';
|
|
import { fetchAttributes } from '../../api/attributesApi';
|
|
import type { AttributeDefinition } from '../../api/attributesApi';
|
|
import { resolveColumnTypes } from '../../utils/columnTypeResolver';
|
|
import api from '../../api';
|
|
import styles from './Billing.module.css';
|
|
|
|
import { useLanguage } from '../../providers/language/LanguageContext';
|
|
|
|
const _TERMINAL_STATUSES = new Set(['EXPIRED']);
|
|
|
|
const AdminSubscriptionsPage: React.FC = () => {
|
|
const { t } = useLanguage();
|
|
const { request } = useApiRequest();
|
|
const [backendAttributes, setBackendAttributes] = useState<AttributeDefinition[]>([]);
|
|
|
|
const { confirm, ConfirmDialog } = useConfirm();
|
|
const { data: subscriptions, pagination, loading, refetch } = useAdminSubscriptions();
|
|
|
|
useEffect(() => {
|
|
fetchAttributes(request, 'MandateSubscriptionView')
|
|
.then(setBackendAttributes)
|
|
.catch(() => setBackendAttributes([]));
|
|
}, [request]);
|
|
|
|
const _rawColumns: ColumnConfig[] = useMemo(() => [
|
|
{ key: 'mandateName', label: t('Mandant'), sortable: true, filterable: true, width: 180 },
|
|
{ key: 'planTitle', label: t('Plan'), sortable: true, filterable: true, width: 180 },
|
|
{ key: 'status', label: t('Status'), sortable: true, filterable: true, width: 110 },
|
|
{ key: 'recurring', label: t('Wiederkehrend'), sortable: true, filterable: true, width: 120 },
|
|
{ key: 'activeUsers', label: t('Benutzer'), sortable: true, width: 70 },
|
|
{ key: 'activeInstances', label: t('Module'), sortable: true, width: 90 },
|
|
{ key: 'monthlyRevenueCHF', label: t('Umsatz pro Monat'), sortable: true, width: 140 },
|
|
{ key: 'startedAt', label: t('Gestartet'), sortable: true, filterable: true, width: 130 },
|
|
{ key: 'currentPeriodEnd', label: t('Periodenende'), sortable: true, filterable: true, width: 130 },
|
|
{ key: 'snapshotPricePerUserCHF', label: t('Preis pro Benutzer'), sortable: true, width: 100 },
|
|
{ key: 'snapshotPricePerInstanceCHF', label: t('Preis pro Modul'), sortable: true, width: 110 },
|
|
], [t]);
|
|
|
|
const columns = useMemo(
|
|
() => resolveColumnTypes(_rawColumns, backendAttributes),
|
|
[_rawColumns, backendAttributes],
|
|
);
|
|
|
|
const _handleForceCancel = useCallback(async (row: any) => {
|
|
const ok = await confirm(
|
|
t('Subscription «{plan}» für Mandant «{mandate}» sofort kündigen? Dies wird auch auf Stripe sofort storniert.', { plan: row.planTitle, mandate: row.mandateName }),
|
|
{ confirmLabel: t('Sofort kündigen'), cancelLabel: t('Abbrechen'), variant: 'danger' },
|
|
);
|
|
if (!ok) return;
|
|
|
|
try {
|
|
await api.post('/api/subscription/force-cancel', { subscriptionId: row.id });
|
|
await refetch();
|
|
} catch (err) {
|
|
console.error('Force cancel failed:', err);
|
|
}
|
|
}, [confirm, refetch, t]);
|
|
|
|
return (
|
|
<div className={styles.billingDashboard} style={{ minHeight: 0 }}>
|
|
<header className={styles.pageHeader} style={{ flexShrink: 0 }}>
|
|
<h1>{t('Abonnementübersicht')}</h1>
|
|
<p className={styles.subtitle}>{t('Alle Abonnements aller Mandanten')}</p>
|
|
</header>
|
|
|
|
<div style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>
|
|
<FormGeneratorTable
|
|
data={subscriptions}
|
|
columns={columns}
|
|
apiEndpoint="/api/subscription/admin/all"
|
|
loading={loading}
|
|
pagination={true}
|
|
pageSize={50}
|
|
selectable={false}
|
|
hookData={{ refetch, pagination }}
|
|
customActions={[
|
|
{
|
|
id: 'forceCancel',
|
|
title: t('Sofort stornieren'),
|
|
icon: '✕',
|
|
onClick: (row: any) => _handleForceCancel(row),
|
|
visible: (row: any) => !_TERMINAL_STATUSES.has(row._rawStatus),
|
|
},
|
|
]}
|
|
emptyMessage={t('Keine Abonnements vorhanden')}
|
|
/>
|
|
</div>
|
|
|
|
<ConfirmDialog />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AdminSubscriptionsPage;
|