184 lines
5.4 KiB
TypeScript
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,
|
|
};
|
|
};
|