ver productos y detalles
This commit is contained in:
@@ -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`);
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.' })
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -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({
|
||||||
@@ -77,4 +75,9 @@ export const productApiResponseSchema = z.object({
|
|||||||
export const productMutate = z.object({
|
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,
|
||||||
})
|
})
|
||||||
@@ -68,10 +68,9 @@ 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: () => {
|
||||||
form.reset();
|
form.reset();
|
||||||
|
|||||||
Reference in New Issue
Block a user