From 9c21eaf72485aee32a9f1cfaee4346889b141800 Mon Sep 17 00:00:00 2001 From: "Au Chen Xi, Gabriel" Date: Thu, 2 Nov 2023 17:39:51 +0800 Subject: [PATCH] Add pagination to modal --- src/components/code/QuestionToggleModal.tsx | 228 +++++++++++++++----- src/server/api/routers/question.ts | 49 +++++ 2 files changed, 223 insertions(+), 54 deletions(-) diff --git a/src/components/code/QuestionToggleModal.tsx b/src/components/code/QuestionToggleModal.tsx index 38ec7dc..09ba70d 100644 --- a/src/components/code/QuestionToggleModal.tsx +++ b/src/components/code/QuestionToggleModal.tsx @@ -4,67 +4,187 @@ import { useEffect, useState } from "react"; import { StyledInput } from "../StyledInput"; import { Difficulty } from "~/types/global"; import { StyledButton } from "../StyledButton"; +import { api } from "~/utils/api"; +import useDebounce from "~/hooks/useDebounce"; +type ReducedQuestion = { + title: string; + id: string; + category: string; + difficulty: Difficulty; +}; type QuestionToggleModalProps = { - questionTitleList: { title: string; id: string; category: string; difficulty: Difficulty }[]; - setQuestionId: (questionTitle: string) => void; + questionTitleList: ReducedQuestion[]; + setQuestionId: (questionTitle: string) => void; }; -/** - * Pagination not needed -> only text - */ -const QuestionToggleModal = ({ - questionTitleList, - setQuestionId -}: QuestionToggleModalProps) => { - const [filter, setFilter] = useState(""); - const [openModal, setOpenModal] = useState(false); - // generate the pages of the question titles - // add autocomplete to the questions - const [filteredQuestions, setFilteredQuestions] = useState(questionTitleList); - useEffect(() => { - setFilteredQuestions(questionTitleList.filter((val) => { - if (filter === "") return true; - return val.title.includes(filter); - })); - }, [questionTitleList, filter]); - return setOpenModal(val)} > -
- setFilter(e.target.value)} /> - {/* Table */} -
- - - - - - - - - - {filteredQuestions.map((data) => { - return { - e.preventDefault(); - setQuestionId(data.id); - setOpenModal(false); - }}> - - - - - })} - -
TitleCategoryDifficulty
{data.title}{data.category}{data.difficulty}
-
-
-
-} - -export default QuestionToggleModal; - +type SelectPageDivProps = { + setPage: (inputFunc: (val: number) => number) => void; + currentPage: number; + totalPages: number; + displayedRange: number; +}; +const SelectPageDiv = ({ + currentPage, + totalPages, + displayedRange, + setPage, +}: SelectPageDivProps) => { + const [allPages, setAllPages] = useState([]); + const offset = Math.floor(displayedRange / 2); + const actualRange = Math.ceil(displayedRange); + useEffect(() => { + // update the pages range + const newPages = []; + const startPage = currentPage - offset < 0 ? 0 : currentPage - offset; + for ( + let i = startPage; + i < totalPages && i < startPage + actualRange; + ++i + ) { + newPages.push(i); + } + setAllPages(newPages); + }, [totalPages, currentPage, displayedRange]); + return ( +
+ + {allPages.map((page) => { + return ( + + ); + })} + +
+ ); +}; +const pagingLimit = 50; +const QuestionToggleModal = ({ setQuestionId }: QuestionToggleModalProps) => { + const [filter, setFilter] = useState(""); + const [sentFilter, setSentFilter] = useState(""); + const [openModal, setOpenModal] = useState(false); + const [page, setPage] = useState(0); + const [filteredQuestions, setFilteredQuestions] = useState( + [], + ); + const pagedTitleData = api.question.getAllReducedInfinite.useInfiniteQuery( + { + limit: pagingLimit, + titleFilter: sentFilter === "" ? undefined : sentFilter, + }, + { + getNextPageParam: (prevPage) => prevPage.nextCursor, + }, + ); + useEffect(() => { + setFilteredQuestions(pagedTitleData.data?.pages.at(page)?.items ?? []); + }, [page, pagedTitleData.data?.pages]); + return ( + setOpenModal(val)} + > +
+
{ + e.preventDefault(); + setSentFilter(filter); + }} + className="flex flex-row gap-2" + > + { + setFilter(e.target.value); + setPage(0); + }} + /> + +
+ {/* Table */} +
+ number) => { + // need to fetch first + const fetchPages = async () => { + const newPage = val(page); + for (let i = page; i < newPage; ++i) { + await pagedTitleData.fetchNextPage(); + } + setPage(val); + }; + void fetchPages(); + return; + }} + currentPage={page} + totalPages={ + (pagedTitleData.data?.pages.at(0)?.totalCount ?? 0) / pagingLimit + } + /> + + + + + + + + + + {filteredQuestions.map((data) => { + return ( + { + e.preventDefault(); + setQuestionId(data.id); + setOpenModal(false); + }} + > + + + + + ); + })} + +
TitleCategoryDifficulty
{data.title}{data.category}{data.difficulty}
+
+
+
+ ); +}; +export default QuestionToggleModal; diff --git a/src/server/api/routers/question.ts b/src/server/api/routers/question.ts index 6d953eb..9c12726 100644 --- a/src/server/api/routers/question.ts +++ b/src/server/api/routers/question.ts @@ -26,8 +26,12 @@ export const questionRouter = createTRPCRouter({ getAll: publicProcedure.query(({ ctx }) => { return ctx.prismaMongo.question.findMany(); }), + /** + * Used to get only the first 100 questions + */ getAllReduced: publicProcedure.query(({ ctx }) => { return ctx.prismaMongo.question.findMany({ + take: 100, select: { title: true, id: true, @@ -36,6 +40,51 @@ export const questionRouter = createTRPCRouter({ }, }); }), + getAllReducedInfinite: publicProcedure + .input(z.object({ cursor: z.string().optional(), limit: z.number().min(1).max(100).nullable(), titleFilter: z.string().optional() })) + .query( + async ({input, ctx}) => { + const limit = input.limit ?? 50; + const totalCount = await ctx.prismaMongo.question.count({ + where: input.titleFilter ? { + OR: [ + { + title: { + contains: input.titleFilter + }, + }, + { + body: { + contains: input.titleFilter + } + } + ] + } : undefined, + }); + const items = await ctx.prismaMongo.question.findMany({ + take: limit + 1, + where: input.titleFilter ? { + title: { + contains: input.titleFilter + } + } : undefined, + cursor: input.cursor ? { + title: input.cursor + } : undefined, + orderBy: { + title: 'asc' + } + }); + let nextCursor: string | undefined = undefined; + if (items.length === limit + 1) { + const top = items.pop(); + nextCursor = top!.title; + } + return { + items, nextCursor, totalCount + }; + } + ), addOne: maintainerProcedure .input(questionObject) .mutation(async ({ ctx, input }) => {