This commit is contained in:
David Arranz 2025-09-23 13:48:19 +02:00
parent bec5a54756
commit 01ca0bd5dc
8 changed files with 94 additions and 47 deletions

View File

@ -134,8 +134,8 @@ export const CustomerInvoicesListGrid = () => {
resizable: true,
},
pagination: true,
paginationPageSize: 10,
paginationPageSizeSelector: [10, 20, 30, 50],
paginationPageSize: 15,
paginationPageSizeSelector: [10, 15, 20, 30, 50],
localeText: AG_GRID_LOCALE_ES,
}),
[autoSizeStrategy, colDefs]

View File

@ -4,18 +4,18 @@ export const CreateCustomerRequestSchema = z.object({
id: z.string().nonempty(),
reference: z.string().optional(),
is_company: z.string().toLowerCase().default("false"),
is_company: z.string().toLowerCase().default("true"),
name: z.string(),
trade_name: z.string().optional(),
tin: z.string().optional(),
default_taxes: z.array(z.string()).default([]).optional(),
default_taxes: z.array(z.string()).default([]),
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(),
country: z.string().toLowerCase().default("es").optional(),
email_primary: z.string().optional(),
email_secondary: z.string().optional(),

View File

@ -31,7 +31,11 @@ export const CustomersListGrid = () => {
isLoading: isLoadingCustomers,
isError: isLoadError,
error: loadError,
} = useCustomersQuery();
} = useCustomersQuery({
pagination: {
pageSize: 999,
},
});
// Column Definitions: Defines & controls grid columns.
const [colDefs] = useState<ColDef[]>([
@ -113,8 +117,8 @@ export const CustomersListGrid = () => {
resizable: true,
},
pagination: true,
paginationPageSize: 10,
paginationPageSizeSelector: [10, 20, 30, 50],
paginationPageSize: 15,
paginationPageSizeSelector: [10, 15, 20, 30, 50],
localeText: AG_GRID_LOCALE_ES,
}),
[autoSizeStrategy, colDefs]

View File

@ -22,6 +22,7 @@ export const CustomerBasicInfoFields = () => {
const { control } = useFormContext<CustomerFormData>();
const isCompany = useWatch({
control,
name: "is_company",
defaultValue: "true",
});
@ -53,7 +54,9 @@ export const CustomerBasicInfoFields = () => {
<FormLabel>{t("form_fields.customer_type.label")}</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
onValueChange={(value: string) => {
field.onChange(value === "false" ? "false" : "true");
}}
defaultValue={field.value ? "true" : "false"}
className='flex items-center gap-6'
>

View File

@ -2,7 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { FieldErrors, FormProvider, useForm } from "react-hook-form";
import { useEffect } from "react";
import { CustomerFormData, CustomerFormSchema } from "../../schemas";
import { CreateCustomerFormSchema, CustomerFormData } from "../../schemas";
import { FormDebug } from "../form-debug";
import { CustomerAdditionalConfigFields } from "./customer-additional-config-fields";
import { CustomerAddressFields } from "./customer-address-fields";
@ -27,7 +27,7 @@ export function CustomerEditForm({
onDirtyChange,
}: CustomerFormProps) {
const form = useForm<CustomerFormData>({
resolver: zodResolver(CustomerFormSchema),
resolver: zodResolver(CreateCustomerFormSchema),
defaultValues: initialValues,
disabled,
});

View File

@ -1,6 +1,7 @@
import { useDataSource } from "@erp/core/hooks";
import { UniqueID } from "@repo/rdx-ddd";
import { UniqueID, ValidationErrorCollection } from "@repo/rdx-ddd";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { CreateCustomerRequestSchema, CustomerCreationResponseDTO } from "../../common";
import { CustomerFormData } from "../schemas";
import { CUSTOMERS_LIST_KEY } from "./use-update-customer-mutation";
@ -11,19 +12,35 @@ type CreateCustomerPayload = {
export function useCreateCustomerMutation() {
const queryClient = useQueryClient();
const dataSource = useDataSource();
const schema = CreateCustomerRequestSchema;
return useMutation<CustomerFormData, Error, CreateCustomerPayload>({
return useMutation<CustomerCreationResponseDTO, Error, CreateCustomerPayload>({
mutationKey: ["customer:create"],
mutationFn: async (payload) => {
const { data } = payload;
const customerId = UniqueID.generateNewID();
const created = await dataSource.createOne("customers", {
const newCustomerData = {
...data,
id: customerId.toString(),
});
return created as CustomerFormData;
};
const result = schema.safeParse(newCustomerData);
if (!result.success) {
// Construye errores detallados
const validationErrors = result.error.issues.map((err) => ({
field: err.path.join("."),
message: err.message,
}));
console.debug(validationErrors);
throw new ValidationErrorCollection("Validation failed", validationErrors);
}
const created = await dataSource.createOne("customers", newCustomerData);
return created as CustomerCreationResponseDTO;
},
onSuccess: () => {

View File

@ -17,35 +17,35 @@ export const CustomerCreate = () => {
// 2) Estado de creación (mutación)
const {
mutateAsync,
mutate,
isPending: isCreating,
isError: isCreateError,
error: createError,
} = useCreateCustomerMutation();
// 3) Submit con navegación condicionada por éxito
const handleSubmit = async (formData: CustomerFormData) => {
console.log("formData => ", formData);
const handleSubmit = (formData: CustomerFormData) => {
mutate(
{ data: formData },
{
onSuccess(data) {
setIsDirty(false);
showSuccessToast(t("pages.create.successTitle"), t("pages.create.successMsg"));
/*const changedData: Record<string, string> = {};
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");
// El timeout es para que a React le dé tiempo a procesar
// el cambio de estado de isDirty / setIsDirty.
setTimeout(() => {
navigate("/customers/list", {
state: { customerId: data.id, isNew: true },
replace: true,
});
}, 0);
},
onError(error) {
showErrorToast(t("pages.create.errorTitle"), error.message);
},
}
} catch (e) {
showErrorToast(t("pages.create.errorTitle"), (e as Error).message);
} finally {
}
);
};
const handleError = (errors: FieldErrors<CustomerFormData>) => {

View File

@ -1,20 +1,30 @@
import * as z from "zod/v4";
export const CustomerFormSchema = z.object({
export const CreateCustomerFormSchema = z.object({
reference: z.string().optional(),
is_company: z.string().optional(),
name: z.string().nonempty(),
is_company: z.enum(["true", "false"]),
name: z
.string({
error: "El nombre es obligatorio",
})
.min(1, "El nombre no puede estar vacío"),
trade_name: z.string().optional(),
tin: z.string().optional(),
default_taxes: z.array(z.string()).optional(), // completo (sustituye), o null => vaciar
default_taxes: z.array(z.string()).default([]),
street: z.string().optional(),
street2: z.string().optional(),
city: z.string().optional(),
province: z.string().optional(),
postal_code: z.string().optional(),
country: z.string().optional(),
country: z
.string({
error: "El país es obligatorio",
})
.min(1, "El país no puede estar vacío")
.toLowerCase() // asegura minúsculas
.default("es"),
email_primary: z.string().optional(),
email_secondary: z.string().optional(),
@ -28,20 +38,33 @@ export const CustomerFormSchema = z.object({
legal_record: z.string().optional(),
language_code: z.string().nonempty(),
currency_code: z.string().nonempty(),
language_code: z
.string({
error: "El idioma es obligatorio",
})
.min(1, "Debe indicar un idioma")
.toUpperCase() // asegura mayúsculas
.default("es"),
currency_code: z
.string({
error: "La moneda es obligatoria",
})
.min(1, "La moneda no puede estar vacía")
.toUpperCase() // asegura mayúsculas
.default("EUR"),
});
export type CustomerFormData = z.infer<typeof CustomerFormSchema>;
export type CustomerFormData = z.infer<typeof CreateCustomerFormSchema>;
export const defaultCustomerFormData: CustomerFormData = {
reference: "",
is_company: "false",
is_company: "true",
name: "",
trade_name: "",
tin: "",
default_taxes: [],
default_taxes: ["iva_21"],
street: "",
street2: "",