|
|
@ -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 PAGE_LIMIT = 10;
|
|
|
|
|
|
|
|
const skip = (currentPage - 1) * PAGE_LIMIT;
|
|
|
|
|
|
|
|
|
|
|
|
const allResumesQuery = trpc.useQuery(['resumes.resume.findAll'], {
|
|
|
|
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,21 +371,30 @@ 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>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</main>
|
|
|
|
</main>
|
|
|
|
</>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
);
|
|
|
|