recibir la imagen en la api corectamente
This commit is contained in:
@@ -29,7 +29,7 @@ export class UsersController {
|
|||||||
@ApiOperation({ summary: 'Get all products with pagination and filters' })
|
@ApiOperation({ summary: 'Get all products with pagination and filters' })
|
||||||
@ApiResponse({ status: 200, description: 'Return paginated products.' })
|
@ApiResponse({ status: 200, description: 'Return paginated products.' })
|
||||||
async findAllByUserId(@Req() req: Request, @Query() paginationDto: PaginationDto) {
|
async findAllByUserId(@Req() req: Request, @Query() paginationDto: PaginationDto) {
|
||||||
console.log(req['user'].id)
|
// console.log(req['user'].id)
|
||||||
// const id = 1
|
// const id = 1
|
||||||
const id = Number(req['user'].id);
|
const id = Number(req['user'].id);
|
||||||
const result = await this.inventoryService.findAllByUserId(id,paginationDto);
|
const result = await this.inventoryService.findAllByUserId(id,paginationDto);
|
||||||
|
|||||||
@@ -8,8 +8,14 @@ export class PicturesController {
|
|||||||
constructor(private readonly picturesService: PicturesService) {}
|
constructor(private readonly picturesService: PicturesService) {}
|
||||||
|
|
||||||
@Post('upload')
|
@Post('upload')
|
||||||
@UseInterceptors(FilesInterceptor('files'))
|
@UseInterceptors(FilesInterceptor('urlImg'))
|
||||||
async uploadFile(@UploadedFiles() files: Express.Multer.File[]) {
|
async uploadFile(@UploadedFiles() files: Express.Multer.File[]) {
|
||||||
return this.picturesService.saveImages(files);
|
console.log(files);
|
||||||
|
|
||||||
|
|
||||||
|
// const result = await this.picturesService.saveImages(files);
|
||||||
|
// console.log(result);
|
||||||
|
|
||||||
|
return {data: ["result"]}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,18 +12,24 @@ export class PicturesService {
|
|||||||
*/
|
*/
|
||||||
async saveImages(file: Express.Multer.File[]): Promise<string[]> {
|
async saveImages(file: Express.Multer.File[]): Promise<string[]> {
|
||||||
|
|
||||||
const picturesPath = join(__dirname, '..', '..', 'pictures');
|
const picturesPath = join(__dirname, '..', '..', '..', '..', 'uploads','pict');
|
||||||
|
|
||||||
let images : string[] = [];
|
let images : string[] = [];
|
||||||
|
|
||||||
|
console.log(file);
|
||||||
|
|
||||||
file.forEach(async (pic) => {
|
|
||||||
const fileName = `${Date.now()}-${pic.originalname}`;
|
|
||||||
const filePath = join(picturesPath, fileName);
|
|
||||||
await writeFile(filePath, pic.buffer);
|
|
||||||
images.push(`/pictures/${fileName}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
// file.forEach(async (file) => {
|
||||||
|
// // count++
|
||||||
|
// // const fileName = `${Date.now()}-${count++}-${file.originalname}`;
|
||||||
|
// // console.log(fileName);
|
||||||
|
// // const filePath = join(picturesPath, fileName);
|
||||||
|
// // await writeFile(filePath, file.buffer);
|
||||||
|
// // images.push(fileName);
|
||||||
|
// });
|
||||||
|
// return [file[0].originalname]
|
||||||
return images;
|
return images;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
ApiResponseSchema,
|
ApiResponseSchema,
|
||||||
InventoryTable,
|
InventoryTable,
|
||||||
productMutate,
|
productMutate,
|
||||||
|
test,
|
||||||
// editInventory,
|
// editInventory,
|
||||||
productApiResponseSchema,
|
productApiResponseSchema,
|
||||||
getProduct
|
getProduct
|
||||||
@@ -138,6 +139,36 @@ export const createProductAction = async (payload: InventoryTable) => {
|
|||||||
return payloadWithoutId;
|
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) => {
|
export const updateUserAction = async (payload: InventoryTable) => {
|
||||||
try {
|
try {
|
||||||
const { id, ...payloadWithoutId } = payload;
|
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 { editInventory, EditInventory } from '@/feactures/inventory/schemas/inventory'; // Renombrado EditInventory para claridad
|
||||||
import { Textarea } from '@repo/shadcn/components/ui/textarea';
|
import { Textarea } from '@repo/shadcn/components/ui/textarea';
|
||||||
import {STATUS} from '@/constants/status'
|
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 {
|
interface UpdateFormProps {
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
@@ -37,10 +54,12 @@ export function UpdateForm({
|
|||||||
const {
|
const {
|
||||||
mutate: saveAccountingAccounts,
|
mutate: saveAccountingAccounts,
|
||||||
isPending: isSaving,
|
isPending: isSaving,
|
||||||
isError, // isError no se usa en el template, podrías usarlo para mostrar un mensaje global
|
isError,
|
||||||
} = useUpdateUser();
|
} = useUpdateUser();
|
||||||
|
|
||||||
const defaultformValues: EditInventory = {
|
const [sizeFile, setSizeFile] = useState('0 bytes');
|
||||||
|
|
||||||
|
const defaultformValues: FormDataWithFiles = { // Usamos el nuevo tipo aquí
|
||||||
id: defaultValues?.id,
|
id: defaultValues?.id,
|
||||||
title: defaultValues?.title || '',
|
title: defaultValues?.title || '',
|
||||||
description: defaultValues?.description || '',
|
description: defaultValues?.description || '',
|
||||||
@@ -48,29 +67,52 @@ export function UpdateForm({
|
|||||||
address: defaultValues?.address || '',
|
address: defaultValues?.address || '',
|
||||||
status: defaultValues?.status || 'BORRADOR',
|
status: defaultValues?.status || 'BORRADOR',
|
||||||
stock: defaultValues?.stock ?? 0,
|
stock: defaultValues?.stock ?? 0,
|
||||||
urlImg: [],
|
urlImg: undefined, // Inicializamos como undefined o null para el FileList
|
||||||
userId: defaultValues?.userId
|
userId: defaultValues?.userId
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = useForm<EditInventory>({
|
const form = useForm<FormDataWithFiles>({ // Usamos el nuevo tipo aquí
|
||||||
resolver: zodResolver(editInventory),
|
resolver: zodResolver(formSchemaWithFiles), // Usamos el esquema extendido
|
||||||
defaultValues: defaultformValues,
|
defaultValues: defaultformValues,
|
||||||
mode: 'onChange', // Enable real-time validation
|
mode: 'onChange',
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (data: EditInventory) => {
|
const onSubmit = async (data: FormDataWithFiles) => {
|
||||||
console.log(data);
|
// --- MODIFICACIÓN CLAVE: Crear FormData ---
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
saveAccountingAccounts(data, {
|
// 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: () => {
|
onSuccess: () => {
|
||||||
form.reset();
|
form.reset();
|
||||||
onSuccess?.();
|
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);
|
console.error("Error al guardar el producto:", error);
|
||||||
form.setError('root', {
|
form.setError('root', {
|
||||||
type: 'manual',
|
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 } }) => (
|
render={({ field: { onChange, onBlur, name, ref } }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Imagen</FormLabel>
|
<FormLabel>Imagen</FormLabel>
|
||||||
|
<p>Peso máximo: 2MB / {sizeFile}</p>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -193,7 +236,14 @@ export function UpdateForm({
|
|||||||
name={name}
|
name={name}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onChange={(e) => {
|
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>
|
</FormControl>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { EditInventory } from "../schemas/inventory";
|
import { EditInventory } from "../schemas/inventory";
|
||||||
import { updateUserAction, createProductAction } from "../actions/actions";
|
import { updateUserAction, createProductAction, updateUserAction2 } from "../actions/actions";
|
||||||
|
|
||||||
// Create mutation
|
// Create mutation
|
||||||
export function useCreateUser() {
|
export function useCreateUser() {
|
||||||
@@ -8,7 +8,6 @@ export function useCreateUser() {
|
|||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: (data: EditInventory) => createProductAction(data),
|
mutationFn: (data: EditInventory) => createProductAction(data),
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product'] }),
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product'] }),
|
||||||
// onError: (e) => console.error('Error:', e),
|
|
||||||
})
|
})
|
||||||
return mutation
|
return mutation
|
||||||
}
|
}
|
||||||
@@ -17,7 +16,8 @@ export function useCreateUser() {
|
|||||||
export function useUpdateUser() {
|
export function useUpdateUser() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: (data: EditInventory) => updateUserAction(data),
|
// mutationFn: (data: EditInventory) => updateUserAction(data),
|
||||||
|
mutationFn: (data: any) => updateUserAction2(data),
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product'] }),
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product'] }),
|
||||||
onError: (e) => console.error('Error:', e)
|
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({
|
export const productMutate = z.object({
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
data: product,
|
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;
|
||||||
|
}
|
||||||
@@ -6,9 +6,7 @@ import { z } from 'zod';
|
|||||||
// Crear instancia de Axios con la URL base validada
|
// Crear instancia de Axios con la URL base validada
|
||||||
const fetchApi = axios.create({
|
const fetchApi = axios.create({
|
||||||
baseURL: env.API_URL, // Aquí usamos env.API_URL en vez de process.env.BACKEND_URL
|
baseURL: env.API_URL, // Aquí usamos env.API_URL en vez de process.env.BACKEND_URL
|
||||||
headers: {
|
// No establecer Content-Type aquí. Axios lo manejará automáticamente con FormData.
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Interceptor para incluir el token automáticamente en las peticiones
|
// Interceptor para incluir el token automáticamente en las peticiones
|
||||||
@@ -22,6 +20,16 @@ fetchApi.interceptors.request.use(async (config: any) => {
|
|||||||
if (token) {
|
if (token) {
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **Importante:** Si el body es FormData, elimina el Content-Type para que Axios lo configure automáticamente.
|
||||||
|
// Esto es necesario porque 'multipart/form-data' requiere un 'boundary' que Axios añade.
|
||||||
|
if (config.data instanceof FormData) {
|
||||||
|
delete config.headers['Content-Type'];
|
||||||
|
} else {
|
||||||
|
// Para otros tipos de datos, asegura que el Content-Type sea 'application/json'
|
||||||
|
config.headers['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting auth token:', error);
|
console.error('Error getting auth token:', error);
|
||||||
}
|
}
|
||||||
@@ -33,22 +41,25 @@ fetchApi.interceptors.request.use(async (config: any) => {
|
|||||||
* Función para hacer peticiones con validación de respuesta
|
* Función para hacer peticiones con validación de respuesta
|
||||||
* @param schema - Esquema de Zod para validar la respuesta
|
* @param schema - Esquema de Zod para validar la respuesta
|
||||||
* @param url - Endpoint a consultar
|
* @param url - Endpoint a consultar
|
||||||
* @param config - Configuración opcional de Axios
|
* @param method - Método HTTP (GET, POST, PUT, PATCH, DELETE)
|
||||||
|
* @param data - Datos a enviar (puede ser un objeto JSON o FormData para archivos)
|
||||||
* @returns [error, data] -> Devuelve el error como objeto estructurado si hay fallo, o los datos validados
|
* @returns [error, data] -> Devuelve el error como objeto estructurado si hay fallo, o los datos validados
|
||||||
*/
|
*/
|
||||||
export const safeFetchApi = async <T extends z.ZodSchema<any>>(
|
export const safeFetchApi = async <T extends z.ZodSchema<any>>(
|
||||||
schema: T,
|
schema: T,
|
||||||
url: string,
|
url: string,
|
||||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET',
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET',
|
||||||
body?: any,
|
data?: any, // Renombrado a 'data' para mayor claridad y consistencia con Axios
|
||||||
): Promise<
|
): Promise<
|
||||||
[{ type: string; message: string; details?: any } | null, z.infer<T> | null]
|
[{ type: string; message: string; details?: any } | null, z.infer<T> | null]
|
||||||
> => {
|
> => {
|
||||||
try {
|
try {
|
||||||
|
console.log(url,method,data);
|
||||||
|
|
||||||
const response = await fetchApi({
|
const response = await fetchApi({
|
||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
data: body,
|
data, // Axios usa 'data' para el body de POST/PUT/PATCH
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsed = schema.safeParse(response.data);
|
const parsed = schema.safeParse(response.data);
|
||||||
@@ -60,7 +71,6 @@ export const safeFetchApi = async <T extends z.ZodSchema<any>>(
|
|||||||
expectedSchema: schema,
|
expectedSchema: schema,
|
||||||
data: response.data.data,
|
data: response.data.data,
|
||||||
});
|
});
|
||||||
// console.error(parsed.error.errors)
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'VALIDATION_ERROR',
|
type: 'VALIDATION_ERROR',
|
||||||
@@ -84,7 +94,6 @@ export const safeFetchApi = async <T extends z.ZodSchema<any>>(
|
|||||||
headers: error.config?.headers,
|
headers: error.config?.headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log(error)
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
type: 'API_ERROR',
|
type: 'API_ERROR',
|
||||||
|
|||||||
99
apps/web/lib/fetch.api2.ts
Normal file
99
apps/web/lib/fetch.api2.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
'use server';
|
||||||
|
import { env } from '@/lib/env'; // Importamos la configuración de entorno validada
|
||||||
|
import axios from 'axios';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
// Crear instancia de Axios con la URL base validada
|
||||||
|
const fetchApi = axios.create({
|
||||||
|
baseURL: env.API_URL, // Aquí usamos env.API_URL en vez de process.env.BACKEND_URL
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Interceptor para incluir el token automáticamente en las peticiones
|
||||||
|
fetchApi.interceptors.request.use(async (config: any) => {
|
||||||
|
try {
|
||||||
|
// Importación dinámica para evitar la referencia circular
|
||||||
|
const { auth } = await import('@/lib/auth');
|
||||||
|
const session = await auth();
|
||||||
|
const token = session?.access_token;
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting auth token:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Función para hacer peticiones con validación de respuesta
|
||||||
|
* @param schema - Esquema de Zod para validar la respuesta
|
||||||
|
* @param url - Endpoint a consultar
|
||||||
|
* @param config - Configuración opcional de Axios
|
||||||
|
* @returns [error, data] -> Devuelve el error como objeto estructurado si hay fallo, o los datos validados
|
||||||
|
*/
|
||||||
|
export const safeFetchApi = async <T extends z.ZodSchema<any>>(
|
||||||
|
schema: T,
|
||||||
|
url: string,
|
||||||
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET',
|
||||||
|
body?: any,
|
||||||
|
): Promise<
|
||||||
|
[{ type: string; message: string; details?: any } | null, z.infer<T> | null]
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
const response = await fetchApi({
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
data: body,
|
||||||
|
});
|
||||||
|
|
||||||
|
const parsed = schema.safeParse(response.data);
|
||||||
|
|
||||||
|
if (!parsed.success) {
|
||||||
|
console.error('Validation Error Details:', {
|
||||||
|
errors: parsed.error.errors,
|
||||||
|
receivedData: response.data,
|
||||||
|
expectedSchema: schema,
|
||||||
|
data: response.data.data,
|
||||||
|
});
|
||||||
|
// console.error(parsed.error.errors)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'VALIDATION_ERROR',
|
||||||
|
message: 'Validation error',
|
||||||
|
details: parsed.error.errors,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [null, parsed.data];
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorDetails = {
|
||||||
|
status: error.response?.status,
|
||||||
|
statusText: error.response?.statusText,
|
||||||
|
message: error.message,
|
||||||
|
url: error.config?.url,
|
||||||
|
method: error.config?.method,
|
||||||
|
requestData: error.config?.data,
|
||||||
|
responseData: error.response?.data,
|
||||||
|
headers: error.config?.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
// console.log(error)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'API_ERROR',
|
||||||
|
message: error.response?.data?.message || 'Unknown API error',
|
||||||
|
details: errorDetails,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { fetchApi };
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {};
|
const nextConfig = {
|
||||||
|
experimental: {
|
||||||
|
serverActions: {
|
||||||
|
bodySizeLimit: '2mb',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
Reference in New Issue
Block a user