From a53c10483e38cc3efeaa0319f6bb2e34db30a7d7 Mon Sep 17 00:00:00 2001 From: Su Yin <53945359+tnsyn@users.noreply.github.com> Date: Wed, 19 Oct 2022 18:37:41 +0800 Subject: [PATCH] [resumes][feat] Add pagination on browse page (#388) * [resumes][feat] Add pagination on browse page * [resume][fix] Remove unused type --- .../resumes/browse/ResumeFilterPill.tsx | 14 +- .../resumes/browse/resumeFilters.ts | 15 +- apps/portal/src/pages/resumes/browse.tsx | 217 +++++++++++------- .../router/resumes/resumes-resume-router.ts | 59 ++++- .../resumes/resumes-resume-user-router.ts | 133 ++++++++++- 5 files changed, 324 insertions(+), 114 deletions(-) diff --git a/apps/portal/src/components/resumes/browse/ResumeFilterPill.tsx b/apps/portal/src/components/resumes/browse/ResumeFilterPill.tsx index 3961ac9d..a8aa10d7 100644 --- a/apps/portal/src/components/resumes/browse/ResumeFilterPill.tsx +++ b/apps/portal/src/components/resumes/browse/ResumeFilterPill.tsx @@ -1,12 +1,22 @@ +import clsx from 'clsx'; + type Props = Readonly<{ + isSelected: boolean; onClick?: (event: React.MouseEvent) => void; title: string; }>; -export default function ResumeFilterPill({ title, onClick }: Props) { +export default function ResumeFilterPill({ + title, + onClick, + isSelected, +}: Props) { return ( +
+
+
+ + +
+
+ + {Object.entries(SORT_OPTIONS).map(([key, value]) => ( + + setSortOrder(key) + }> + ))} + +
+
+ +
@@ -265,6 +296,7 @@ export default function ResumeHomePage() { {SHORTCUTS.map((shortcut) => (
  • onShortcutChange(shortcut)} /> @@ -339,17 +371,26 @@ export default function ResumeHomePage() { {renderSignInButton && ( )} + {totalPages === 0 && ( +
    Nothing to see here.
    + )} +
    + setCurrentPage(page)} + /> +
    diff --git a/apps/portal/src/server/router/resumes/resumes-resume-router.ts b/apps/portal/src/server/router/resumes/resumes-resume-router.ts index 4f5c33d8..00c9f13b 100644 --- a/apps/portal/src/server/router/resumes/resumes-resume-router.ts +++ b/apps/portal/src/server/router/resumes/resumes-resume-router.ts @@ -6,8 +6,36 @@ import type { Resume } from '~/types/resume'; export const resumesRouter = createRouter() .query('findAll', { - async resolve({ ctx }) { + input: z.object({ + experienceFilters: z.string().array(), + locationFilters: z.string().array(), + numComments: z.number().optional(), + roleFilters: z.string().array(), + skip: z.number(), + sortOrder: z.string(), + }), + async resolve({ ctx, input }) { + const { + roleFilters, + locationFilters, + experienceFilters, + sortOrder, + numComments, + skip, + } = input; const userId = ctx.session?.user?.id; + const totalRecords = await ctx.prisma.resumesResume.count({ + where: { + ...(numComments === 0 && { + comments: { + none: {}, + }, + }), + experience: { in: experienceFilters }, + location: { in: locationFilters }, + role: { in: roleFilters }, + }, + }); const resumesData = await ctx.prisma.resumesResume.findMany({ include: { _count: { @@ -16,6 +44,7 @@ export const resumesRouter = createRouter() stars: true, }, }, + comments: true, stars: { where: { OR: { @@ -29,11 +58,32 @@ export const resumesRouter = createRouter() }, }, }, - orderBy: { - createdAt: 'desc', + orderBy: + sortOrder === 'latest' + ? { + createdAt: 'desc', + } + : sortOrder === 'popular' + ? { + stars: { + _count: 'desc', + }, + } + : { comments: { _count: 'desc' } }, + skip, + take: 10, + where: { + ...(numComments === 0 && { + comments: { + none: {}, + }, + }), + experience: { in: experienceFilters }, + location: { in: locationFilters }, + role: { in: roleFilters }, }, }); - return resumesData.map((r) => { + const mappedResumeData = resumesData.map((r) => { const resume: Resume = { additionalInfo: r.additionalInfo, createdAt: r.createdAt, @@ -50,6 +100,7 @@ export const resumesRouter = createRouter() }; return resume; }); + return { mappedResumeData, totalRecords }; }, }) .query('findOne', { diff --git a/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts b/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts index a61aa3ac..e368365f 100644 --- a/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts +++ b/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts @@ -45,8 +45,39 @@ export const resumesResumeUserRouter = createProtectedRouter() }, }) .query('findUserStarred', { - async resolve({ ctx }) { + input: z.object({ + experienceFilters: z.string().array(), + locationFilters: z.string().array(), + numComments: z.number().optional(), + roleFilters: z.string().array(), + skip: z.number(), + sortOrder: z.string(), + }), + async resolve({ ctx, input }) { const userId = ctx.session.user.id; + const { + roleFilters, + locationFilters, + experienceFilters, + sortOrder, + numComments, + skip, + } = input; + const totalRecords = await ctx.prisma.resumesStar.count({ + where: { + resume: { + ...(numComments === 0 && { + comments: { + none: {}, + }, + }), + experience: { in: experienceFilters }, + location: { in: locationFilters }, + role: { in: roleFilters }, + }, + userId, + }, + }); const resumeStarsData = await ctx.prisma.resumesStar.findMany({ include: { resume: { @@ -65,14 +96,46 @@ export const resumesResumeUserRouter = createProtectedRouter() }, }, }, - orderBy: { - createdAt: 'desc', - }, + orderBy: + sortOrder === 'latest' + ? { + resume: { + createdAt: 'desc', + }, + } + : sortOrder === 'popular' + ? { + resume: { + stars: { + _count: 'desc', + }, + }, + } + : { + resume: { + comments: { + _count: 'desc', + }, + }, + }, + skip, + take: 10, where: { + resume: { + ...(numComments === 0 && { + comments: { + none: {}, + }, + }), + experience: { in: experienceFilters }, + location: { in: locationFilters }, + role: { in: roleFilters }, + }, userId, }, }); - return resumeStarsData.map((rs) => { + + const mappedResumeData = resumeStarsData.map((rs) => { const resume: Resume = { additionalInfo: rs.resume.additionalInfo, createdAt: rs.resume.createdAt, @@ -89,11 +152,41 @@ export const resumesResumeUserRouter = createProtectedRouter() }; return resume; }); + return { mappedResumeData, totalRecords }; }, }) .query('findUserCreated', { - async resolve({ ctx }) { + input: z.object({ + experienceFilters: z.string().array(), + locationFilters: z.string().array(), + numComments: z.number().optional(), + roleFilters: z.string().array(), + skip: z.number(), + sortOrder: z.string(), + }), + async resolve({ ctx, input }) { const userId = ctx.session.user.id; + const { + roleFilters, + locationFilters, + experienceFilters, + sortOrder, + numComments, + skip, + } = input; + const totalRecords = await ctx.prisma.resumesResume.count({ + where: { + ...(numComments === 0 && { + comments: { + none: {}, + }, + }), + experience: { in: experienceFilters }, + location: { in: locationFilters }, + role: { in: roleFilters }, + userId, + }, + }); const resumesData = await ctx.prisma.resumesResume.findMany({ include: { _count: { @@ -113,14 +206,33 @@ export const resumesResumeUserRouter = createProtectedRouter() }, }, }, - orderBy: { - createdAt: 'desc', - }, + orderBy: + sortOrder === 'latest' + ? { + createdAt: 'desc', + } + : sortOrder === 'popular' + ? { + stars: { + _count: 'desc', + }, + } + : { comments: { _count: 'desc' } }, + skip, + take: 10, where: { + ...(numComments === 0 && { + comments: { + none: {}, + }, + }), + experience: { in: experienceFilters }, + location: { in: locationFilters }, + role: { in: roleFilters }, userId, }, }); - return resumesData.map((r) => { + const mappedResumeData = resumesData.map((r) => { const resume: Resume = { additionalInfo: r.additionalInfo, createdAt: r.createdAt, @@ -137,5 +249,6 @@ export const resumesResumeUserRouter = createProtectedRouter() }; return resume; }); + return { mappedResumeData, totalRecords }; }, });