almacenar img (sin terminar)

This commit is contained in:
2025-07-21 15:39:27 -04:00
parent c377ab69da
commit a15505ff2c
8 changed files with 153 additions and 50 deletions

View File

@@ -37,19 +37,19 @@
} }
}, },
"dependencies": { "dependencies": {
"@nestjs/common": "^11.0.0", "@nestjs/common": "11.0.0",
"@nestjs/core": "^11.0.0", "@nestjs/core": "11.0.0",
"@nestjs/platform-express": "^11.0.0", "@nestjs/platform-express": "11.0.0",
"dotenv": "^16.5.0", "dotenv": "16.5.0",
"drizzle-orm": "^0.40.0", "drizzle-orm": "0.40.0",
"express": "^5.1.0", "express": "5.1.0",
"joi": "^17.13.3", "joi": "17.13.3",
"moment": "^2.30.1", "moment": "2.30.1",
"path-to-regexp": "^8.2.0", "path-to-regexp": "8.2.0",
"pg": "^8.13.3", "pg": "8.13.3",
"pino-pretty": "^13.0.0", "pino-pretty": "13.0.0",
"reflect-metadata": "^0.2.0", "reflect-metadata": "0.2.0",
"rxjs": "^7.8.1" "rxjs": "7.8.1"
}, },
"devDependencies": { "devDependencies": {
"@nestjs-modules/mailer": "^2.0.2", "@nestjs-modules/mailer": "^2.0.2",

View File

@@ -19,6 +19,7 @@ import { RolesModule } from './features/roles/roles.module';
import { UserRolesModule } from './features/user-roles/user-roles.module'; import { UserRolesModule } from './features/user-roles/user-roles.module';
import { SurveysModule } from './features/surveys/surveys.module'; import { SurveysModule } from './features/surveys/surveys.module';
import {InventoryModule} from './features/inventory/inventory.module' import {InventoryModule} from './features/inventory/inventory.module'
import { PicturesModule } from './features/pictures/pictures.module';
@Module({ @Module({
providers: [ providers: [
@@ -59,7 +60,8 @@ import {InventoryModule} from './features/inventory/inventory.module'
ConfigurationsModule, ConfigurationsModule,
SurveysModule, SurveysModule,
LocationModule, LocationModule,
InventoryModule InventoryModule,
PicturesModule
], ],
}) })
export class AppModule {} export class AppModule {}

View 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);
}
}

View 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 {}

