Fundament: ISMS-Module entfernt; Craftvia-Rollen, Module, Navigation, i18n-Split

- ISMS-Routen, Actions, Server-/Lib-Code, Komponenten, Prisma-Modelle, Seeds,
  Importer, Skripte und ISMS-Tests entfernt (Fundament bleibt: Auth, Identity,
  MFA/WebAuthn, RBAC, Audit, Mail, Storage, Backup/DSGVO, Plattform-Admin)
- Schema auf Fundament-Modelle reduziert; TenantSettings generisch (+phone/email)
- TENANT_MODELS (db.ts, backup/topology.ts) und PII-Felder ausgedünnt
- RBAC: Rollen tenant-admin/backoffice/team-lead/technician + Craftvia-Permissions
- Modul-Katalog (customers, sites, teams, work_orders, imports, field, reports,
  emergency, documents, notifications, lotse) + Navigation aus src/lib/nav.ts
- Modul-Routen mit requireModule-Layout und Platzhalterseite
- Message-Katalog je Namespace (messages/<locale>/<namespace>.json), fs-Loader
- check-module-guards: Modul-Key aus src/server/actions/<moduleKey>/
- Provisionierung, Admin-Konsole, Einstellungen, Files-Route, Mail entkoppelt
- Seed minimal (demo/demo2, Nutzer je Rolle); Fundament-Tests auf Role/
  NotificationPreference-Fixtures umgestellt

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 11:35:44 +02:00
co-authored by Claude Opus 5
parent c8e6f30a27
commit 8491c7f173
443 changed files with 1325 additions and 87773 deletions
+65
View File
@@ -0,0 +1,65 @@
import {
LayoutDashboard,
ClipboardList,
FileInput,
Users,
Building2,
UsersRound,
FileText,
FolderOpen,
Settings,
type LucideIcon,
} from "lucide-react";
import type { ModuleKey } from "@/lib/modules";
import type { Permission } from "@/server/rbac";
/**
* Sidebar-Navigation des Backoffice (src/app/(app)/layout.tsx).
*
* Ein Eintrag ist sichtbar, wenn
* - sein Modul (falls gesetzt) für den Mandanten aktiv ist UND
* - die Session mindestens EINE der `permissions` hat (leer = immer).
* Das ist reiner Komfort — Seiten und Actions prüfen Modul + Rechte serverseitig selbst.
*
* Neue Backoffice-Seite ⇒ hier eintragen; `label` ist ein Schlüssel in messages nav.*.
* Mobile-Einträge (/m, /m/emergency) gehören NICHT hierher (eigene Mobile-Navigation).
*/
export interface NavItem {
href: string;
label: string;
icon: LucideIcon;
module?: ModuleKey;
permissions?: readonly Permission[];
section: "main" | "admin";
}
export const NAV_ITEMS: readonly NavItem[] = [
{ href: "/dashboard", label: "dashboard", icon: LayoutDashboard, section: "main" },
{
href: "/work-orders",
label: "workOrders",
icon: ClipboardList,
module: "work_orders",
permissions: ["work_order:read_all", "work_order:read_team"],
section: "main",
},
{ href: "/imports", label: "imports", icon: FileInput, module: "imports", permissions: ["import:write"], section: "main" },
{ href: "/customers", label: "customers", icon: Users, module: "customers", permissions: ["customer:read"], section: "main" },
{ href: "/sites", label: "sites", icon: Building2, module: "sites", permissions: ["site:read"], section: "main" },
{ href: "/teams", label: "teams", icon: UsersRound, module: "teams", permissions: ["team:read"], section: "main" },
{ href: "/reports", label: "reports", icon: FileText, module: "reports", permissions: ["report:read"], section: "main" },
{ href: "/documents", label: "documents", icon: FolderOpen, module: "documents", permissions: ["document:read"], section: "main" },
{ href: "/settings", label: "settings", icon: Settings, permissions: ["tenant:manage"], section: "admin" },
];
/** Filtert die Navigation nach aktiven Modulen und Rechten der Session. */
export function visibleNavItems(
items: readonly NavItem[],
opts: { disabledModules: ReadonlySet<string>; permissions: readonly string[] },
): NavItem[] {
return items.filter(
(item) =>
(!item.module || !opts.disabledModules.has(item.module)) &&
(!item.permissions?.length || item.permissions.some((p) => opts.permissions.includes(p))),
);
}