agregado Scroll infinito para las encuestas
This commit is contained in:
@@ -3,7 +3,7 @@ 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, viewProductsStore } 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, ne } 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, Store, Inventory } from './entities/inventory.entity';
|
import { Product, Store, Inventory } from './entities/inventory.entity';
|
||||||
@@ -28,12 +28,21 @@ export class InventoryService {
|
|||||||
let searchCondition: SQL<unknown> | undefined;
|
let searchCondition: SQL<unknown> | undefined;
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
searchCondition = and(or(
|
searchCondition = and(
|
||||||
like(products.title, `%${search}%`),
|
or(
|
||||||
like(products.description, `%${search}%`),
|
like(products.title, `%${search}%`),
|
||||||
), eq(products.userId, id));
|
like(products.description, `%${search}%`),
|
||||||
|
),
|
||||||
|
and(
|
||||||
|
eq(products.userId, id),
|
||||||
|
ne(products.status, 'ELIMINADO')
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
searchCondition = eq(products.userId, id);
|
searchCondition = and(
|
||||||
|
eq(products.userId, id),
|
||||||
|
ne(products.status, 'ELIMINADO')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build sort condition
|
// Build sort condition
|
||||||
@@ -98,7 +107,7 @@ export class InventoryService {
|
|||||||
like(viewProductsStore.title, `%${search}%`),
|
like(viewProductsStore.title, `%${search}%`),
|
||||||
like(viewProductsStore.description, `%${search}%`)
|
like(viewProductsStore.description, `%${search}%`)
|
||||||
),
|
),
|
||||||
or(eq(viewProductsStore.status, 'PUBLICADO'), eq(viewProductsStore.status, 'AGOTADO'))
|
and(ne(viewProductsStore.status, 'BORRADOR'), ne(viewProductsStore.status, 'ELIMINADO'))
|
||||||
)
|
)
|
||||||
} else if (search) {
|
} else if (search) {
|
||||||
or(
|
or(
|
||||||
@@ -106,7 +115,7 @@ export class InventoryService {
|
|||||||
like(viewProductsStore.description, `%${search}%`)
|
like(viewProductsStore.description, `%${search}%`)
|
||||||
)
|
)
|
||||||
} else if (isStore) {
|
} else if (isStore) {
|
||||||
searchCondition = or(eq(viewProductsStore.status, 'PUBLICADO'), eq(viewProductsStore.status, 'AGOTADO'))
|
searchCondition = and(ne(viewProductsStore.status, 'BORRADOR'), ne(viewProductsStore.status, 'ELIMINADO'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build sort condition
|
// Build sort condition
|
||||||
@@ -177,7 +186,7 @@ export class InventoryService {
|
|||||||
phone: viewProductsStore.phone
|
phone: viewProductsStore.phone
|
||||||
})
|
})
|
||||||
.from(viewProductsStore)
|
.from(viewProductsStore)
|
||||||
.where(eq(viewProductsStore.id, id));
|
.where(and(eq(viewProductsStore.id, id), ne(viewProductsStore.status, 'ELIMINADO')));
|
||||||
|
|
||||||
if (find.length === 0) {
|
if (find.length === 0) {
|
||||||
throw new HttpException('Product does not exist', HttpStatus.NOT_FOUND);
|
throw new HttpException('Product does not exist', HttpStatus.NOT_FOUND);
|
||||||
@@ -290,23 +299,21 @@ export class InventoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async remove(productId: number, userId: number): Promise<{ message: string }> {
|
async remove(productId: number, userId: number): Promise<{ message: string }> {
|
||||||
const picturesPath = join(__dirname, '..', '..', '..', '..', 'web', 'public', 'uploads', 'inventory', userId.toString() , productId.toString());
|
// const picturesPath = join(__dirname, '..', '..', '..', '..', 'web', 'public', 'uploads', 'inventory', userId.toString() , productId.toString());
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
// Borra el directorio y todos sus contenidos de forma recursiva y forzada.
|
// // Borra el directorio y todos sus contenidos de forma recursiva y forzada.
|
||||||
await rm(picturesPath, { recursive: true, force: true });
|
// await rm(picturesPath, { recursive: true, force: true });
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
// Es buena práctica manejar el error, aunque `force: true` lo hace menos probable.
|
// console.error(`No se pudo eliminar el directorio ${picturesPath}:`, error);
|
||||||
// Podrías registrar el error, pero no detener la ejecución.
|
// }
|
||||||
console.error(`No se pudo eliminar el directorio ${picturesPath}:`, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if exists
|
// Check if exists
|
||||||
await this.findOne(productId);
|
await this.findOne(productId);
|
||||||
|
|
||||||
// Delete user (this will cascade delete related records due to foreign key constraints)
|
// Delete user (this will cascade delete related records due to foreign key constraints)
|
||||||
await this.drizzle.delete(products).where(eq(products.id, productId));
|
// await this.drizzle.delete(products).where(eq(products.id, productId));
|
||||||
// await this.drizzle.update(products).set({ status: 'ELIMINADO' }).where(eq(products.id, productId));
|
await this.drizzle.update(products).set({ status: 'ELIMINADO' }).where(eq(products.id, productId));
|
||||||
|
|
||||||
return { message: 'Product deleted successfully' };
|
return { message: 'Product deleted successfully' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|||||||
import * as schema from '@/database/index';
|
import * as schema from '@/database/index';
|
||||||
import { CreateSurveyDto } from './dto/create-survey.dto';
|
import { CreateSurveyDto } from './dto/create-survey.dto';
|
||||||
import { UpdateSurveyDto } from './dto/update-survey.dto';
|
import { UpdateSurveyDto } from './dto/update-survey.dto';
|
||||||
import { and, count, eq, ilike, isNull, or, sql } from 'drizzle-orm';
|
import { and, count, eq, ilike, isNull, like, or, sql } from 'drizzle-orm';
|
||||||
import { SurveyDetailDto, SurveyStatisticsResponseDto } from './dto/statistics-response.dto';
|
import { SurveyDetailDto, SurveyStatisticsResponseDto } from './dto/statistics-response.dto';
|
||||||
import { PaginationDto } from '@/common/dto/pagination.dto';
|
import { PaginationDto } from '@/common/dto/pagination.dto';
|
||||||
import { AnswersSurveyDto } from './dto/response-survey.dto';
|
import { AnswersSurveyDto } from './dto/response-survey.dto';
|
||||||
@@ -92,7 +92,15 @@ export class SurveysService {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
if (findForUserDto.rol[0].rol !== 'superadmin' && findForUserDto.rol[0].rol !== 'admin') {
|
if (findForUserDto.rol[0].rol !== 'superadmin' && findForUserDto.rol[0].rol !== 'admin') {
|
||||||
searchCondition = or(eq(surveys.targetAudience, findForUserDto.rol[0].rol), eq(surveys.targetAudience, 'all'))
|
searchCondition = and(
|
||||||
|
or(
|
||||||
|
eq(surveys.targetAudience, findForUserDto.rol[0].rol),
|
||||||
|
eq(surveys.targetAudience, 'all')
|
||||||
|
),
|
||||||
|
like(surveys.title, `%${search}%`)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
searchCondition = like(surveys.title, `%${search}%`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(searchCondition);
|
// console.log(searchCondition);
|
||||||
|
|||||||
@@ -1,28 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
// import PageContainer from '@/components/layout/page-container';
|
|
||||||
// import { ProductList } from '@/feactures/inventory/components/products/product-list';
|
|
||||||
import { ProductList } from '@/feactures/inventory/components/products/product-list-scroll';
|
import { ProductList } from '@/feactures/inventory/components/products/product-list-scroll';
|
||||||
import { Button } from '@repo/shadcn/components/ui/button';
|
import { Button } from '@repo/shadcn/components/ui/button';
|
||||||
// import { Metadata } from 'next';
|
// import { Metadata } from 'next';
|
||||||
import { useAllProductQuery } from '@/feactures/inventory/hooks/use-query-products';
|
|
||||||
import { allProducts } from '@/feactures/inventory/schemas/inventory';
|
|
||||||
|
|
||||||
|
|
||||||
// export const metadata: Metadata = {
|
|
||||||
// title: 'Productos - Fondemi',
|
|
||||||
// description: 'Listado de productos disponibles',
|
|
||||||
// };
|
|
||||||
|
|
||||||
export default function SurveysPage() {
|
export default function SurveysPage() {
|
||||||
// const { data } = useAllProductQuery();
|
|
||||||
// let products: allProducts[] = []
|
|
||||||
|
|
||||||
// if (data?.data) {
|
|
||||||
// products = data.data
|
|
||||||
// }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// <PageContainer>
|
|
||||||
<main className='p-4 md:px-6'>
|
<main className='p-4 md:px-6'>
|
||||||
<header className="w-full flex flex-col sm:flex-row sm:justify-between">
|
<header className="w-full flex flex-col sm:flex-row sm:justify-between">
|
||||||
<h1 className="text-2xl font-bold mb-1">Productos disponibles</h1>
|
<h1 className="text-2xl font-bold mb-1">Productos disponibles</h1>
|
||||||
@@ -32,7 +15,5 @@ export default function SurveysPage() {
|
|||||||
</header>
|
</header>
|
||||||
<ProductList/>
|
<ProductList/>
|
||||||
</main>
|
</main>
|
||||||
// </PageContainer>
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
export const STATUS = {
|
export const STATUS = {
|
||||||
|
PUBLICADO:"Publicado",
|
||||||
|
AGOTADO:"Agotado",
|
||||||
|
BORRADOR:"Borrador"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PRIVATESTATUS = {
|
||||||
PUBLICADO:"Publicado",
|
PUBLICADO:"Publicado",
|
||||||
AGOTADO:"Agotado",
|
AGOTADO:"Agotado",
|
||||||
BORRADOR:"Borrador",
|
BORRADOR:"Borrador",
|
||||||
|
ELIMINADO:"Eliminado",
|
||||||
|
BLOQUEADO:"Bloqueado"
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ import { Textarea } from '@repo/shadcn/components/ui/textarea';
|
|||||||
import {STATUS} from '@/constants/status'
|
import {STATUS} from '@/constants/status'
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import {sizeFormate} from "@/feactures/inventory/utils/sizeFormate"
|
import {sizeFormate} from "@/feactures/inventory/utils/sizeFormate"
|
||||||
import { z } from 'zod'; // Asegúrate de importar Zod
|
// import { z } from 'zod'; // Asegúrate de importar Zod
|
||||||
|
|
||||||
// --- MODIFICACIÓN CLAVE ---
|
// --- MODIFICACIÓN CLAVE ---
|
||||||
// Extiende tu esquema para incluir el campo de imagen como FileList para el frontend
|
// Extiende tu esquema para incluir el campo de imagen como FileList para el frontend
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useAllProductInfiniteQuery } from '@/feactures/inventory/hooks/use-query-products';
|
import { useAllProductInfiniteQuery } from '@/feactures/inventory/hooks/use-query-products';
|
||||||
import { ProductCard } from '@/feactures/inventory/components/products/productCard';
|
import { ProductCard } from '@/feactures/inventory/components/products/productCard';
|
||||||
import { allProducts } from '@/feactures/inventory/schemas/inventory';
|
// import { allProducts } from '@/feactures/inventory/schemas/inventory';
|
||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState } from 'react';
|
||||||
import { Input } from '@repo/shadcn/components/ui/input';
|
import { Input } from '@repo/shadcn/components/ui/input';
|
||||||
import { Button } from '@repo/shadcn/components/ui/button';
|
import { Button } from '@repo/shadcn/components/ui/button';
|
||||||
@@ -20,21 +20,6 @@ export function ProductList() {
|
|||||||
isLoading
|
isLoading
|
||||||
} = useAllProductInfiniteQuery(search);
|
} = useAllProductInfiniteQuery(search);
|
||||||
|
|
||||||
// Funcion al hacer click en un producto
|
|
||||||
const goTo = (id: number) => {
|
|
||||||
router.push(`/dashboard/productos/${id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// funcion para el buscador
|
|
||||||
const formSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault()
|
|
||||||
const formdata = new FormData(e.currentTarget)
|
|
||||||
setSearch(formdata.get('search') as string)
|
|
||||||
console.log('submit')
|
|
||||||
}
|
|
||||||
|
|
||||||
const products = data?.pages.flatMap(page => page.data) || [];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!lastProductRef.current || !hasNextPage || isFetchingNextPage) return;
|
if (!lastProductRef.current || !hasNextPage || isFetchingNextPage) return;
|
||||||
|
|
||||||
@@ -58,26 +43,39 @@ export function ProductList() {
|
|||||||
};
|
};
|
||||||
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||||
|
|
||||||
|
// Funcion al hacer click en un producto
|
||||||
|
const goTo = (id: number) => {
|
||||||
|
router.push(`/dashboard/productos/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// funcion para el buscador
|
||||||
|
const formSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
const formdata = new FormData(e.currentTarget)
|
||||||
|
setSearch(formdata.get('search') as string)
|
||||||
|
console.log('submit')
|
||||||
|
}
|
||||||
|
|
||||||
|
const products = data?.pages.flatMap(page => page.data) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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">
|
<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">
|
||||||
|
<form onSubmit={formSubmit} action={''} className='col-span-full text-center py-3 flex gap-3'>
|
||||||
|
<Input name='search' type='text' placeholder='Buscar...' className='' defaultValue={search}/>
|
||||||
|
<Button variant={'outline'} className=''>Buscar</Button>
|
||||||
|
</form>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<section className="col-span-full text-center py-10">
|
<section className="col-span-full text-center py-10">
|
||||||
<p className="text-muted-foreground">Cargando productos...</p>
|
<p className="text-muted-foreground">Cargando productos...</p>
|
||||||
</section>
|
</section>
|
||||||
) : products.length === 0 ? (
|
) : products.length === 0 ? (
|
||||||
<section className="col-span-full text-center py-10">
|
<section className="col-span-full text-center py-10">
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">No hay productos disponibles en este momento.</p>
|
||||||
No hay productos disponibles en este momento.
|
|
||||||
</p>
|
|
||||||
</section>
|
</section>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<form onSubmit={formSubmit} action={''} className='col-span-full text-center py-3 flex gap-3'>
|
|
||||||
<Input name='search' type='text' placeholder='Buscar...' className='' defaultValue={search}/>
|
|
||||||
<Button variant={'outline'} className=''>Buscar</Button>
|
|
||||||
</form>
|
|
||||||
{products.map((item, index) => {
|
{products.map((item, index) => {
|
||||||
const isLastElement = index === products.length - 1;
|
const isLastElement = index === products.length - 1;
|
||||||
return (
|
return (
|
||||||
<div ref={isLastElement ? lastProductRef : null} key={item.id}>
|
<div ref={isLastElement ? lastProductRef : null} key={item.id}>
|
||||||
<ProductCard product={item} onClick={() => goTo(Number(item.id))} />
|
<ProductCard product={item} onClick={() => goTo(Number(item.id))} />
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
'use client';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { useAllProductQuery } from '@/feactures/inventory/hooks/use-query-products';
|
|
||||||
import { allProducts } from '../../schemas/inventory';
|
|
||||||
import { ProductCard } from '@/feactures/inventory/components/products/productCard'
|
|
||||||
|
|
||||||
export function ProductList() {
|
|
||||||
const router = useRouter();
|
|
||||||
const { data: produts } = useAllProductQuery();
|
|
||||||
|
|
||||||
const handle = (id: number) => {
|
|
||||||
router.push(`/dashboard/productos/${id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<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 ? (
|
|
||||||
<section className="col-span-full text-center py-10">
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
No hay productos disponibles en este momento.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
) : (
|
|
||||||
produts?.data.map((data: allProducts) => (
|
|
||||||
<ProductCard product={data} onClick={() => handle(Number(data.id))} key={data.id} />
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -10,34 +10,30 @@ import {
|
|||||||
import { allProducts } from '../../schemas/inventory';
|
import { allProducts } from '../../schemas/inventory';
|
||||||
|
|
||||||
interface cardProps {
|
interface cardProps {
|
||||||
product: allProducts;
|
product: allProducts;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProductCard({ product, onClick }: cardProps) {
|
export function ProductCard({ product, onClick }: cardProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="cursor-pointer flex flex-col" onClick={onClick}
|
<Card className="cursor-pointer flex flex-col" onClick={onClick}>
|
||||||
>
|
<CardTitle className="text-base font-bold truncate p-3 text-primary">
|
||||||
{/* <CardHeader> */}
|
{product.title.charAt(0).toUpperCase() + product.title.slice(1)}
|
||||||
<CardTitle className="text-base font-bold truncate p-3 text-primary">
|
</CardTitle>
|
||||||
{product.title.charAt(0).toUpperCase() + product.title.slice(1)}
|
<CardContent className="p-0 flex-grow">
|
||||||
</CardTitle>
|
<img
|
||||||
{/* </CardHeader> */}
|
className="object-cover w-full h-full aspect-square border"
|
||||||
<CardContent className="p-0 flex-grow">
|
src={`/uploads/inventory/${product.userId}/${product.id}/${product.urlImg}`}
|
||||||
<img
|
alt=""
|
||||||
className="object-cover w-full h-full aspect-square border"
|
/>
|
||||||
src={`/uploads/inventory/${product.userId}/${product.id}/${product.urlImg}`}
|
</CardContent>
|
||||||
alt=""
|
<CardFooter className="flex-col items-start p-4">
|
||||||
/>
|
{product.status === 'AGOTADO' ? (
|
||||||
</CardContent>
|
<p className="font-semibold text-lg text-red-900">AGOTADO</p>
|
||||||
<CardFooter className="flex-col items-start p-4">
|
) : ('')}
|
||||||
{product.status === 'AGOTADO' ? (
|
<p className="font-semibold text-lg">$ {product.price}</p>
|
||||||
<p className="font-semibold text-lg text-red-900">AGOTADO</p>
|
</CardFooter>
|
||||||
): ('')}
|
</Card>
|
||||||
<p className="font-semibold text-lg">$ {product.price}</p>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
55
apps/web/feactures/surveys/components/survey-card.tsx
Normal file
55
apps/web/feactures/surveys/components/survey-card.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Button } from '@repo/shadcn/button';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@repo/shadcn/card';
|
||||||
|
import { Badge } from '@repo/shadcn/badge';
|
||||||
|
import { BadgeCheck } from 'lucide-react';
|
||||||
|
import { SurveyAnswerForUser } from '../schemas/survey';
|
||||||
|
|
||||||
|
|
||||||
|
interface cardProps {
|
||||||
|
data: SurveyAnswerForUser;
|
||||||
|
onClick: (id: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SurveyCard ({ data, onClick }: cardProps) {
|
||||||
|
return (
|
||||||
|
<Card key={data.surveys.id} className="flex flex-col">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{data.surveys.title}</CardTitle>
|
||||||
|
<CardDescription>{data.surveys.description}</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex-grow">
|
||||||
|
<section className="space-y-2 text-sm">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-muted-foreground">Fecha de creación:</span>
|
||||||
|
<span>{new Date(data.surveys.created_at).toLocaleDateString()}</span>
|
||||||
|
</div>
|
||||||
|
{data.surveys.closingDate && (
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-muted-foreground">Fecha de cierre:</span>
|
||||||
|
<span>{new Date(data.surveys.closingDate).toLocaleDateString()}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex justify-center">
|
||||||
|
{data.answers_surveys === null ? (
|
||||||
|
<Button className="w-full" onClick={() => onClick(Number(data.surveys.id))}>
|
||||||
|
Responder
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Badge className="px-4 py-2 w-full bg-green-600 text-black">
|
||||||
|
<BadgeCheck size={28} />
|
||||||
|
Realizada
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,83 +4,99 @@
|
|||||||
// - Permite editar encuestas existentes
|
// - Permite editar encuestas existentes
|
||||||
// - Permite eliminar encuestas con confirmación
|
// - Permite eliminar encuestas con confirmación
|
||||||
// - Muestra el estado (publicada/borrador), fechas y conteo de respuestas
|
// - Muestra el estado (publicada/borrador), fechas y conteo de respuestas
|
||||||
|
|
||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Button } from '@repo/shadcn/button';
|
import { Button } from '@repo/shadcn/button';
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@repo/shadcn/card';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useSurveysForUserQuery } from '@/feactures/surveys/hooks/use-query-surveys';
|
import { useAllSurveysInfiniteQuery } from '@/feactures/surveys/hooks/use-query-surveys';
|
||||||
import { SurveyAnswerForUser } from '../schemas/survey';
|
import { SurveyCard } from '@/feactures/surveys/components/survey-card';
|
||||||
import { Badge } from '@repo/shadcn/badge';
|
|
||||||
import { BadgeCheck } from 'lucide-react';
|
import { SurveyAnswerForUser } from '../schemas/survey';
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { Input } from '@repo/shadcn/components/ui/input';
|
||||||
|
|
||||||
export function SurveyList() {
|
export function SurveyList() {
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {data: surveys} = useSurveysForUserQuery()
|
const lastProductRef = useRef(null);
|
||||||
|
const [search, setSearch] = useState("")
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isFetchingNextPage,
|
||||||
|
isLoading
|
||||||
|
} = useAllSurveysInfiniteQuery(search)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!lastProductRef.current || !hasNextPage || isFetchingNextPage) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
if (entries[0]?.isIntersecting) {
|
||||||
|
fetchNextPage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: null,
|
||||||
|
rootMargin: '200px',
|
||||||
|
threshold: 1.0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
observer.observe(lastProductRef.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
||||||
|
|
||||||
|
const surveys = data?.pages.flatMap(page => page.data) || [];
|
||||||
|
|
||||||
|
// funcion para el buscador
|
||||||
|
const formSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
const formdata = new FormData(e.currentTarget)
|
||||||
|
setSearch(formdata.get('search') as string)
|
||||||
|
// console.log('submit')
|
||||||
|
}
|
||||||
|
|
||||||
const handleRespond = (surveyId: number) => {
|
const handleRespond = (surveyId: number) => {
|
||||||
router.push(`/dashboard/encuestas/${surveyId}/responder`);
|
router.push(`/dashboard/encuestas/${surveyId}/responder`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log(surveys?.data)
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{surveys?.meta.totalPages === 0 ? (
|
<form onSubmit={formSubmit} action={''} className='col-span-full text-center py-3 flex gap-3'>
|
||||||
<div className="col-span-full text-center py-10">
|
<Input name='search' type='text' placeholder='Buscar...' className='' defaultValue={search}/>
|
||||||
|
<Button variant={'outline'} className=''>Buscar</Button>
|
||||||
|
</form>
|
||||||
|
{isLoading ? (
|
||||||
|
<section className="col-span-full text-center py-10">
|
||||||
|
<p className="text-muted-foreground">Cargando productos...</p>
|
||||||
|
</section>
|
||||||
|
) : surveys.length === 0 ? (
|
||||||
|
<section className="col-span-full text-center py-10">
|
||||||
<p className="text-muted-foreground">No hay encuestas disponibles en este momento.</p>
|
<p className="text-muted-foreground">No hay encuestas disponibles en este momento.</p>
|
||||||
</div>
|
</section>
|
||||||
) : (
|
) : (
|
||||||
surveys?.data.map((data: SurveyAnswerForUser) => (
|
<>
|
||||||
|
{surveys.map((data: SurveyAnswerForUser, index) => {
|
||||||
<Card key={data.surveys.id} className="flex flex-col">
|
const isLastElement = index === surveys.length - 1;
|
||||||
<CardHeader>
|
return (
|
||||||
<CardTitle>{data.surveys.title}</CardTitle>
|
<div ref={isLastElement ? lastProductRef : null} key={data.surveys.id}>
|
||||||
<CardDescription>{data.surveys.description}</CardDescription>
|
<SurveyCard data={data} onClick={handleRespond}/>
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex-grow">
|
|
||||||
<div className="space-y-2 text-sm">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-muted-foreground">Fecha de creación:</span>
|
|
||||||
{/* <span>{data.surveys.created_at.toLocaleDateString()}</span> */}
|
|
||||||
<span>{new Date(data.surveys.created_at).toLocaleDateString()}</span>
|
|
||||||
</div>
|
|
||||||
{data.surveys.closingDate && (
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-muted-foreground">Fecha de cierre:</span>
|
|
||||||
{/* <span>{data.surveys.closingDate.toLocaleDateString()}</span> */}
|
|
||||||
<span>{new Date(data.surveys.closingDate).toLocaleDateString()}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
)
|
||||||
<CardFooter className="flex justify-center">
|
})}
|
||||||
{data.answers_surveys === null ? (
|
{isFetchingNextPage && (
|
||||||
<Button
|
<section className="col-span-full text-center py-10">
|
||||||
className="w-full"
|
<p className="text-muted-foreground">Cargando más productos...</p>
|
||||||
onClick={() => handleRespond(Number(data.surveys.id))}
|
</section>
|
||||||
>
|
)}
|
||||||
Responder
|
</>
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Badge className="px-4 py-2 w-full bg-green-600 text-black">
|
|
||||||
<BadgeCheck size={28} />
|
|
||||||
Realizada
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
))
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useSafeQuery } from "@/hooks/use-safe-query";
|
import { useSafeInfiniteQuery, useSafeQuery } from "@/hooks/use-safe-query";
|
||||||
import { getSurveyByIdAction, getSurveysAction, getSurveysForUserAction } from "../actions/surveys-actions";
|
import { getSurveyByIdAction, getSurveysAction, getSurveysForUserAction } from "../actions/surveys-actions";
|
||||||
|
|
||||||
|
|
||||||
@@ -8,13 +8,25 @@ export function useSurveysQuery(params = {}) {
|
|||||||
return useSafeQuery(['surveys',params], () => getSurveysAction(params))
|
return useSafeQuery(['surveys',params], () => getSurveysAction(params))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useAllSurveysInfiniteQuery(search: string = '') {
|
||||||
|
return useSafeInfiniteQuery(
|
||||||
|
['surveys', search],
|
||||||
|
// pageParam + 1 para evitar duplicación de datos
|
||||||
|
({ pageParam = 0 }) => getSurveysForUserAction({ page: pageParam + 1, limit: 10, search }),
|
||||||
|
(lastPage, allPages) => {
|
||||||
|
// Esta lógica determina el 'pageParam' para la siguiente página
|
||||||
|
const nextPage = allPages.length;
|
||||||
|
// Puedes añadir una condición para saber si hay más páginas
|
||||||
|
if (lastPage.data.length < 10) return undefined;
|
||||||
|
return nextPage;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function useSurveysForUserQuery(params = {}) {
|
export function useSurveysForUserQuery(params = {}) {
|
||||||
return useSafeQuery(['surveys',params], () => getSurveysForUserAction(params))
|
return useSafeQuery(['surveys',params], () => getSurveysForUserAction(params))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function useSurveysByIdQuery(id: number) {
|
export function useSurveysByIdQuery(id: number) {
|
||||||
return useSafeQuery(['surveys',id], () => getSurveyByIdAction(id))
|
return useSafeQuery(['surveys',id], () => getSurveyByIdAction(id))
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,6 @@ export function useSafeInfiniteQuery<T, K = unknown>(
|
|||||||
queryKey: [string, K?],
|
queryKey: [string, K?],
|
||||||
queryFn: ({ pageParam }: { pageParam: number }) => Promise<T>,
|
queryFn: ({ pageParam }: { pageParam: number }) => Promise<T>,
|
||||||
getNextPageParam: (lastPage: T, allPages: T[]) => number | undefined,
|
getNextPageParam: (lastPage: T, allPages: T[]) => number | undefined,
|
||||||
// options?: Omit<UseQueryOptions<T>, 'queryKey' | 'queryFn'>
|
|
||||||
) {
|
) {
|
||||||
return useInfiniteQuery({
|
return useInfiniteQuery({
|
||||||
queryKey,
|
queryKey,
|
||||||
@@ -25,20 +24,4 @@ export function useSafeInfiniteQuery<T, K = unknown>(
|
|||||||
getNextPageParam,
|
getNextPageParam,
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function useAllProductInfiniteQuery(){
|
|
||||||
// return useInfiniteQuery({
|
|
||||||
// queryKey:['product'],
|
|
||||||
// queryFn: ({ pageParam = 0 }) => getAllProducts({ page: pageParam + 1, limit: 10 }),
|
|
||||||
// getNextPageParam: (lastPage, allPages) => {
|
|
||||||
// // Esta lógica determina el 'pageParam' para la siguiente página
|
|
||||||
// const nextPage = allPages.length;
|
|
||||||
// // Puedes añadir una condición para saber si hay más páginas
|
|
||||||
// if (lastPage.data.length < 10) return undefined;
|
|
||||||
// return nextPage;
|
|
||||||
// },
|
|
||||||
// initialPageParam: 0,
|
|
||||||
// })
|
|
||||||
|
|
||||||
// }
|
|
||||||
Reference in New Issue
Block a user