View 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}`;
}
}

View File

@@ -20,7 +20,7 @@ import { Textarea } from '@repo/shadcn/textarea';
import { Input } from '@repo/shadcn/input'; import { Input } from '@repo/shadcn/input';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useCreateUser } from "@/feactures/inventory/hooks/use-mutation"; 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' import {STATUS} from '@/constants/status'
interface CreateFormProps { interface CreateFormProps {
@@ -44,12 +44,12 @@ export function CreateForm({
description: '', description: '',
address: '', address: '',
price: '', price: '',
stock: '', stock: 0,
urlImg: '', urlImg: '',
status: '' status: ''
} }
const form = useForm<formDataInput>({ const form = useForm<EditInventory>({
resolver: zodResolver(editInventory), resolver: zodResolver(editInventory),
defaultValues: defaultformValues, defaultValues: defaultformValues,
mode: 'onChange', // Enable real-time validation mode: 'onChange', // Enable real-time validation
@@ -145,7 +145,7 @@ export function CreateForm({
<FormItem> <FormItem>
<FormLabel>Cantidad/Stock</FormLabel> <FormLabel>Cantidad/Stock</FormLabel>
<FormControl> <FormControl>
<Input {...field} type='number' /> <Input {...field} type='number' onChange={(e) => field.onChange(Number(e.target.value))}/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@@ -201,4 +201,4 @@ export function CreateForm({
</form> </form>
</Form> </Form>
); );
} }

View File

@@ -19,7 +19,7 @@ import {
import { Input } from '@repo/shadcn/input'; import { Input } from '@repo/shadcn/input';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useUpdateUser } from "@/feactures/inventory/hooks/use-mutation"; 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 { Textarea } from '@repo/shadcn/components/ui/textarea';
import {STATUS} from '@/constants/status' 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 isError, // isError no se usa en el template, podrías usarlo para mostrar un mensaje global
} = useUpdateUser(); } = useUpdateUser();
const defaultformValues: formDataInput = { const defaultformValues: EditInventory = {
id: defaultValues?.id, id: defaultValues?.id,
title: defaultValues?.title || '', title: defaultValues?.title || '',
description: defaultValues?.description || '', description: defaultValues?.description || '',
price: defaultValues?.price || '', price: defaultValues?.price || '',
address: defaultValues?.address || '', address: defaultValues?.address || '',
status: defaultValues?.status || 'BORRADOR', status: defaultValues?.status || 'BORRADOR',
stock: (defaultValues?.stock ?? '').toString(), stock: defaultValues?.stock ?? 0,
urlImg: defaultValues?.urlImg || '', urlImg: [],
userId: defaultValues?.userId userId: defaultValues?.userId
}; };
const form = useForm<formDataInput>({ const form = useForm<EditInventory>({
resolver: zodResolver(editInventory), resolver: zodResolver(editInventory),
defaultValues: defaultformValues, defaultValues: defaultformValues,
mode: 'onChange', // Enable real-time validation mode: 'onChange', // Enable real-time validation
}); });
const onSubmit = async (data: formDataInput) => { const onSubmit = async (data: EditInventory) => {
console.log(data); console.log(data);
saveAccountingAccounts(data, { saveAccountingAccounts(data, {
@@ -106,7 +106,6 @@ export function UpdateForm({
<FormItem > <FormItem >
<FormLabel>Precio</FormLabel> <FormLabel>Precio</FormLabel>
<FormControl> <FormControl>
{/* Simplificado. price es z.string(), field.value ya es string o undefined. */}
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -121,7 +120,6 @@ export function UpdateForm({
<FormItem className='col-span-2'> <FormItem className='col-span-2'>
<FormLabel>Dirección</FormLabel> <FormLabel>Dirección</FormLabel>
<FormControl> <FormControl>
{/* Simplificado. price es z.string(), field.value ya es string o undefined. */}
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -150,7 +148,7 @@ export function UpdateForm({
<FormItem> <FormItem>
<FormLabel>Cantidad/Stock</FormLabel> <FormLabel>Cantidad/Stock</FormLabel>
<FormControl> <FormControl>
<Input {...field} type="number" /> <Input {...field} type="number" onChange={(e) => field.onChange(Number(e.target.value))}/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@@ -180,19 +178,30 @@ export function UpdateForm({
)} )}
/> />
<FormField <div className="col-span-2">
control={form.control} <FormField
name="urlImg" control={form.control}
render={({ field }) => ( name="urlImg"
<FormItem> render={({ field: { onChange, onBlur, name, ref } }) => (
<FormLabel>Imagen</FormLabel> <FormItem>
<FormControl> <FormLabel>Imagen</FormLabel>
<Input {...field} /> <FormControl>
</FormControl> <Input
<FormMessage /> type="file"
</FormItem> multiple
)} onBlur={onBlur}
/> name={name}
ref={ref}
onChange={(e) => {
onChange(e.target.files);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div> </div>
<div className="flex justify-end gap-4"> <div className="flex justify-end gap-4">

View File

@@ -1,13 +1,38 @@
import { user } from '@/feactures/auth/schemas/register'; // import { user } from '@/feactures/auth/schemas/register';
import { all } from 'axios'; // import { all } from 'axios';
import { z } from 'zod'; import { z } from 'zod';
export type InventoryTable = z.infer<typeof product>; export type InventoryTable = z.infer<typeof product>;
export type EditInventory = z.infer<typeof editInventory>; //output export type EditInventory = z.infer<typeof editInventory>;
export type formDataInput = z.input<typeof editInventory>;
export type ProductApiResponseSchema = z.infer<typeof productApiResponseSchema>; export type ProductApiResponseSchema = z.infer<typeof productApiResponseSchema>;
export type allProducts = z.infer<typeof productDetails>; 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({ export const product = z.object({
id: z.number().optional(), id: z.number().optional(),
title: z.string(), title: z.string(),
@@ -16,7 +41,8 @@ export const product = z.object({
// category: z.string(), // category: z.string(),
stock: z.number(), stock: z.number(),
price: z.string(), price: z.string(),
urlImg: z.string(), urlImg: z.any(),
// urlImg: z.string(),
status: z.string(), status: z.string(),
userId: z.number().optional() userId: z.number().optional()
}) })
@@ -31,12 +57,10 @@ export const editInventory = z.object({
id: z.number().optional(), id: z.number().optional(),
title: z.string().min(5, { message: "Debe de tener 5 o más caracteres" }), 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" }), description: z.string().min(10, { message: "Debe de tener 10 o más caracteres" }),
stock: z.string().transform(val => Number(val)).pipe(z.number( stock: z.number(),
{ invalid_type_error: 'El stock debe ser un número' }).min(0, { message: "El stock debe ser mayor a 0" })
),
address: z.string().min(5, { message: "Debe de tener 5 o más caracteres" }), address: z.string().min(5, { message: "Debe de tener 5 o más caracteres" }),
price: z.string(), price: z.string(),
urlImg: z.string(), urlImg: z.any(),
status: z.string().min(1, { message: "Debe de seleccionar un valor" }), status: z.string().min(1, { message: "Debe de seleccionar un valor" }),
userId: z.number().optional(), userId: z.number().optional(),
}) })