feat(tenants): UI fuer Mandanten-Verwaltung — Modul 4/16 des UI-Backlogs

Backend existierte vollstaendig (list, create, list users, assign user;
tenants:read/write), Frontend hatte 0% Abdeckung.

- api/tenants.ts: TanStack-Hooks (useTenants, useTenantUsers mit
  enabled-Gating, create, assignUser mit Cache-Invalidierung)
- pages/Tenants.tsx: Tenant-Karten (Name, Slug, Standard-Badge),
  expandierbare User-Liste pro Tenant (Rolle, E-Mail), Create-Dialog
  (Name + Slug mit Auto-Normalisierung), Assign-User-Dialog mit
  User-Picker (bereits zugewiesene gefiltert) — Create/Users/Assign
  hinter tenants:write gegated
- Platzierung: Settings-Subpage /settings/tenants (statisch, Core-Route)
  + Settings-Nav-Item
- i18n tenants.* de/en

Verifikation: Vitest 8/8 (Rendering, Standard-Badge, Expand-User-Liste,
Create-Flow, Assign-Flow, Permission-Gating, Empty/Error) · tsc exit 0 ·
production build exit 0.
This commit is contained in:
Agent Zero
2026-09-13 09:29:42 +02:00
parent 8d8beebd38
commit 79ca1cbe6d
7 changed files with 655 additions and 0 deletions
+2
View File
@@ -46,6 +46,7 @@ const ActivityTimelinePage = React.lazy(() => import('@/pages/ActivityTimeline')
const ApprovalsPage = React.lazy(() => import('@/pages/Approvals').then(m => ({ default: m.ApprovalsPage })));
const DelegationsPage = React.lazy(() => import('@/pages/Delegations').then(m => ({ default: m.DelegationsPage })));
const ApiTokensPage = React.lazy(() => import('@/pages/ApiTokens').then(m => ({ default: m.ApiTokensPage })));
const TenantsPage = React.lazy(() => import('@/pages/Tenants').then(m => ({ default: m.TenantsPage })));
const SettingsWebhooksPage = React.lazy(() => import('@/pages/SettingsWebhooks').then(m => ({ default: m.SettingsWebhooksPage })));
const SettingsBackupPage = React.lazy(() => import('@/pages/SettingsBackup').then(m => ({ default: m.SettingsBackupPage })));
const WorkspaceManagerPage = React.lazy(() => import('@/pages/SettingsWorkspaces').then(m => ({ default: m.WorkspaceManagerPage })));
@@ -206,6 +207,7 @@ const router = createBrowserRouter([
{ path: 'workspaces', element: withSuspense(<WorkspaceManagerPage />) },
{ path: 'backup', element: withSuspense(<SettingsBackupPage />) },
{ path: 'api-tokens', element: withSuspense(<ApiTokensPage />) },
{ path: 'tenants', element: withSuspense(<TenantsPage />) },
{ path: 'rechte', element: <PermissionRoute permission="settings:read">{withSuspense(<SettingsRechtePage />)}</PermissionRoute> },
// Phase Q2: plugin settings sub-pages render with bare sub-segments
{ path: '*', element: <ErrorBoundary>{<PluginRouteRenderer variant="settings" />}</ErrorBoundary> },