Vista (intefaz y bd), esquema y endpoints de store agregados

This commit is contained in:
2025-07-02 15:10:54 -04:00
parent f5962efb8b
commit 365cbd0d7a
19 changed files with 1909 additions and 76 deletions

View File

@@ -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);

File diff suppressed because it is too large Load Diff

View File

@@ -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
} }
] ]
} }

View File

@@ -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`);

View File

@@ -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;
// }

View File

@@ -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' })

View File

@@ -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({

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

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

View File

@@ -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;
} }

View File

@@ -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"

View File

@@ -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;

View File

@@ -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"

View File

@@ -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>

View File

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

View File

@@ -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))
} }

View File

@@ -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,

View File

@@ -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',

View File

@@ -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">