mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-29 13:13:54 +08:00
[resumes][feat] Add pagination on browse page (#388)
* [resumes][feat] Add pagination on browse page * [resume][fix] Remove unused type
This commit is contained in:
@ -1,12 +1,22 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
type Props = Readonly<{
|
type Props = Readonly<{
|
||||||
|
isSelected: boolean;
|
||||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||||
title: string;
|
title: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export default function ResumeFilterPill({ title, onClick }: Props) {
|
export default function ResumeFilterPill({
|
||||||
|
title,
|
||||||
|
onClick,
|
||||||
|
isSelected,
|
||||||
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="rounded-xl border border-indigo-500 border-transparent bg-white px-2 py-1 text-xs font-medium text-indigo-500 focus:bg-indigo-500 focus:text-white"
|
className={clsx(
|
||||||
|
'rounded-xl border border-indigo-500 border-transparent px-2 py-1 text-xs font-medium focus:bg-indigo-500 focus:text-white',
|
||||||
|
isSelected ? 'bg-indigo-500 text-white' : 'bg-white text-indigo-500',
|
||||||
|
)}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}>
|
onClick={onClick}>
|
||||||
{title}
|
{title}
|
||||||
|
@ -41,11 +41,6 @@ export type FilterState = Partial<CustomFilter> &
|
|||||||
|
|
||||||
export type SortOrder = 'latest' | 'popular' | 'topComments';
|
export type SortOrder = 'latest' | 'popular' | 'topComments';
|
||||||
|
|
||||||
type SortOption = {
|
|
||||||
name: string;
|
|
||||||
value: SortOrder;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Shortcut = {
|
export type Shortcut = {
|
||||||
customFilters?: CustomFilter;
|
customFilters?: CustomFilter;
|
||||||
filters: FilterState;
|
filters: FilterState;
|
||||||
@ -59,11 +54,11 @@ export const BROWSE_TABS_VALUES = {
|
|||||||
STARRED: 'starred',
|
STARRED: 'starred',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SORT_OPTIONS: Array<SortOption> = [
|
export const SORT_OPTIONS: Record<string, string> = {
|
||||||
{ name: 'Latest', value: 'latest' },
|
latest: 'Latest',
|
||||||
{ name: 'Popular', value: 'popular' },
|
popular: 'Popular',
|
||||||
{ name: 'Top Comments', value: 'topComments' },
|
topComments: 'Top Comments',
|
||||||
];
|
};
|
||||||
|
|
||||||
export const ROLE: Array<FilterOption<RoleFilter>> = [
|
export const ROLE: Array<FilterOption<RoleFilter>> = [
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import compareAsc from 'date-fns/compareAsc';
|
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Disclosure } from '@headlessui/react';
|
import { Disclosure } from '@headlessui/react';
|
||||||
import { MinusIcon, PlusIcon } from '@heroicons/react/20/solid';
|
import { MinusIcon, PlusIcon } from '@heroicons/react/20/solid';
|
||||||
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
|
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
|
||||||
@ -10,6 +9,7 @@ import {
|
|||||||
CheckboxInput,
|
CheckboxInput,
|
||||||
CheckboxList,
|
CheckboxList,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
Pagination,
|
||||||
Tabs,
|
Tabs,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from '@tih/ui';
|
} from '@tih/ui';
|
||||||
@ -18,10 +18,7 @@ import ResumeFilterPill from '~/components/resumes/browse/ResumeFilterPill';
|
|||||||
import type {
|
import type {
|
||||||
Filter,
|
Filter,
|
||||||
FilterId,
|
FilterId,
|
||||||
FilterState,
|
|
||||||
FilterValue,
|
|
||||||
Shortcut,
|
Shortcut,
|
||||||
SortOrder,
|
|
||||||
} from '~/components/resumes/browse/resumeFilters';
|
} from '~/components/resumes/browse/resumeFilters';
|
||||||
import {
|
import {
|
||||||
BROWSE_TABS_VALUES,
|
BROWSE_TABS_VALUES,
|
||||||
@ -58,59 +55,64 @@ const filters: Array<Filter> = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const filterResumes = (
|
|
||||||
resumes: Array<Resume>,
|
|
||||||
searchValue: string,
|
|
||||||
userFilters: FilterState,
|
|
||||||
) =>
|
|
||||||
resumes
|
|
||||||
.filter((resume) =>
|
|
||||||
resume.title.toLowerCase().includes(searchValue.toLocaleLowerCase()),
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
({ experience, location, role }) =>
|
|
||||||
userFilters.role.includes(role as FilterValue) &&
|
|
||||||
userFilters.experience.includes(experience as FilterValue) &&
|
|
||||||
userFilters.location.includes(location as FilterValue),
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
({ numComments }) =>
|
|
||||||
userFilters.numComments === undefined ||
|
|
||||||
numComments === userFilters.numComments,
|
|
||||||
);
|
|
||||||
|
|
||||||
const sortComparators: Record<
|
|
||||||
SortOrder,
|
|
||||||
(resume1: Resume, resume2: Resume) => number
|
|
||||||
> = {
|
|
||||||
latest: (resume1, resume2) =>
|
|
||||||
compareAsc(resume2.createdAt, resume1.createdAt),
|
|
||||||
popular: (resume1, resume2) => resume2.numStars - resume1.numStars,
|
|
||||||
topComments: (resume1, resume2) => resume2.numComments - resume1.numComments,
|
|
||||||
};
|
|
||||||
const sortResumes = (resumes: Array<Resume>, sortOrder: SortOrder) =>
|
|
||||||
resumes.sort(sortComparators[sortOrder]);
|
|
||||||
|
|
||||||
export default function ResumeHomePage() {
|
export default function ResumeHomePage() {
|
||||||
const { data: sessionData } = useSession();
|
const { data: sessionData } = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [tabsValue, setTabsValue] = useState(BROWSE_TABS_VALUES.ALL);
|
const [tabsValue, setTabsValue] = useState(BROWSE_TABS_VALUES.ALL);
|
||||||
const [sortOrder, setSortOrder] = useState(SORT_OPTIONS[0].value);
|
const [sortOrder, setSortOrder] = useState('latest');
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const [userFilters, setUserFilters] = useState(INITIAL_FILTER_STATE);
|
const [userFilters, setUserFilters] = useState(INITIAL_FILTER_STATE);
|
||||||
|
const [shortcutSelected, setShortcutSelected] = useState('All');
|
||||||
const [resumes, setResumes] = useState<Array<Resume>>([]);
|
const [resumes, setResumes] = useState<Array<Resume>>([]);
|
||||||
const [renderSignInButton, setRenderSignInButton] = useState(false);
|
const [renderSignInButton, setRenderSignInButton] = useState(false);
|
||||||
const [signInButtonText, setSignInButtonText] = useState('');
|
const [signInButtonText, setSignInButtonText] = useState('');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
|
|
||||||
const allResumesQuery = trpc.useQuery(['resumes.resume.findAll'], {
|
const PAGE_LIMIT = 10;
|
||||||
|
const skip = (currentPage - 1) * PAGE_LIMIT;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}, [userFilters, sortOrder]);
|
||||||
|
|
||||||
|
const allResumesQuery = trpc.useQuery(
|
||||||
|
[
|
||||||
|
'resumes.resume.findAll',
|
||||||
|
{
|
||||||
|
experienceFilters: userFilters.experience,
|
||||||
|
locationFilters: userFilters.location,
|
||||||
|
numComments: userFilters.numComments,
|
||||||
|
roleFilters: userFilters.role,
|
||||||
|
skip,
|
||||||
|
sortOrder,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
enabled: tabsValue === BROWSE_TABS_VALUES.ALL,
|
enabled: tabsValue === BROWSE_TABS_VALUES.ALL,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
setResumes(data);
|
setTotalPages(
|
||||||
|
data.totalRecords % PAGE_LIMIT === 0
|
||||||
|
? data.totalRecords / PAGE_LIMIT
|
||||||
|
: Math.floor(data.totalRecords / PAGE_LIMIT) + 1,
|
||||||
|
);
|
||||||
|
setResumes(data.mappedResumeData);
|
||||||
setRenderSignInButton(false);
|
setRenderSignInButton(false);
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
const starredResumesQuery = trpc.useQuery(
|
const starredResumesQuery = trpc.useQuery(
|
||||||
['resumes.resume.user.findUserStarred'],
|
[
|
||||||
|
'resumes.resume.user.findUserStarred',
|
||||||
|
{
|
||||||
|
experienceFilters: userFilters.experience,
|
||||||
|
locationFilters: userFilters.location,
|
||||||
|
numComments: userFilters.numComments,
|
||||||
|
roleFilters: userFilters.role,
|
||||||
|
skip,
|
||||||
|
sortOrder,
|
||||||
|
},
|
||||||
|
],
|
||||||
{
|
{
|
||||||
enabled: tabsValue === BROWSE_TABS_VALUES.STARRED,
|
enabled: tabsValue === BROWSE_TABS_VALUES.STARRED,
|
||||||
onError: () => {
|
onError: () => {
|
||||||
@ -119,13 +121,28 @@ export default function ResumeHomePage() {
|
|||||||
setSignInButtonText('to view starred resumes');
|
setSignInButtonText('to view starred resumes');
|
||||||
},
|
},
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
setResumes(data);
|
setTotalPages(
|
||||||
|
data.totalRecords % PAGE_LIMIT === 0
|
||||||
|
? data.totalRecords / PAGE_LIMIT
|
||||||
|
: Math.floor(data.totalRecords / PAGE_LIMIT) + 1,
|
||||||
|
);
|
||||||
|
setResumes(data.mappedResumeData);
|
||||||
},
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const myResumesQuery = trpc.useQuery(
|
const myResumesQuery = trpc.useQuery(
|
||||||
['resumes.resume.user.findUserCreated'],
|
[
|
||||||
|
'resumes.resume.user.findUserCreated',
|
||||||
|
{
|
||||||
|
experienceFilters: userFilters.experience,
|
||||||
|
locationFilters: userFilters.location,
|
||||||
|
numComments: userFilters.numComments,
|
||||||
|
roleFilters: userFilters.role,
|
||||||
|
skip,
|
||||||
|
sortOrder,
|
||||||
|
},
|
||||||
|
],
|
||||||
{
|
{
|
||||||
enabled: tabsValue === BROWSE_TABS_VALUES.MY,
|
enabled: tabsValue === BROWSE_TABS_VALUES.MY,
|
||||||
onError: () => {
|
onError: () => {
|
||||||
@ -134,7 +151,12 @@ export default function ResumeHomePage() {
|
|||||||
setSignInButtonText('to view your submitted resumes');
|
setSignInButtonText('to view your submitted resumes');
|
||||||
},
|
},
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
setResumes(data);
|
setTotalPages(
|
||||||
|
data.totalRecords % PAGE_LIMIT === 0
|
||||||
|
? data.totalRecords / PAGE_LIMIT
|
||||||
|
: Math.floor(data.totalRecords / PAGE_LIMIT) + 1,
|
||||||
|
);
|
||||||
|
setResumes(data.mappedResumeData);
|
||||||
},
|
},
|
||||||
retry: false,
|
retry: false,
|
||||||
},
|
},
|
||||||
@ -171,11 +193,18 @@ export default function ResumeHomePage() {
|
|||||||
const onShortcutChange = ({
|
const onShortcutChange = ({
|
||||||
sortOrder: shortcutSortOrder,
|
sortOrder: shortcutSortOrder,
|
||||||
filters: shortcutFilters,
|
filters: shortcutFilters,
|
||||||
|
name: shortcutName,
|
||||||
}: Shortcut) => {
|
}: Shortcut) => {
|
||||||
|
setShortcutSelected(shortcutName);
|
||||||
setSortOrder(shortcutSortOrder);
|
setSortOrder(shortcutSortOrder);
|
||||||
setUserFilters(shortcutFilters);
|
setUserFilters(shortcutFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onTabChange = (tab: string) => {
|
||||||
|
setTabsValue(tab);
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@ -195,7 +224,7 @@ export default function ResumeHomePage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-span-10">
|
<div className="col-span-10">
|
||||||
<div className="border-grey-200 grid grid-cols-12 items-center gap-4 border-b pb-2">
|
<div className="border-grey-200 grid grid-cols-12 items-center gap-4 border-b pb-2">
|
||||||
<div className="col-span-7">
|
<div className="col-span-5">
|
||||||
<Tabs
|
<Tabs
|
||||||
label="Resume Browse Tabs"
|
label="Resume Browse Tabs"
|
||||||
tabs={[
|
tabs={[
|
||||||
@ -213,10 +242,11 @@ export default function ResumeHomePage() {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={tabsValue}
|
value={tabsValue}
|
||||||
onChange={setTabsValue}
|
onChange={onTabChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-3 self-end">
|
<div className="col-span-7 flex items-center justify-evenly">
|
||||||
|
<div className="w-64">
|
||||||
<form>
|
<form>
|
||||||
<TextInput
|
<TextInput
|
||||||
label=""
|
label=""
|
||||||
@ -229,20 +259,20 @@ export default function ResumeHomePage() {
|
|||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-1 justify-self-center">
|
<div>
|
||||||
<DropdownMenu align="end" label="Sort">
|
<DropdownMenu align="end" label={SORT_OPTIONS[sortOrder]}>
|
||||||
{SORT_OPTIONS.map((option) => (
|
{Object.entries(SORT_OPTIONS).map(([key, value]) => (
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
key={option.name}
|
key={key}
|
||||||
isSelected={sortOrder === option.value}
|
isSelected={sortOrder === key}
|
||||||
label={option.name}
|
label={value}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setSortOrder(option.value)
|
setSortOrder(key)
|
||||||
}></DropdownMenu.Item>
|
}></DropdownMenu.Item>
|
||||||
))}
|
))}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-1">
|
<div>
|
||||||
<button
|
<button
|
||||||
className="rounded-md bg-indigo-500 py-1 px-3 text-sm font-medium text-white"
|
className="rounded-md bg-indigo-500 py-1 px-3 text-sm font-medium text-white"
|
||||||
type="button"
|
type="button"
|
||||||
@ -253,6 +283,7 @@ export default function ResumeHomePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-12">
|
<div className="grid grid-cols-12">
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
@ -265,6 +296,7 @@ export default function ResumeHomePage() {
|
|||||||
{SHORTCUTS.map((shortcut) => (
|
{SHORTCUTS.map((shortcut) => (
|
||||||
<li key={shortcut.name}>
|
<li key={shortcut.name}>
|
||||||
<ResumeFilterPill
|
<ResumeFilterPill
|
||||||
|
isSelected={shortcutSelected === shortcut.name}
|
||||||
title={shortcut.name}
|
title={shortcut.name}
|
||||||
onClick={() => onShortcutChange(shortcut)}
|
onClick={() => onShortcutChange(shortcut)}
|
||||||
/>
|
/>
|
||||||
@ -339,17 +371,26 @@ export default function ResumeHomePage() {
|
|||||||
{renderSignInButton && (
|
{renderSignInButton && (
|
||||||
<ResumeSignInButton text={signInButtonText} />
|
<ResumeSignInButton text={signInButtonText} />
|
||||||
)}
|
)}
|
||||||
|
{totalPages === 0 && (
|
||||||
|
<div className="mt-4">Nothing to see here.</div>
|
||||||
|
)}
|
||||||
<ResumeListItems
|
<ResumeListItems
|
||||||
isLoading={
|
isLoading={
|
||||||
allResumesQuery.isFetching ||
|
allResumesQuery.isFetching ||
|
||||||
starredResumesQuery.isFetching ||
|
starredResumesQuery.isFetching ||
|
||||||
myResumesQuery.isFetching
|
myResumesQuery.isFetching
|
||||||
}
|
}
|
||||||
resumes={sortResumes(
|
resumes={resumes}
|
||||||
filterResumes(resumes, searchValue, userFilters),
|
|
||||||
sortOrder,
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
<div className="my-4 flex justify-center">
|
||||||
|
<Pagination
|
||||||
|
current={currentPage}
|
||||||
|
end={totalPages}
|
||||||
|
label="pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={(page) => setCurrentPage(page)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,8 +6,36 @@ import type { Resume } from '~/types/resume';
|
|||||||
|
|
||||||
export const resumesRouter = createRouter()
|
export const resumesRouter = createRouter()
|
||||||
.query('findAll', {
|
.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 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({
|
const resumesData = await ctx.prisma.resumesResume.findMany({
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
@ -16,6 +44,7 @@ export const resumesRouter = createRouter()
|
|||||||
stars: true,
|
stars: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
comments: true,
|
||||||
stars: {
|
stars: {
|
||||||
where: {
|
where: {
|
||||||
OR: {
|
OR: {
|
||||||
@ -29,11 +58,32 @@ export const resumesRouter = createRouter()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy:
|
||||||
|
sortOrder === 'latest'
|
||||||
|
? {
|
||||||
createdAt: 'desc',
|
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 = {
|
const resume: Resume = {
|
||||||
additionalInfo: r.additionalInfo,
|
additionalInfo: r.additionalInfo,
|
||||||
createdAt: r.createdAt,
|
createdAt: r.createdAt,
|
||||||
@ -50,6 +100,7 @@ export const resumesRouter = createRouter()
|
|||||||
};
|
};
|
||||||
return resume;
|
return resume;
|
||||||
});
|
});
|
||||||
|
return { mappedResumeData, totalRecords };
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.query('findOne', {
|
.query('findOne', {
|
||||||
|
@ -45,8 +45,39 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.query('findUserStarred', {
|
.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 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({
|
const resumeStarsData = await ctx.prisma.resumesStar.findMany({
|
||||||
include: {
|
include: {
|
||||||
resume: {
|
resume: {
|
||||||
@ -65,14 +96,46 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy:
|
||||||
|
sortOrder === 'latest'
|
||||||
|
? {
|
||||||
|
resume: {
|
||||||
createdAt: 'desc',
|
createdAt: 'desc',
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: sortOrder === 'popular'
|
||||||
|
? {
|
||||||
|
resume: {
|
||||||
|
stars: {
|
||||||
|
_count: 'desc',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
resume: {
|
||||||
|
comments: {
|
||||||
|
_count: 'desc',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: 10,
|
||||||
where: {
|
where: {
|
||||||
|
resume: {
|
||||||
|
...(numComments === 0 && {
|
||||||
|
comments: {
|
||||||
|
none: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
|
},
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return resumeStarsData.map((rs) => {
|
|
||||||
|
const mappedResumeData = resumeStarsData.map((rs) => {
|
||||||
const resume: Resume = {
|
const resume: Resume = {
|
||||||
additionalInfo: rs.resume.additionalInfo,
|
additionalInfo: rs.resume.additionalInfo,
|
||||||
createdAt: rs.resume.createdAt,
|
createdAt: rs.resume.createdAt,
|
||||||
@ -89,11 +152,41 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
};
|
};
|
||||||
return resume;
|
return resume;
|
||||||
});
|
});
|
||||||
|
return { mappedResumeData, totalRecords };
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.query('findUserCreated', {
|
.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 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({
|
const resumesData = await ctx.prisma.resumesResume.findMany({
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
@ -113,14 +206,33 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy:
|
||||||
|
sortOrder === 'latest'
|
||||||
|
? {
|
||||||
createdAt: 'desc',
|
createdAt: 'desc',
|
||||||
|
}
|
||||||
|
: sortOrder === 'popular'
|
||||||
|
? {
|
||||||
|
stars: {
|
||||||
|
_count: 'desc',
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: { comments: { _count: 'desc' } },
|
||||||
|
skip,
|
||||||
|
take: 10,
|
||||||
where: {
|
where: {
|
||||||
|
...(numComments === 0 && {
|
||||||
|
comments: {
|
||||||
|
none: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
experience: { in: experienceFilters },
|
||||||
|
location: { in: locationFilters },
|
||||||
|
role: { in: roleFilters },
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return resumesData.map((r) => {
|
const mappedResumeData = resumesData.map((r) => {
|
||||||
const resume: Resume = {
|
const resume: Resume = {
|
||||||
additionalInfo: r.additionalInfo,
|
additionalInfo: r.additionalInfo,
|
||||||
createdAt: r.createdAt,
|
createdAt: r.createdAt,
|
||||||
@ -137,5 +249,6 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
};
|
};
|
||||||
return resume;
|
return resume;
|
||||||
});
|
});
|
||||||
|
return { mappedResumeData, totalRecords };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user