recibir la imagen en la api corectamente

This commit is contained in:
2025-07-30 13:47:13 -04:00
parent a15505ff2c
commit 339ce85e46
13 changed files with 493 additions and 37 deletions

View File

@@ -0,0 +1,231 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@repo/shadcn/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@repo/shadcn/form';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@repo/shadcn/select';
import { Input } from '@repo/shadcn/input';
import { useForm } from 'react-hook-form';
import { useUpdateUser } from "@/feactures/inventory/hooks/use-mutation";
import { editInventory, EditInventory } from '@/feactures/inventory/schemas/inventory'; // Renombrado EditInventory para claridad
import { Textarea } from '@repo/shadcn/components/ui/textarea';
import {STATUS} from '@/constants/status'
import { useState } from 'react';
import {sizeFormate} from "@/feactures/inventory/utils/sizeFormate"
interface UpdateFormProps {
onSuccess?: () => void;
onCancel?: () => void;
defaultValues?: Partial<EditInventory>;
}
export function UpdateForm({
onSuccess,
onCancel,
defaultValues,
}: UpdateFormProps) {
const {
mutate: saveAccountingAccounts,
isPending: isSaving,
isError, // isError no se usa en el template, podrías usarlo para mostrar un mensaje global
} = useUpdateUser();
const [sizeFile, setSizeFile] = useState('0 bytes');
const defaultformValues: EditInventory = {
id: defaultValues?.id,
title: defaultValues?.title || '',
description: defaultValues?.description || '',
price: defaultValues?.price || '',
address: defaultValues?.address || '',
status: defaultValues?.status || 'BORRADOR',
stock: defaultValues?.stock ?? 0,
urlImg: [],
userId: defaultValues?.userId
};
const form = useForm<EditInventory>({
resolver: zodResolver(editInventory),
defaultValues: defaultformValues,
mode: 'onChange', // Enable real-time validation
});
const onSubmit = async (data: EditInventory) => {
// console.log(data);
saveAccountingAccounts(data, {
onSuccess: () => {
form.reset();
onSuccess?.();
},
onError: (error) => { // Captura el error para mostrar un mensaje más específico si es posible
console.error("Error al guardar el producto:", error);
form.setError('root', {
type: 'manual',
message: error.message || 'Error al guardar el producto', // Mejor mensaje de error
});
},
});
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{form.formState.errors.root && (
<div className="text-destructive text-sm">
{form.formState.errors.root.message}
</div>
)}
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Nombre/Título</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="price"
render={({ field }) => (
<FormItem >
<FormLabel>Precio</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="address"
render={({ field }) => (
<FormItem className='col-span-2'>
<FormLabel>Dirección</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem className='col-span-2'>
<FormLabel>Descripción</FormLabel>
<FormControl>
<Textarea {...field} className="resize-none"/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="stock"
render={({ field }) => (
<FormItem>
<FormLabel>Cantidad/Stock</FormLabel>
<FormControl>
<Input {...field} type="number" onChange={(e) => field.onChange(Number(e.target.value))}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="status"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Estatus</FormLabel>
<Select value={field.value} onValueChange={(value) => field.onChange(value)}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Seleccione un estatus" />
</SelectTrigger>
<SelectContent>
{Object.entries(STATUS).map(([value, label]) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<div className="col-span-2">
<FormField
control={form.control}
name="urlImg"
render={({ field: { onChange, onBlur, name, ref } }) => (
<FormItem>
<FormLabel>Imagen</FormLabel>
<p>Peso máximo: 2MB / {sizeFile}</p>
<FormControl>
<Input
type="file"
multiple
onBlur={onBlur}
name={name}
ref={ref}
onChange={(e) => {
if (e.target.files) {
const files = Array.from(e.target.files);
// Calcular el tamaño total de los archivos
let size = 0;
files.forEach(element => size += element.size)
const tamañoFormateado = sizeFormate(size)
setSizeFile(tamañoFormateado);
}
onChange(e.target.files);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
<div className="flex justify-end gap-4">
<Button variant="outline" type="button" onClick={onCancel}>
Cancelar
</Button>
<Button type="submit" disabled={isSaving}>
{isSaving ? 'Guardando...' : 'Guardar'}
</Button>
</div>
</form>
</Form>
);
}

View File

@@ -22,6 +22,23 @@ import { useUpdateUser } from "@/feactures/inventory/hooks/use-mutation";
import { editInventory, EditInventory } from '@/feactures/inventory/schemas/inventory'; // Renombrado EditInventory para claridad
import { Textarea } from '@repo/shadcn/components/ui/textarea';
import {STATUS} from '@/constants/status'
import { useState } from 'react';
import {sizeFormate} from "@/feactures/inventory/utils/sizeFormate"
import { z } from 'zod'; // Asegúrate de importar Zod
// --- MODIFICACIÓN CLAVE ---
// Extiende tu esquema para incluir el campo de imagen como FileList para el frontend
// En el esquema Zod principal (editInventory), urlImg puede seguir siendo string[] si es lo que guardas en DB.
// Pero para la validación del formulario temporalmente, necesitamos manejar FileList.
// Si tu EditInventory original no contempla FileList, crea un esquema para el formulario.
// Ejemplo de cómo podrías adaptar tu esquema para el formulario
const formSchemaWithFiles = editInventory.extend({
urlImg: z.custom<FileList | undefined | null>().optional(), // Ahora permite FileList para el input file
});
// Define un tipo para los datos del formulario que incluye el FileList
type FormDataWithFiles = z.infer<typeof formSchemaWithFiles>;
interface UpdateFormProps {
onSuccess?: () => void;
@@ -37,10 +54,12 @@ export function UpdateForm({
const {
mutate: saveAccountingAccounts,
isPending: isSaving,
isError, // isError no se usa en el template, podrías usarlo para mostrar un mensaje global
isError,
} = useUpdateUser();
const defaultformValues: EditInventory = {
const [sizeFile, setSizeFile] = useState('0 bytes');
const defaultformValues: FormDataWithFiles = { // Usamos el nuevo tipo aquí
id: defaultValues?.id,
title: defaultValues?.title || '',
description: defaultValues?.description || '',
@@ -48,29 +67,52 @@ export function UpdateForm({
address: defaultValues?.address || '',
status: defaultValues?.status || 'BORRADOR',
stock: defaultValues?.stock ?? 0,
urlImg: [],
urlImg: undefined, // Inicializamos como undefined o null para el FileList
userId: defaultValues?.userId
};
const form = useForm<EditInventory>({
resolver: zodResolver(editInventory),
const form = useForm<FormDataWithFiles>({ // Usamos el nuevo tipo aquí
resolver: zodResolver(formSchemaWithFiles), // Usamos el esquema extendido
defaultValues: defaultformValues,
mode: 'onChange', // Enable real-time validation
mode: 'onChange',
});
const onSubmit = async (data: EditInventory) => {
console.log(data);
saveAccountingAccounts(data, {
const onSubmit = async (data: FormDataWithFiles) => {
// --- MODIFICACIÓN CLAVE: Crear FormData ---
const formData = new FormData();
// Añadir otros campos de texto al FormData
// Asegúrate de que los nombres coincidan con lo que tu backend espera
formData.append('id', data.id ? String(data.id) : ''); // Los IDs a menudo son numéricos, conviértelos a string
formData.append('title', data.title);
formData.append('description', data.description);
formData.append('price', String(data.price)); // Convertir a string
formData.append('address', data.address);
formData.append('status', data.status);
formData.append('stock', String(data.stock)); // Convertir a string
formData.append('userId', data.userId ? String(data.userId) : '');
// Añadir los archivos al FormData
if (data.urlImg && data.urlImg.length > 0) {
// Importante: El nombre del campo 'files' debe coincidir con el interceptor de NestJS (FilesInterceptor('files'))
for (let i = 0; i < data.urlImg.length; i++) {
formData.append('urlImg', data.urlImg[i]); // Asegúrate de que el nombre del campo sea 'files'
}
}
// --- IMPORTANTE: Tu hook `useUpdateUser` DEBE ser capaz de aceptar FormData ---
// Si `useUpdateUser` llama a `safeFetchApi`, entonces `safeFetchApi` ya está preparado
// para recibir `FormData`.
saveAccountingAccounts(formData as any, { // Forzamos el tipo a 'any' si `useUpdateUser` no espera FormData en su tipo
onSuccess: () => {
form.reset();
onSuccess?.();
},
onError: (error) => { // Captura el error para mostrar un mensaje más específico si es posible
onError: (error) => {
console.error("Error al guardar el producto:", error);
form.setError('root', {
type: 'manual',
message: error.message || 'Error al guardar el producto', // Mejor mensaje de error
message: error.message || 'Error al guardar el producto',
});
},
});
@@ -185,6 +227,7 @@ export function UpdateForm({
render={({ field: { onChange, onBlur, name, ref } }) => (
<FormItem>
<FormLabel>Imagen</FormLabel>
<p>Peso máximo: 2MB / {sizeFile}</p>
<FormControl>
<Input
type="file"
@@ -193,7 +236,14 @@ export function UpdateForm({
name={name}
ref={ref}
onChange={(e) => {
onChange(e.target.files);
if (e.target.files) {
const files = Array.from(e.target.files);
let size = 0;
files.forEach(element => size += element.size)
const tamañoFormateado = sizeFormate(size)
setSizeFile(tamañoFormateado);
onChange(e.target.files); // Esto ahora pasará FileList a react-hook-form
}
}}
/>
</FormControl>