| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- "use client";
- import React from "react";
- import { adminUpdateUser, ApiClientError } from "@/lib/frontend/apiClient";
- import { buildLoginUrl, LOGIN_REASONS } from "@/lib/frontend/authRedirect";
- import { useSearchBranches } from "@/lib/frontend/search/useSearchBranches";
- import {
- evaluateBranchExistence,
- isValidBranchIdFormat,
- normalizeBranchIdInput,
- } from "@/lib/frontend/admin/users/userManagementUx";
- import {
- notifySuccess,
- notifyError,
- notifyApiError,
- notifyInfo,
- } from "@/lib/frontend/ui/toast";
- import {
- EDIT_ROLE_OPTIONS,
- EMAIL_RE,
- normalizeUsername,
- normalizeEmail,
- } from "@/components/admin/users/edit-user/editUserUtils";
- function isNonEmptyString(value) {
- return typeof value === "string" && value.trim().length > 0;
- }
- function buildInitialFormFromUser(user) {
- return {
- username: typeof user?.username === "string" ? user.username : "",
- email: typeof user?.email === "string" ? user.email : "",
- role: typeof user?.role === "string" ? user.role : "branch",
- branchId: typeof user?.branchId === "string" ? user.branchId : "",
- mustChangePassword: Boolean(user?.mustChangePassword),
- };
- }
- function validateClient(form) {
- const username = normalizeUsername(form?.username);
- const email = normalizeEmail(form?.email);
- const role = String(form?.role || "").trim();
- const isKnownRole = EDIT_ROLE_OPTIONS.some((x) => x.value === role);
- const branchId =
- role === "branch" ? normalizeBranchIdInput(form?.branchId || "") : "";
- if (!username) return { title: "Benutzername fehlt.", description: null };
- if (!email) return { title: "E-Mail fehlt.", description: null };
- if (!EMAIL_RE.test(email)) {
- return {
- title: "E-Mail ist ungültig.",
- description: "Bitte prüfen Sie die Eingabe.",
- };
- }
- if (!role || !isKnownRole) {
- return { title: "Rolle fehlt.", description: null };
- }
- if (role === "branch") {
- if (!branchId) {
- return {
- title: "Niederlassung fehlt.",
- description: "Für Niederlassungs-User ist eine NL erforderlich.",
- };
- }
- if (!isValidBranchIdFormat(branchId)) {
- return {
- title: "Niederlassung ist ungültig.",
- description: "Format: NL01, NL02, ...",
- };
- }
- }
- return null;
- }
- function extractDuplicateField(details) {
- if (!details || typeof details !== "object") return null;
- if (typeof details.field === "string" && details.field.trim()) {
- return details.field.trim();
- }
- return null;
- }
- function mapDuplicateFieldToGermanMessage(field) {
- if (field === "username") {
- return {
- title: "Benutzername existiert bereits.",
- description: "Bitte wählen Sie einen anderen Benutzernamen.",
- };
- }
- if (field === "email") {
- return {
- title: "E-Mail existiert bereits.",
- description: "Bitte wählen Sie eine andere E-Mail-Adresse.",
- };
- }
- return null;
- }
- function buildNormalizedForm(form) {
- const role = String(form?.role || "").trim();
- return {
- username: normalizeUsername(form?.username),
- email: normalizeEmail(form?.email),
- role,
- branchId:
- role === "branch" ? normalizeBranchIdInput(form?.branchId || "") : "",
- mustChangePassword: Boolean(form?.mustChangePassword),
- };
- }
- function buildPatch({ user, form }) {
- const initial = buildNormalizedForm(buildInitialFormFromUser(user));
- const current = buildNormalizedForm(form);
- const patch = {};
- if (current.username && current.username !== initial.username) {
- patch.username = current.username;
- }
- if (current.email && current.email !== initial.email) {
- patch.email = current.email;
- }
- if (current.role && current.role !== initial.role) {
- patch.role = current.role;
- }
- // branchId handling:
- // - Only meaningful for role=branch
- // - If role becomes non-branch, backend will clear branchId automatically.
- if (current.role === "branch") {
- // If role is branch (either unchanged or changed), enforce branchId in patch when different
- if (current.branchId !== initial.branchId) {
- patch.branchId = current.branchId;
- }
- } else {
- // If user was branch before and we did not change role explicitly (rare),
- // we do not auto-clear here. Backend will enforce consistency based on role updates.
- }
- if (current.mustChangePassword !== initial.mustChangePassword) {
- patch.mustChangePassword = current.mustChangePassword;
- }
- return patch;
- }
- export function useEditUserDialog({ user, disabled = false, onUpdated } = {}) {
- const [open, setOpen] = React.useState(false);
- const [isSubmitting, setIsSubmitting] = React.useState(false);
- const [form, setForm] = React.useState(() => buildInitialFormFromUser(user));
- const [error, setError] = React.useState(null);
- const role = String(form?.role || "branch").trim();
- const shouldLoadBranches = open && role === "branch";
- const { status: branchesStatus, branches: availableBranchIds } =
- useSearchBranches({ enabled: shouldLoadBranches });
- const effectiveDisabled = Boolean(disabled || isSubmitting || !user?.id);
- const branchExistence = React.useMemo(
- () =>
- evaluateBranchExistence({
- role,
- branchId: form?.branchId,
- branchesStatus,
- availableBranchIds,
- }),
- [role, form?.branchId, branchesStatus, availableBranchIds],
- );
- const patchPreview = React.useMemo(() => {
- return buildPatch({ user, form });
- }, [
- user?.id,
- user?.username,
- user?.email,
- user?.role,
- user?.branchId,
- user?.mustChangePassword,
- form?.username,
- form?.email,
- form?.role,
- form?.branchId,
- form?.mustChangePassword,
- ]);
- const canSubmit = React.useMemo(() => {
- return (
- !effectiveDisabled &&
- Object.keys(patchPreview).length > 0 &&
- !branchExistence.shouldBlockSubmit
- );
- }, [effectiveDisabled, patchPreview, branchExistence.shouldBlockSubmit]);
- const setPatch = React.useCallback((patch) => {
- setForm((prev) => ({ ...prev, ...(patch || {}) }));
- }, []);
- const resetForm = React.useCallback(() => {
- setForm(buildInitialFormFromUser(user));
- setError(null);
- }, [user]);
- const redirectToLoginExpired = React.useCallback(() => {
- const next =
- typeof window !== "undefined"
- ? `${window.location.pathname}${window.location.search}`
- : "/admin/users";
- window.location.replace(
- buildLoginUrl({ reason: LOGIN_REASONS.EXPIRED, next }),
- );
- }, []);
- const handleOpenChange = React.useCallback(
- (nextOpen) => {
- setOpen(nextOpen);
- // On open -> initialize from current user snapshot.
- // On close -> clear error and reset form so reopening is always clean.
- if (nextOpen) {
- setForm(buildInitialFormFromUser(user));
- setError(null);
- } else {
- resetForm();
- }
- },
- [user, resetForm],
- );
- const handleSubmit = React.useCallback(
- async (e) => {
- e?.preventDefault?.();
- if (effectiveDisabled) return;
- setError(null);
- const clientErr = validateClient(form);
- if (clientErr) {
- setError(clientErr);
- notifyError({
- title: clientErr.title,
- description: clientErr.description,
- });
- return;
- }
- if (branchExistence.shouldBlockSubmit) {
- const mapped = {
- title: "Niederlassung existiert nicht.",
- description:
- "Die gewählte Niederlassung ist nicht in der aktuellen Liste vorhanden.",
- };
- setError(mapped);
- notifyError(mapped);
- return;
- }
- const patch = buildPatch({ user, form });
- if (Object.keys(patch).length === 0) {
- notifyInfo({
- title: "Keine Änderungen",
- description: "Es wurden keine Felder geändert.",
- });
- setOpen(false);
- resetForm();
- return;
- }
- setIsSubmitting(true);
- try {
- await adminUpdateUser(String(user.id), patch);
- notifySuccess({
- title: "Benutzer aktualisiert",
- description: `Benutzer "${normalizeUsername(form.username)}" wurde gespeichert.`,
- });
- setOpen(false);
- resetForm();
- if (typeof onUpdated === "function") onUpdated();
- } catch (err) {
- if (err instanceof ApiClientError) {
- if (err.code === "AUTH_UNAUTHENTICATED") {
- notifyApiError(err);
- redirectToLoginExpired();
- return;
- }
- if (err.code === "USER_NOT_FOUND") {
- const mapped = {
- title: "Benutzer nicht gefunden.",
- description:
- "Der Benutzer existiert nicht (mehr). Bitte aktualisieren Sie die Liste.",
- };
- setError(mapped);
- notifyError(mapped);
- return;
- }
- if (err.code === "VALIDATION_MISSING_FIELD") {
- const fields = err.details?.fields;
- if (Array.isArray(fields) && fields.includes("branchId")) {
- const mapped = {
- title: "Niederlassung fehlt.",
- description:
- "Für Niederlassungs-User ist eine Niederlassung erforderlich.",
- };
- setError(mapped);
- notifyError(mapped);
- return;
- }
- }
- if (err.code === "VALIDATION_INVALID_FIELD") {
- const field = extractDuplicateField(err.details);
- const mapped = mapDuplicateFieldToGermanMessage(field);
- if (mapped) {
- setError(mapped);
- notifyError(mapped);
- return;
- }
- }
- setError({
- title: "Benutzer konnte nicht aktualisiert werden.",
- description:
- "Bitte prüfen Sie die Eingaben und versuchen Sie es erneut.",
- });
- notifyApiError(err, {
- fallbackTitle: "Benutzer konnte nicht aktualisiert werden.",
- fallbackDescription:
- "Bitte prüfen Sie die Eingaben und versuchen Sie es erneut.",
- });
- return;
- }
- setError({
- title: "Benutzer konnte nicht aktualisiert werden.",
- description: "Bitte versuchen Sie es erneut.",
- });
- notifyError({
- title: "Benutzer konnte nicht aktualisiert werden.",
- description: "Bitte versuchen Sie es erneut.",
- });
- } finally {
- setIsSubmitting(false);
- }
- },
- [
- effectiveDisabled,
- form,
- user,
- onUpdated,
- redirectToLoginExpired,
- resetForm,
- branchExistence.shouldBlockSubmit,
- ],
- );
- return {
- open,
- handleOpenChange,
- form,
- setPatch,
- error,
- branchesStatus,
- branchExistence,
- isSubmitting,
- effectiveDisabled,
- canSubmit,
- handleSubmit,
- };
- }
|