[resumes][feat] add sorting of resumes

pull/374/head
Wu Peirong 3 years ago
parent 58cc7604af
commit 54f4219262

@ -1,4 +1,4 @@
import { formatDistanceToNow } from 'date-fns';
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
import Link from 'next/link';
import type { UrlObject } from 'url';
import { ChevronRightIcon } from '@heroicons/react/20/solid';

@ -4,10 +4,16 @@ export const BROWSE_TABS_VALUES = {
STARRED: 'starred',
};
export const SORT_OPTIONS = [
{ current: true, href: '#', name: 'Latest' },
{ current: false, href: '#', name: 'Popular' },
{ current: false, href: '#', name: 'Top Comments' },
export type SortOrder = 'latest' | 'popular' | 'topComments';
type SortOption = {
name: string;
value: SortOrder;
};
export const SORT_OPTIONS: Array<SortOption> = [
{ name: 'Latest', value: 'latest' },
{ name: 'Popular', value: 'popular' },
{ name: 'Top Comments', value: 'topComments' },
];
export const TOP_HITS = [
@ -18,50 +24,45 @@ export const TOP_HITS = [
];
export type FilterOption = {
checked: boolean;
label: string;
value: string;
};
export const ROLE: Array<FilterOption> = [
{
checked: false,
label: 'Full-Stack Engineer',
value: 'Full-Stack Engineer',
},
{ checked: false, label: 'Frontend Engineer', value: 'Frontend Engineer' },
{ checked: false, label: 'Backend Engineer', value: 'Backend Engineer' },
{ checked: false, label: 'DevOps Engineer', value: 'DevOps Engineer' },
{ checked: false, label: 'iOS Engineer', value: 'iOS Engineer' },
{ checked: false, label: 'Android Engineer', value: 'Android Engineer' },
{ label: 'Frontend Engineer', value: 'Frontend Engineer' },
{ label: 'Backend Engineer', value: 'Backend Engineer' },
{ label: 'DevOps Engineer', value: 'DevOps Engineer' },
{ label: 'iOS Engineer', value: 'iOS Engineer' },
{ label: 'Android Engineer', value: 'Android Engineer' },
];
export const EXPERIENCE: Array<FilterOption> = [
{ checked: false, label: 'Freshman', value: 'Freshman' },
{ checked: false, label: 'Sophomore', value: 'Sophomore' },
{ checked: false, label: 'Junior', value: 'Junior' },
{ checked: false, label: 'Senior', value: 'Senior' },
{ label: 'Freshman', value: 'Freshman' },
{ label: 'Sophomore', value: 'Sophomore' },
{ label: 'Junior', value: 'Junior' },
{ label: 'Senior', value: 'Senior' },
{
checked: false,
label: 'Fresh Grad (0-1 years)',
value: 'Fresh Grad (0-1 years)',
},
{
checked: false,
label: 'Mid-level (2 - 5 years)',
value: 'Mid-level (2 - 5 years)',
},
{
checked: false,
label: 'Senior (5+ years)',
value: 'Senior (5+ years)',
},
];
export const LOCATION: Array<FilterOption> = [
{ checked: false, label: 'Singapore', value: 'Singapore' },
{ checked: false, label: 'United States', value: 'United States' },
{ checked: false, label: 'India', value: 'India' },
{ label: 'Singapore', value: 'Singapore' },
{ label: 'United States', value: 'United States' },
{ label: 'India', value: 'India' },
];
export const TEST_RESUMES = [

@ -1,18 +1,23 @@
import clsx from 'clsx';
import compareAsc from 'date-fns/compareAsc';
import Head from 'next/head';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
import { Fragment, useState } from 'react';
import { Disclosure, Menu, Transition } from '@headlessui/react';
import {
ChevronDownIcon,
MinusIcon,
PlusIcon,
} from '@heroicons/react/20/solid';
import { useState } from 'react';
import { Disclosure } from '@headlessui/react';
import { MinusIcon, PlusIcon } from '@heroicons/react/20/solid';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { CheckboxInput, CheckboxList, Tabs, TextInput } from '@tih/ui';
import {
CheckboxInput,
CheckboxList,
DropdownMenu,
Tabs,
TextInput,
} from '@tih/ui';
import type { FilterOption } from '~/components/resumes/browse/resumeConstants';
import type {
FilterOption,
SortOrder,
} from '~/components/resumes/browse/resumeConstants';
import {
BROWSE_TABS_VALUES,
EXPERIENCE,
@ -66,8 +71,8 @@ const filterResumes = (
resumes: Array<Resume>,
searchValue: string,
userFilters: FilterState,
) => {
return resumes
) =>
resumes
.filter((resume) =>
resume.title.toLowerCase().includes(searchValue.toLocaleLowerCase()),
)
@ -77,12 +82,24 @@ const filterResumes = (
userFilters.experience.includes(experience) &&
userFilters.location.includes(location),
);
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() {
const { data: sessionData } = useSession();
const router = useRouter();
const [tabsValue, setTabsValue] = useState(BROWSE_TABS_VALUES.ALL);
const [sortOrder, setSortOrder] = useState(SORT_OPTIONS[0].value);
const [searchValue, setSearchValue] = useState('');
const [userFilters, setUserFilters] = useState(INITIAL_FILTER_STATE);
const [resumes, setResumes] = useState<Array<Resume>>([]);
@ -207,49 +224,17 @@ export default function ResumeHomePage() {
</form>
</div>
<div className="col-span-1 justify-self-center">
<Menu as="div" className="relative inline-block text-left">
<div>
{/* TODO: Sort logic */}
<Menu.Button className="group inline-flex justify-center text-sm font-medium text-gray-700 hover:text-gray-900">
Sort
<ChevronDownIcon
aria-hidden="true"
className="-mr-1 ml-1 h-5 w-5 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95">
<Menu.Items className="absolute right-0 z-10 mt-2 w-40 origin-top-right rounded-md bg-white shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-1">
{SORT_OPTIONS.map((option) => (
<Menu.Item key={option.name}>
{({ active }) => (
<a
className={clsx(
option.current
? 'font-medium text-gray-900'
: 'text-gray-500',
active ? 'bg-gray-100' : '',
'block px-4 py-2 text-sm',
)}
href={option.href}>
{option.name}
</a>
)}
</Menu.Item>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
<DropdownMenu align="end" label="Sort">
{SORT_OPTIONS.map((option) => (
<DropdownMenu.Item
key={option.name}
isSelected={sortOrder === option.value}
label={option.name}
onClick={() =>
setSortOrder(option.value)
}></DropdownMenu.Item>
))}
</DropdownMenu>
</div>
<div className="col-span-1">
<button
@ -310,28 +295,6 @@ export default function ResumeHomePage() {
</Disclosure.Button>
</h3>
<Disclosure.Panel className="pt-4">
{/* <div className="space-y-4">
{section.options.map((option, optionIdx) => (
<div
key={option.value}
className="flex items-center">
<input
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
defaultChecked={option.checked}
defaultValue={option.value}
id={`filter-${section.id}-${optionIdx}`}
name={`${section.id}[]`}
type="checkbox"
onChange={(value) => console.log(value)}
/>
<label
className="ml-3 text-sm text-gray-600"
htmlFor={`filter-${section.id}-${optionIdx}`}>
{option.label}
</label>
</div>
))}
</div> */}
<CheckboxList
description=""
isLabelHidden={true}
@ -375,7 +338,10 @@ export default function ResumeHomePage() {
starredResumesQuery.isFetching ||
myResumesQuery.isFetching
}
resumes={filterResumes(resumes, searchValue, userFilters)}
resumes={sortResumes(
filterResumes(resumes, searchValue, userFilters),
sortOrder,
)}
/>
</div>
</div>

Loading…
Cancel
Save