recibir la imagen en la api corectamente
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
ApiResponseSchema,
|
||||
InventoryTable,
|
||||
productMutate,
|
||||
test,
|
||||
// editInventory,
|
||||
productApiResponseSchema,
|
||||
getProduct
|
||||
@@ -138,6 +139,36 @@ export const createProductAction = async (payload: InventoryTable) => {
|
||||
return payloadWithoutId;
|
||||
};
|
||||
|
||||
export const updateUserAction2 = async (payload: InventoryTable) => {
|
||||
try {
|
||||
// const { id, urlImg, ...payloadWithoutId } = payload;
|
||||
|
||||
// const formData = new FormData();
|
||||
// formData.append('file', urlImg);
|
||||
|
||||
|
||||
// console.log(formData);
|
||||
|
||||
|
||||
const [error, data] = await safeFetchApi(
|
||||
test,
|
||||
`/pictures/upload`,
|
||||
'POST',
|
||||
payload,
|
||||
);
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
throw new Error(error?.message || 'Error al actualizar el producto');
|
||||
}
|
||||
// console.log(data);
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const updateUserAction = async (payload: InventoryTable) => {
|
||||
try {
|
||||
const { id, ...payloadWithoutId } = payload;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { EditInventory } from "../schemas/inventory";
|
||||
import { updateUserAction, createProductAction } from "../actions/actions";
|
||||
import { updateUserAction, createProductAction, updateUserAction2 } from "../actions/actions";
|
||||
|
||||
// Create mutation
|
||||
export function useCreateUser() {
|
||||
@@ -8,7 +8,6 @@ export function useCreateUser() {
|
||||
const mutation = useMutation({
|
||||
mutationFn: (data: EditInventory) => createProductAction(data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product'] }),
|
||||
// onError: (e) => console.error('Error:', e),
|
||||
})
|
||||
return mutation
|
||||
}
|
||||
@@ -17,7 +16,8 @@ export function useCreateUser() {
|
||||
export function useUpdateUser() {
|
||||
const queryClient = useQueryClient();
|
||||
const mutation = useMutation({
|
||||
mutationFn: (data: EditInventory) => updateUserAction(data),
|
||||
// mutationFn: (data: EditInventory) => updateUserAction(data),
|
||||
mutationFn: (data: any) => updateUserAction2(data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product'] }),
|
||||
onError: (e) => console.error('Error:', e)
|
||||
})
|
||||
|
||||
@@ -106,6 +106,11 @@ export const productApiResponseSchema = z.object({
|
||||
}),
|
||||
})
|
||||
|
||||
export const test = z.object({
|
||||
// message: z.string(),
|
||||
data: z.array(z.string()),
|
||||
})
|
||||
|
||||
export const productMutate = z.object({
|
||||
message: z.string(),
|
||||
data: product,
|
||||
|
||||
13
apps/web/feactures/inventory/utils/sizeFormate.ts
Normal file
13
apps/web/feactures/inventory/utils/sizeFormate.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export const sizeFormate = (size: number) => {
|
||||
let tamañoFormateado = '';
|
||||
if (size < 1024) {
|
||||
tamañoFormateado = size + ' bytes';
|
||||
} else if (size < 1024 * 1024) {
|
||||
tamañoFormateado = (size / 1024).toFixed(2) + ' KB';
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
tamañoFormateado = (size / (1024 * 1024)).toFixed(2) + ' MB';
|
||||
} else {
|
||||
tamañoFormateado = (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
||||
}
|
||||
return tamañoFormateado;
|
||||
}
|
||||
Reference in New Issue
Block a user