From 247a60efab8d7a48e74796127a9ce99a041fd118 Mon Sep 17 00:00:00 2001
From: Zhang Ziqing <69516975+ziqing26@users.noreply.github.com>
Date: Wed, 9 Nov 2022 00:05:28 +0800
Subject: [PATCH] [offers][feat] add sortable columns to table (#541)
* [offers][feat] add sortable columns to table
* [offers][fix] fix mobile compatibility for sorter
---
.../components/offers/table/OffersHeader.tsx | 83 +++++++++
.../components/offers/table/OffersTable.tsx | 176 +++++++++---------
.../src/components/offers/table/types.ts | 67 ++++---
3 files changed, 216 insertions(+), 110 deletions(-)
create mode 100644 apps/portal/src/components/offers/table/OffersHeader.tsx
diff --git a/apps/portal/src/components/offers/table/OffersHeader.tsx b/apps/portal/src/components/offers/table/OffersHeader.tsx
new file mode 100644
index 00000000..fdf45922
--- /dev/null
+++ b/apps/portal/src/components/offers/table/OffersHeader.tsx
@@ -0,0 +1,83 @@
+import clsx from 'clsx';
+
+import type { OfferTableSortType } from '~/components/offers/table/types';
+import {
+ getOppositeSortOrder,
+ OFFER_TABLE_SORT_ORDER,
+} from '~/components/offers/table/types';
+
+export type OffersTableHeaderProps = Readonly<{
+ header: string;
+ isLastColumn: boolean;
+ onSort?: (
+ sortDirection: OFFER_TABLE_SORT_ORDER,
+ sortType: OfferTableSortType,
+ ) => void;
+ sortDirection?: OFFER_TABLE_SORT_ORDER;
+ sortType?: OfferTableSortType;
+}>;
+
+export default function OffersHeader({
+ header,
+ isLastColumn,
+ onSort,
+ sortDirection,
+ sortType,
+}: OffersTableHeaderProps) {
+ return (
+
{
+ onSort(
+ sortDirection
+ ? getOppositeSortOrder(sortDirection)
+ : OFFER_TABLE_SORT_ORDER.ASC,
+ sortType,
+ );
+ })
+ }>
+
+ {header}
+ {onSort && sortType && (
+
+
+ ▲
+
+
+ ▼
+
+
+ )}
+
+ |
+ );
+}
diff --git a/apps/portal/src/components/offers/table/OffersTable.tsx b/apps/portal/src/components/offers/table/OffersTable.tsx
index a3817d23..54d9fe3c 100644
--- a/apps/portal/src/components/offers/table/OffersTable.tsx
+++ b/apps/portal/src/components/offers/table/OffersTable.tsx
@@ -1,16 +1,21 @@
-import clsx from 'clsx';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useRef, useState } from 'react';
import { JobType } from '@prisma/client';
import { DropdownMenu, Spinner, useToast } from '@tih/ui';
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
+import OffersRow from '~/components/offers/table//OffersRow';
+import OffersHeader from '~/components/offers/table/OffersHeader';
import OffersTablePagination from '~/components/offers/table/OffersTablePagination';
-import type { OfferTableSortByType } from '~/components/offers/table/types';
+import type {
+ OfferTableColumn,
+ OfferTableSortType,
+} from '~/components/offers/table/types';
+import { OFFER_TABLE_SORT_ORDER } from '~/components/offers/table/types';
+import { InternOfferTableColumns } from '~/components/offers/table/types';
+import { FullTimeOfferTableColumns } from '~/components/offers/table/types';
import {
- OfferTableFilterOptions,
OfferTableYoeOptions,
- YOE_CATEGORY,
YOE_CATEGORY_PARAM,
} from '~/components/offers/table/types';
@@ -19,8 +24,6 @@ import CurrencySelector from '~/utils/offers/currency/CurrencySelector';
import { useSearchParamSingle } from '~/utils/offers/useSearchParam';
import { trpc } from '~/utils/trpc';
-import OffersRow from './OffersRow';
-
import type { DashboardOffer, GetOffersResponse, Paging } from '~/types/offers';
const NUMBER_OF_OFFERS_PER_PAGE = 20;
@@ -63,12 +66,26 @@ export default function OffersTable({
isYoeCategoryInitialized,
] = useSearchParamSingle('yoeCategory');
- const [selectedSortBy, setSelectedSortBy, isSortByInitialized] =
- useSearchParamSingle('sortBy');
+ const [
+ selectedSortDirection,
+ setSelectedSortDirection,
+ isSortDirectionInitialized,
+ ] = useSearchParamSingle('sortDirection');
+
+ const [selectedSortType, setSelectedSortType, isSortTypeInitialized] =
+ useSearchParamSingle('sortType');
const areFilterParamsInitialized = useMemo(() => {
- return isYoeCategoryInitialized && isSortByInitialized;
- }, [isYoeCategoryInitialized, isSortByInitialized]);
+ return (
+ isYoeCategoryInitialized &&
+ isSortDirectionInitialized &&
+ isSortTypeInitialized
+ );
+ }, [
+ isYoeCategoryInitialized,
+ isSortDirectionInitialized,
+ isSortTypeInitialized,
+ ]);
const { pathname } = router;
useEffect(() => {
@@ -80,7 +97,8 @@ export default function OffersTable({
companyId: companyFilter,
companyName,
jobTitleId: jobTitleFilter,
- sortBy: selectedSortBy,
+ sortDirection: selectedSortDirection,
+ sortType: selectedSortType,
yoeCategory: selectedYoeCategory,
},
},
@@ -102,11 +120,16 @@ export default function OffersTable({
countryFilter,
companyFilter,
jobTitleFilter,
- selectedSortBy,
+ selectedSortDirection,
+ selectedSortType,
selectedYoeCategory,
pathname,
]);
+ useEffect(() => {
+ setSelectedSortDirection(OFFER_TABLE_SORT_ORDER.UNSORTED);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [selectedYoeCategory]);
const topRef = useRef(null);
const { showToast } = useToast();
const { isLoading: isResultsLoading } = trpc.useQuery(
@@ -118,7 +141,11 @@ export default function OffersTable({
currency,
limit: NUMBER_OF_OFFERS_PER_PAGE,
offset: pagination.currentPage,
- sortBy: selectedSortBy ?? '-monthYearReceived',
+ // SortBy: selectedSortBy ?? '-monthYearReceived',
+ sortBy:
+ selectedSortDirection && selectedSortType
+ ? `${selectedSortDirection}${selectedSortType}`
+ : '-monthYearReceived',
title: jobTitleFilter,
yoeCategory: selectedYoeCategory
? YOE_CATEGORY_PARAM[selectedYoeCategory as string]
@@ -131,6 +158,7 @@ export default function OffersTable({
title: 'Error loading the page.',
variant: 'failure',
});
+ setIsLoading(false);
},
onSuccess: (response: GetOffersResponse) => {
setOffers(response.data);
@@ -141,6 +169,19 @@ export default function OffersTable({
},
);
+ const onSort = (
+ sortDirection: OFFER_TABLE_SORT_ORDER,
+ sortType: OfferTableSortType,
+ ) => {
+ gaEvent({
+ action: 'offers_table_sort',
+ category: 'engagement',
+ label: `${sortType} - ${sortDirection}`,
+ });
+ setSelectedSortType(sortType);
+ setSelectedSortDirection(sortDirection);
+ };
+
function renderFilters() {
return (
@@ -182,75 +223,33 @@ export default function OffersTable({
selectedCurrency={currency}
/>
-
- itemValue === selectedSortBy,
- ).length > 0
- ? OfferTableFilterOptions.filter(
- ({ value: itemValue }) => itemValue === selectedSortBy,
- )[0].label
- : OfferTableFilterOptions[0].label
- }
- size="inherit">
- {OfferTableFilterOptions.map(({ label: itemLabel, value }) => (
- {
- setSelectedSortBy(value as OfferTableSortByType);
- }}
- />
- ))}
-
-
);
}
function renderHeader() {
- let columns = [
- 'Company',
- 'Title',
- 'YOE',
- selectedYoeCategory === YOE_CATEGORY.INTERN
- ? 'Monthly Salary'
- : 'Annual TC',
- 'Date Offered',
- 'Actions',
- ];
- if (jobType === JobType.FULLTIME) {
- columns = [
- 'Company',
- 'Title',
- 'YOE',
- 'Annual TC',
- 'Annual Base / Bonus / Stocks',
- 'Date Offered',
- 'Actions',
- ];
- }
+ const columns: Array =
+ jobType === JobType.FULLTIME
+ ? FullTimeOfferTableColumns
+ : InternOfferTableColumns;
return (
{columns.map((header, index) => (
-
- {header}
- |
+
))}
@@ -276,28 +275,29 @@ export default function OffersTable({
pagination={pagination}
startNumber={pagination.currentPage * NUMBER_OF_OFFERS_PER_PAGE + 1}
/>
- {isLoading ? (
-
-
-
- ) : (
-
-
- {renderHeader()}
+
+
+ {renderHeader()}
+ {!isLoading && (
{offers.map((offer) => (
))}
-
- {!offers ||
- (offers.length === 0 && (
-
- ))}
-
- )}
+ )}
+
+ {isLoading && (
+
+
+
+ )}
+ {(!isLoading && !offers) ||
+ (offers.length === 0 && (
+
+ ))}
+
= [
+ { label: 'Company', sortType: 'companyName' },
+ { label: 'Title', sortType: 'jobTitle' },
+ { label: 'YOE', sortType: 'totalYoe' },
+ { label: 'Annual TC', sortType: 'totalCompensation' },
+ { label: 'Annual Base / Bonus / Stocks' },
+ { label: 'Date Offered', sortType: 'monthYearReceived' },
+ { label: 'Actions' },
];
-export type OfferTableSortByType =
- | '-monthYearReceived'
- | '-totalCompensation'
- | '-totalYoe'
- | '+totalYoe';
\ No newline at end of file
+export const InternOfferTableColumns: Array = [
+ { label: 'Company', sortType: 'companyName' },
+ { label: 'Title', sortType: 'jobTitle' },
+ { label: 'YOE', sortType: 'totalYoe' },
+ { label: 'Monthly Salary', sortType: 'totalCompensation' },
+ { label: 'Date Offered', sortType: 'monthYearReceived' },
+ { label: 'Actions' },
+];