diff --git a/modules/core/src/web/hooks/use-unsaved-changes-notifier/index.ts b/modules/core/src/web/hooks/use-unsaved-changes-notifier/index.ts index 51391328..a9f44ad1 100644 --- a/modules/core/src/web/hooks/use-unsaved-changes-notifier/index.ts +++ b/modules/core/src/web/hooks/use-unsaved-changes-notifier/index.ts @@ -1,2 +1,4 @@ export * from "./use-unsaved-changes-notifier"; +export * from "./use-warn-about-change"; +export * from "./warn-about-change-context"; export * from "./warn-about-change-provider"; diff --git a/modules/core/src/web/hooks/use-unsaved-changes-notifier/use-unsaved-changes-notifier.tsx b/modules/core/src/web/hooks/use-unsaved-changes-notifier/use-unsaved-changes-notifier.tsx index 8ae6b264..8a58f2d5 100644 --- a/modules/core/src/web/hooks/use-unsaved-changes-notifier/use-unsaved-changes-notifier.tsx +++ b/modules/core/src/web/hooks/use-unsaved-changes-notifier/use-unsaved-changes-notifier.tsx @@ -51,6 +51,7 @@ export const useUnsavedChangesNotifier = ({ onConfirm: () => { resolve(true); onConfirm?.(); + window.onbeforeunload = null; // limpirar de cambios }, onCancel: () => { resolve(false); @@ -71,7 +72,12 @@ export const useUnsavedChangesNotifier = ({ useEffect(() => { if (isDirty) { - window.onbeforeunload = () => texts.subtitle; + window.onbeforeunload = (e) => { + e.preventDefault(); + return texts.subtitle; + }; + } else { + window.onbeforeunload = null; } return () => { @@ -79,7 +85,5 @@ export const useUnsavedChangesNotifier = ({ }; }, [isDirty, texts.subtitle]); - return { - confirm, - }; + return { confirm }; }; diff --git a/modules/core/src/web/hooks/use-unsaved-changes-notifier/use-warn-about-change.tsx b/modules/core/src/web/hooks/use-unsaved-changes-notifier/use-warn-about-change.tsx index 2355b0ec..14b478d9 100644 --- a/modules/core/src/web/hooks/use-unsaved-changes-notifier/use-warn-about-change.tsx +++ b/modules/core/src/web/hooks/use-unsaved-changes-notifier/use-warn-about-change.tsx @@ -3,8 +3,8 @@ import { UnsavedWarnContext } from "./warn-about-change-context"; export const useWarnAboutChange = () => { const context = useContext(UnsavedWarnContext); - if (context === null) - throw new Error("useWarnAboutChange must be used within a UnsavedWarnProvider"); - + if (context === null) { + throw new Error("useWarnAboutChange must be used within an UnsavedWarnProvider"); + } return context; }; diff --git a/modules/core/src/web/hooks/use-unsaved-changes-notifier/warn-about-change-context.tsx b/modules/core/src/web/hooks/use-unsaved-changes-notifier/warn-about-change-context.tsx index 0eaea95b..37f13f78 100644 --- a/modules/core/src/web/hooks/use-unsaved-changes-notifier/warn-about-change-context.tsx +++ b/modules/core/src/web/hooks/use-unsaved-changes-notifier/warn-about-change-context.tsx @@ -4,6 +4,7 @@ import type { UnsavedChangesNotifierProps } from "./use-unsaved-changes-notifier export interface IUnsavedWarnContextState { show: (options: NullOr) => void; + hide: () => void; } export const UnsavedWarnContext = createContext>(null); diff --git a/modules/core/src/web/hooks/use-unsaved-changes-notifier/warn-about-change-provider.tsx b/modules/core/src/web/hooks/use-unsaved-changes-notifier/warn-about-change-provider.tsx index 623362c6..c15a4378 100644 --- a/modules/core/src/web/hooks/use-unsaved-changes-notifier/warn-about-change-provider.tsx +++ b/modules/core/src/web/hooks/use-unsaved-changes-notifier/warn-about-change-provider.tsx @@ -4,38 +4,43 @@ import { type PropsWithChildren, useCallback, useMemo, useState } from "react"; import type { UnsavedChangesNotifierProps } from "./use-unsaved-changes-notifier"; import { UnsavedWarnContext } from "./warn-about-change-context"; -export const UnsavedWarnProvider = ({ children }: PropsWithChildren) => { - const [confirm, setConfirm] = useState>(null); +// Aseguramos que las props mínimas para el diálogo siempre estén presentes +type ConfirmOptions = Required< + Pick +> & + Omit; - const [open, toggle] = useState(false); +export const UnsavedWarnProvider = ({ children }: PropsWithChildren) => { + const [confirm, setConfirm] = useState>(null); + const [open, setOpen] = useState(false); const show = useCallback((confirmOptions: NullOr) => { - setConfirm(confirmOptions); - toggle(true); + if (confirmOptions) { + setConfirm(confirmOptions as ConfirmOptions); + setOpen(true); + } }, []); - const onConfirm = () => { + const hide = useCallback(() => setOpen(false), []); + + const onConfirm = useCallback(() => { confirm?.onConfirm?.(); - toggle(false); - }; + hide(); + }, [confirm, hide]); - const onCancel = () => { + const onCancel = useCallback(() => { confirm?.onCancel?.(); - toggle(false); - }; + hide(); + }, [confirm, hide]); - const value = useMemo(() => ({ show }), [show]); + const value = useMemo(() => ({ show, hide }), [show, hide]); return ( {children} { - //console.log("onCancel"); - onCancel(); - }} - onConfirm={() => onConfirm()} + onCancel={onCancel} + onConfirm={onConfirm} title={confirm?.title} description={confirm?.subtitle} confirmLabel={confirm?.confirmText} diff --git a/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts b/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts index 8ca0cc1c..c0edb8b8 100644 --- a/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts +++ b/modules/customer-invoices/src/common/dto/response/get-customer-invoice-by-id.response.dto.ts @@ -32,6 +32,13 @@ export const GetCustomerInvoiceByIdResponseSchema = z.object({ taxes: z.string(), + payment_method: z + .object({ + payment_id: z.string(), + payment_description: z.string(), + }) + .optional(), + subtotal_amount: MoneySchema, discount_percentage: PercentageSchema, discount_amount: MoneySchema, diff --git a/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx b/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx index 43f4adf7..c3b80f01 100644 --- a/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx +++ b/modules/customer-invoices/src/web/components/customer-invoices-list-grid.tsx @@ -100,7 +100,7 @@ export const CustomerInvoicesListGrid = () => { size='icon' className='size-8' onClick={() => { - navigate(`${data.id}/edit`); + navigate(`/customer-invoices/${data.id}/edit`); }} > diff --git a/modules/customers/src/common/dto/request/create-customer.request.dto.ts b/modules/customers/src/common/dto/request/create-customer.request.dto.ts index 60af2202..47336c2c 100644 --- a/modules/customers/src/common/dto/request/create-customer.request.dto.ts +++ b/modules/customers/src/common/dto/request/create-customer.request.dto.ts @@ -1,38 +1,35 @@ import * as z from "zod/v4"; export const CreateCustomerRequestSchema = z.object({ - id: z.uuid(), - company_id: z.uuid(), - reference: z.string().default(""), + reference: z.string().optional(), - is_company: z.string().toLowerCase().default("false"), - name: z.string().default(""), - trade_name: z.string().default(""), - tin: z.string().default(""), + is_company: z.string().toLowerCase().default("false").optional(), + name: z.string(), + trade_name: z.string().optional(), + tin: z.string().optional(), + default_taxes: z.array(z.string()).default([]).optional(), - street: z.string().default(""), - street2: z.string().default(""), - city: z.string().default(""), - province: z.string().default(""), - postal_code: z.string().default(""), - country: z.string().default("es"), + street: z.string().optional(), + street2: z.string().optional(), + city: z.string().optional(), + province: z.string().optional(), + postal_code: z.string().optional(), + country: z.string().default("es").optional(), - email_primary: z.string().default(""), - email_secondary: z.string().default(""), - phone_primary: z.string().default(""), - phone_secondary: z.string().default(""), - mobile_primary: z.string().default(""), - mobile_secondary: z.string().default(""), + email_primary: z.string().optional(), + email_secondary: z.string().optional(), + phone_primary: z.string().optional(), + phone_secondary: z.string().optional(), + mobile_primary: z.string().optional(), + mobile_secondary: z.string().optional(), - fax: z.string().default(""), - website: z.string().default(""), + fax: z.string().optional(), + website: z.string().optional(), - legal_record: z.string().default(""), + legal_record: z.string().optional(), - default_taxes: z.array(z.string()).default([]), - status: z.string().toLowerCase().default("active"), - language_code: z.string().toLowerCase().default("es"), - currency_code: z.string().toUpperCase().default("EUR"), + language_code: z.string().toLowerCase().default("es").optional(), + currency_code: z.string().toUpperCase().default("EUR").optional(), }); export type CreateCustomerRequestDTO = z.infer; diff --git a/modules/customers/src/common/locales/en.json b/modules/customers/src/common/locales/en.json index b509711b..f1dd48e2 100644 --- a/modules/customers/src/common/locales/en.json +++ b/modules/customers/src/common/locales/en.json @@ -33,10 +33,10 @@ }, "form_fields": { "customer_type": { - "label": "Customer type", + "label": "This contact is...", "description": "Select the type of customer", - "company": "Company", - "individual": "Individual" + "company": "a company", + "individual": "a person" }, "name": { "label": "Name", diff --git a/modules/customers/src/common/locales/es.json b/modules/customers/src/common/locales/es.json index 1fa8ac1f..35c739f5 100644 --- a/modules/customers/src/common/locales/es.json +++ b/modules/customers/src/common/locales/es.json @@ -35,10 +35,10 @@ }, "form_fields": { "customer_type": { - "label": "Tipo de cliente", + "label": "Este cliente es...", "description": "Seleccione el tipo de cliente", - "company": "Empresa", - "individual": "Persona física" + "company": "una empresa", + "individual": "una persona" }, "name": { "label": "Nombre", diff --git a/modules/customers/src/web/components/customers-list-grid.tsx b/modules/customers/src/web/components/customers-list-grid.tsx index 782adcdc..0c493983 100644 --- a/modules/customers/src/web/components/customers-list-grid.tsx +++ b/modules/customers/src/web/components/customers-list-grid.tsx @@ -79,7 +79,7 @@ export const CustomersListGrid = () => { size='icon' className='size-8' onClick={() => { - navigate(`${data.id}/edit`, { relative: "route" }); + navigate(`/customers/${data.id}/edit`); }} > diff --git a/modules/customers/src/web/pages/update/customer-additional-config-fields.tsx b/modules/customers/src/web/components/editor/customer-additional-config-fields.tsx similarity index 87% rename from modules/customers/src/web/pages/update/customer-additional-config-fields.tsx rename to modules/customers/src/web/components/editor/customer-additional-config-fields.tsx index 3b55e1ed..a39c4b65 100644 --- a/modules/customers/src/web/pages/update/customer-additional-config-fields.tsx +++ b/modules/customers/src/web/components/editor/customer-additional-config-fields.tsx @@ -6,15 +6,14 @@ import { CardHeader, CardTitle, } from "@repo/shadcn-ui/components"; -import { Control } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; import { CURRENCY_OPTIONS, LANGUAGE_OPTIONS } from "../../constants"; import { useTranslation } from "../../i18n"; -import { CustomerUpdateData } from "../../schemas"; +import { CustomerFormData } from "../../schemas"; -export const CustomerAdditionalConfigFields = ({ - control, -}: { control: Control }) => { +export const CustomerAdditionalConfigFields = () => { const { t } = useTranslation(); + const { control } = useFormContext(); return ( diff --git a/modules/customers/src/web/pages/update/customer-address-fields.tsx b/modules/customers/src/web/components/editor/customer-address-fields.tsx similarity index 92% rename from modules/customers/src/web/pages/update/customer-address-fields.tsx rename to modules/customers/src/web/components/editor/customer-address-fields.tsx index 25280e71..6a0f9e16 100644 --- a/modules/customers/src/web/pages/update/customer-address-fields.tsx +++ b/modules/customers/src/web/components/editor/customer-address-fields.tsx @@ -6,13 +6,14 @@ import { CardHeader, CardTitle, } from "@repo/shadcn-ui/components"; -import { Control } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; import { COUNTRY_OPTIONS } from "../../constants"; import { useTranslation } from "../../i18n"; -import { CustomerUpdateData } from "../../schemas"; +import { CustomerFormData } from "../../schemas"; -export const CustomerAddressFields = ({ control }: { control: Control }) => { +export const CustomerAddressFields = () => { const { t } = useTranslation(); + const { control } = useFormContext(); return ( diff --git a/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx b/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx new file mode 100644 index 00000000..ebf11161 --- /dev/null +++ b/modules/customers/src/web/components/editor/customer-basic-info-fields.tsx @@ -0,0 +1,131 @@ +import { TaxesMultiSelectField } from "@erp/core/components"; +import { TextAreaField, TextField } from "@repo/rdx-ui/components"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + RadioGroup, + RadioGroupItem, +} from "@repo/shadcn-ui/components"; +import { useFormContext, useWatch } from "react-hook-form"; +import { useTranslation } from "../../i18n"; +import { CustomerFormData } from "../../schemas"; + +export const CustomerBasicInfoFields = () => { + const { t } = useTranslation(); + const { control } = useFormContext(); + + const isCompany = useWatch({ + name: "is_company", + defaultValue: "true", + }); + + return ( + + + Identificación + + +
+
+ +
+ +
+ ( + + {t("form_fields.customer_type.label")} + + + + + + + + {t("form_fields.customer_type.company")} + + + + + + + + {t("form_fields.customer_type.individual")} + + + + + + + )} + /> +
+ + {isCompany === "false" ? ( +
+ +
+ ) : ( + <> + )} + +
+ +
+ +
+ +
+ + +
+
+
+ ); +}; diff --git a/modules/customers/src/web/pages/update/customer-contact-fields.tsx b/modules/customers/src/web/components/editor/customer-contact-fields.tsx similarity index 96% rename from modules/customers/src/web/pages/update/customer-contact-fields.tsx rename to modules/customers/src/web/components/editor/customer-contact-fields.tsx index c0a1b92e..d0270622 100644 --- a/modules/customers/src/web/pages/update/customer-contact-fields.tsx +++ b/modules/customers/src/web/components/editor/customer-contact-fields.tsx @@ -12,13 +12,13 @@ import { import { TextField } from "@repo/rdx-ui/components"; import { ChevronDown, MailIcon, PhoneIcon, SmartphoneIcon } from "lucide-react"; import { useState } from "react"; -import { Control } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; import { useTranslation } from "../../i18n"; -import { CustomerUpdateData } from "../../schemas"; -export const CustomerContactFields = ({ control }: { control: Control }) => { +export const CustomerContactFields = () => { const { t } = useTranslation(); const [open, setOpen] = useState(true); + const { control } = useFormContext(); return ( @@ -124,7 +124,7 @@ export const CustomerContactFields = ({ control }: { control: Control -
+
void; + onError: (errors: FieldErrors) => void; + disabled?: boolean; + onDirtyChange: (isDirty: boolean) => void; +} + +export function CustomerEditForm({ + formId, + initialValues, + onSubmit, + onError, + disabled, + onDirtyChange, +}: CustomerFormProps) { + const form = useForm({ + resolver: zodResolver(CustomerFormSchema), + defaultValues: initialValues, + disabled, + }); + + const { + formState: { isDirty }, + } = form; + + useEffect(() => { + if (onDirtyChange) { + onDirtyChange(isDirty); + } + }, [isDirty, onDirtyChange]); + + // Resetear el form si cambian los valores iniciales + useEffect(() => { + form.reset(initialValues); + }, [initialValues, form]); + + return ( + + +
+
+ + + + +
+
+
+ ); +} diff --git a/modules/customers/src/web/components/editor/index.ts b/modules/customers/src/web/components/editor/index.ts new file mode 100644 index 00000000..b8428a3a --- /dev/null +++ b/modules/customers/src/web/components/editor/index.ts @@ -0,0 +1 @@ +export * from "./customer-edit-form"; diff --git a/modules/customers/src/web/components/form-debug.tsx b/modules/customers/src/web/components/form-debug.tsx index e2f62033..a2c8d047 100644 --- a/modules/customers/src/web/components/form-debug.tsx +++ b/modules/customers/src/web/components/form-debug.tsx @@ -1,6 +1,8 @@ -import { UseFormReturn } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; + +export const FormDebug = () => { + const form = useFormContext(); // ✅ mantiene el tipo de T -export const FormDebug = ({ form }: { form: UseFormReturn }) => { const { watch, formState: { isDirty, dirtyFields, defaultValues }, diff --git a/modules/customers/src/web/components/index.ts b/modules/customers/src/web/components/index.ts index 72f39232..502825e2 100644 --- a/modules/customers/src/web/components/index.ts +++ b/modules/customers/src/web/components/index.ts @@ -2,6 +2,7 @@ export * from "./client-selector"; export * from "./customer-editor-skeleton"; export * from "./customers-layout"; export * from "./customers-list-grid"; +export * from "./editor"; export * from "./error-alert"; export * from "./form-debug"; export * from "./not-found-card"; diff --git a/modules/customers/src/web/hooks/use-create-customer-mutation.ts b/modules/customers/src/web/hooks/use-create-customer-mutation.ts index b0e0e889..0d2e85af 100644 --- a/modules/customers/src/web/hooks/use-create-customer-mutation.ts +++ b/modules/customers/src/web/hooks/use-create-customer-mutation.ts @@ -1,20 +1,33 @@ import { useDataSource } from "@erp/core/hooks"; +import { UniqueID } from "@repo/rdx-ddd"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { CustomerCreateData, CustomerData } from "../schemas"; import { CUSTOMERS_LIST_KEY } from "./use-update-customer-mutation"; +type CreateCustomerPayload = { + data: CustomerCreateData; +}; + export function useCreateCustomerMutation() { const queryClient = useQueryClient(); const dataSource = useDataSource(); - return useMutation({ + return useMutation({ mutationKey: ["customer:create"], - mutationFn: async (data: CustomerCreateData) => { - const created = await dataSource.createOne("customers", data); + + mutationFn: async (payload) => { + const { data } = payload; + const customerId = UniqueID.generateNewID(); + + const created = await dataSource.createOne("customers", { + ...data, + id: customerId.toString(), + }); return created as CustomerData; }, + onSuccess: () => { - // Invalida el listado de clientes para incluir el nuevo + // Invalida el listado para refrescar desde servidor queryClient.invalidateQueries({ queryKey: CUSTOMERS_LIST_KEY }); }, }); diff --git a/modules/customers/src/web/pages/create/create.tsx b/modules/customers/src/web/pages/create/create.tsx deleted file mode 100644 index 7bc3f2ed..00000000 --- a/modules/customers/src/web/pages/create/create.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { AppBreadcrumb, AppContent, ButtonGroup } from "@repo/rdx-ui/components"; -import { Button } from "@repo/shadcn-ui/components"; -import { useNavigate } from "react-router-dom"; - -import { useCreateCustomerMutation } from "../../hooks/use-create-customer-mutation"; -import { useTranslation } from "../../i18n"; -import { CustomerEditForm } from "./customer-edit-form"; - -export const CustomerCreate = () => { - const { t } = useTranslation(); - const navigate = useNavigate(); - - const { mutate, isPending, isError, error } = useCreateCustomerMutation(); - - const handleSubmit = (data: any) => { - // Handle form submission logic here - console.log("Form submitted with data:", data); - mutate(data); - - // Navigate to the list page after submission - navigate("/customers/list"); - }; - - if (isError) { - console.error("Error creating customer:", error); - // Optionally, you can show an error message to the user - } - - // Render the component - // You can also handle loading state if needed - // For example, you can disable the submit button while the mutation is in progress - // const isLoading = useCreateCustomerMutation().isLoading; - - // Return the JSX for the component - // You can customize the form and its fields as needed - // For example, you can use a form library like react-hook-form or Formik to handle form state and validation - // Here, we are using a simple form with a submit button - - // Note: Make sure to replace the form fields with your actual invoice fields - // and handle validation as needed. - // This is just a basic example to demonstrate the structure of the component. - - // If you are using a form library, you can pass the handleSubmit function to the form's onSubmit prop - // and use the form library's methods to handle form state and validation. - - // Example of a simple form submission handler - // You can replace this with your actual form handling logic - // const handleSubmit = (event: React.FormEvent) => { - // event.preventDefault(); - // const formData = new FormData(event.currentTarget); - - return ( - <> - - -
-
-

- {t("pages.create.title")} -

-

- {t("pages.create.description")} -

-
- - - - - -
-
- -
-
- - ); -}; diff --git a/modules/customers/src/web/pages/create/customer-create.tsx b/modules/customers/src/web/pages/create/customer-create.tsx new file mode 100644 index 00000000..72db8a03 --- /dev/null +++ b/modules/customers/src/web/pages/create/customer-create.tsx @@ -0,0 +1,124 @@ +import { AppBreadcrumb, AppContent, ButtonGroup } from "@repo/rdx-ui/components"; +import { Button } from "@repo/shadcn-ui/components"; +import { useNavigate } from "react-router-dom"; + +import { useUnsavedChangesNotifier } from "@erp/core/hooks"; +import { showErrorToast, showSuccessToast } from "@repo/shadcn-ui/lib/utils"; +import { useState } from "react"; +import { FieldErrors } from "react-hook-form"; +import { CustomerEditForm, ErrorAlert } from "../../components"; +import { useCreateCustomerMutation } from "../../hooks"; +import { useTranslation } from "../../i18n"; +import { CustomerFormData, defaultCustomerFormData } from "../../schemas"; + +export const CustomerCreate = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const [isDirty, setIsDirty] = useState(false); + + // 2) Estado de creación (mutación) + const { + mutateAsync, + isPending: isCreating, + isError: isCreateError, + error: createError, + } = useCreateCustomerMutation(); + + const { confirm: confirmCancel } = useUnsavedChangesNotifier({ + isDirty, + }); + + // 3) Submit con navegación condicionada por éxito + const handleSubmit = async (formData: CustomerFormData) => { + /*const changedData: Record = {}; + + Object.keys(dirtyFields).forEach((field) => { + const value = String(currentValues[field as keyof CustomerFormData]); + changedData[field] = value; + });*/ + + try { + const result = await mutateAsync({ data: formData }); + console.log(result); + + if (result) { + showSuccessToast(t("pages.create.successTitle"), t("pages.create.successMsg")); + navigate("/customers/list"); + } + } catch (e) { + showErrorToast(t("pages.create.errorTitle"), (e as Error).message); + } finally { + } + }; + + const handleError = (errors: FieldErrors) => { + console.error("Errores en el formulario:", errors); + // Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario + }; + + return ( + <> + + +
+
+

+ {t("pages.create.title")} +

+

+ {t("pages.create.description")} +

+
+ + + + + +
+ {/* Alerta de error de actualización (si ha fallado el último intento) */} + {isCreateError && ( + + )} + +
+ +
+
+ + ); +}; diff --git a/modules/customers/src/web/pages/create/customer-edit-form.tsx b/modules/customers/src/web/pages/create/customer-edit-form.tsx deleted file mode 100644 index 945db6a9..00000000 --- a/modules/customers/src/web/pages/create/customer-edit-form.tsx +++ /dev/null @@ -1,350 +0,0 @@ -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; - -import { TaxesMultiSelectField } from "@erp/core/components"; -import { SelectField, TextAreaField, TextField } from "@repo/rdx-ui/components"; -import { - Button, - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - RadioGroup, - RadioGroupItem, -} from "@repo/shadcn-ui/components"; - -import { useUnsavedChangesNotifier } from "@erp/core/hooks"; -import { useTranslation } from "../../i18n"; -import { CustomerData, CustomerUpdateSchema } from "../../schemas"; - -const defaultCustomerData = { - id: "5e4dc5b3-96b9-4968-9490-14bd032fec5f", - is_company: true, - status: "active", - tin: "B12345678", - name: "Pepe", - trade_name: "Pepe's Shop", - email: "pepe@example.com", - phone: "+34 123 456 789", - website: "https://pepe.com", - fax: "+34 123 456 789", - street: "Calle Falsa 123", - city: "Madrid", - country: "ES", - postal_code: "28080", - province: "Madrid", - language_code: "es", - currency_code: "EUR", - legal_record: "Registro Mercantil de Madrid, Tomo 12345, Folio 67, Hoja M-123456", - default_taxes: ["iva_21", "rec_5_2"], -}; - -interface CustomerFormProps { - initialData?: CustomerData; - isPending?: boolean; - /** - * Callback function to handle form submission. - * @param data - The customer data submitted by the form. - */ - onSubmit?: (data: CustomerData) => void; -} - -export const CustomerEditForm = ({ - initialData = defaultCustomerData, - onSubmit, - isPending, -}: CustomerFormProps) => { - const { t } = useTranslation(); - - const form = useForm({ - resolver: zodResolver(CustomerUpdateSchema), - defaultValues: initialData, - disabled: isPending, - }); - - useUnsavedChangesNotifier({ - isDirty: form.formState.isDirty, - }); - - const handleSubmit = (data: CustomerData) => { - console.log("Datos del formulario:", data); - onSubmit?.(data); - }; - - const handleError = (errors: any) => { - console.error("Errores en el formulario:", errors); - // Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario - }; - - const handleCancel = () => { - form.reset(initialData); - }; - - return ( -
- -
- {/* Información básica */} - - - {t("form_groups.basic_info.title")} - {t("form_groups.basic_info.description")} - - - ( - - {t("form_fields.customer_type.label")} - - - - - - - - {t("form_fields.customer_type.company")} - - - - - - - - - {t("form_fields.customer_type.individual")} - - - - - - - )} - /> - - - - - - - - - - - - {/* Dirección */} - - - {t("form_groups.address.title")} - {t("form_groups.address.description")} - - - - - - - - - - - - - - - {/* Contacto */} - - - {t("form_groups.contact_info.title")} - {t("form_groups.contact_info.description")} - - - - - - - - - - - - {/* Configuraciones Adicionales */} - - - {t("form_groups.additional_config.title")} - {t("form_groups.additional_config.description")} - - - - - - - - - - - -
- -
- - ); -}; diff --git a/modules/customers/src/web/pages/create/index.ts b/modules/customers/src/web/pages/create/index.ts index c6262006..67210436 100644 --- a/modules/customers/src/web/pages/create/index.ts +++ b/modules/customers/src/web/pages/create/index.ts @@ -1 +1 @@ -export * from "./create"; +export * from "./customer-create"; diff --git a/modules/customers/src/web/pages/update/customer-basic-info-fields.tsx b/modules/customers/src/web/pages/update/customer-basic-info-fields.tsx deleted file mode 100644 index 396d6b6f..00000000 --- a/modules/customers/src/web/pages/update/customer-basic-info-fields.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import { TaxesMultiSelectField } from "@erp/core/components"; -import { TextAreaField, TextField } from "@repo/rdx-ui/components"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - RadioGroup, - RadioGroupItem, -} from "@repo/shadcn-ui/components"; -import { Control } from "react-hook-form"; -import { useTranslation } from "../../i18n"; -import { CustomerUpdateData } from "../../schemas"; - -export const CustomerBasicInfoFields = ({ control }: { control: Control }) => { - const { t } = useTranslation(); - - return ( - - - Identificación - - -
-
- ( - - {t("form_fields.customer_type.label")} - - - - - - - - {t("form_fields.customer_type.company")} - - - - - - - - {t("form_fields.customer_type.individual")} - - - - - - - )} - /> -
-
-
-
- -
- -
- -
- -
- -
-
- -
- -
-
-
- ); - - return ( -
-
-

- {t("form_groups.basic_info.title")} -

-

- {t("form_groups.basic_info.description")} -

- -
-
- ( - - {t("form_fields.customer_type.label")} - - - - - - - - {t("form_fields.customer_type.company")} - - - - - - - - {t("form_fields.customer_type.individual")} - - - - - - - )} - /> -
- -
- -
- -
- -
- -
- -
-
-
-
- ); - - return ( - - - {t("form_groups.basic_info.title")} - {t("form_groups.basic_info.description")} - - - ( - - {t("form_fields.customer_type.label")} - - - - - - - - {t("form_fields.customer_type.company")} - - - - - - - - {t("form_fields.customer_type.individual")} - - - - - - - )} - /> - - - - - - - ); -}; diff --git a/modules/customers/src/web/pages/update/customer-edit-form.tsx b/modules/customers/src/web/pages/update/customer-edit-form.tsx deleted file mode 100644 index b431de83..00000000 --- a/modules/customers/src/web/pages/update/customer-edit-form.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { zodResolver } from "@hookform/resolvers/zod"; -import { FieldErrors, useForm } from "react-hook-form"; - -import { Form } from "@repo/shadcn-ui/components"; - -import { useUnsavedChangesNotifier } from "@erp/core/hooks"; -import { FormDebug } from "../../components/form-debug"; -import { useTranslation } from "../../i18n"; -import { CustomerData, CustomerUpdateData, CustomerUpdateSchema } from "../../schemas"; -import { CustomerAdditionalConfigFields } from "./customer-additional-config-fields"; -import { CustomerAddressFields } from "./customer-address-fields"; -import { CustomerBasicInfoFields } from "./customer-basic-info-fields"; -import { CustomerContactFields } from "./customer-contact-fields"; - -interface CustomerFormProps { - defaultValues: CustomerData; // ✅ ya no recibe DTO - isPending?: boolean; - onSubmit: (data: CustomerUpdateData) => void; - onError: (errors: FieldErrors) => void; -} - -export const CustomerEditForm = ({ defaultValues, onSubmit, isPending }: CustomerFormProps) => { - const { t } = useTranslation(); - - const form = useForm({ - resolver: zodResolver(CustomerUpdateSchema), - defaultValues, - disabled: isPending, - }); - - const { - watch, - formState: { isDirty, dirtyFields }, - } = form; - - useUnsavedChangesNotifier({ - isDirty, - }); - - const currentValues = watch(); - - const handleSubmit = (data: CustomerUpdateData) => { - console.log("Datos del formulario:", data); - const changedData: Record = {}; - - Object.keys(dirtyFields).forEach((field) => { - const value = String(currentValues[field as keyof CustomerUpdateData]); - changedData[field] = value; - }); - - console.log(changedData); - - onSubmit(changedData); - }; - - const handleError = (errors: FieldErrors) => { - console.error("Errores en el formulario:", errors); - // Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario - }; - - const handleCancel = () => { - form.reset(defaultValues); - }; - - return ( -
- - -
-
- - - - -
-
- - - ); -}; diff --git a/modules/customers/src/web/pages/update/update.tsx b/modules/customers/src/web/pages/update/customer-update.tsx similarity index 88% rename from modules/customers/src/web/pages/update/update.tsx rename to modules/customers/src/web/pages/update/customer-update.tsx index 778cbe66..0cc03ea0 100644 --- a/modules/customers/src/web/pages/update/update.tsx +++ b/modules/customers/src/web/pages/update/customer-update.tsx @@ -5,15 +5,19 @@ import { useNavigate } from "react-router-dom"; import { useUrlParamId } from "@erp/core/hooks"; import { showErrorToast, showSuccessToast } from "@repo/shadcn-ui/lib/utils"; import { FieldErrors } from "react-hook-form"; -import { CustomerEditorSkeleton, ErrorAlert, NotFoundCard } from "../../components"; +import { + CustomerEditForm, + CustomerEditorSkeleton, + ErrorAlert, + NotFoundCard, +} from "../../components"; import { useCustomerQuery, useUpdateCustomerMutation } from "../../hooks"; import { useTranslation } from "../../i18n"; -import { CustomerUpdateData } from "../../schemas"; -import { CustomerEditForm } from "./customer-edit-form"; +import { CustomerFormData } from "../../schemas"; export const CustomerUpdate = () => { - const { t } = useTranslation(); const customerId = useUrlParamId(); + const { t } = useTranslation(); const navigate = useNavigate(); // 1) Estado de carga del cliente (query) @@ -33,7 +37,8 @@ export const CustomerUpdate = () => { } = useUpdateCustomerMutation(); // 3) Submit con navegación condicionada por éxito - const handleSubmit = async (formData: CustomerUpdateData) => { + const handleSubmit = async (formData: CustomerFormData) => { + console.log(formData); try { const result = await mutateAsync({ id: customerId!, data: formData }); console.log(result); @@ -48,7 +53,7 @@ export const CustomerUpdate = () => { } }; - const handleError = (errors: FieldErrors) => { + const handleError = (errors: FieldErrors) => { console.error("Errores en el formulario:", errors); // Aquí puedes manejar los errores, por ejemplo, mostrar un mensaje al usuario }; @@ -110,6 +115,7 @@ export const CustomerUpdate = () => { className='cursor-pointer' onClick={(e) => { e.preventDefault(); + navigate("/customers/list"); }} > {t("common.cancel")} @@ -117,7 +123,7 @@ export const CustomerUpdate = () => {