almacenar img (sin terminar)
This commit is contained in:
@@ -37,19 +37,19 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.0.0",
|
||||
"@nestjs/core": "^11.0.0",
|
||||
"@nestjs/platform-express": "^11.0.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "^0.40.0",
|
||||
"express": "^5.1.0",
|
||||
"joi": "^17.13.3",
|
||||
"moment": "^2.30.1",
|
||||
"path-to-regexp": "^8.2.0",
|
||||
"pg": "^8.13.3",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"reflect-metadata": "^0.2.0",
|
||||
"rxjs": "^7.8.1"
|
||||
"@nestjs/common": "11.0.0",
|
||||
"@nestjs/core": "11.0.0",
|
||||
"@nestjs/platform-express": "11.0.0",
|
||||
"dotenv": "16.5.0",
|
||||
"drizzle-orm": "0.40.0",
|
||||
"express": "5.1.0",
|
||||
"joi": "17.13.3",
|
||||
"moment": "2.30.1",
|
||||
"path-to-regexp": "8.2.0",
|
||||
"pg": "8.13.3",
|
||||
"pino-pretty": "13.0.0",
|
||||
"reflect-metadata": "0.2.0",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs-modules/mailer": "^2.0.2",
|
||||
|
||||
@@ -19,6 +19,7 @@ import { RolesModule } from './features/roles/roles.module';
|
||||
import { UserRolesModule } from './features/user-roles/user-roles.module';
|
||||
import { SurveysModule } from './features/surveys/surveys.module';
|
||||
import {InventoryModule} from './features/inventory/inventory.module'
|
||||
import { PicturesModule } from './features/pictures/pictures.module';
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
@@ -59,7 +60,8 @@ import {InventoryModule} from './features/inventory/inventory.module'
|
||||
ConfigurationsModule,
|
||||
SurveysModule,
|
||||
LocationModule,
|
||||
InventoryModule
|
||||
InventoryModule,
|
||||
PicturesModule
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
15
apps/api/src/features/pictures/pictures.controller.ts
Normal file
15
apps/api/src/features/pictures/pictures.controller.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
import { Controller, Post, UploadedFiles, UseInterceptors } from '@nestjs/common';
|
||||
import { FilesInterceptor } from '@nestjs/platform-express';
|
||||
import { PicturesService } from './pictures.service';
|
||||
|
||||
@Controller('pictures')
|
||||
export class PicturesController {
|
||||
constructor(private readonly picturesService: PicturesService) {}
|
||||
|
||||
@Post('upload')
|
||||
@UseInterceptors(FilesInterceptor('files'))
|
||||
async uploadFile(@UploadedFiles() files: Express.Multer.File[]) {
|
||||
return this.picturesService.saveImages(files);
|
||||
}
|
||||
}
|
||||
10
apps/api/src/features/pictures/pictures.module.ts
Normal file
10
apps/api/src/features/pictures/pictures.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PicturesController } from './pictures.controller';
|
||||
import { PicturesService } from './pictures.service';
|
||||
|
||||
@Module({
|
||||
controllers: [PicturesController],
|
||||
providers: [PicturesService],
|
||||
})
|
||||
export class PicturesModule {}
|
||||
43
apps/api/src/features/pictures/pictures.service.ts
Normal file
43
apps/api/src/features/pictures/pictures.service.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
@Injectable()
|
||||
export class PicturesService {
|
||||
/**
|
||||
* Guarda una imagen en el directorio de imágenes.
|
||||
* @param file - El archivo de imagen a guardar.
|
||||
* @returns La ruta de la imagen guardada.
|
||||
*/
|
||||
async saveImages(file: Express.Multer.File[]): Promise<string[]> {
|
||||
|
||||
const picturesPath = join(__dirname, '..', '..', 'pictures');
|
||||
|
||||
let images : string[] = [];
|
||||
|
||||
|
||||
file.forEach(async (pic) => {
|
||||
const fileName = `${Date.now()}-${pic.originalname}`;
|
||||
const filePath = join(picturesPath, fileName);
|
||||
await writeFile(filePath, pic.buffer);
|
||||
images.push(`/pictures/${fileName}`);
|
||||
});
|
||||
|
||||
return images;
|
||||
|
||||
|
||||
// // Construye la ruta al directorio de imágenes.
|
||||
// const picturesPath = join(__dirname, '..', '..', 'pictures');
|
||||
// // Crea un nombre de archivo único para la imagen.
|
||||
// const fileName = `${Date.now()}-${file.originalname}`;
|
||||
// // Construye la ruta completa al archivo de imagen.
|
||||
// const filePath = join(picturesPath, fileName);
|
||||
|
||||
// // Escribe el archivo de imagen en el disco.
|
||||
// await writeFile(filePath, file.buffer);
|
||||
|
||||
// // Devuelve la ruta de la imagen guardada.
|
||||
// return `/pictures/${fileName}`;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import { Textarea } from '@repo/shadcn/textarea';
|
||||
import { Input } from '@repo/shadcn/input';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useCreateUser } from "@/feactures/inventory/hooks/use-mutation";
|
||||
import { EditInventory, editInventory, formDataInput } from '@/feactures/inventory/schemas/inventory';
|
||||
import { EditInventory, editInventory } from '@/feactures/inventory/schemas/inventory';
|
||||
import {STATUS} from '@/constants/status'
|
||||
|
||||
interface CreateFormProps {
|
||||
@@ -44,12 +44,12 @@ export function CreateForm({
|
||||
description: '',
|
||||
address: '',
|
||||
price: '',
|
||||
stock: '',
|
||||
stock: 0,
|
||||
urlImg: '',
|
||||
status: ''
|
||||
}
|
||||
|
||||
const form = useForm<formDataInput>({
|
||||
const form = useForm<EditInventory>({
|
||||
resolver: zodResolver(editInventory),
|
||||
defaultValues: defaultformValues,
|
||||
mode: 'onChange', // Enable real-time validation
|
||||
@@ -145,7 +145,7 @@ export function CreateForm({
|
||||
<FormItem>
|
||||
<FormLabel>Cantidad/Stock</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type='number' />
|
||||
<Input {...field} type='number' onChange={(e) => field.onChange(Number(e.target.value))}/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { Input } from '@repo/shadcn/input';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useUpdateUser } from "@/feactures/inventory/hooks/use-mutation";
|
||||
import { editInventory, formDataInput, 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 {STATUS} from '@/constants/status'
|
||||
|
||||
@@ -40,25 +40,25 @@ export function UpdateForm({
|
||||
isError, // isError no se usa en el template, podrías usarlo para mostrar un mensaje global
|
||||
} = useUpdateUser();
|
||||
|
||||
const defaultformValues: formDataInput = {
|
||||
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 ?? '').toString(),
|
||||
urlImg: defaultValues?.urlImg || '',
|
||||
stock: defaultValues?.stock ?? 0,
|
||||
urlImg: [],
|
||||
userId: defaultValues?.userId
|
||||
};
|
||||
|
||||
const form = useForm<formDataInput>({
|
||||
const form = useForm<EditInventory>({
|
||||
resolver: zodResolver(editInventory),
|
||||
defaultValues: defaultformValues,
|
||||
mode: 'onChange', // Enable real-time validation
|
||||
});
|
||||
|
||||
const onSubmit = async (data: formDataInput) => {
|
||||
const onSubmit = async (data: EditInventory) => {
|
||||
console.log(data);
|
||||
|
||||
saveAccountingAccounts(data, {
|
||||
@@ -106,7 +106,6 @@ export function UpdateForm({
|
||||
<FormItem >
|
||||
<FormLabel>Precio</FormLabel>
|
||||
<FormControl>
|
||||
{/* Simplificado. price es z.string(), field.value ya es string o undefined. */}
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -121,7 +120,6 @@ export function UpdateForm({
|
||||
<FormItem className='col-span-2'>
|
||||
<FormLabel>Dirección</FormLabel>
|
||||
<FormControl>
|
||||
{/* Simplificado. price es z.string(), field.value ya es string o undefined. */}
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -150,7 +148,7 @@ export function UpdateForm({
|
||||
<FormItem>
|
||||
<FormLabel>Cantidad/Stock</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="number" />
|
||||
<Input {...field} type="number" onChange={(e) => field.onChange(Number(e.target.value))}/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -180,20 +178,31 @@ export function UpdateForm({
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="col-span-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="urlImg"
|
||||
render={({ field }) => (
|
||||
render={({ field: { onChange, onBlur, name, ref } }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Imagen</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
<Input
|
||||
type="file"
|
||||
multiple
|
||||
onBlur={onBlur}
|
||||
name={name}
|
||||
ref={ref}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.files);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button variant="outline" type="button" onClick={onCancel}>
|
||||
|
||||
@@ -1,13 +1,38 @@
|
||||
import { user } from '@/feactures/auth/schemas/register';
|
||||
import { all } from 'axios';
|
||||
// import { user } from '@/feactures/auth/schemas/register';
|
||||
// import { all } from 'axios';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type InventoryTable = z.infer<typeof product>;
|
||||
export type EditInventory = z.infer<typeof editInventory>; //output
|
||||
export type formDataInput = z.input<typeof editInventory>;
|
||||
export type EditInventory = z.infer<typeof editInventory>;
|
||||
export type ProductApiResponseSchema = z.infer<typeof productApiResponseSchema>;
|
||||
export type allProducts = z.infer<typeof productDetails>;
|
||||
|
||||
// --- Esquemas de validación para archivos ---
|
||||
|
||||
// Esquema básico para un solo archivo
|
||||
const fileSchema = z.object({
|
||||
name: z.string().min(1, 'El nombre del archivo no puede estar vacío.'),
|
||||
size: z.number().int().positive('El tamaño del archivo debe ser positivo.'),
|
||||
type: z.string().refine(
|
||||
(type) => type.startsWith('image/'),
|
||||
'Solo se permiten archivos de imagen.'
|
||||
),
|
||||
// Puedes añadir más validaciones personalizadas aquí
|
||||
// Por ejemplo, limitar el tamaño máximo a 5MB (5 * 1024 * 1024 bytes)
|
||||
}).refine(
|
||||
(file) => file.size <= 5 * 1024 * 1024,
|
||||
'El tamaño del archivo no debe exceder los 5MB.'
|
||||
);
|
||||
|
||||
// Esquema para un array de archivos (cuando el input es multiple)
|
||||
const filesArraySchema = z.array(fileSchema).max(5, 'Solo se permiten hasta 5 archivos.');
|
||||
|
||||
// const formSchema = z.object({
|
||||
// file: z
|
||||
// .instanceof(FileList)
|
||||
// .refine((file) => file?.length == 1, 'File is required.')
|
||||
// });
|
||||
|
||||
export const product = z.object({
|
||||
id: z.number().optional(),
|
||||
title: z.string(),
|
||||
@@ -16,7 +41,8 @@ export const product = z.object({
|
||||
// category: z.string(),
|
||||
stock: z.number(),
|
||||
price: z.string(),
|
||||
urlImg: z.string(),
|
||||
urlImg: z.any(),
|
||||
// urlImg: z.string(),
|
||||
status: z.string(),
|
||||
userId: z.number().optional()
|
||||
})
|
||||
@@ -31,12 +57,10 @@ export const editInventory = z.object({
|
||||
id: z.number().optional(),
|
||||
title: z.string().min(5, { message: "Debe de tener 5 o más caracteres" }),
|
||||
description: z.string().min(10, { message: "Debe de tener 10 o más caracteres" }),
|
||||
stock: z.string().transform(val => Number(val)).pipe(z.number(
|
||||
{ invalid_type_error: 'El stock debe ser un número' }).min(0, { message: "El stock debe ser mayor a 0" })
|
||||
),
|
||||
stock: z.number(),
|
||||
address: z.string().min(5, { message: "Debe de tener 5 o más caracteres" }),
|
||||
price: z.string(),
|
||||
urlImg: z.string(),
|
||||
urlImg: z.any(),
|
||||
status: z.string().min(1, { message: "Debe de seleccionar un valor" }),
|
||||
userId: z.number().optional(),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user