diff --git a/modules/core/src/web/manifest.ts b/modules/core/src/web/manifest.ts index f52129d3..64a2b60a 100644 --- a/modules/core/src/web/manifest.ts +++ b/modules/core/src/web/manifest.ts @@ -8,7 +8,7 @@ export const CoreModuleManifest: IModuleClient = { version: MODULE_VERSION, dependencies: [], protected: true, - layout: "app", + layout: "app-sidebar", routes: (params: ModuleClientParams) => { return []; @@ -18,3 +18,4 @@ export const CoreModuleManifest: IModuleClient = { export default CoreModuleManifest; export * from "./lib"; +export * from "./utils"; diff --git a/modules/core/src/web/utils/index.ts b/modules/core/src/web/utils/index.ts new file mode 100644 index 00000000..b9e0e086 --- /dev/null +++ b/modules/core/src/web/utils/index.ts @@ -0,0 +1 @@ +export * from "./text-normalization-utils"; diff --git a/modules/core/src/web/utils/text-normalization-utils.ts b/modules/core/src/web/utils/text-normalization-utils.ts new file mode 100644 index 00000000..94af6bca --- /dev/null +++ b/modules/core/src/web/utils/text-normalization-utils.ts @@ -0,0 +1,21 @@ +/** + * Normalizes a required text form value. + * + * It trims surrounding whitespace and always returns a string. + */ +export const toRequiredText = (value: string): string => { + return value.trim(); +}; + +/** + * Normalizes a nullable text form value. + * + * Rules: + * - empty or whitespace-only string => null + * - otherwise => trimmed string + */ +export const toNullableText = (value: string): string | null => { + const trimmedValue = value.trim(); + + return trimmedValue === "" ? null : trimmedValue; +}; 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 c685049f..5e6856c3 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,32 +1,45 @@ +import { + CountryCodeSchema, + CurrencyCodeSchema, + EmailSchema, + LandPhoneSchema, + LanguageCodeSchema, + MobilePhoneSchema, + PostalCodeSchema, + TinSchema, + WebsiteSchema, +} from "@erp/core"; import { z } from "zod/v4"; -export const CreateCustomerRequestSchema = z.object({ - id: z.string().nonempty(), - - reference: z.string().optional(), - is_company: z.boolean().default(true), - name: z.string(), - trade_name: z.string().optional(), - tin: z.string().optional(), - +export const CreateCustomerAddressRequestSchema = z.object({ street: z.string().optional(), street2: z.string().optional(), city: z.string().optional(), province: z.string().optional(), - postal_code: z.string().optional(), - country: z.string().toLowerCase().default("es").optional(), + postal_code: PostalCodeSchema.optional(), + country: CountryCodeSchema, +}); - 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(), +export const CreateCustomerContactRequestSchema = z.object({ + email_primary: EmailSchema.optional(), + email_secondary: EmailSchema.optional(), + phone_primary: LandPhoneSchema.optional(), + phone_secondary: LandPhoneSchema.optional(), + mobile_primary: MobilePhoneSchema.optional(), + mobile_secondary: MobilePhoneSchema.optional(), + fax: LandPhoneSchema.optional(), + website: WebsiteSchema.optional(), +}); - fax: z.string().optional(), - website: z.string().optional(), +export const CreateCustomerRequestSchema = z.object({ + id: z.uuid(), - legal_record: z.string().optional(), + reference: z.string().optional(), + + is_company: z.boolean(), + name: z.string(), + trade_name: z.string().optional(), + tin: TinSchema.optional(), payment_method_id: z.uuid().nullable().optional(), payment_term_id: z.uuid().nullable().optional(), @@ -34,8 +47,13 @@ export const CreateCustomerRequestSchema = z.object({ uses_equivalence_surcharge: z.boolean().default(false), uses_retention: z.boolean().default(false), - language_code: z.string().toLowerCase().default("es"), - currency_code: z.string().toUpperCase().default("EUR"), + address: CreateCustomerAddressRequestSchema, + contact: CreateCustomerContactRequestSchema.optional(), + + legal_record: z.string().optional(), + + language_code: LanguageCodeSchema, + currency_code: CurrencyCodeSchema, }); export type CreateCustomerRequestDTO = z.infer; diff --git a/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts b/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts index 9bbf0dbb..31bbae11 100644 --- a/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts +++ b/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts @@ -8,7 +8,7 @@ import { MobilePhoneSchema, PostalCodeSchema, TinSchema, - URLSchema, + WebsiteSchema, } from "@erp/core"; import { z } from "zod/v4"; @@ -48,7 +48,7 @@ export const GetCustomerByIdResponseSchema = z.object({ mobile_secondary: MobilePhoneSchema.nullable(), fax: LandPhoneSchema.nullable(), - website: URLSchema.nullable(), + website: WebsiteSchema.nullable(), }), legal_record: z.string().nullable(), diff --git a/modules/customers/src/common/locales/en.json b/modules/customers/src/common/locales/en.json index 0b124acc..cd87b487 100644 --- a/modules/customers/src/common/locales/en.json +++ b/modules/customers/src/common/locales/en.json @@ -89,8 +89,18 @@ "success": { "message": "The customer has been created successfully.", "title": "Customer created" + }, + "validation": { + "message": "There are validation errors. Fix the highlighted fields.", + "title": "Check the fields" } }, + "header": { + "back": "Back", + "delete": "Delete", + "keyboard_shortcuts": "View keyboard shortcuts", + "modified": "Modified" + }, "title": "New customer" }, "update": { @@ -331,4 +341,4 @@ "placeholder": "Select taxes" } } -} \ No newline at end of file +} diff --git a/modules/customers/src/common/locales/es.json b/modules/customers/src/common/locales/es.json index 29f1c1dc..cc7521fc 100644 --- a/modules/customers/src/common/locales/es.json +++ b/modules/customers/src/common/locales/es.json @@ -89,8 +89,18 @@ "success": { "message": "Se ha creado el cliente correctamente.", "title": "Cliente creado" + }, + "validation": { + "message": "Hay errores de validación. Corrige los campos indicados.", + "title": "Revisa los campos" } }, + "header": { + "back": "Volver", + "delete": "Eliminar", + "keyboard_shortcuts": "Ver atajos de teclado", + "modified": "Modificada" + }, "title": "Nuevo cliente" }, "update": { @@ -331,4 +341,4 @@ "placeholder": "Selecciona los impuestos" } } -} \ No newline at end of file +} diff --git a/modules/customers/src/web/create/adapters/customer-to-customer-create-form.adapter.ts b/modules/customers/src/web/create/adapters/customer-to-customer-create-form.adapter.ts deleted file mode 100644 index a29b64fe..00000000 --- a/modules/customers/src/web/create/adapters/customer-to-customer-create-form.adapter.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Customer } from "../../shared"; -import type { CustomerCreateForm } from "../entities"; - -/** - * Mapea un cliente a un formulario de creación de cliente. - * Es decir, adapta el shape de datos del dominio al shape de datos - * que necesita la UI para mostrar el formulario de creación. - * - * @param customer: El cliente que se va a adaptar al formulario de creación. - * @returns Un objeto con el shape de CustomerCreateForm, - * con los datos del cliente adaptados para mostrar - * en el formulario de creación. - */ - -export const mapCustomerToCustomerCreateForm = (customer: Customer): CustomerCreateForm => { - return { - reference: customer.reference ?? "", - isCompany: customer.isCompany, - name: customer.name ?? "", - tradeName: customer.tradeName ?? "", - tin: customer.tin ?? "", - - defaultTaxes: customer.defaultTaxes ?? [], - - street: customer.street ?? "", - street2: customer.street2 ?? "", - city: customer.city ?? "", - province: customer.province ?? "", - postalCode: customer.postalCode ?? "", - country: customer.country ?? "es", - - primaryEmail: customer.primaryEmail ?? "", - secondaryEmail: customer.secondaryEmail ?? "", - primaryPhone: customer.primaryPhone ?? "", - secondaryPhone: customer.secondaryPhone ?? "", - primaryMobile: customer.primaryMobile ?? "", - secondaryMobile: customer.secondaryMobile ?? "", - - fax: customer.fax ?? "", - website: customer.website ?? "", - - legalRecord: customer.legalRecord ?? "", - - languageCode: customer.languageCode ?? "es", - currencyCode: customer.currencyCode ?? "EUR", - }; -}; diff --git a/modules/customers/src/web/create/adapters/index.ts b/modules/customers/src/web/create/adapters/index.ts deleted file mode 100644 index 5d44a71b..00000000 --- a/modules/customers/src/web/create/adapters/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./customer-to-customer-create-form.adapter"; diff --git a/modules/customers/src/web/create/controllers/index.ts b/modules/customers/src/web/create/controllers/index.ts index 14dd4901..c007c093 100644 --- a/modules/customers/src/web/create/controllers/index.ts +++ b/modules/customers/src/web/create/controllers/index.ts @@ -1,2 +1,3 @@ export * from "./use-customer-create.controller"; +export * from "./use-customer-create-fiscal-controller"; export * from "./use-customer-create-page.controller"; diff --git a/modules/customers/src/web/create/controllers/use-customer-create-fiscal-controller.ts b/modules/customers/src/web/create/controllers/use-customer-create-fiscal-controller.ts new file mode 100644 index 00000000..20e502b0 --- /dev/null +++ b/modules/customers/src/web/create/controllers/use-customer-create-fiscal-controller.ts @@ -0,0 +1,79 @@ +import { + getPaymentMethodOptions, + usePaymentMethodsListQuery, +} from "@erp/catalogs/client/payment-methods"; +import { + getPaymentTermOptions, + usePaymentTermsListQuery, +} from "@erp/catalogs/client/payment-terms"; +import { getTaxRegimeOptions, useTaxRegimesListQuery } from "@erp/catalogs/client/tax-regimes"; +import { useMemo } from "react"; + +export const useCustomerCreateFiscalController = () => { + const paymentMethodsQuery = usePaymentMethodsListQuery({ + criteria: { + pageSize: 999, + filters: [ + { + field: "isActive", + operator: "EQUALS", + value: "true", + }, + ], + }, + }); + + const paymentTermsQuery = usePaymentTermsListQuery({ + criteria: { + filters: [ + { + field: "isActive", + operator: "EQUALS", + value: "true", + }, + ], + }, + }); + + const taxRegimesQuery = useTaxRegimesListQuery({ + criteria: { + filters: [ + { + field: "isActive", + operator: "EQUALS", + value: "true", + }, + ], + }, + }); + + const paymentMethodOptions = useMemo(() => { + return getPaymentMethodOptions(paymentMethodsQuery.data?.items ?? []); + }, [paymentMethodsQuery.data?.items]); + + const paymentTermOptions = useMemo(() => { + return getPaymentTermOptions(paymentTermsQuery.data?.items ?? []); + }, [paymentTermsQuery.data?.items]); + + const taxRegimeOptions = useMemo(() => { + return getTaxRegimeOptions(taxRegimesQuery.data?.items ?? []); + }, [taxRegimesQuery.data?.items]); + + return { + paymentMethodOptions, + paymentTermOptions, + taxRegimeOptions, + + isLoading: paymentMethodsQuery.isLoading || paymentTermsQuery.isLoading, + + isFetching: paymentMethodsQuery.isFetching || paymentTermsQuery.isFetching, + + isError: paymentMethodsQuery.isError || paymentTermsQuery.isError, + + error: paymentMethodsQuery.error ?? paymentTermsQuery.error, + }; +}; + +export type UseCustomerCreateFiscalControllerResult = ReturnType< + typeof useCustomerCreateFiscalController +>; diff --git a/modules/customers/src/web/create/controllers/use-customer-create-page.controller.ts b/modules/customers/src/web/create/controllers/use-customer-create-page.controller.ts index 2da45c95..13c587db 100644 --- a/modules/customers/src/web/create/controllers/use-customer-create-page.controller.ts +++ b/modules/customers/src/web/create/controllers/use-customer-create-page.controller.ts @@ -3,15 +3,17 @@ import { useNavigate, useSearchParams } from "react-router-dom"; import { useCustomerCreateController } from "./use-customer-create.controller"; export const useCustomerCreatePageController = () => { - const navigate = useNavigate(); const [searchParams] = useSearchParams(); - - const returnTo = searchParams.get("returnTo") ?? "/customers"; + const navigate = useNavigate(); const createCtrl = useCustomerCreateController({ - onCreated: (customer) => navigate(`/customers/${customer.id}`), + onCreated: (createdCustomer) => { + navigate(`/customers/${createdCustomer.id}/edit`); + }, }); + const returnTo = searchParams.get("returnTo") ?? "/customers"; + return { createCtrl, returnTo, diff --git a/modules/customers/src/web/create/controllers/use-customer-create.controller.ts b/modules/customers/src/web/create/controllers/use-customer-create.controller.ts index 47ced575..862ac08f 100644 --- a/modules/customers/src/web/create/controllers/use-customer-create.controller.ts +++ b/modules/customers/src/web/create/controllers/use-customer-create.controller.ts @@ -1,28 +1,49 @@ +import { applyValidationErrorCollection } from "@erp/core"; +import { formHasAnyDirty } from "@erp/core/client"; import { useHookForm } from "@erp/core/hooks"; -import { showErrorToast, showSuccessToast, showWarningToast } from "@repo/rdx-ui/helpers"; +import { + UniqueID, + type ValidationErrorCollection, + isValidationErrorCollection, +} from "@repo/rdx-ddd"; +import { + focusFirstInputFormError, + showErrorToast, + showSuccessToast, + showWarningToast, +} from "@repo/rdx-ui/helpers"; import { useId } from "react"; import type { FieldErrors } from "react-hook-form"; import { useTranslation } from "../../i18n"; -import type { CreateCustomerParams, Customer } from "../../shared"; -import { useCustomerCreateMutation } from "../../shared/hooks/use-customer-create-mutation"; -import { mapCustomerToCustomerCreateForm } from "../adapters"; -import { - type CustomerCreateForm, - CustomerCreateFormSchema, - defaultCustomerCreateForm, -} from "../entities"; -import { buildCreateCustomerParams } from "../utils"; +import { type CreateCustomerParams, type Customer, useCustomerCreateMutation } from "../../shared"; +import { type CustomerCreateForm, CustomerCreateFormSchema } from "../entities"; +import { buildCreateCustomerParams, buildCustomerCreateDefault } from "../utils"; +import { buildCustomerCreateData } from "../utils/build-customer-create-data"; + +import { useCustomerCreateFiscalController } from "./use-customer-create-fiscal-controller"; export interface UseCustomerCreateControllerOptions { onCreated?(created: Customer): void; - successToasts?: boolean; // mostrar o no toast automáticcamente + successToasts?: boolean; // mostrar o no toast automáticamente - onError?(error: Error, inputPayload: CreateCustomerParams): void; - errorToasts?: boolean; // mostrar o no toast automáticcamente + onError?(error: Error, params: CreateCustomerParams): void; + errorToasts?: boolean; // mostrar o no toast automáticamente } -export const useCustomerCreateController = (options?: UseCustomerCreateControllerOptions) => { +const normalizeSubmitError = (error: unknown): Error | ValidationErrorCollection => { + if (isValidationErrorCollection(error)) { + return error; + } + + if (error instanceof Error) { + return error; + } + + return new Error("Unknown error"); +}; + +export const useCreateCustomerController = (options?: UseCustomerCreateControllerOptions) => { const { t } = useTranslation(); const formId = useId(); // id único por instancia @@ -37,29 +58,38 @@ export const useCustomerCreateController = (options?: UseCustomerCreateControlle // 2) Form hook const form = useHookForm({ resolverSchema: CustomerCreateFormSchema, - initialValues: defaultCustomerCreateForm, + initialValues: buildCustomerCreateDefault(), disabled: isCreating, }); /** Handlers */ const resetForm = () => { - form.reset(defaultCustomerCreateForm, { keepDirty: false }); + form.reset(buildCustomerCreateDefault(), { keepDirty: false }); }; const submitHandler = form.handleSubmit( async (formData: CustomerCreateForm) => { - const params: CreateCustomerParams = buildCreateCustomerParams(formData); + if (!formHasAnyDirty(form.formState.dirtyFields)) { + showWarningToast( + t("create.feedback.no_changes.title"), + t("create.feedback.no_changes.message") + ); + return; + } + + const data = buildCustomerCreateData(formData); + const customerId = UniqueID.generateNewID().toString(); + + const params = buildCreateCustomerParams(customerId, data); + console.log("Enviando creación con params:", params); try { - // Enviamos cambios al servidor - const created = await mutateAsync(params); // payload es CustomerCreatePayload + const created = await mutateAsync(params); - // Ha ido bien -> actualizamos form con datos reales + // Ha ido bien -> actualizamos form con datos por defecto // keepDirty = false -> deja el formulario sin cambios sin tener que esperar al siguiente render. - form.reset(mapCustomerToCustomerCreateForm(created), { - keepDirty: false, - }); + resetForm(); if (options?.successToasts !== false) { showSuccessToast( @@ -69,22 +99,45 @@ export const useCustomerCreateController = (options?: UseCustomerCreateControlle } options?.onCreated?.(created); } catch (error: unknown) { - const normalizedError = - error instanceof Error ? error : new Error(t("create.errors.unknown")); + console.log(error); + const normalizedError = normalizeSubmitError(error); + console.debug(normalizedError); + + // No revierto el form para que no se pierdan los cambios que + // ha hecho el usuario y no han sido guardados. + /*resetForm()*/ + + if (isValidationErrorCollection(normalizedError)) { + applyValidationErrorCollection(form, normalizedError); + + console.log("Errores de validación aplicados al form:", form.formState.errors); + + focusFirstInputFormError(form); + + if (options?.errorToasts !== false) { + showWarningToast( + t("create.feedback.validation.title"), + t("create.feedback.validation.message") + ); + } + + options?.onError?.(normalizedError, params); + return; + } if (options?.errorToasts !== false) { - showErrorToast(t("create.errors.title"), normalizedError.message); + showErrorToast( + t("create.errors.title"), + normalizedError.message || t("create.errors.unknown") + ); } options?.onError?.(normalizedError, params); } }, (errors: FieldErrors) => { - const firstKey = Object.keys(errors)[0] as keyof CustomerCreateForm | undefined; - - if (firstKey) { - document.querySelector(`[name="${String(firstKey)}"]`)?.focus(); - } + console.error(errors); + focusFirstInputFormError(form); showWarningToast( t("common.feedback.validation.title"), @@ -94,11 +147,13 @@ export const useCustomerCreateController = (options?: UseCustomerCreateControlle ); // Evento onSubmit ya preparado para el
- const onSubmit = (event: React.FormEvent) => { + const onSubmit = (event: React.SubmitEvent) => { event.stopPropagation(); // <-- evita que el submit se propage por los padre en el árbol DOM submitHandler(event); }; + const fiscalCtrl = useCustomerCreateFiscalController(); + return { // form form, @@ -113,8 +168,13 @@ export const useCustomerCreateController = (options?: UseCustomerCreateControlle isCreateError, createError, + // Otros controladores + fiscalCtrl, + // No devolver FormProvider, así el controller es más // flexible y reusable (p.ej. para un modal) // FormProvider, }; }; + +export const useCustomerCreateController = useCreateCustomerController; diff --git a/modules/customers/src/web/create/entities/customer-create-data.entity.ts b/modules/customers/src/web/create/entities/customer-create-data.entity.ts new file mode 100644 index 00000000..7b2c67b1 --- /dev/null +++ b/modules/customers/src/web/create/entities/customer-create-data.entity.ts @@ -0,0 +1,46 @@ +/** + * CustomerCreateData representa los datos normalizados de creación + * antes de construir el contrato de API. + * + * Reglas: + * - mantiene naming camelCase porque sigue siendo contrato frontend. + * - los selects opcionales sin selección quedan como undefined. + * - no es DTO backend. + */ + +export type CustomerCreateData = { + isCompany: boolean; + name: string; + tradeName?: string; + tin?: string; + reference?: string; + + street?: string; + street2?: string; + city?: string; + province?: string; + postalCode?: string; + country: string; + + primaryEmail?: string; + secondaryEmail?: string; + primaryPhone?: string; + secondaryPhone?: string; + primaryMobile?: string; + secondaryMobile?: string; + + fax?: string; + website?: string; + + legalRecord?: string; + + paymentMethodId?: string; + paymentTermId?: string; + taxRegimeCode?: string; + + usesEquivalenceSurcharge: boolean; + usesRetention: boolean; + + languageCode: string; + currencyCode: string; +}; diff --git a/modules/customers/src/web/create/entities/customer-create-form.entity.ts b/modules/customers/src/web/create/entities/customer-create-form.entity.ts index 61ecca56..9588bb19 100644 --- a/modules/customers/src/web/create/entities/customer-create-form.entity.ts +++ b/modules/customers/src/web/create/entities/customer-create-form.entity.ts @@ -39,6 +39,13 @@ export interface CustomerCreateForm { legalRecord: string; + paymentMethodId: string; + paymentTermId: string; + taxRegimeCode: string; + + usesEquivalenceSurcharge: boolean; + usesRetention: boolean; + languageCode: string; currencyCode: string; } diff --git a/modules/customers/src/web/create/entities/customer-create-form.schema.ts b/modules/customers/src/web/create/entities/customer-create-form.schema.ts index b073ef39..5d05a875 100644 --- a/modules/customers/src/web/create/entities/customer-create-form.schema.ts +++ b/modules/customers/src/web/create/entities/customer-create-form.schema.ts @@ -24,34 +24,46 @@ import { z } from "zod/v4"; * - sin detalles impuestos por el widget */ -export const CustomerCreateFormSchema = z.object({ - reference: z.string(), - isCompany: z.boolean(), +export const CustomerCreateFormSchema = z.object( + { + reference: z.string(), + isCompany: z.boolean(), - name: z.string().min(1, "El nombre es obligatorio"), - tradeName: z.string(), - tin: TinSchema.or(z.literal("")), + name: z.string().min(1, "El nombre es obligatorio"), + tradeName: z.string().or(z.literal("")), + tin: TinSchema.or(z.literal("")), - street: z.string(), - street2: z.string(), - city: z.string(), - province: z.string(), - postalCode: PostalCodeSchema.or(z.literal("")), - country: CountryCodeSchema.or(z.literal("")), + street: z.string(), + street2: z.string(), + city: z.string(), + province: z.string(), + postalCode: PostalCodeSchema.or(z.literal("")), + country: CountryCodeSchema.or(z.literal("")), - primaryEmail: z.email("Email inválido").or(z.literal("")), - secondaryEmail: z.email("Email inválido").or(z.literal("")), + primaryEmail: z.email("Email inválido").or(z.literal("")), + secondaryEmail: z.email("Email inválido").or(z.literal("")), - primaryPhone: LandPhoneSchema.or(z.literal("")), - secondaryPhone: LandPhoneSchema.or(z.literal("")), - primaryMobile: MobilePhoneSchema.or(z.literal("")), - secondaryMobile: MobilePhoneSchema.or(z.literal("")), + primaryPhone: LandPhoneSchema.or(z.literal("")), + secondaryPhone: LandPhoneSchema.or(z.literal("")), + primaryMobile: MobilePhoneSchema.or(z.literal("")), + secondaryMobile: MobilePhoneSchema.or(z.literal("")), - fax: LandPhoneSchema.or(z.literal("")), - website: WebsiteSchema.or(z.literal("")), + fax: LandPhoneSchema.or(z.literal("")), + website: WebsiteSchema.or(z.literal("")), - legalRecord: z.string().or(z.literal("")), + legalRecord: z.string().or(z.literal("")), - languageCode: LanguageCodeSchema, - currencyCode: CurrencyCodeSchema, -}); + paymentMethodId: z.string().or(z.literal("")), + paymentTermId: z.string().or(z.literal("")), + taxRegimeCode: z.string().or(z.literal("")), + + usesEquivalenceSurcharge: z.boolean(), + usesRetention: z.boolean(), + + languageCode: LanguageCodeSchema, + currencyCode: CurrencyCodeSchema, + }, + { + error: "error de esquema", + } +); diff --git a/modules/customers/src/web/create/entities/index.ts b/modules/customers/src/web/create/entities/index.ts index 03c7bb93..59d92cc0 100644 --- a/modules/customers/src/web/create/entities/index.ts +++ b/modules/customers/src/web/create/entities/index.ts @@ -1,3 +1,3 @@ +export * from "./customer-create-data.entity"; export * from "./customer-create-form.entity"; export * from "./customer-create-form.schema"; -export * from "./customer-create-form-default.entity"; diff --git a/modules/customers/src/web/create/ui/components/customer-editor-skeleton.tsx b/modules/customers/src/web/create/ui/components/customer-editor-skeleton.tsx new file mode 100644 index 00000000..2e2320e9 --- /dev/null +++ b/modules/customers/src/web/create/ui/components/customer-editor-skeleton.tsx @@ -0,0 +1,32 @@ +import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components"; +import { Button } from "@repo/shadcn-ui/components"; + +import { useTranslation } from "../../../i18n"; + +export const CustomerEditorSkeleton = () => { + const { t } = useTranslation(); + return ( + <> + +
+