feat(approvals): UI für Freigaben — Review-Queue mit Approve/Reject (Modul 1/16)

Backend:
- Phantom-Permission-Bug gefixt: approvals:read/write/approve fehlten in
  CORE_PERMISSIONS (Rollen konnten sie nie zugewiesen bekommen — gleiche
  Fehlerklasse wie dashboard:read in M2)

Frontend:
- api/approvals.ts: TanStack Hooks (list/detail/approve/reject/expire/create)
- pages/Approvals.tsx: Review-Queue — Status-Tabs (Offen/Alle/Genehmigt/
  Abgelehnt/Abgelaufen), Karten mit Aktion/Entity/Requester/Metadata,
  Approve/Reject mit Kommentar-Modal, Permission-Gating (approvals:approve)
- Route /approvals (PermissionRoute approvals:read), Sidebar-Eintrag
- i18n approvals.* + nav.approvals (de/en)

Verifikation: Vitest 10/10 (Rendering, Tabs, Approve/Reject-Flow,
Kommentar, Permission-Gating, Resolved-Zustände), RBAC-Regression 102/102,
tsc clean, Build OK
This commit is contained in:
Agent Zero
2026-09-08 23:26:56 +02:00
parent 4eb05d96ad
commit ecc7a24c1c
8 changed files with 708 additions and 3 deletions
+2 -1
View File
@@ -3,7 +3,7 @@ import clsx from 'clsx';
import { NavLink, useLocation, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useUIStore } from '@/store/uiStore';
import { ChevronRight, FileText, Home, Settings, Users, ArrowLeft } from 'lucide-react';
import { ChevronRight, FileText, Home, Settings, Users, ArrowLeft , CheckCircle2 } from 'lucide-react';
import { usePluginStore } from '@/store/pluginStore';
// Curated icon map — avoids `import * as LucideIcons` which loads ALL icons and causes OOM in tests
import {
@@ -55,6 +55,7 @@ function getIcon(name: string): React.ReactNode {
const singleItems: NavSingleItem[] = [
{ to: '/dashboard', labelKey: 'nav.dashboard', icon: <Home className="w-5 h-5 flex-shrink-0" aria-hidden="true" strokeWidth={2} />, order: 0 },
{ to: '/system-dashboard', labelKey: 'nav.systemDashboard', icon: <Activity className="w-5 h-5 flex-shrink-0" aria-hidden="true" strokeWidth={2} />, order: 90 },
{ to: '/approvals', labelKey: 'nav.approvals', icon: <CheckCircle2 className="w-5 h-5 flex-shrink-0" aria-hidden="true" strokeWidth={2} />, order: 91 },
];
const bottomItems: NavSingleItem[] = [];