Kuma/src/modules/employee/ui/views/employee-create-view.tsx
2026-04-17 20:00:41 +02:00

350 lines
13 KiB
TypeScript

"use client";
import InputComponent from "@/src/components/layout/InputComponent";
import MultiSelectDropdown from "@/src/components/layout/MultiSelectDropdown";
import { Button } from "@/src/components/ui/button";
import { Checkbox } from "@/src/components/ui/checkbox";
import { Label } from "@/src/components/ui/label";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { createEmployee, updateEmployee } from "../../actions/employee-actions";
import { toast } from "sonner";
const LOCATION = [
{ value: "hainburg", label: "Hainburg" },
{ value: "steinfurt", label: "Steinfurt" },
{ value: "frankfurt", label: "Frankfurt" },
{ value: "muenster", label: "Münster" },
];
const INITIAL_ROLES = [
{ value: "trainer", label: "Trainer" },
{ value: "admin", label: "Administrator" },
{ value: "manager", label: "Manager" },
];
const PERMISSION_GROUPS = [
{ key: "employees" as const, label: "Mitarbeiter" },
{ key: "courses" as const, label: "Kurse" },
{ key: "rooms" as const, label: "Räume" },
];
const PERMISSION_ACTIONS = [
{ key: "view" as const, label: "Ansehen" },
{ key: "create" as const, label: "Erstellen" },
{ key: "edit" as const, label: "Bearbeiten" },
{ key: "delete" as const, label: "Löschen" },
];
type PermissionsState = {
employees: { view: boolean; create: boolean; edit: boolean; delete: boolean };
courses: { view: boolean; create: boolean; edit: boolean; delete: boolean };
rooms: { view: boolean; create: boolean; edit: boolean; delete: boolean };
};
type CallerPermissions = {
employees: { view: boolean; create: boolean; edit: boolean; delete: boolean };
courses: { view: boolean; create: boolean; edit: boolean; delete: boolean };
rooms: { view: boolean; create: boolean; edit: boolean; delete: boolean };
} | null;
type InitialEmployeeData = {
email: string;
roles: string[];
locations: string[];
permissions: unknown;
status: string | null;
};
interface EmployeeCreateViewProps {
customRoles: Array<{ value: string; label: string }>;
callerPermissions: CallerPermissions;
mode?: "create" | "edit";
employeeId?: string;
initialData?: InitialEmployeeData;
}
const EmployeeCreateView = ({
customRoles,
callerPermissions,
mode = "create",
employeeId,
initialData,
}: EmployeeCreateViewProps) => {
const router = useRouter();
const initialPermissions = (): PermissionsState => {
if (initialData?.permissions && typeof initialData.permissions === "object") {
const p = initialData.permissions as Partial<PermissionsState>;
return {
employees: p.employees ?? { view: false, create: false, edit: false, delete: false },
courses: p.courses ?? { view: false, create: false, edit: false, delete: false },
rooms: p.rooms ?? { view: false, create: false, edit: false, delete: false },
};
}
return {
employees: { view: false, create: false, edit: false, delete: false },
courses: { view: false, create: false, edit: false, delete: false },
rooms: { view: false, create: false, edit: false, delete: false },
};
};
const [employeeMail, setEmplyoeeMail] = useState(initialData?.email ?? "");
const [isMultipleLocation, setIsMultipleLocation] = useState(
(initialData?.locations?.length ?? 0) > 0
);
const [selectedLocations, setSelectedLocations] = useState<string[]>(
initialData?.locations ?? []
);
const [selectedRoles, setSelectedRoles] = useState<string[]>(initialData?.roles ?? []);
// Kombiniere Standard-Rollen mit custom Rollen (ohne Duplikate)
const allRoleValues = new Set([
...INITIAL_ROLES.map((r) => r.value),
...customRoles.map((r) => r.value),
]);
const combinedRoles = Array.from(allRoleValues).map((value) => {
const customRole = customRoles.find((r) => r.value === value);
const initialRole = INITIAL_ROLES.find((r) => r.value === value);
return customRole || initialRole || { value, label: value };
});
const [roles, setRoles] = useState(combinedRoles);
const [newlyCreatedRoles, setNewlyCreatedRoles] = useState<string[]>([]);
const [permissionsState, setPermissionsState] = useState<PermissionsState>(initialPermissions());
const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState<Record<string, string[]>>({});
const handleCreateRole = (newRole: { value: string; label: string }) => {
setRoles([...roles, newRole]);
setNewlyCreatedRoles([...newlyCreatedRoles, newRole.value]);
};
const handleDeleteRole = (valueToDelete: string) => {
setRoles(roles.filter((role) => role.value !== valueToDelete));
setNewlyCreatedRoles(newlyCreatedRoles.filter((v) => v !== valueToDelete));
setSelectedRoles(selectedRoles.filter((v) => v !== valueToDelete));
};
const handlePermissionChange = (
resource: keyof PermissionsState,
action: "view" | "create" | "edit" | "delete",
value: boolean
) => {
setPermissionsState((prev) => {
const updated = { ...prev, [resource]: { ...prev[resource], [action]: value } };
// Andere Aktionen erfordern view
if (action !== "view" && value) {
updated[resource] = { ...updated[resource], view: true };
}
// view deaktivieren → alles deaktivieren
if (action === "view" && !value) {
updated[resource] = { view: false, create: false, edit: false, delete: false };
}
return updated;
});
};
const buildData = () => ({
email: employeeMail,
roles: selectedRoles,
isMultipleLocation,
locations: isMultipleLocation ? selectedLocations : [],
permissions: permissionsState,
});
const handleSubmit = async (status: "draft" | "published") => {
setIsSubmitting(true);
setErrors({});
try {
const data = buildData();
const result =
mode === "edit" && employeeId
? await updateEmployee(employeeId, data, status)
: await createEmployee(data, status);
if (result.success) {
toast.success(result.message);
router.push("/employee");
} else {
toast.error(result.error || "Ein Fehler ist aufgetreten");
if ("fieldErrors" in result && result.fieldErrors)
setErrors(result.fieldErrors as Record<string, string[]>);
}
} catch {
toast.error("Ein unerwarteter Fehler ist aufgetreten");
} finally {
setIsSubmitting(false);
}
};
const handlePublish = () => handleSubmit("published");
const handleSaveDraft = () => handleSubmit("draft");
return (
<div>
<h1 className="text-3xl font-bold">
{mode === "edit" ? "Mitarbeiter bearbeiten" : "Mitarbeiter anlegen"}
</h1>
<p className="text-xl mt-2">
Alle Daten können nach dem Speichern geändert oder gelöscht werden.
</p>
<div className="mt-6 flex flex-col gap-4 max-w-xl">
<div>
<InputComponent
isLabel
label="E-Mail"
type="email"
id="employee-create-email"
placeholder="max.mustermann@gmx.com"
value={employeeMail}
onChange={(e) => setEmplyoeeMail(e.target.value)}
/>
{errors.email && <p className="text-red-500 text-sm mt-1">{errors.email[0]}</p>}
</div>
<div>
<MultiSelectDropdown
label="Welche Rollen hat der Mitarbeiter?"
labelButton="Neue Rolle anlegen"
selected={selectedRoles}
onSelect={setSelectedRoles}
options={roles}
allowCreate={true}
onCreateOption={handleCreateRole}
onDeleteOption={handleDeleteRole}
deletableValues={newlyCreatedRoles}
/>
{errors.roles && <p className="text-red-500 text-sm mt-1">{errors.roles[0]}</p>}
</div>
<div className="flex items-center gap-2">
<Checkbox
id="employee-creating-multiple-location"
className="shadow-none h-5 w-5 bg-white border-blue cursor-pointer"
checked={isMultipleLocation}
onCheckedChange={(checked) => {
setIsMultipleLocation(checked === true);
}}
/>
<Label
htmlFor="employee-creating-multiple-location"
className="cursor-pointer text-lg mt-2"
>
Für mehrere Standorte anlegen
</Label>
</div>
{isMultipleLocation && (
<div>
<MultiSelectDropdown
label="An welchen Standorten ist der Trainer aktiv?"
selected={selectedLocations}
onSelect={setSelectedLocations}
options={LOCATION}
/>
{errors.locations && <p className="text-red-500 text-sm mt-1">{errors.locations[0]}</p>}
</div>
)}
<div>
<Label className="p-1 mb-3 text-blue text-lg font-semibold">Administrationsrechte</Label>
<div className="grid grid-cols-[auto_1fr] items-center gap-2 mb-4">
<Checkbox
id="all-permissions"
className="shadow-none h-5 w-5 bg-white border-blue cursor-pointer"
checked={PERMISSION_GROUPS.every((g) =>
PERMISSION_ACTIONS.every((a) => {
if (callerPermissions && !callerPermissions[g.key][a.key]) return true;
return permissionsState[g.key][a.key];
})
)}
onCheckedChange={(checked) => {
const value = checked === true;
setPermissionsState({
employees: {
view: value && (callerPermissions ? callerPermissions.employees.view : true),
create:
value && (callerPermissions ? callerPermissions.employees.create : true),
edit: value && (callerPermissions ? callerPermissions.employees.edit : true),
delete:
value && (callerPermissions ? callerPermissions.employees.delete : true),
},
courses: {
view: value && (callerPermissions ? callerPermissions.courses.view : true),
create: value && (callerPermissions ? callerPermissions.courses.create : true),
edit: value && (callerPermissions ? callerPermissions.courses.edit : true),
delete: value && (callerPermissions ? callerPermissions.courses.delete : true),
},
rooms: {
view: value && (callerPermissions ? callerPermissions.rooms.view : true),
create: value && (callerPermissions ? callerPermissions.rooms.create : true),
edit: value && (callerPermissions ? callerPermissions.rooms.edit : true),
delete: value && (callerPermissions ? callerPermissions.rooms.delete : true),
},
});
}}
/>
<Label htmlFor="all-permissions" className="cursor-pointer text-blue font-semibold">
Alle Berechtigungen
</Label>
</div>
<div className="flex flex-col gap-4 mt-2">
{PERMISSION_GROUPS.map((group) => (
<div key={group.key}>
<p className="text-sm font-semibold text-gray-700 mb-2">{group.label}</p>
<div className="grid grid-cols-4 gap-2">
{PERMISSION_ACTIONS.map((action) => {
const id = `${group.key}-${action.key}`;
const callerHas =
callerPermissions === null || callerPermissions[group.key][action.key];
return (
<div key={id} className="grid grid-cols-[auto_1fr] items-center gap-2">
<Checkbox
id={id}
className="shadow-none h-5 w-5 bg-white border-blue cursor-pointer"
checked={permissionsState[group.key][action.key]}
disabled={!callerHas}
onCheckedChange={(checked) =>
handlePermissionChange(group.key, action.key, checked === true)
}
/>
<Label
htmlFor={id}
className={`text-blue text-sm ${callerHas ? "cursor-pointer" : "cursor-not-allowed opacity-40"}`}
>
{action.label}
</Label>
</div>
);
})}
</div>
</div>
))}
</div>
</div>
<div className="flex gap-2">
<Button onClick={handlePublish} disabled={isSubmitting}>
{isSubmitting ? "Wird veröffentlicht..." : "Veröffentlichen"}
</Button>
<Button
variant="outline"
onClick={() => router.push("/employee")}
disabled={isSubmitting}
>
Abbrechen
</Button>
</div>
<button
onClick={handleSaveDraft}
disabled={isSubmitting}
className="mt-6 w-fit cursor-pointer hover:underline disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? "Wird gespeichert..." : "Entwurf speichern"}
</button>
</div>
</div>
);
};
export default EmployeeCreateView;