import { formHasAnyDirty, pickFormDirtyValues } 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, FormProvider } from "react-hook-form"; import { useTranslation } from "../../i18n"; import { type Customer, useCustomerGetQuery, useCustomerUpdateMutation } from "../../shared"; import { type CustomerFormData, CustomerFormSchema, defaultCustomerFormData } from "../types"; export interface UseCustomerUpdateControllerOptions { onUpdated?(updated: Customer): void; successToasts?: boolean; // mostrar o no toast automáticcamente onError?(error: Error, patchData: ReturnType): void; errorToasts?: boolean; // mostrar o no toast automáticcamente } 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(() => customerData ?? defaultCustomerFormData, [customerData]); // 3) Form hook const form = useHookForm({ resolverSchema: CustomerFormSchema, initialValues, disabled: isLoading || isUpdating, }); /** Reiniciar el form al recibir datos */ useEffect(() => { // keepDirty = false -> deja el formulario sin cambios sin tener que esperar al siguiente render. if (customerData) { console.log("Reseteando form con datos del cliente:", customerData); form.reset(customerData, { keepDirty: false }); } }, [customerData, form]); /** Handlers */ const resetForm = () => form.reset(customerData ?? defaultCustomerFormData); // Versión sincronizada 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 patchData = pickFormDirtyValues(formData, dirtyFields); const previousData = customerData; try { // Enviamos cambios al servidor const updated = await mutateAsync({ id: customerId, data: patchData }); // Ha ido bien -> actualizamos form con datos reales // keepDirty = false -> deja el formulario sin cambios sin tener que esperar al siguiente render. form.reset(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: any) { // Algo ha fallado -> revertimos cambios form.reset(previousData ?? defaultCustomerFormData); if (options?.errorToasts !== false) { showErrorToast(t("pages.update.error.title"), error.message); } options?.onError?.(error, patchData); } }, (errors: FieldErrors) => { const firstKey = Object.keys(errors)[0] as keyof CustomerFormData | undefined; if (firstKey) document.querySelector(`[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
const onSubmit = (event: React.FormEvent) => { 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, // Por comodidad FormProvider, }; };