= ({
// Summary icon for button
const summaryIcon = useMemo(() => {
- if (selectedProviders.length === 0 || selectedProviders.length === allowedProviders.length) {
- return '🤖';
- }
if (selectedProviders.length === 1) {
return PROVIDER_ICONS[selectedProviders[0]] || '🔌';
}
return '🤖';
- }, [selectedProviders, allowedProviders]);
+ }, [selectedProviders]);
return (
= ({
type="button"
onClick={handleSelectNone}
disabled={disabled}
- className={styles.actionButton}
+ className={`${styles.actionButton} ${isNoneSelected ? styles.active : ''}`}
>
Keine
@@ -225,7 +220,7 @@ export const ProviderMultiSelect: React.FC
= ({
>
handleToggle(provider)}
disabled={disabled}
/>
diff --git a/src/pages/billing/BillingAdmin.tsx b/src/pages/billing/BillingAdmin.tsx
index 8efb3cf..8a27eda 100644
--- a/src/pages/billing/BillingAdmin.tsx
+++ b/src/pages/billing/BillingAdmin.tsx
@@ -268,7 +268,7 @@ const CreditAdder: React.FC = ({ settings, accounts, users, on
const balanceInfo = account ? ` (${formatCurrency(account.balance)})` : ' (kein Konto)';
return (
);
})}
@@ -339,7 +339,7 @@ const AccountsOverview: React.FC = ({ accounts, users, lo
for (const user of users) {
const displayName = user.displayName
|| [user.firstName, user.lastName].filter(Boolean).join(' ')
- || user.email
+ || user.username
|| user.id;
map.set(user.id, displayName);
}
diff --git a/src/pages/billing/BillingDashboard.tsx b/src/pages/billing/BillingDashboard.tsx
index 4e3f963..8e73efb 100644
--- a/src/pages/billing/BillingDashboard.tsx
+++ b/src/pages/billing/BillingDashboard.tsx
@@ -114,6 +114,28 @@ const StatisticsChart: React.FC = ({ statistics, loading }
)}
+
+
Kosten nach Modell
+ {Object.entries(statistics.costByModel || {}).length === 0 ? (
+
Keine Daten
+ ) : (
+
+ {Object.entries(statistics.costByModel || {}).map(([model, cost]) => (
+
+
{model}
+
+
{formatCurrency(cost)}
+
+ ))}
+
+ )}
+
+
Kosten nach Feature
{Object.entries(statistics.costByFeature).length === 0 ? (
diff --git a/src/pages/billing/BillingDataView.tsx b/src/pages/billing/BillingDataView.tsx
index f11360d..504bedc 100644
--- a/src/pages/billing/BillingDataView.tsx
+++ b/src/pages/billing/BillingDataView.tsx
@@ -35,6 +35,7 @@ interface ViewStatistics {
totalCost: number;
transactionCount: number;
costByProvider: Record;
+ costByModel: Record;
costByFeature: Record;
costByMandate: Record;
timeSeries: Array<{ date: string; cost: number; count: number }>;
@@ -134,6 +135,7 @@ function _recordToChartData(record: Record): ReportChartDataPoin
function _buildOverviewSections(viewStats: ViewStatistics): ReportSection[] {
const topProvider = Object.entries(viewStats.costByProvider).sort((a, b) => b[1] - a[1])[0];
+ const topModel = Object.entries(viewStats.costByModel || {}).sort((a, b) => b[1] - a[1])[0];
const topFeature = Object.entries(viewStats.costByFeature).sort((a, b) => b[1] - a[1])[0];
return [
@@ -150,15 +152,15 @@ function _buildOverviewSections(viewStats: ViewStatistics): ReportSection[] {
value: Object.keys(viewStats.costByProvider).length,
subtitle: topProvider ? `Top: ${topProvider[0]}` : 'Keine Nutzung'
},
+ {
+ label: 'Modelle',
+ value: Object.keys(viewStats.costByModel || {}).length,
+ subtitle: topModel ? `Top: ${topModel[0]}` : 'Keine Nutzung'
+ },
{
label: 'Features',
value: Object.keys(viewStats.costByFeature).length,
subtitle: topFeature ? `Top: ${topFeature[0]}` : 'Keine Nutzung'
- },
- {
- label: 'Mandanten',
- value: Object.keys(viewStats.costByMandate).length,
- subtitle: 'aktiv genutzt'
}
]
},
@@ -169,6 +171,13 @@ function _buildOverviewSections(viewStats: ViewStatistics): ReportSection[] {
formatValue: _formatCurrency,
span: 'half' as const
},
+ {
+ type: 'horizontalBar',
+ title: 'Kosten nach Modell',
+ data: _recordToChartData(viewStats.costByModel || {}),
+ formatValue: _formatCurrency,
+ span: 'half' as const
+ },
{
type: 'horizontalBar',
title: 'Kosten nach Feature',
@@ -206,6 +215,14 @@ function _buildStatisticsSections(viewStats: ViewStatistics): ReportSection[] {
donut: true,
span: 'half' as const
},
+ {
+ type: 'pieChart',
+ title: 'Verteilung nach Modell',
+ data: _recordToChartData(viewStats.costByModel || {}),
+ formatValue: _formatCurrency,
+ donut: true,
+ span: 'half' as const
+ },
{
type: 'pieChart',
title: 'Verteilung nach Feature',
@@ -234,6 +251,7 @@ function _buildStatisticsSections(viewStats: ViewStatistics): ReportSection[] {
{ metric: 'Transaktionen', value: String(viewStats.transactionCount) },
{ metric: 'Durchschnitt / Transaktion', value: _formatCurrency(avgCost) },
{ metric: 'Anbieter', value: String(Object.keys(viewStats.costByProvider).length) },
+ { metric: 'Modelle', value: String(Object.keys(viewStats.costByModel || {}).length) },
{ metric: 'Features', value: String(Object.keys(viewStats.costByFeature).length) },
{ metric: 'Mandanten', value: String(Object.keys(viewStats.costByMandate).length) }
]
@@ -304,8 +322,10 @@ export const BillingDataView: React.FC = () => {
setTransactionsError(null);
const params: any = {};
- if (paginationParams) {
- params.pagination = JSON.stringify(paginationParams);
+ // Only serialize if it's a plain pagination object (not a React event or other non-serializable object)
+ if (paginationParams && typeof paginationParams === 'object' && 'page' in paginationParams) {
+ const { page, pageSize, sortBy, sortDirection, search, filters } = paginationParams;
+ params.pagination = JSON.stringify({ page, pageSize, sortBy, sortDirection, search, filters });
}
const response = await api.get('/api/billing/view/users/transactions', { params });
@@ -353,6 +373,7 @@ export const BillingDataView: React.FC = () => {
{ key: 'transactionType', label: 'Typ', type: 'text' as any, sortable: true, filterable: true, width: 100 },
{ key: 'description', label: 'Beschreibung', type: 'text' as any, searchable: true, width: 250 },
{ key: 'aicoreProvider', label: 'Anbieter', type: 'text' as any, sortable: true, filterable: true, width: 120 },
+ { key: 'aicoreModel', label: 'Modell', type: 'text' as any, sortable: true, filterable: true, width: 150 },
{ key: 'featureCode', label: 'Feature', type: 'text' as any, sortable: true, filterable: true, width: 120 },
{ key: 'amount', label: 'Betrag (CHF)', type: 'number' as any, sortable: true, width: 120 },
], []);
diff --git a/src/pages/billing/BillingMandateView.tsx b/src/pages/billing/BillingMandateView.tsx
index 82d022f..f6792c6 100644
--- a/src/pages/billing/BillingMandateView.tsx
+++ b/src/pages/billing/BillingMandateView.tsx
@@ -146,6 +146,7 @@ const TransactionTable: React.FC = ({ transactions }) =>
Typ |
Beschreibung |
Anbieter |
+ Modell |
Feature |
Betrag |
@@ -162,6 +163,7 @@ const TransactionTable: React.FC = ({ transactions }) =>
{t.description} |
{t.aicoreProvider || '-'} |
+ {t.aicoreModel || '-'} |
{t.featureCode || '-'} |
{t.transactionType === 'DEBIT' ? '-' : '+'}{formatCurrency(t.amount)}
diff --git a/src/pages/billing/BillingTransactions.tsx b/src/pages/billing/BillingTransactions.tsx
index ddd7f2c..d64bde7 100644
--- a/src/pages/billing/BillingTransactions.tsx
+++ b/src/pages/billing/BillingTransactions.tsx
@@ -65,6 +65,7 @@ const TransactionRow: React.FC = ({ transaction }) => {
|
{transaction.description} |
{transaction.aicoreProvider || '-'} |
+ {transaction.aicoreModel || '-'} |
{transaction.featureCode || '-'} |
{transaction.transactionType === 'DEBIT' ? '-' : '+'}{formatCurrency(transaction.amount)}
@@ -114,6 +115,7 @@ export const BillingTransactions: React.FC = () => {
| Typ |
Beschreibung |
Anbieter |
+ Modell |
Feature |
Betrag |
diff --git a/src/pages/billing/BillingUserView.tsx b/src/pages/billing/BillingUserView.tsx
index 194be7d..4587acc 100644
--- a/src/pages/billing/BillingUserView.tsx
+++ b/src/pages/billing/BillingUserView.tsx
@@ -228,6 +228,7 @@ const UserTransactionTable: React.FC = ({
Typ |
Beschreibung |
Anbieter |
+ Modell |
Feature |
Betrag |
@@ -245,6 +246,7 @@ const UserTransactionTable: React.FC = ({
{t.description} |
{t.aicoreProvider || '-'} |
+ {t.aicoreModel || '-'} |
{t.featureCode || '-'} |
{t.transactionType === 'DEBIT' ? '-' : '+'}{formatCurrency(t.amount)}
|