Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { requireSession } from "@/server/auth";
|
||||
import { dbForTenant } from "@/server/db";
|
||||
import { hasPermission, PERMISSIONS, ROLE_DEFS } from "@/server/rbac";
|
||||
import { createUser, updateUser, setUserRoles, setUserStatus } from "@/server/actions/tenant-users";
|
||||
import { PageHead } from "@/components/mockup-ui";
|
||||
import { Modal } from "@/components/modal";
|
||||
import { UserTable } from "@/components/user-table";
|
||||
import { UserCreateForm, UserEditForm } from "@/components/user-forms";
|
||||
import { RoleManager, CreateRoleForm } from "@/components/role-manager";
|
||||
|
||||
/**
|
||||
* Benutzer- & Rollenverwaltung im Mandanten (Paket B). Sichtbar nur mit user:manage
|
||||
* bzw. role:manage; strikt auf den eigenen Mandanten begrenzt (dbForTenant).
|
||||
* Benutzer werden über Popups (?new/?edit) angelegt/bearbeitet, die Tabelle zeigt nur.
|
||||
*/
|
||||
export default async function UsersRolesPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ new?: string; edit?: string; newRole?: string }>;
|
||||
}) {
|
||||
const session = await requireSession();
|
||||
const canUsers = hasPermission(session, "user:manage");
|
||||
const canRoles = hasPermission(session, "role:manage");
|
||||
if (!canUsers && !canRoles) redirect("/dashboard");
|
||||
const db = dbForTenant(session.user.tenantId);
|
||||
const sp = await searchParams;
|
||||
|
||||
const [users, roles] = await Promise.all([
|
||||
db.user.findMany({
|
||||
select: { id: true, name: true, email: true, status: true, userRoles: { select: { role: { select: { id: true, name: true } } } } },
|
||||
orderBy: { createdAt: "asc" },
|
||||
}),
|
||||
db.role.findMany({
|
||||
include: { rolePermissions: { include: { permission: true } }, _count: { select: { userRoles: true } } },
|
||||
orderBy: { name: "asc" },
|
||||
}),
|
||||
]);
|
||||
|
||||
const standardKeys = new Set(Object.keys(ROLE_DEFS));
|
||||
const roleOptions = roles.map((r) => ({ id: r.id, name: r.name }));
|
||||
const tableUsers = users.map((u) => ({
|
||||
id: u.id, name: u.name, email: u.email, status: u.status,
|
||||
roleNames: u.userRoles.map((ur) => ur.role.name), isSelf: u.id === session.user.id,
|
||||
}));
|
||||
const managedRoles = roles.map((r) => ({
|
||||
id: r.id, key: r.key, name: r.name, isStandard: standardKeys.has(r.key),
|
||||
permKeys: r.rolePermissions.map((rp) => rp.permission.key), userCount: r._count.userRoles,
|
||||
}));
|
||||
|
||||
const editUser = sp.edit ? users.find((u) => u.id === sp.edit) : null;
|
||||
const base = "/settings/users";
|
||||
|
||||
return (
|
||||
<main className="flex-1 p-6">
|
||||
<Link href="/settings" className="inline-flex items-center gap-1.5 text-[12.5px] font-semibold text-muted-foreground hover:text-foreground">
|
||||
<ArrowLeft className="size-4" /> Zurück zu Einstellungen
|
||||
</Link>
|
||||
<div className="mt-3">
|
||||
<PageHead crumb="Organisation" title="Benutzer & Rollen" sub="Benutzer anlegen und verwalten, Rollen und Berechtigungen pflegen — nur für den eigenen Mandanten." />
|
||||
</div>
|
||||
|
||||
{canUsers && (
|
||||
<section className="mt-5">
|
||||
<h2 className="mb-3 font-heading text-sm font-semibold">Benutzer</h2>
|
||||
<UserTable users={tableUsers} newHref={`${base}?new=1`} editHref={(id) => `${base}?edit=${id}`} />
|
||||
</section>
|
||||
)}
|
||||
|
||||
{canRoles && (
|
||||
<section className="mt-8">
|
||||
<h2 className="mb-3 font-heading text-sm font-semibold">Rollen & Berechtigungen</h2>
|
||||
<p className="mb-3 text-[12.5px] text-muted-foreground">Rolle anklicken, um ihre Berechtigungen zu sehen und (bei eigenen Rollen) zu bearbeiten.</p>
|
||||
<RoleManager roles={managedRoles} allPermissions={[...PERMISSIONS]} newRoleHref={`${base}?newRole=1`} />
|
||||
</section>
|
||||
)}
|
||||
|
||||
{canUsers && sp.new && (
|
||||
<Modal title="Benutzer anlegen" sub="Neuen Nutzer mit Rollen und Initialpasswort anlegen" closeHref={base} closeLabel="Schließen">
|
||||
<UserCreateForm action={createUser} roles={roleOptions} closeHref={base} />
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{canRoles && sp.newRole && (
|
||||
<Modal title="Eigene Rolle anlegen" sub="Rollenname und Berechtigungen festlegen (mandantenintern)" closeHref={base} closeLabel="Schließen">
|
||||
<div className="p-5">
|
||||
<CreateRoleForm allPermissions={[...PERMISSIONS]} closeHref={base} />
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{canUsers && editUser && (
|
||||
<Modal title={`Benutzer bearbeiten — ${editUser.name}`} sub={editUser.email} closeHref={base} closeLabel="Schließen">
|
||||
<UserEditForm
|
||||
user={{
|
||||
id: editUser.id, name: editUser.name, email: editUser.email, status: editUser.status,
|
||||
roleIds: editUser.userRoles.map((ur) => ur.role.id), isSelf: editUser.id === session.user.id,
|
||||
}}
|
||||
roles={roleOptions}
|
||||
updateAction={updateUser.bind(null, editUser.id)}
|
||||
rolesAction={setUserRoles.bind(null, editUser.id)}
|
||||
statusAction={setUserStatus.bind(null, editUser.id, editUser.status === "ACTIVE" ? "DEACTIVATED" : "ACTIVE")}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user