@ -1,5 +1,7 @@
import clsx from 'clsx' ;
import clsx from 'clsx' ;
import { useRouter } from 'next/router' ;
import { useEffect , useState } from 'react' ;
import { useEffect , useState } from 'react' ;
import { JobType } from '@prisma/client' ;
import { DropdownMenu , Spinner } from '@tih/ui' ;
import { DropdownMenu , Spinner } from '@tih/ui' ;
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics' ;
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics' ;
@ -9,6 +11,7 @@ import {
OfferTableSortBy ,
OfferTableSortBy ,
OfferTableYoeOptions ,
OfferTableYoeOptions ,
YOE_CATEGORY ,
YOE_CATEGORY ,
YOE_CATEGORY_PARAM ,
} from '~/components/offers/table/types' ;
} from '~/components/offers/table/types' ;
import { Currency } from '~/utils/offers/currency/CurrencyEnum' ;
import { Currency } from '~/utils/offers/currency/CurrencyEnum' ;
@ -21,17 +24,18 @@ import type { DashboardOffer, GetOffersResponse, Paging } from '~/types/offers';
const NUMBER_OF_OFFERS_IN_PAGE = 10 ;
const NUMBER_OF_OFFERS_IN_PAGE = 10 ;
export type OffersTableProps = Readonly < {
export type OffersTableProps = Readonly < {
cityFilter : string ;
companyFilter : string ;
companyFilter : string ;
countryFilter : string ;
jobTitleFilter : string ;
jobTitleFilter : string ;
} > ;
} > ;
export default function OffersTable ( {
export default function OffersTable ( {
c it yFilter,
c ountr yFilter,
companyFilter ,
companyFilter ,
jobTitleFilter ,
jobTitleFilter ,
} : OffersTableProps ) {
} : OffersTableProps ) {
const [ currency , setCurrency ] = useState ( Currency . SGD . toString ( ) ) ; // TODO: Detect location
const [ currency , setCurrency ] = useState ( Currency . SGD . toString ( ) ) ; // TODO: Detect location
const [ selectedYoe , setSelectedYoe ] = useState ( YOE_CATEGORY . ENTRY ) ;
const [ selectedYoe , setSelectedYoe ] = useState ( '' ) ;
const [ jobType , setJobType ] = useState < JobType > ( JobType . FULLTIME ) ;
const [ pagination , setPagination ] = useState < Paging > ( {
const [ pagination , setPagination ] = useState < Paging > ( {
currentPage : 0 ,
currentPage : 0 ,
numOfItems : 0 ,
numOfItems : 0 ,
@ -43,6 +47,10 @@ export default function OffersTable({
OfferTableFilterOptions [ 0 ] . value ,
OfferTableFilterOptions [ 0 ] . value ,
) ;
) ;
const { event : gaEvent } = useGoogleAnalytics ( ) ;
const { event : gaEvent } = useGoogleAnalytics ( ) ;
const router = useRouter ( ) ;
const { yoeCategory = '' } = router . query ;
const [ isLoading , setIsLoading ] = useState ( true ) ;
useEffect ( ( ) = > {
useEffect ( ( ) = > {
setPagination ( {
setPagination ( {
currentPage : 0 ,
currentPage : 0 ,
@ -50,20 +58,26 @@ export default function OffersTable({
numOfPages : 0 ,
numOfPages : 0 ,
totalItems : 0 ,
totalItems : 0 ,
} ) ;
} ) ;
} , [ selectedYoe , currency ] ) ;
setIsLoading ( true ) ;
const offersQuery = trpc . useQuery (
} , [ selectedYoe , currency , countryFilter , companyFilter , jobTitleFilter ] ) ;
useEffect ( ( ) = > {
setSelectedYoe ( yoeCategory as YOE_CATEGORY ) ;
event ? . preventDefault ( ) ;
} , [ yoeCategory ] ) ;
trpc . useQuery (
[
[
'offers.list' ,
'offers.list' ,
{
{
companyId : companyFilter ,
companyId : companyFilter ,
// Location: 'Singapore, Singapore', // TODO: Geolocation
countryId : countryFilter ,
countryId : cityFilter ,
currency ,
currency ,
limit : NUMBER_OF_OFFERS_IN_PAGE ,
limit : NUMBER_OF_OFFERS_IN_PAGE ,
offset : pagination.currentPage ,
offset : pagination.currentPage ,
sortBy : OfferTableSortBy [ selectedFilter ] ? ? '-monthYearReceived' ,
sortBy : OfferTableSortBy [ selectedFilter ] ? ? '-monthYearReceived' ,
title : jobTitleFilter ,
title : jobTitleFilter ,
yoeCategory : selectedYoe ,
yoeCategory : YOE_CATEGORY_PARAM[ yoeCategory as string ] ? ? undefined ,
} ,
} ,
] ,
] ,
{
{
@ -73,6 +87,8 @@ export default function OffersTable({
onSuccess : ( response : GetOffersResponse ) = > {
onSuccess : ( response : GetOffersResponse ) = > {
setOffers ( response . data ) ;
setOffers ( response . data ) ;
setPagination ( response . paging ) ;
setPagination ( response . paging ) ;
setJobType ( response . jobType ) ;
setIsLoading ( false ) ;
} ,
} ,
} ,
} ,
) ;
) ;
@ -80,14 +96,43 @@ export default function OffersTable({
function renderFilters() {
function renderFilters() {
return (
return (
< div className = "flex items-center justify-between p-4 text-sm sm:grid-cols-4 md:text-base" >
< div className = "flex items-center justify-between p-4 text-sm sm:grid-cols-4 md:text-base" >
< DropdownMenu align = "start" label = "Filters" size = "inherit" >
< DropdownMenu
align = "start"
label = {
OfferTableYoeOptions . filter (
( { value : itemValue } ) = > itemValue === selectedYoe ,
) [ 0 ] . label
}
size = "inherit" >
{ OfferTableYoeOptions . map ( ( { label : itemLabel , value } ) = > (
{ OfferTableYoeOptions . map ( ( { label : itemLabel , value } ) = > (
< DropdownMenu.Item
< DropdownMenu.Item
key = { value }
key = { value }
isSelected = { value === selectedYoe }
isSelected = { value === selectedYoe }
label = { itemLabel }
label = { itemLabel }
onClick = { ( ) = > {
onClick = { ( ) = > {
setSelectedYoe ( value ) ;
if ( value === '' ) {
router . replace (
{
pathname : router.pathname ,
query : undefined ,
} ,
undefined ,
// Do not refresh the page
{ shallow : true } ,
) ;
} else {
const params = new URLSearchParams ( {
[ 'yoeCategory' ] : value ,
} ) ;
router . replace (
{
pathname : location.pathname ,
search : params.toString ( ) ,
} ,
undefined ,
{ shallow : true } ,
) ;
}
gaEvent ( {
gaEvent ( {
action : ` offers.table_filter_yoe_category_ ${ value } ` ,
action : ` offers.table_filter_yoe_category_ ${ value } ` ,
category : 'engagement' ,
category : 'engagement' ,
@ -98,7 +143,7 @@ export default function OffersTable({
) ) }
) ) }
< / DropdownMenu >
< / DropdownMenu >
< div className = "divide-x-slate-200 col-span-3 flex items-center justify-end space-x-4 divide-x" >
< div className = "divide-x-slate-200 col-span-3 flex items-center justify-end space-x-4 divide-x" >
< div className = "justify-left flex items-center space-x-2 ">
< div className = "justify-left flex items-center space-x-2 font-medium text-slate-700 ">
< span className = "sr-only sm:not-sr-only sm:inline" >
< span className = "sr-only sm:not-sr-only sm:inline" >
Display offers in
Display offers in
< / span >
< / span >
@ -134,7 +179,7 @@ export default function OffersTable({
}
}
function renderHeader() {
function renderHeader() {
cons t columns = [
le t columns = [
'Company' ,
'Company' ,
'Title' ,
'Title' ,
'YOE' ,
'YOE' ,
@ -142,6 +187,18 @@ export default function OffersTable({
'Date Offered' ,
'Date Offered' ,
'Actions' ,
'Actions' ,
] ;
] ;
if ( jobType === JobType . FULLTIME ) {
columns = [
'Company' ,
'Title' ,
'YOE' ,
'Annual TC' ,
'Annual Base / Bonus / Stocks' ,
'Date Offered' ,
'Actions' ,
] ;
}
return (
return (
< thead className = "text-slate-700" >
< thead className = "text-slate-700" >
< tr className = "divide-x divide-slate-200" >
< tr className = "divide-x divide-slate-200" >
@ -149,7 +206,7 @@ export default function OffersTable({
< th
< th
key = { header }
key = { header }
className = { clsx (
className = { clsx (
'bg-slate-100 py-3 px- 6 ',
'bg-slate-100 py-3 px- 4 ',
// Make last column sticky.
// Make last column sticky.
index === columns . length - 1 &&
index === columns . length - 1 &&
'sticky right-0 drop-shadow md:drop-shadow-none' ,
'sticky right-0 drop-shadow md:drop-shadow-none' ,
@ -172,20 +229,29 @@ export default function OffersTable({
return (
return (
< div className = "relative w-full border border-slate-200" >
< div className = "relative w-full border border-slate-200" >
{ renderFilters ( ) }
{ renderFilters ( ) }
{ offersQuery. isLoading ? (
{ isLoading ? (
< div className = "col-span-10 p t-4 ">
< div className = "col-span-10 p y-32 ">
< Spinner display = "block" size = "lg" / >
< Spinner display = "block" size = "lg" / >
< / div >
< / div >
) : (
) : (
< div className = "overflow-x-auto ">
< div className = "overflow-x-auto text-slate-600 ">
< table className = "w-full divide-y divide-slate-200 border-y border-slate-200 text-left text-slate-600 ">
< table className = "w-full divide-y divide-slate-200 border-y border-slate-200 text-left ">
{ renderHeader ( ) }
{ renderHeader ( ) }
< tbody >
< tbody >
{ offers . map ( ( offer ) = > (
{ offers . map ( ( offer ) = > (
< OffersRow key = { offer . id } row= { offer } / >
< OffersRow key = { offer . id } jobType= { jobType } row= { offer } / >
) ) }
) ) }
< / tbody >
< / tbody >
< / table >
< / table >
{ ! offers ||
( offers . length === 0 && (
< div className = "py-16 text-lg" >
< div className = "flex justify-center" > No data yet 🥺 < / div >
< div className = "flex justify-center" >
Please try another set of filters .
< / div >
< / div >
) ) }
< / div >
< / div >
) }
) }
< OffersTablePagination
< OffersTablePagination