Files
craftvia/src/app/(app)/onboarding/steps/roles/role-edit-dialog.tsx
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
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>
2026-09-14 11:05:39 +02:00

180 lines
6.2 KiB
TypeScript

"use client";
import { UserPlus, UserMinus, Mail, CircleSlash, Pencil } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Pill } from "@/components/mockup-ui";
import {
assignFunction,
unassignFunction,
markFunctionUnfilled,
inviteFunctionHolder,
} from "@/server/actions/onboarding-team";
import { Popup } from "./popup";
export interface HolderView {
id: string;
userName: string | null;
userEmail: string | null;
invitedEmail: string | null;
}
export interface FunctionCardData {
key: string;
label: string;
desc: string;
domain: string | null;
multi: boolean;
soll: string | null;
holders: HolderView[];
}
/**
* Bearbeiten einer ISMS-Funktion im Popup — Logik wie beim Risiken-Popup, nutzt die
* bestehenden M1-Server-Actions (`assignFunction`, `unassignFunction`,
* `markFunctionUnfilled`, `inviteFunctionHolder`). Auslöser ist der „Bearbeiten“-Button
* auf der Funktionskarte (siehe step.tsx).
*/
export function FunctionEditDialog({
fn,
users,
canUse,
canInvite,
}: {
fn: FunctionCardData;
users: { id: string; name: string; email: string }[];
canUse: boolean;
canInvite: boolean;
}) {
const filled = fn.holders.length > 0;
const canAssignMore = canUse && (fn.multi || !filled);
return (
<Popup
triggerLabel={canUse ? "Bearbeiten" : "Ansehen"}
triggerIcon={<Pencil className="size-3.5" />}
triggerVariant="outline"
triggerSize="sm"
title={fn.label}
sub={fn.domain ? `Bereich: ${fn.domain}` : undefined}
>
{() => (
<div className="space-y-4 p-5">
<div className="rounded-xl border border-l-[3px] border-l-[var(--primary)] bg-[var(--surface-soft)] p-4">
<p className="text-[12.5px] leading-relaxed">{fn.desc}</p>
{fn.soll && (
<p className="mt-2 text-[11.5px] text-muted-foreground">
Soll (zentral): <b>{fn.soll}</b>
</p>
)}
<div className="mt-2">
{filled ? (
<Pill tone="ok">besetzt{fn.multi ? ` (${fn.holders.length})` : ""}</Pill>
) : (
<Pill tone="warn">unbesetzt</Pill>
)}
</div>
</div>
{/* Aktuelle Halter */}
<div>
<p className="text-[12.5px] font-medium">Aktuelle Besetzung</p>
{filled ? (
<ul className="mt-1.5 space-y-1">
{fn.holders.map((h) => (
<li
key={h.id}
className="flex items-center justify-between gap-2 rounded-lg border bg-background px-3 py-2 text-[12.5px]"
>
<span>
<b>{h.userName ?? h.invitedEmail ?? "—"}</b>
{h.userEmail && (
<span className="ml-1 text-[11px] text-muted-foreground">{h.userEmail}</span>
)}
{h.invitedEmail && !h.userName && (
<span className="ml-1 text-[11px] text-[var(--info)]">eingeladen</span>
)}
</span>
{canUse && (
<form action={unassignFunction.bind(null, h.id)}>
<Button type="submit" size="sm" variant="ghost" className="h-7 px-2 text-[11.5px]">
<UserMinus className="size-3.5" /> entfernen
</Button>
</form>
)}
</li>
))}
</ul>
) : (
<p className="mt-1 text-[12px] text-muted-foreground">Noch niemand zugewiesen.</p>
)}
</div>
{/* Zuweisen / Einladen / unbesetzt lassen */}
{canAssignMore && (
<div className="space-y-3 border-t pt-3">
<form action={assignFunction.bind(null, fn.key)} className="flex items-center gap-1.5">
<select
name="userId"
required
defaultValue=""
className="h-8 flex-1 rounded-md border border-input bg-background px-2 text-[12.5px]"
>
<option value="" disabled>
Account auswählen …
</option>
{users.map((u) => (
<option key={u.id} value={u.id}>
{u.name} ({u.email})
</option>
))}
</select>
<Button type="submit" size="sm" variant="outline" className="h-8">
<UserPlus className="size-3.5" /> zuweisen
</Button>
</form>
<form action={inviteFunctionHolder.bind(null, fn.key)} className="flex items-center gap-1.5">
<Input
name="email"
type="email"
required
placeholder="E-Mail einladen"
className="h-8 flex-1 text-[12.5px]"
disabled={!canInvite}
/>
<Button type="submit" size="sm" variant="outline" className="h-8" disabled={!canInvite}>
<Mail className="size-3.5" /> einladen
</Button>
</form>
{!canInvite && (
<p className="text-[11px] text-muted-foreground">
Einladen neuer Accounts erfordert das Recht „Benutzerverwaltung“.
</p>
)}
{!filled && (
<form action={markFunctionUnfilled.bind(null, fn.key)}>
<Button
type="submit"
size="sm"
variant="ghost"
className="h-7 px-2 text-[11.5px] text-muted-foreground"
>
<CircleSlash className="size-3.5" /> unbesetzt lassen (Aufgabe anlegen)
</Button>
</form>
)}
</div>
)}
{!canUse && (
<p className="border-t pt-3 text-[12px] text-muted-foreground">
Nur Lesezugriff — Änderungen erfordern das Recht „Onboarding nutzen“.
</p>
)}
</div>
)}
</Popup>
);
}