+
{t('Compliance & AI-Audit')}
+
+ {t('Transparente Übersicht aller AI-Datenflüsse und Sicherheitsereignisse Ihres Mandanten.')}
+
+
+ {/* Mandate selector */}
+
+
+
+
+
+ {!selectedMandateId ? (
+
{t('Bitte wählen Sie einen Mandanten aus.')}
+ ) : (
+ <>
+ {/* Tab bar */}
+
+ {_tabs.map(tab => (
+
+ ))}
+
+
+ {/* ── Tab A: AI Data-Flow Log ── */}
+ {activeTab === 'ai-log' && (
+
+ ,
+ onClick: _handleContentDownload,
+ },
+ ]}
+ />
+
+ )}
+
+ {/* ── Tab B: Audit Log ── */}
+ {activeTab === 'audit-log' && (
+
+
+
+ )}
+
+ {/* ── Tab C: Statistics ── */}
+ {activeTab === 'stats' && (
+
+
+ {[7, 30, 90].map(d => (
+
+ ))}
+
+
+ {statsLoading ? (
+
{t('Lade Statistiken…')}
+ ) : !stats ? (
+
{t('Keine Daten verfügbar.')}
+ ) : (
+ <>
+ {/* KPIs */}
+
+
+
{stats.totalCalls}
+
{t('AI-Aufrufe')}
+
+
+
{stats.neutralizationPercent}%
+
{t('Neutralisierungsquote')}
+
+
+
{Object.keys(stats.callsByModel).length}
+
{t('Genutzte Modelle')}
+
+
+
+ {stats.costPerDay.reduce((s, d) => s + d.cost, 0).toFixed(2)}
+
+
{t('Gesamtkosten (CHF)')}
+
+
+
+ {/* Charts row 1: Calls/Day + Cost/Day */}
+
+
+
{t('AI-Aufrufe pro Tag')}
+ {stats.callsPerDay.length === 0 ? (
+
{t('Keine Daten')}
+ ) : (
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
{t('Kosten-Verlauf (CHF)')}
+ {stats.costPerDay.length === 0 ? (
+
{t('Keine Daten')}
+ ) : (
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ {/* Charts row 2: By Model (pie) + By Feature (bar) */}
+
+
+
{t('AI-Aufrufe nach Modell')}
+ {Object.keys(stats.callsByModel).length === 0 ? (
+
{t('Keine Daten')}
+ ) : (
+
+
+ ({ name, value }))}
+ dataKey="value" nameKey="name"
+ cx="50%" cy="50%" outerRadius={80}
+ label={({ name, percent }) => `${name} ${((percent ?? 0) * 100).toFixed(0)}%`}
+ >
+ {Object.keys(stats.callsByModel).map((_, i) => (
+ |
+ ))}
+
+
+
+
+ )}
+
+
+
{t('AI-Aufrufe nach Feature')}
+ {Object.keys(stats.callsByFeature).length === 0 ? (
+
{t('Keine Daten')}
+ ) : (
+
+ ({ name, value }))}>
+
+
+
+
+
+
+
+ )}
+
+
+
+ {/* Top Users */}
+ {Object.keys(stats.topUsers).length > 0 && (
+
+
{t('Top-Nutzer nach AI-Aufrufen')}
+
+ ({ name, value }))}
+ layout="vertical" margin={{ left: 8, right: 16 }}
+ >
+
+
+
+
+
+
+
+
+ )}
+ >
+ )}
+
+ )}
+ >
+ )}
+
+ );
+};
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
index 4114702..e4c59db 100644
--- a/src/pages/Dashboard.tsx
+++ b/src/pages/Dashboard.tsx
@@ -85,11 +85,9 @@ export const DashboardPage: React.FC = () => {
- {t('Du hast Zugriff auf {instanceCount} {instanceWord} in {mandateCount} {mandateWord}.', {
- instanceCount: totalInstances,
- instanceWord: totalInstances === 1 ? t('Feature-Instanz') : t('Feature-Instanzen'),
- mandateCount: totalMandates,
- mandateWord: totalMandates === 1 ? t('Mandant') : t('Mandanten'),
+ {t('{instanceCount} Feature-Instanzen in {mandateCount} Mandanten', {
+ instanceCount: String(totalInstances),
+ mandateCount: String(totalMandates),
})}
)}
diff --git a/src/pages/IntegrationsOverviewPage.tsx b/src/pages/IntegrationsOverviewPage.tsx
index 184883b..1c4af13 100644
--- a/src/pages/IntegrationsOverviewPage.tsx
+++ b/src/pages/IntegrationsOverviewPage.tsx
@@ -191,7 +191,7 @@ export const IntegrationsOverviewPage: React.FC = () => {
return [
{ value: s.aiCallCount, label: t('AI-Aufrufe'), sub: `${s.aiCallPeriodDays} ${t('Tage')}` },
{ value: s.activeWorkflows, label: t('Aktive Workflows'), sub: s.totalWorkflows > 0 ? `${_formatStatNumber(s.totalWorkflows)} ${t('total')}` : undefined },
- { value: s.totalRuns, label: t('Workflow-Runs'), sub: s.totalTokens > 0 ? `${_formatStatNumber(s.totalTokens)} Tokens` : undefined },
+ { value: s.totalRuns, label: t('Workflow-Läufe'), sub: s.totalTokens > 0 ? `${_formatStatNumber(s.totalTokens)} ${t('Tokens')}` : undefined },
{ value: connectedSystems, label: t('Verbundene Systeme') },
];
}, [diagram, t]);
diff --git a/src/pages/Store.tsx b/src/pages/Store.tsx
index 03f07ff..1a36176 100644
--- a/src/pages/Store.tsx
+++ b/src/pages/Store.tsx
@@ -153,7 +153,7 @@ const StorePage: React.FC = () => {
)}
{subscriptionInfo.budgetAiPerUserCHF != null && subscriptionInfo.budgetAiPerUserCHF > 0 && (
-
{t('Demo Configurations')}
-
{t('Load or remove demo environments for presentations and testing.')}
+
{t('Demo-Konfigurationen')}
+
{t('Demo-Umgebungen für Präsentationen und Tests laden oder entfernen.')}
@@ -104,7 +104,7 @@ export const AdminDemoConfigPage: React.FC = () => {
{lastResult && (
-
{lastResult.action === 'load' ? t('Loaded') : t('Removed')}:{' '}
+
{lastResult.action === 'load' ? t('Geladen') : t('Entfernt')}:{' '}
{lastResult.status === 'ok' ? (
<_SummaryDisplay summary={lastResult.summary} />
) : (
@@ -114,9 +114,9 @@ export const AdminDemoConfigPage: React.FC = () => {
)}
{loading && configs.length === 0 ? (
-
{t('Loading...')}
+
{t('Lade…')}
) : configs.length === 0 ? (
-
{t('No demo configurations found.')}
+
{t('Keine Demo-Konfigurationen gefunden.')}
) : (
{configs.map((cfg) => (
@@ -134,7 +134,7 @@ export const AdminDemoConfigPage: React.FC = () => {
disabled={actionInProgress !== null}
>
{actionInProgress === cfg.code ? : }
- {t('Load')}
+ {t('Laden')}
@@ -155,10 +155,11 @@ export const AdminDemoConfigPage: React.FC = () => {
);
};
-function _SummaryDisplay({ summary }: { summary?: Record
}) {
+const _SummaryDisplay: React.FC<{ summary?: Record }> = ({ summary }) => {
+ const { t } = useLanguage();
if (!summary) return null;
const sections = Object.entries(summary).filter(([, v]) => Array.isArray(v) && (v as unknown[]).length > 0);
- if (sections.length === 0) return Done (no changes);
+ if (sections.length === 0) return {t('Abgeschlossen (keine Änderungen)')};
return (
{sections.map(([key, items]) => (
@@ -168,4 +169,4 @@ function _SummaryDisplay({ summary }: { summary?: Record }) {
))}
);
-}
+};
diff --git a/src/pages/billing/BillingDataView.tsx b/src/pages/billing/BillingDataView.tsx
index bc4f017..f03d9a9 100644
--- a/src/pages/billing/BillingDataView.tsx
+++ b/src/pages/billing/BillingDataView.tsx
@@ -274,7 +274,7 @@ export const BillingDataView: React.FC = () => {
try {
await api.post('/api/billing/checkout/confirm', { sessionId: sessionIdParam });
if (!cancelled) {
- setCheckoutMessage({ type: 'success', text: 'Zahlung erfolgreich. Guthaben wurde verbucht.' });
+ setCheckoutMessage({ type: 'success', text: t('Zahlung erfolgreich. Guthaben wurde verbucht.') });
}
} catch (err: any) {
const detail = err?.response?.data?.detail;