ver productos y detalles

This commit is contained in:
2025-07-09 14:56:58 -04:00
parent 365cbd0d7a
commit 3a0b29d3c1
14 changed files with 144 additions and 91 deletions

View File

@@ -25,8 +25,10 @@ export const viewProductsStore = t.pgView('v_product_store', {
stock: t.integer('stock'), stock: t.integer('stock'),
urlImg: t.text('url_img'), urlImg: t.text('url_img'),
userId: t.integer('user_id'), userId: t.integer('user_id'),
fullname: t.text('fullname') fullname: t.text('fullname'),
email: t.text('email'),
phone: t.text('phone')
}).as(sql` }).as(sql`
select p.id as product_id, p.title, p.description, p.price, p.stock, p.url_img, p.user_id, u.fullname select p.id as product_id, p.title, p.description, p.price, p.stock, p.url_img, p.user_id, u.fullname, u.email, u.phone
from products p from products p
left join auth.users as u on u.id = p.user_id`); left join auth.users as u on u.id = p.user_id`);

View File

@@ -6,6 +6,7 @@ export class Product {
stock: number | null; stock: number | null;
urlImg: string | null; urlImg: string | null;
userId?: number | null; userId?: number | null;
fullname?: string | null;
} }
export class Inventory { export class Inventory {

View File

@@ -30,8 +30,8 @@ export class UsersController {
@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);
return { return {
message: 'products fetched successfully', message: 'products fetched successfully',
@@ -40,7 +40,7 @@ export class UsersController {
}; };
} }
@Get(':id') @Get('/id/:id')
// @Roles('admin') // @Roles('admin')
@ApiOperation({ summary: 'Get a product by ID' }) @ApiOperation({ summary: 'Get a product by ID' })
@ApiResponse({ status: 200, description: 'Return the product.' }) @ApiResponse({ status: 200, description: 'Return the product.' })

View File

@@ -141,15 +141,17 @@ export class InventoryService {
async findOne(id: string): Promise<Product> { async findOne(id: string): Promise<Product> {
const find = await this.drizzle const find = await this.drizzle
.select({ .select({
id: products.id, id: viewProductsStore.id,
title: products.title, title: viewProductsStore.title,
description: products.description, description: viewProductsStore.description,
price: products.price, price: viewProductsStore.price,
urlImg: products.urlImg, urlImg: viewProductsStore.urlImg,
stock: products.stock stock: viewProductsStore.stock,
userId: viewProductsStore.userId,
fullname: viewProductsStore.fullname
}) })
.from(products) .from(viewProductsStore)
.where(eq(products.id, parseInt(id))); .where(eq(viewProductsStore.id, parseInt(id)));
if (find.length === 0) { if (find.length === 0) {
throw new HttpException('Product does not exist', HttpStatus.BAD_REQUEST); throw new HttpException('Product does not exist', HttpStatus.BAD_REQUEST);

View File

@@ -40,12 +40,11 @@ export class UsersController {
@ApiResponse({ status: 201, description: 'User created successfully.' }) @ApiResponse({ status: 201, description: 'User created successfully.' })
async create( async create(
@Body() createUserDto: CreateUserDto, @Body() createUserDto: CreateUserDto,
@Query('roleId') roleId?: string, @Query('role') role?: string,
) { ) {
const data = await this.usersService.create( console.log(role);
createUserDto,
roleId ? parseInt(roleId) : undefined, const data = await this.usersService.create(createUserDto)
);
return { message: 'User created successfully', data }; return { message: 'User created successfully', data };
} }

View File

@@ -159,7 +159,7 @@ export class UsersService {
// Assign role to user // Assign role to user
await tx.insert(usersRole).values({ await tx.insert(usersRole).values({
userId: newUser.id, userId: newUser.id,
roleId: roleId, roleId: createUserDto.role || roleId,
}); });
// Return the created user with role // Return the created user with role

View File

@@ -1,7 +1,16 @@
import PageContainer from '@/components/layout/page-container'; // 'use client';
import { getSurveyByIdAction } from '@/feactures/surveys/actions/surveys-actions';
import { SurveyResponse } from '@/feactures/surveys/components/survey-response';
// import PageContainer from '@/components/layout/page-container';
import { getProductById } from '@/feactures/inventory/actions/actions';
// import { SurveyResponse } from '@/feactures/surveys/components/survey-response';
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from '@repo/shadcn/card';
import { Edit } from 'lucide-react';
export default async function SurveyResponsePage({ export default async function SurveyResponsePage({
@@ -17,17 +26,47 @@ export default async function SurveyResponsePage({
} }
// Call the function passing the dynamic id // Call the function passing the dynamic id
const data = await getSurveyByIdAction(Number(id)); const data = await getProductById(Number(id));
if (!data?.data) { if (!data?.data) {
return <div>Encuesta no encontrada</div>; return <div>Encuesta no encontrada</div>;
} }
const product = data.data
// console.log(data.data);
return ( return (
<PageContainer> // <PageContainer>
<div className="flex flex-1 flex-col space-y-4"> <main className='p-4 md:px-6 flex flex-col md:flex-row gap-4 md:relative'>
<SurveyResponse survey={data?.data} /> <img
</div> className="border-2 object-cover w-full h-full aspect-square rounded-2xl"
</PageContainer> src={`http://localhost:3000/${product.urlImg}`}
alt=""
/>
<Card className="flex flex-col md:w-[500px] md:max-h-[90vh] md:overflow-auto md:sticky top-0 right-0">
<CardHeader>
<CardTitle className="font-bold text-2xl">
{product.title.charAt(0).toUpperCase() + product.title.slice(1)}
</CardTitle>
<p className='font-semibold'>$ {product.price}</p>
</CardHeader>
<CardContent className="flex-grow">
<p className='font-semibold text-lg border-t border-b'> Descripción</p>
<p className='p-1'>{product.description}</p>
</CardContent>
<CardFooter className="">
<div>
<p className='font-semibold text-lg border-t border-b mt-4'>Información del vendedor</p>
<p>{product.fullname}</p>
<p>Correo@gmail.com</p>
<p>0412-7848101</p>
</div>
{/* <p className="font-semibold text-lg">$ {product.price}</p> */}
</CardFooter>
</Card>
</main>
); );
} }

View File

@@ -1,5 +1,6 @@
import PageContainer from '@/components/layout/page-container'; import PageContainer from '@/components/layout/page-container';
import { SurveyList } from '@/feactures/inventory/components/products/product-list'; import { ProductList } from '@/feactures/inventory/components/products/product-list';
import { Button } from '@repo/shadcn/components/ui/button';
import { Metadata } from 'next'; import { Metadata } from 'next';
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -8,14 +9,18 @@ export const metadata: Metadata = {
}; };
export default function SurveysPage() { export default function SurveysPage() {
return ( return (
<PageContainer> // <PageContainer>
<div className="w-full"> <main className='p-4 md:px-6'>
<h1 className="text-2xl font-bold mb-6">Productos Disponibles</h1> <header className="w-full flex flex-col sm:flex-row sm:justify-between">
<SurveyList /> <h1 className="text-2xl font-bold mb-1">Productos Disponibles</h1>
</div> <a className='mb-1' href="/dashboard/inventario">
</PageContainer> <Button>Mi inventario</Button>
</a>
</header>
<ProductList />
</main>
// </PageContainer>
); );
} }

View File

@@ -11,8 +11,8 @@ export const GeneralItems: NavItem[] = [
items: [], // No child items items: [], // No child items
}, },
{ {
title: 'Inventario', title: 'ProduTienda',
url: '/dashboard/inventario/', url: '/dashboard/productos/',
icon: 'blocks', icon: 'blocks',
shortcut: ['p', 'p'], shortcut: ['p', 'p'],
isActive: false, isActive: false,

View File

@@ -5,7 +5,8 @@ import {
InventoryTable, InventoryTable,
productMutate, productMutate,
// editInventory, // editInventory,
productApiResponseSchema productApiResponseSchema,
getProduct
} from '../schemas/inventory'; } from '../schemas/inventory';
import { auth } from '@/lib/auth'; import { auth } from '@/lib/auth';
@@ -81,7 +82,7 @@ export const getAllProducts = async (params: {
); );
if (error) { if (error) {
console.error('Error:', error); console.error('Errorrrrr:', error.details);
throw new Error(error.message); throw new Error(error.message);
} }
@@ -100,6 +101,21 @@ export const getAllProducts = async (params: {
}; };
}; };
export const getProductById = async (id: number) => {
const [error, data] = await safeFetchApi(
getProduct,
`/products/id/${id}`,
'GET',
);
if (error) {
console.error('❌ Error en la API:', error);
throw new Error(error.message);
}
return data;
};
export const createProductAction = async (payload: InventoryTable) => { export const createProductAction = async (payload: InventoryTable) => {
const session = await auth() const session = await auth()
const userId = session?.user?.id const userId = session?.user?.id

View File

@@ -9,9 +9,9 @@ export const columns: ColumnDef<InventoryTable>[] = [
accessorKey: 'urlImg', accessorKey: 'urlImg',
header: 'img', header: 'img',
cell: ({ row }) => { cell: ({ row }) => {
const status = row.getValue("urlImg") as string | undefined; const url = row.getValue("urlImg") as string | undefined;
return ( return (
<img src={`http://localhost:3000/${status}`} alt="Image" width={64} height={64} className="rounded"/> <img src={`http://localhost:3000/${url}`} alt="" width={64} height={64} className="rounded"/>
) )
}, },
}, },

View File

@@ -1,67 +1,54 @@
// Este componente maneja la lista de encuestas en el panel de administración
// Funcionalidades:
// - Muestra todas las encuestas en una tabla
// - Permite editar encuestas existentes
// - Permite eliminar encuestas con confirmación
// - Muestra el estado (publicada/borrador), fechas y conteo de respuestas
'use client'; 'use client';
import { Button } from '@repo/shadcn/button';
import { import {
Card, Card,
CardContent, CardContent,
CardDescription,
CardFooter, CardFooter,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from '@repo/shadcn/card'; } from '@repo/shadcn/card';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
// import { useSurveysForUserQuery } from '@/feactures/surveys/hooks/use-query-surveys';
import { useAllProductQuery } from '@/feactures/inventory/hooks/use-query-products'; import { useAllProductQuery } from '@/feactures/inventory/hooks/use-query-products';
import { allProducts } from '../../schemas/inventory'; import { allProducts } from '../../schemas/inventory';
import { Badge } from '@repo/shadcn/badge'; import { ImageIcon } from 'lucide-react';
import { BadgeCheck } from 'lucide-react';
export function SurveyList() {
export function ProductList() {
const router = useRouter(); const router = useRouter();
const {data: produts} = useAllProductQuery() const { data: produts } = useAllProductQuery();
const handleRespond = (surveyId: number) => { const handle = (id: number) => {
router.push(`/dashboard/productos/${surveyId}`); router.push(`/dashboard/productos/${id}`);
}; };
// console.log(produts?.data)
return ( return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="w-full grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
{produts?.meta.totalPages === 0 ? ( {produts?.meta.totalPages === 0 ? (
<div className="col-span-full text-center py-10"> <section className="col-span-full text-center py-10">
<p className="text-muted-foreground">No hay productos disponibles en este momento.</p> <p className="text-muted-foreground">
</div> No hay productos disponibles en este momento.
</p>
</section>
) : ( ) : (
produts?.data.map((data: allProducts) => ( produts?.data.map((data: allProducts) => (
<Card
<Card key={data.id} className="flex flex-col"> key={data.id}
className="cursor-pointer flex flex-col"
onClick={() => handle(Number(data.id))}
>
<CardHeader> <CardHeader>
<CardTitle>{data.title}</CardTitle> <CardTitle className="text-base font-bold truncate">
<CardDescription>{data.description}</CardDescription> {data.title.charAt(0).toUpperCase() + data.title.slice(1)}
</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="flex-grow"> <CardContent className="p-0 flex-grow">
<div className="space-y-2 text-sm"> <img
<div className="flex justify-between"> className="object-cover w-full h-full aspect-square"
<img src="/data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII" alt="" /> src={`http://localhost:3000/${data.urlImg}`}
</div> alt=""
</div> />
</CardContent> </CardContent>
<CardFooter className="flex justify-center"> <CardFooter className="flex justify-between items-center p-4">
<Button <p className="font-semibold text-lg">$ {data.price}</p>
className="w-full"
onClick={() => handleRespond(Number(data.id))}
>
Ver
</Button>
</CardFooter> </CardFooter>
</Card> </Card>
)) ))

View File

@@ -16,7 +16,7 @@ export const product = z.object({
stock: z.number(), stock: z.number(),
price: z.string(), price: z.string(),
urlImg: z.string(), urlImg: z.string(),
userId: z.number().optional(), userId: z.number().optional()
}); });
export const editInventory = z.object({ export const editInventory = z.object({
@@ -38,10 +38,8 @@ export const allProducts = z.object({
stock: z.number(), stock: z.number(),
price: z.string(), price: z.string(),
urlImg: z.string(), urlImg: z.string(),
user: z.object({ userId: z.number(),
userId: z.number(), fullname: z.string()
username: z.string(),
})
}) })
export const ApiResponseSchema = z.object({ export const ApiResponseSchema = z.object({
@@ -78,3 +76,8 @@ export const productMutate = z.object({
message: z.string(), message: z.string(),
data: product, data: product,
}) })
export const getProduct = z.object({
message: z.string(),
data: allProducts,
})

View File

@@ -68,9 +68,8 @@ export function CreateUserForm({
mode: 'onChange', // Enable real-time validation mode: 'onChange', // Enable real-time validation
}); });
const onSubmit = async (data: CreateUser) => { const onSubmit = async (formData: CreateUser) => {
console.log(formData);
const formData = data
saveAccountingAccounts(formData, { saveAccountingAccounts(formData, {
onSuccess: () => { onSuccess: () => {