Uecko_ERP/modules/customers/src/web/update/controllers/use-customer-update.controller.ts
2026-04-04 19:38:09 +02:00

184 lines
5.4 KiB
TypeScript

import { formHasAnyDirty } from "@erp/core/client";
import { useHookForm } from "@erp/core/hooks";
import { showErrorToast, showSuccessToast, showWarningToast } from "@repo/rdx-ui/helpers";
import { useEffect, useId, useMemo } from "react";
import type { FieldErrors } from "react-hook-form";
import { useTranslation } from "../../i18n";
import {
type Customer,
type UpdateCustomerByIdParams,
useCustomerGetQuery,
useCustomerUpdateMutation,
} from "../../shared";
import { mapCustomerToCustomerUpdateForm } from "../adapters";
import {
type CustomerUpdateForm,
CustomerUpdateFormSchema,
defaultCustomerUpdateForm,
} from "../entities";
import { buildCustomerUpdatePatch, buildUpdateCustomerByIdParams } from "../utils";
export interface UseCustomerUpdateControllerOptions {
onUpdated?(updated: Customer): void;
successToasts?: boolean; // mostrar o no toast automáticamente
onError?(error: Error, params: UpdateCustomerByIdParams): void;
errorToasts?: boolean; // mostrar o no toast automáticamente
}
export const useCustomerUpdateController = (
customerId?: string,
options?: UseCustomerUpdateControllerOptions
) => {
const { t } = useTranslation();
const formId = useId(); // id único por instancia
// 1) Estado de carga del cliente (query)
const {
data: customerData,
isLoading,
isError: isLoadError,
error: loadError,
} = useCustomerGetQuery(customerId, { enabled: Boolean(customerId) });
// 2) Estado de creación (mutación)
const {
mutateAsync,
isPending: isUpdating,
isError: isUpdateError,
error: updateError,
} = useCustomerUpdateMutation();
const initialValues = useMemo<CustomerUpdateForm>(() => {
if (!customerData) return defaultCustomerUpdateForm;
return mapCustomerToCustomerUpdateForm(customerData);
}, [customerData]);
// 3) Form hook
const form = useHookForm<CustomerUpdateForm>({
resolverSchema: CustomerUpdateFormSchema,
initialValues,
disabled: isLoading || isUpdating,
});
/** Reiniciar el form al recibir datos */
useEffect(() => {
if (!customerData) return;
console.log("Reseteando form con datos del cliente:", customerData);
form.reset(mapCustomerToCustomerUpdateForm(customerData), {
keepDirty: false, // <-- importante: no marca el form como "dirty" al cargar los datos reales
});
}, [customerData, form]);
/** Handlers */
const resetForm = () => {
const initialData = customerData
? mapCustomerToCustomerUpdateForm(customerData)
: defaultCustomerUpdateForm;
form.reset(initialData, { keepDirty: false });
};
const submitHandler = form.handleSubmit(
async (formData) => {
if (!customerId) {
showErrorToast(t("pages.update.error.title"), "Falta el ID del cliente");
return;
}
const { dirtyFields } = form.formState;
if (!formHasAnyDirty(dirtyFields)) {
showWarningToast(t("pages.update.error.no_changes"), "No hay cambios para guardar");
return;
}
const previousData = customerData;
const patchData = buildCustomerUpdatePatch(formData, dirtyFields);
const params: UpdateCustomerByIdParams = buildUpdateCustomerByIdParams(customerId, patchData);
try {
// Enviamos cambios al servidor
const updated = await mutateAsync(params);
// Ha ido bien -> actualizamos form con datos reales
// keepDirty = false -> deja el formulario sin cambios sin tener que esperar al siguiente render.
form.reset(mapCustomerToCustomerUpdateForm(updated), {
keepDirty: false,
});
if (options?.successToasts !== false) {
showSuccessToast(
t("pages.update.success.title", "Cliente modificado"),
t("pages.update.success.message", "Se ha modificado correctamente.")
);
}
options?.onUpdated?.(updated);
} catch (error: unknown) {
const normalizedError =
error instanceof Error ? error : new Error(t("pages.update.error.unknown"));
form.reset(
previousData ? mapCustomerToCustomerUpdateForm(previousData) : defaultCustomerUpdateForm,
{ keepDirty: false }
);
if (options?.errorToasts !== false) {
showErrorToast(t("pages.update.error.title"), normalizedError.message);
}
options?.onError?.(normalizedError, params);
}
},
(errors: FieldErrors<CustomerUpdateForm>) => {
const firstKey = Object.keys(errors)[0] as keyof CustomerUpdateForm | undefined;
if (firstKey) {
document.querySelector<HTMLElement>(`[name="${String(firstKey)}"]`)?.focus();
}
showWarningToast(
t("forms.validation.title", "Revisa los campos"),
t("forms.validation.message", "Hay errores de validación en el formulario.")
);
}
);
// Evento onSubmit ya preparado para el <form>
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.stopPropagation(); // <-- evita que el submit se propage por los padre en el árbol DOM
submitHandler(event);
};
return {
// form
form,
formId,
// handlers del form
onSubmit,
resetForm,
// carga de datos
customerData,
isLoading,
isLoadError,
loadError,
// mutation
isUpdating,
isUpdateError,
updateError,
// No devolver FormProvider, así el controller es más
// flexible y reusable (p.ej. para un modal)
// FormProvider,
};
};