From 40fe8a0a31bf6514c8ef35e257e452c802e0a328 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Sun, 15 Feb 2026 10:44:25 +0100 Subject: [PATCH] feat(billing): scope filter, user balance cards, CSV export fix - Scope dropdown (Meine Kosten / Mandant / Alle) on Overview and Statistics tabs - All user balance cards shown in Overview when scope is mandate or all - Balance cards and statistics react to scope selection - CSV export endpoint fixed: /api/billing/view/users/transactions - Scope selector hidden on Transactions tab (FormGeneratorTable handles own filters) - Fixed unused VoiceLanguage import in TeamsbotSettingsView Co-authored-by: Cursor --- src/pages/billing/BillingDataView.tsx | 172 ++++++++++++++---- .../views/teamsbot/TeamsbotSettingsView.tsx | 2 +- 2 files changed, 139 insertions(+), 35 deletions(-) diff --git a/src/pages/billing/BillingDataView.tsx b/src/pages/billing/BillingDataView.tsx index 3eda4c2..84eb9ab 100644 --- a/src/pages/billing/BillingDataView.tsx +++ b/src/pages/billing/BillingDataView.tsx @@ -266,11 +266,18 @@ function _buildStatisticsSections(viewStats: ViewStatistics): ReportSection[] { export const BillingDataView: React.FC = () => { const [activeTab, setActiveTab] = useState('overview'); + // Scope filter: 'personal' | 'all' | mandateId + const [selectedScope, setSelectedScope] = useState('personal'); + // Dashboard state (for Overview tab) const { balances, loading: dashboardLoading, } = useBilling(); + + // All user balances (for admin overview cards) + const [allUserBalances, setAllUserBalances] = useState([]); + const [allUserBalancesLoading, setAllUserBalancesLoading] = useState(false); // Statistics state (shared by Overview and Statistics tabs) const [viewStats, setViewStats] = useState(null); @@ -282,6 +289,19 @@ export const BillingDataView: React.FC = () => { const [transactionsError, setTransactionsError] = useState(null); const [transactionsPagination, setTransactionsPagination] = useState(null); + // Load all user balances for admin overview + const _loadAllUserBalances = useCallback(async () => { + try { + setAllUserBalancesLoading(true); + const response = await api.get('/api/billing/view/users/balances'); + setAllUserBalances(Array.isArray(response.data) ? response.data : []); + } catch { + setAllUserBalances([]); + } finally { + setAllUserBalancesLoading(false); + } + }, []); + // Load aggregated statistics from the view/statistics route const _loadViewStatistics = useCallback(async (period: string, year: number, month?: number) => { try { @@ -290,6 +310,15 @@ export const BillingDataView: React.FC = () => { if (period === 'day' && month) { params.month = month; } + // Apply scope filter + if (selectedScope === 'personal') { + params.scope = 'personal'; + } else if (selectedScope !== 'all') { + params.scope = 'mandate'; + params.mandateId = selectedScope; + } else { + params.scope = 'all'; + } const response = await api.get('/api/billing/view/statistics', { params }); setViewStats(response.data); } catch (err: any) { @@ -298,7 +327,7 @@ export const BillingDataView: React.FC = () => { } finally { setStatsLoading(false); } - }, []); + }, [selectedScope]); // Handle filter changes from FormGeneratorReport (user changes period/year/month) const _handleStatsFilterChange = useCallback((filterState: ReportFilterState) => { @@ -313,7 +342,10 @@ export const BillingDataView: React.FC = () => { if (activeTab === 'overview' || activeTab === 'statistics') { _loadViewStatistics('month', new Date().getFullYear()); } - }, [activeTab, _loadViewStatistics]); + if (activeTab === 'overview') { + _loadAllUserBalances(); + } + }, [activeTab, _loadViewStatistics, _loadAllUserBalances, selectedScope]); // Load transactions with pagination support const _loadTransactions = useCallback(async (paginationParams?: any) => { @@ -399,11 +431,46 @@ export const BillingDataView: React.FC = () => { defaultMonth: new Date().getMonth() + 1 }), []); + // Build scope options from balances (mandates the user has access to) + const scopeOptions = useMemo(() => { + const options: Array<{ value: string; label: string }> = [ + { value: 'personal', label: 'Meine Kosten' }, + ]; + // Add mandate options from balances + const seen = new Set(); + for (const b of balances) { + if (!seen.has(b.mandateId)) { + seen.add(b.mandateId); + options.push({ value: b.mandateId, label: `Mandant: ${b.mandateName}` }); + } + } + options.push({ value: 'all', label: 'Alle (RBAC)' }); + return options; + }, [balances]); + return (
-

Billing

-

Guthaben, Statistiken und Transaktionen

+
+
+

Billing

+

Guthaben, Statistiken und Transaktionen

+
+ {activeTab !== 'transactions' && ( +
+ + +
+ )} +
@@ -411,36 +478,73 @@ export const BillingDataView: React.FC = () => { {/* ================================================================ */} {/* Tab: Übersicht (My Overview) */} {/* ================================================================ */} - {activeTab === 'overview' && ( - <> - {/* Balance Cards */} -
-

Mein Guthaben

- {dashboardLoading ? ( -
Lade Guthaben...
- ) : balances.length === 0 ? ( -
Keine Abrechnungskonten vorhanden
- ) : ( -
- {balances.map((balance) => ( - - ))} -
- )} -
+ {activeTab === 'overview' && (() => { + // Filter balances and user accounts by scope + const filteredBalances = selectedScope === 'personal' || selectedScope === 'all' + ? balances + : balances.filter(b => b.mandateId === selectedScope); + + const filteredUserBalances = selectedScope === 'personal' + ? [] // personal view: only own balance cards, no other users + : selectedScope === 'all' + ? allUserBalances + : allUserBalances.filter(ub => ub.mandateId === selectedScope); - {/* Usage Statistics via FormGeneratorReport (no period selector - always full year) */} -
- -
- - )} + return ( + <> + {/* Balance Cards - own balances */} +
+

Mein Guthaben

+ {dashboardLoading ? ( +
Lade Guthaben...
+ ) : filteredBalances.length === 0 ? ( +
Keine Abrechnungskonten vorhanden
+ ) : ( +
+ {filteredBalances.map((balance) => ( + + ))} +
+ )} +
+ + {/* All User Balance Cards (mandate/all scope) */} + {filteredUserBalances.length > 0 && ( +
+

Benutzer-Guthaben

+ {allUserBalancesLoading ? ( +
Lade Benutzer-Guthaben...
+ ) : ( +
+ {filteredUserBalances.map((ub, idx) => ( +
+
+

{ub.userName || ub.userId?.slice(0, 8)}

+ {ub.mandateName} +
+
+ {_formatCurrency(ub.balance || 0)} +
+
+ ))} +
+ )} +
+ )} + + {/* Usage Statistics via FormGeneratorReport */} +
+ +
+ + ); + })()} {/* ================================================================ */} {/* Tab: Statistik (Dashboard) */} @@ -474,7 +578,7 @@ export const BillingDataView: React.FC = () => {