Vista (intefaz y bd), esquema y endpoints de store agregados
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE "products" ALTER COLUMN "user_id" SET NOT NULL;--> statement-breakpoint
|
||||||
|
CREATE VIEW "public"."v_product_store" AS (
|
||||||
|
select p.id as product_id, p.title, p.description, p.price, p.stock, p.url_img, p.user_id, u.fullname
|
||||||
|
from products p
|
||||||
|
left join auth.users as u on u.id = p.user_id);
|
||||||
1498
apps/api/src/database/migrations/meta/0003_snapshot.json
Normal file
1498
apps/api/src/database/migrations/meta/0003_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,13 @@
|
|||||||
"when": 1750442271575,
|
"when": 1750442271575,
|
||||||
"tag": "0002_polite_franklin_richards",
|
"tag": "0002_polite_franklin_richards",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 3,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1751482400155,
|
||||||
|
"tag": "0003_icy_gertrude_yorkes",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as t from 'drizzle-orm/pg-core';
|
import * as t from 'drizzle-orm/pg-core';
|
||||||
import { timestamps } from '../timestamps';
|
import { timestamps } from '../timestamps';
|
||||||
import { users } from './auth';
|
import { users } from './auth';
|
||||||
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
export const products = t.pgTable(
|
export const products = t.pgTable(
|
||||||
'products',
|
'products',
|
||||||
@@ -11,7 +12,21 @@ export const products = t.pgTable(
|
|||||||
price: t.numeric('price').notNull(),
|
price: t.numeric('price').notNull(),
|
||||||
stock: t.integer('stock').notNull(),
|
stock: t.integer('stock').notNull(),
|
||||||
urlImg: t.text('url_img').notNull(),
|
urlImg: t.text('url_img').notNull(),
|
||||||
userId: t.integer('user_id').references(() => users.id, { onDelete: 'cascade' }),
|
userId: t.integer('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const viewProductsStore = t.pgView('v_product_store', {
|
||||||
|
id: t.integer('product_id'),
|
||||||
|
title: t.text('title'),
|
||||||
|
description: t.text('description'),
|
||||||
|
price: t.numeric('price'),
|
||||||
|
stock: t.integer('stock'),
|
||||||
|
urlImg: t.text('url_img'),
|
||||||
|
userId: t.integer('user_id'),
|
||||||
|
fullname: t.text('fullname')
|
||||||
|
}).as(sql`
|
||||||
|
select p.id as product_id, p.title, p.description, p.price, p.stock, p.url_img, p.user_id, u.fullname
|
||||||
|
from products p
|
||||||
|
left join auth.users as u on u.id = p.user_id`);
|
||||||
@@ -1,19 +1,39 @@
|
|||||||
export class Product {
|
export class Product {
|
||||||
id?: number;
|
id?: number | null;
|
||||||
title: string;
|
title: string | null;
|
||||||
description: string;
|
description: string | null;
|
||||||
price: string;
|
price: string | null;
|
||||||
stock: number;
|
stock: number | null;
|
||||||
urlImg: string;
|
urlImg: string | null;
|
||||||
UserId?: number;
|
userId?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateProduct {
|
export class Inventory {
|
||||||
id: number;
|
id: number | null;
|
||||||
title: string;
|
title: string | null;
|
||||||
description: string;
|
description: string | null;
|
||||||
price: string;
|
price: string | null;
|
||||||
stock: string;
|
stock: number | null;
|
||||||
urlImg: string;
|
urlImg: string | null;
|
||||||
UserId: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Store {
|
||||||
|
id: number | null;
|
||||||
|
title: string | null;
|
||||||
|
description: string | null;
|
||||||
|
price: string | null;
|
||||||
|
stock: number | null;
|
||||||
|
urlImg: string | null;
|
||||||
|
userId: number | null;
|
||||||
|
fullname?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export class CreateProduct {
|
||||||
|
// id: number;
|
||||||
|
// title: string;
|
||||||
|
// description: string;
|
||||||
|
// price: string;
|
||||||
|
// stock: string;
|
||||||
|
// urlImg: string;
|
||||||
|
// UserId: number;
|
||||||
|
// }
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
|
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, Req } from '@nestjs/common';
|
||||||
import { InventoryService } from './inventory.service';
|
import { InventoryService } from './inventory.service';
|
||||||
import { CreateProductDto } from './dto/create-product.dto';
|
import { CreateProductDto } from './dto/create-product.dto';
|
||||||
import { UpdateProductDto } from './dto/update-product.dto';
|
import { UpdateProductDto } from './dto/update-product.dto';
|
||||||
@@ -6,12 +6,12 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|||||||
// import { Roles } from '../../common/decorators/roles.decorator';
|
// import { Roles } from '../../common/decorators/roles.decorator';
|
||||||
import { PaginationDto } from '../../common/dto/pagination.dto';
|
import { PaginationDto } from '../../common/dto/pagination.dto';
|
||||||
|
|
||||||
@ApiTags('inventory')
|
@ApiTags('products')
|
||||||
@Controller('inventory')
|
@Controller('products')
|
||||||
export class UsersController {
|
export class UsersController {
|
||||||
constructor(private readonly inventoryService: InventoryService) {}
|
constructor(private readonly inventoryService: InventoryService) {}
|
||||||
|
|
||||||
@Get()
|
@Get('/store')
|
||||||
// @Roles('admin')
|
// @Roles('admin')
|
||||||
@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.' })
|
||||||
@@ -24,6 +24,22 @@ export class UsersController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('/inventory')
|
||||||
|
// @Roles('admin')
|
||||||
|
@ApiOperation({ summary: 'Get all products with pagination and filters' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Return paginated products.' })
|
||||||
|
async findAllByUserId(@Req() req: Request, @Query() paginationDto: PaginationDto) {
|
||||||
|
console.log(req['user'].id)
|
||||||
|
const id = 1
|
||||||
|
// const id = Number(req['user'].id);
|
||||||
|
const result = await this.inventoryService.findAllByUserId(id,paginationDto);
|
||||||
|
return {
|
||||||
|
message: 'products fetched successfully',
|
||||||
|
data: result.data,
|
||||||
|
meta: result.meta
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
// @Roles('admin')
|
// @Roles('admin')
|
||||||
@ApiOperation({ summary: 'Get a product by ID' })
|
@ApiOperation({ summary: 'Get a product by ID' })
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider';
|
import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider';
|
||||||
import { Inject, Injectable, HttpException, HttpStatus, NotFoundException, UnauthorizedException } from '@nestjs/common';
|
import { Inject, Injectable, HttpException, HttpStatus } from '@nestjs/common';
|
||||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||||
import * as schema from 'src/database/index';
|
import * as schema from 'src/database/index';
|
||||||
import { products } from 'src/database/index';
|
import { products, viewProductsStore } from 'src/database/index';
|
||||||
import { eq, like, or, SQL, sql, and, not } from 'drizzle-orm';
|
import { eq, like, or, SQL, sql, and, not } from 'drizzle-orm';
|
||||||
import { CreateProductDto } from './dto/create-product.dto';
|
import { CreateProductDto } from './dto/create-product.dto';
|
||||||
import { UpdateProductDto } from './dto/update-product.dto';
|
import { UpdateProductDto } from './dto/update-product.dto';
|
||||||
import { Product, CreateProduct } from './entities/inventory.entity';
|
import { Product, Store, Inventory } from './entities/inventory.entity';
|
||||||
import { PaginationDto } from '../../common/dto/pagination.dto';
|
import { PaginationDto } from '../../common/dto/pagination.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -15,7 +15,7 @@ export class InventoryService {
|
|||||||
@Inject(DRIZZLE_PROVIDER) private drizzle: NodePgDatabase<typeof schema>,
|
@Inject(DRIZZLE_PROVIDER) private drizzle: NodePgDatabase<typeof schema>,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async findAll(paginationDto?: PaginationDto): Promise<{ data: Product[], meta: any }> {
|
async findAllByUserId(id: number, paginationDto?: PaginationDto): Promise<{ data: Inventory[], meta: any }> {
|
||||||
const { page = 1, limit = 10, search = '', sortBy = 'id', sortOrder = 'asc' } = paginationDto || {};
|
const { page = 1, limit = 10, search = '', sortBy = 'id', sortOrder = 'asc' } = paginationDto || {};
|
||||||
|
|
||||||
// Calculate offset
|
// Calculate offset
|
||||||
@@ -23,12 +23,14 @@ export class InventoryService {
|
|||||||
|
|
||||||
// Build search condition
|
// Build search condition
|
||||||
let searchCondition: SQL<unknown> | undefined;
|
let searchCondition: SQL<unknown> | undefined;
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
searchCondition = or(
|
searchCondition = and(or(
|
||||||
like(products.title, `%${search}%`),
|
like(products.title, `%${search}%`),
|
||||||
like(products.description, `%${search}%`),
|
like(products.description, `%${search}%`),
|
||||||
// like(users.fullname, `%${search}%`)
|
), eq(products.userId, id));
|
||||||
);
|
}else{
|
||||||
|
searchCondition = eq(products.userId, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build sort condition
|
// Build sort condition
|
||||||
@@ -52,8 +54,8 @@ export class InventoryService {
|
|||||||
title: products.title,
|
title: products.title,
|
||||||
description: products.description,
|
description: products.description,
|
||||||
price: products.price,
|
price: products.price,
|
||||||
urlImg: products.urlImg,
|
|
||||||
stock: products.stock,
|
stock: products.stock,
|
||||||
|
urlImg: products.urlImg
|
||||||
})
|
})
|
||||||
.from(products)
|
.from(products)
|
||||||
.where(searchCondition)
|
.where(searchCondition)
|
||||||
@@ -75,6 +77,67 @@ export class InventoryService {
|
|||||||
return { data, meta };
|
return { data, meta };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findAll(paginationDto?: PaginationDto): Promise<{ data: Store[], meta: any }> {
|
||||||
|
const { page = 1, limit = 10, search = '', sortBy = 'id', sortOrder = 'asc' } = paginationDto || {};
|
||||||
|
|
||||||
|
// Calculate offset
|
||||||
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
|
// Build search condition
|
||||||
|
let searchCondition: SQL<unknown> | undefined;
|
||||||
|
if (search) {
|
||||||
|
searchCondition = or(
|
||||||
|
like(viewProductsStore.title, `%${search}%`),
|
||||||
|
like(viewProductsStore.description, `%${search}%`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build sort condition
|
||||||
|
const orderBy = sortOrder === 'asc'
|
||||||
|
? sql`${viewProductsStore[sortBy as keyof typeof viewProductsStore]} asc`
|
||||||
|
: sql`${viewProductsStore[sortBy as keyof typeof viewProductsStore]} desc`;
|
||||||
|
|
||||||
|
// Get total count for pagination
|
||||||
|
const totalCountResult = await this.drizzle
|
||||||
|
.select({ count: sql<number>`count(*)` })
|
||||||
|
.from(viewProductsStore)
|
||||||
|
.where(searchCondition);
|
||||||
|
|
||||||
|
const totalCount = Number(totalCountResult[0].count);
|
||||||
|
const totalPages = Math.ceil(totalCount / limit);
|
||||||
|
|
||||||
|
// Get paginated data
|
||||||
|
const data = await this.drizzle
|
||||||
|
.select({
|
||||||
|
id: viewProductsStore.id,
|
||||||
|
title: viewProductsStore.title,
|
||||||
|
description: viewProductsStore.description,
|
||||||
|
price: viewProductsStore.price,
|
||||||
|
urlImg: viewProductsStore.urlImg,
|
||||||
|
stock: viewProductsStore.stock,
|
||||||
|
userId: viewProductsStore.userId,
|
||||||
|
fullname: viewProductsStore.fullname
|
||||||
|
})
|
||||||
|
.from(viewProductsStore)
|
||||||
|
.where(searchCondition)
|
||||||
|
.orderBy(orderBy)
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset);
|
||||||
|
|
||||||
|
// Build pagination metadata
|
||||||
|
const meta = {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalCount,
|
||||||
|
totalPages,
|
||||||
|
hasNextPage: page < totalPages,
|
||||||
|
hasPreviousPage: page > 1,
|
||||||
|
nextPage: page < totalPages ? page + 1 : null,
|
||||||
|
previousPage: page > 1 ? page - 1 : null,
|
||||||
|
};
|
||||||
|
return { data, meta };
|
||||||
|
}
|
||||||
|
|
||||||
async findOne(id: string): Promise<Product> {
|
async findOne(id: string): Promise<Product> {
|
||||||
const find = await this.drizzle
|
const find = await this.drizzle
|
||||||
.select({
|
.select({
|
||||||
|
|||||||
33
apps/web/app/dashboard/productos/[id]/page.tsx
Normal file
33
apps/web/app/dashboard/productos/[id]/page.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import PageContainer from '@/components/layout/page-container';
|
||||||
|
import { getSurveyByIdAction } from '@/feactures/surveys/actions/surveys-actions';
|
||||||
|
import { SurveyResponse } from '@/feactures/surveys/components/survey-response';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default async function SurveyResponsePage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ id: string }>
|
||||||
|
}) {
|
||||||
|
const { id } = await params; // You can still destructure id from params
|
||||||
|
|
||||||
|
if (!id || id === '') {
|
||||||
|
// Handle the case where no id is provided
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the function passing the dynamic id
|
||||||
|
const data = await getSurveyByIdAction(Number(id));
|
||||||
|
|
||||||
|
if (!data?.data) {
|
||||||
|
return <div>Encuesta no encontrada</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<div className="flex flex-1 flex-col space-y-4">
|
||||||
|
<SurveyResponse survey={data?.data} />
|
||||||
|
</div>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
apps/web/app/dashboard/productos/page.tsx
Normal file
21
apps/web/app/dashboard/productos/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import PageContainer from '@/components/layout/page-container';
|
||||||
|
import { SurveyList } from '@/feactures/inventory/components/products/product-list';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Productos - Fondemi',
|
||||||
|
description: 'Listado de productos disponibles',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SurveysPage() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<div className="w-full">
|
||||||
|
<h1 className="text-2xl font-bold mb-6">Productos Disponibles</h1>
|
||||||
|
<SurveyList />
|
||||||
|
</div>
|
||||||
|
</PageContainer>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@ import {
|
|||||||
ApiResponseSchema,
|
ApiResponseSchema,
|
||||||
InventoryTable,
|
InventoryTable,
|
||||||
productMutate,
|
productMutate,
|
||||||
editInventory
|
// editInventory,
|
||||||
|
productApiResponseSchema
|
||||||
} from '../schemas/inventory';
|
} from '../schemas/inventory';
|
||||||
|
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
@@ -27,8 +28,8 @@ export const getInventoryAction = async (params: {
|
|||||||
|
|
||||||
const [error, response] = await safeFetchApi(
|
const [error, response] = await safeFetchApi(
|
||||||
ApiResponseSchema,
|
ApiResponseSchema,
|
||||||
`/inventory?${searchParams}`,
|
`/products/inventory?${searchParams}`,
|
||||||
'GET',
|
'GET'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -53,6 +54,52 @@ export const getInventoryAction = async (params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAllProducts = async (params: {
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
search?: string;
|
||||||
|
sortBy?: string;
|
||||||
|
sortOrder?: 'asc' | 'desc';
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const session = await auth()
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
page: (params.page || 1).toString(),
|
||||||
|
limit: (params.limit || 10).toString(),
|
||||||
|
...(params.search && { search: params.search }),
|
||||||
|
...(params.sortBy && { sortBy: params.sortBy }),
|
||||||
|
...(params.sortOrder && { sortOrder: params.sortOrder }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = session?.user.id
|
||||||
|
|
||||||
|
const [error, response] = await safeFetchApi(
|
||||||
|
productApiResponseSchema,
|
||||||
|
`/products/store?${searchParams}`,
|
||||||
|
'GET'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: response?.data || [],
|
||||||
|
meta: response?.meta || {
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
totalCount: 0,
|
||||||
|
totalPages: 1,
|
||||||
|
hasNextPage: false,
|
||||||
|
hasPreviousPage: false,
|
||||||
|
nextPage: null,
|
||||||
|
previousPage: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
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
|
||||||
@@ -62,14 +109,14 @@ export const createProductAction = async (payload: InventoryTable) => {
|
|||||||
|
|
||||||
const [error, data] = await safeFetchApi(
|
const [error, data] = await safeFetchApi(
|
||||||
productMutate,
|
productMutate,
|
||||||
'/inventory',
|
'/products',
|
||||||
'POST',
|
'POST',
|
||||||
payloadWithoutId,
|
payloadWithoutId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw new Error('Error al crear el usuario');
|
throw new Error('Error al crear el producto');
|
||||||
}
|
}
|
||||||
|
|
||||||
return payloadWithoutId;
|
return payloadWithoutId;
|
||||||
@@ -81,16 +128,14 @@ export const updateUserAction = async (payload: InventoryTable) => {
|
|||||||
|
|
||||||
const [error, data] = await safeFetchApi(
|
const [error, data] = await safeFetchApi(
|
||||||
productMutate,
|
productMutate,
|
||||||
`/inventory/${id}`,
|
`/products/${id}`,
|
||||||
'PATCH',
|
'PATCH',
|
||||||
payloadWithoutId,
|
payloadWithoutId,
|
||||||
);
|
);
|
||||||
|
|
||||||
// console.log(data);
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
throw new Error(error?.message || 'Error al actualizar el producto');
|
||||||
throw new Error(error?.message || 'Error al actualizar el usuario');
|
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -98,17 +143,14 @@ export const updateUserAction = async (payload: InventoryTable) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteUserAction = async (id: Number) => {
|
export const deleteProductAction = async (id: Number) => {
|
||||||
const [error] = await safeFetchApi(
|
const [error] = await safeFetchApi(
|
||||||
productMutate,
|
productMutate,
|
||||||
`/users/${id}`,
|
`/products/${id}`,
|
||||||
'DELETE'
|
'DELETE'
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
if (error) throw new Error(error.message || 'Error al eliminar el usuario')
|
||||||
|
|
||||||
// if (error) throw new Error(error.message || 'Error al eliminar el usuario')
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@repo/shadcn/form';
|
} from '@repo/shadcn/form';
|
||||||
|
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";
|
||||||
@@ -83,20 +84,6 @@ export function CreateForm({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="description"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Descripción</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="price"
|
name="price"
|
||||||
@@ -113,6 +100,21 @@ export function CreateForm({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className='col-span-2'>
|
||||||
|
<FormLabel>Descripción</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
{/* <Input {...field} /> */}
|
||||||
|
<Textarea {...field} className="resize-none"/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="stock"
|
name="stock"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { DataTable } from '@repo/shadcn/table/data-table';
|
import { DataTable } from '@repo/shadcn/table/data-table';
|
||||||
import { DataTableSkeleton } from '@repo/shadcn/table/data-table-skeleton';
|
import { DataTableSkeleton } from '@repo/shadcn/table/data-table-skeleton';
|
||||||
import { columns } from './product-tables/columns';
|
import { columns } from './product-tables/columns';
|
||||||
import { useProductQuery } from '../../hooks/use-query-users';
|
import { useProductQuery } from '../../hooks/use-query-products';
|
||||||
|
|
||||||
interface SurveysAdminListProps {
|
interface SurveysAdminListProps {
|
||||||
initialPage: number;
|
initialPage: number;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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, formDataInput, EditInventory } from '@/feactures/inventory/schemas/inventory'; // Renombrado EditInventory para claridad
|
||||||
|
import { Textarea } from '@repo/shadcn/components/ui/textarea';
|
||||||
|
|
||||||
interface UpdateFormProps {
|
interface UpdateFormProps {
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
@@ -87,19 +88,7 @@ export function UpdateForm({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="description"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Descripción</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -116,6 +105,21 @@ export function UpdateForm({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className='col-span-2'>
|
||||||
|
<FormLabel>Descripción</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
{/* <Input {...field} /> */}
|
||||||
|
<Textarea {...field} className="resize-none"/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="stock"
|
name="stock"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function UsersHeader() {
|
|||||||
description="Gestione aquí los productos que usted registre en la plataforma"
|
description="Gestione aquí los productos que usted registre en la plataforma"
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => setOpen(true)} size="sm">
|
<Button onClick={() => setOpen(true)} size="sm">
|
||||||
<Plus className="mr-2 h-4 w-4" /> Agregar Producto
|
<Plus className="h-4 w-4" /><span className='hidden md:inline'>Agregar Producto</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// 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';
|
||||||
|
|
||||||
|
import { Button } from '@repo/shadcn/button';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@repo/shadcn/card';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
// import { useSurveysForUserQuery } from '@/feactures/surveys/hooks/use-query-surveys';
|
||||||
|
import { useAllProductQuery } from '@/feactures/inventory/hooks/use-query-products';
|
||||||
|
import { allProducts } from '../../schemas/inventory';
|
||||||
|
import { Badge } from '@repo/shadcn/badge';
|
||||||
|
import { BadgeCheck } from 'lucide-react';
|
||||||
|
|
||||||
|
export function SurveyList() {
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const {data: produts} = useAllProductQuery()
|
||||||
|
|
||||||
|
const handleRespond = (surveyId: number) => {
|
||||||
|
router.push(`/dashboard/productos/${surveyId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// console.log(produts?.data)
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{produts?.meta.totalPages === 0 ? (
|
||||||
|
<div className="col-span-full text-center py-10">
|
||||||
|
<p className="text-muted-foreground">No hay productos disponibles en este momento.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
produts?.data.map((data: allProducts) => (
|
||||||
|
|
||||||
|
<Card key={data.id} className="flex flex-col">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{data.title}</CardTitle>
|
||||||
|
<CardDescription>{data.description}</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex-grow">
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<img src="/data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex justify-center">
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => handleRespond(Number(data.id))}
|
||||||
|
>
|
||||||
|
Ver
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useSafeQuery } from "@/hooks/use-safe-query";
|
import { useSafeQuery } from "@/hooks/use-safe-query";
|
||||||
import { getInventoryAction} from "../actions/actions";
|
import { getInventoryAction, getAllProducts } from "../actions/actions";
|
||||||
|
|
||||||
// Hook for users
|
// Hook for users
|
||||||
export function useProductQuery(params = {}) {
|
export function useProductQuery(params = {}) {
|
||||||
return useSafeQuery(['product',params], () => getInventoryAction(params))
|
return useSafeQuery(['product',params], () => getInventoryAction(params))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useAllProductQuery(params = {}) {
|
||||||
|
return useSafeQuery(['product',params], () => getAllProducts(params))
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
|
import { user } from '@/feactures/auth/schemas/register';
|
||||||
|
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>; //output
|
||||||
export type formDataInput = z.input<typeof editInventory>;
|
export type formDataInput = z.input<typeof editInventory>;
|
||||||
|
export type ProductApiResponseSchema = z.infer<typeof productApiResponseSchema>;
|
||||||
|
export type allProducts = z.infer<typeof allProducts>;
|
||||||
|
|
||||||
export const product = z.object({
|
export const product = z.object({
|
||||||
id: z.number().optional(),
|
id: z.number().optional(),
|
||||||
@@ -27,6 +31,18 @@ export const editInventory = z.object({
|
|||||||
userId: z.number().optional(),
|
userId: z.number().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const allProducts = 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.number(),
|
||||||
|
price: z.string(),
|
||||||
|
urlImg: z.string(),
|
||||||
|
user: z.object({
|
||||||
|
userId: z.number(),
|
||||||
|
username: z.string(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
export const ApiResponseSchema = z.object({
|
export const ApiResponseSchema = z.object({
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
@@ -43,6 +59,21 @@ export const ApiResponseSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const productApiResponseSchema = z.object({
|
||||||
|
message: z.string(),
|
||||||
|
data: z.array(allProducts),
|
||||||
|
meta: z.object({
|
||||||
|
page: z.number(),
|
||||||
|
limit: z.number(),
|
||||||
|
totalCount: z.number(),
|
||||||
|
totalPages: z.number(),
|
||||||
|
hasNextPage: z.boolean(),
|
||||||
|
hasPreviousPage: z.boolean(),
|
||||||
|
nextPage: z.number().nullable(),
|
||||||
|
previousPage: z.number().nullable(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
export const productMutate = z.object({
|
export const productMutate = z.object({
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
data: product,
|
data: product,
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ 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',
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export function DataTable<TData, TValue>({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col space-y-4">
|
<div className="flex flex-1 flex-col space-y-4">
|
||||||
<div className="relative flex flex-1">
|
<div className="relative flex flex-1 min-h-[300px]">
|
||||||
<div className="absolute bottom-0 left-0 right-0 top-0 flex rounded-md border">
|
<div className="absolute bottom-0 left-0 right-0 top-0 flex rounded-md border">
|
||||||
<Table className="relative">
|
<Table className="relative">
|
||||||
<TableHeader className="sticky top-0 z-10 bg-background">
|
<TableHeader className="sticky top-0 z-10 bg-background">
|
||||||
|
|||||||
Reference in New Issue
Block a user