From c76607acfed3ddbfafc4002cf9eaa8b51d243769 Mon Sep 17 00:00:00 2001
From: Zhang Ziqing <69516975+ziqing26@users.noreply.github.com>
Date: Thu, 3 Nov 2022 10:59:09 +0800
Subject: [PATCH] [offers][feat] add default filters and more income columns
(#495)
* [offers][feat] add yoe query param and display all by default
* [offers][feat] add base bonus stocks to table and default homepage
* [offers][style] style loading spinner
---
.../src/components/offers/table/OffersRow.tsx | 39 +++++--
.../components/offers/table/OffersTable.tsx | 102 ++++++++++++++----
.../src/components/offers/table/types.ts | 16 ++-
apps/portal/src/pages/offers/index.tsx | 26 ++---
.../pages/offers/profile/[offerProfileId].tsx | 2 +-
.../offers/submit/result/[offerProfileId].tsx | 2 +-
6 files changed, 142 insertions(+), 45 deletions(-)
diff --git a/apps/portal/src/components/offers/table/OffersRow.tsx b/apps/portal/src/components/offers/table/OffersRow.tsx
index a5f51208..77c7ab44 100644
--- a/apps/portal/src/components/offers/table/OffersRow.tsx
+++ b/apps/portal/src/components/offers/table/OffersRow.tsx
@@ -1,5 +1,6 @@
import clsx from 'clsx';
import Link from 'next/link';
+import { JobType } from '@prisma/client';
import type { JobTitleType } from '~/components/shared/JobTitles';
import { getLabelForJobTitleType } from '~/components/shared/JobTitles';
@@ -9,25 +10,47 @@ import { formatDate } from '~/utils/offers/time';
import type { DashboardOffer } from '~/types/offers';
-export type OfferTableRowProps = Readonly<{ row: DashboardOffer }>;
+export type OfferTableRowProps = Readonly<{
+ jobType: JobType;
+ row: DashboardOffer;
+}>;
export default function OfferTableRow({
- row: { company, id, income, monthYearReceived, profileId, title, totalYoe },
+ jobType,
+ row: {
+ baseSalary,
+ bonus,
+ company,
+ id,
+ income,
+ monthYearReceived,
+ profileId,
+ stocks,
+ title,
+ totalYoe,
+ },
}: OfferTableRowProps) {
return (
-
+ |
{company.name}
|
-
+ |
{getLabelForJobTitleType(title as JobTitleType)}
|
- {totalYoe} |
- {convertMoneyToString(income)} |
- {formatDate(monthYearReceived)} |
+ {totalYoe} |
+ {convertMoneyToString(income)} |
+ {jobType === JobType.FULLTIME && (
+
+ {`${baseSalary && convertMoneyToString(baseSalary)} / ${
+ bonus && convertMoneyToString(bonus)
+ } / ${stocks && convertMoneyToString(stocks)}`}
+ |
+ )}
+ {formatDate(monthYearReceived)} |
;
export default function OffersTable({
- cityFilter,
+ countryFilter,
companyFilter,
jobTitleFilter,
}: OffersTableProps) {
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.FULLTIME);
const [pagination, setPagination] = useState({
currentPage: 0,
numOfItems: 0,
@@ -43,6 +47,10 @@ export default function OffersTable({
OfferTableFilterOptions[0].value,
);
const { event: gaEvent } = useGoogleAnalytics();
+ const router = useRouter();
+ const { yoeCategory = '' } = router.query;
+ const [isLoading, setIsLoading] = useState(true);
+
useEffect(() => {
setPagination({
currentPage: 0,
@@ -50,20 +58,26 @@ export default function OffersTable({
numOfPages: 0,
totalItems: 0,
});
- }, [selectedYoe, currency]);
- const offersQuery = trpc.useQuery(
+ setIsLoading(true);
+ }, [selectedYoe, currency, countryFilter, companyFilter, jobTitleFilter]);
+
+ useEffect(() => {
+ setSelectedYoe(yoeCategory as YOE_CATEGORY);
+ event?.preventDefault();
+ }, [yoeCategory]);
+
+ trpc.useQuery(
[
'offers.list',
{
companyId: companyFilter,
- // Location: 'Singapore, Singapore', // TODO: Geolocation
- countryId: cityFilter,
+ countryId: countryFilter,
currency,
limit: NUMBER_OF_OFFERS_IN_PAGE,
offset: pagination.currentPage,
sortBy: OfferTableSortBy[selectedFilter] ?? '-monthYearReceived',
title: jobTitleFilter,
- yoeCategory: selectedYoe,
+ yoeCategory: YOE_CATEGORY_PARAM[yoeCategory as string] ?? undefined,
},
],
{
@@ -73,6 +87,8 @@ export default function OffersTable({
onSuccess: (response: GetOffersResponse) => {
setOffers(response.data);
setPagination(response.paging);
+ setJobType(response.jobType);
+ setIsLoading(false);
},
},
);
@@ -80,14 +96,43 @@ export default function OffersTable({
function renderFilters() {
return (
-
+ itemValue === selectedYoe,
+ )[0].label
+ }
+ size="inherit">
{OfferTableYoeOptions.map(({ label: itemLabel, value }) => (
{
- 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({
action: `offers.table_filter_yoe_category_${value}`,
category: 'engagement',
@@ -98,7 +143,7 @@ export default function OffersTable({
))}
-
+
Display offers in
@@ -134,7 +179,7 @@ export default function OffersTable({
}
function renderHeader() {
- const columns = [
+ let columns = [
'Company',
'Title',
'YOE',
@@ -142,6 +187,18 @@ export default function OffersTable({
'Date Offered',
'Actions',
];
+ if (jobType === JobType.FULLTIME) {
+ columns = [
+ 'Company',
+ 'Title',
+ 'YOE',
+ 'Annual TC',
+ 'Annual Base / Bonus / Stocks',
+ 'Date Offered',
+ 'Actions',
+ ];
+ }
+
return (
@@ -149,7 +206,7 @@ export default function OffersTable({
{renderFilters()}
- {offersQuery.isLoading ? (
-
+ {isLoading ? (
+
) : (
-
-
+
+
{renderHeader()}
{offers.map((offer) => (
-
+
))}
+ {!offers ||
+ (offers.length === 0 && (
+
+ No data yet🥺
+
+ Please try another set of filters.
+
+
+ ))}
)}
= {
+ entry: 1,
+ intern: 0,
+ mid: 2,
+ senior: 3,
+};
+
export const OfferTableYoeOptions = [
+ { label: 'All Full Time YOE', value: '' },
{
label: 'Fresh Grad (0-2 YOE)',
value: YOE_CATEGORY.ENTRY,
diff --git a/apps/portal/src/pages/offers/index.tsx b/apps/portal/src/pages/offers/index.tsx
index f1581e01..9eaeb955 100644
--- a/apps/portal/src/pages/offers/index.tsx
+++ b/apps/portal/src/pages/offers/index.tsx
@@ -5,18 +5,16 @@ import { Banner } from '@tih/ui';
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
import OffersTable from '~/components/offers/table/OffersTable';
-import CitiesTypeahead from '~/components/shared/CitiesTypeahead';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import Container from '~/components/shared/Container';
+import CountriesTypeahead from '~/components/shared/CountriesTypeahead';
import type { JobTitleType } from '~/components/shared/JobTitles';
import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead';
export default function OffersHomePage() {
- const [jobTitleFilter, setJobTitleFilter] = useState(
- 'software-engineer',
- );
+ const [jobTitleFilter, setJobTitleFilter] = useState('');
const [companyFilter, setCompanyFilter] = useState('');
- const [cityFilter, setCityFilter] = useState('');
+ const [countryFilter, setCountryFilter] = useState('');
const { event: gaEvent } = useGoogleAnalytics();
return (
@@ -28,21 +26,23 @@ export default function OffersHomePage() {
. ⭐
-
+
- {
if (option) {
- setCityFilter(option.value);
+ setCountryFilter(option.value);
gaEvent({
- action: `offers.table_filter_city_${option.value}`,
+ action: `offers.table_filter_country_${option.value}`,
category: 'engagement',
- label: 'Filter by city',
+ label: 'Filter by country',
});
+ } else {
+ setCountryFilter('');
}
}}
/>
@@ -64,7 +64,7 @@ export default function OffersHomePage() {
{
if (option) {
@@ -102,8 +102,8 @@ export default function OffersHomePage() {
diff --git a/apps/portal/src/pages/offers/profile/[offerProfileId].tsx b/apps/portal/src/pages/offers/profile/[offerProfileId].tsx
index 4ea13123..092cf8b9 100644
--- a/apps/portal/src/pages/offers/profile/[offerProfileId].tsx
+++ b/apps/portal/src/pages/offers/profile/[offerProfileId].tsx
@@ -195,7 +195,7 @@ export default function OfferProfile() {
)}
{getProfileQuery.isLoading && (
-
+
diff --git a/apps/portal/src/pages/offers/submit/result/[offerProfileId].tsx b/apps/portal/src/pages/offers/submit/result/[offerProfileId].tsx
index d1c9dd09..35c72796 100644
--- a/apps/portal/src/pages/offers/submit/result/[offerProfileId].tsx
+++ b/apps/portal/src/pages/offers/submit/result/[offerProfileId].tsx
@@ -71,7 +71,7 @@ export default function OffersSubmissionResult() {
<>
{getAnalysis.isLoading && (
| |