mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-08-03 04:04:24 +08:00
[offers][refactor] improve offers table responsiveness
This commit is contained in:
@ -7,7 +7,7 @@ import OffersTablePagination from '~/components/offers/table/OffersTablePaginati
|
|||||||
import {
|
import {
|
||||||
OfferTableFilterOptions,
|
OfferTableFilterOptions,
|
||||||
OfferTableSortBy,
|
OfferTableSortBy,
|
||||||
OfferTableTabOptions,
|
OfferTableYoeOptions,
|
||||||
YOE_CATEGORY,
|
YOE_CATEGORY,
|
||||||
} from '~/components/offers/table/types';
|
} from '~/components/offers/table/types';
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ export default function OffersTable({
|
|||||||
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 [selectedTab, setSelectedTab] = useState(YOE_CATEGORY.ENTRY);
|
const [selectedYoe, setSelectedYoe] = useState(YOE_CATEGORY.ENTRY);
|
||||||
const [pagination, setPagination] = useState<Paging>({
|
const [pagination, setPagination] = useState<Paging>({
|
||||||
currentPage: 0,
|
currentPage: 0,
|
||||||
numOfItems: 0,
|
numOfItems: 0,
|
||||||
@ -48,7 +48,7 @@ export default function OffersTable({
|
|||||||
numOfPages: 0,
|
numOfPages: 0,
|
||||||
totalItems: 0,
|
totalItems: 0,
|
||||||
});
|
});
|
||||||
}, [selectedTab, currency]);
|
}, [selectedYoe, currency]);
|
||||||
const offersQuery = trpc.useQuery(
|
const offersQuery = trpc.useQuery(
|
||||||
[
|
[
|
||||||
'offers.list',
|
'offers.list',
|
||||||
@ -60,7 +60,7 @@ export default function OffersTable({
|
|||||||
offset: pagination.currentPage,
|
offset: pagination.currentPage,
|
||||||
sortBy: OfferTableSortBy[selectedFilter] ?? '-monthYearReceived',
|
sortBy: OfferTableSortBy[selectedFilter] ?? '-monthYearReceived',
|
||||||
title: jobTitleFilter,
|
title: jobTitleFilter,
|
||||||
yoeCategory: selectedTab,
|
yoeCategory: selectedYoe,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
@ -76,22 +76,15 @@ export default function OffersTable({
|
|||||||
|
|
||||||
function renderFilters() {
|
function renderFilters() {
|
||||||
return (
|
return (
|
||||||
<div className="m-4 flex grid grid-cols-1 items-center justify-between gap-6 sm:grid-cols-4">
|
<div className="flex items-center justify-between p-4 text-sm sm:grid-cols-4 md:text-base">
|
||||||
<DropdownMenu
|
<DropdownMenu align="start" label="Filters" size="inherit">
|
||||||
align="start"
|
{OfferTableYoeOptions.map(({ label: itemLabel, value }) => (
|
||||||
label={
|
|
||||||
OfferTableTabOptions.filter(
|
|
||||||
({ value: itemValue }) => itemValue === selectedTab,
|
|
||||||
)[0].label
|
|
||||||
}
|
|
||||||
size="inherit">
|
|
||||||
{OfferTableTabOptions.map(({ label: itemLabel, value }) => (
|
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
key={value}
|
key={value}
|
||||||
isSelected={value === selectedTab}
|
isSelected={value === selectedYoe}
|
||||||
label={itemLabel}
|
label={itemLabel}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedTab(value);
|
setSelectedYoe(value);
|
||||||
gaEvent({
|
gaEvent({
|
||||||
action: `offers.table_filter_yoe_category_${value}`,
|
action: `offers.table_filter_yoe_category_${value}`,
|
||||||
category: 'engagement',
|
category: 'engagement',
|
||||||
@ -103,7 +96,9 @@ 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">
|
||||||
<span>View all offers in</span>
|
<span className="sr-only sm:not-sr-only sm:inline">
|
||||||
|
View all offers in
|
||||||
|
</span>
|
||||||
<CurrencySelector
|
<CurrencySelector
|
||||||
handleCurrencyChange={(value: string) => setCurrency(value)}
|
handleCurrencyChange={(value: string) => setCurrency(value)}
|
||||||
selectedCurrency={currency}
|
selectedCurrency={currency}
|
||||||
@ -140,7 +135,7 @@ export default function OffersTable({
|
|||||||
'Company',
|
'Company',
|
||||||
'Title',
|
'Title',
|
||||||
'YOE',
|
'YOE',
|
||||||
selectedTab === YOE_CATEGORY.INTERN ? 'Monthly Salary' : 'Annual TC',
|
selectedYoe === YOE_CATEGORY.INTERN ? 'Monthly Salary' : 'Annual TC',
|
||||||
'Date Offered',
|
'Date Offered',
|
||||||
'Actions',
|
'Actions',
|
||||||
];
|
];
|
||||||
@ -172,34 +167,32 @@ export default function OffersTable({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-5/6">
|
<div className="relative w-full border border-slate-200">
|
||||||
<div className="relative w-full border border-slate-200">
|
{renderFilters()}
|
||||||
{renderFilters()}
|
{offersQuery.isLoading ? (
|
||||||
{offersQuery.isLoading ? (
|
<div className="col-span-10 pt-4">
|
||||||
<div className="col-span-10 pt-4">
|
<Spinner display="block" size="lg" />
|
||||||
<Spinner display="block" size="lg" />
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<div className="overflow-x-auto">
|
||||||
<div className="overflow-x-auto">
|
<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 text-slate-600">
|
{renderHeader()}
|
||||||
{renderHeader()}
|
<tbody>
|
||||||
<tbody>
|
{offers.map((offer) => (
|
||||||
{offers.map((offer) => (
|
<OffersRow key={offer.id} row={offer} />
|
||||||
<OffersRow key={offer.id} row={offer} />
|
))}
|
||||||
))}
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
<OffersTablePagination
|
||||||
<OffersTablePagination
|
endNumber={
|
||||||
endNumber={
|
pagination.currentPage * NUMBER_OF_OFFERS_IN_PAGE + offers.length
|
||||||
pagination.currentPage * NUMBER_OF_OFFERS_IN_PAGE + offers.length
|
}
|
||||||
}
|
handlePageChange={handlePageChange}
|
||||||
handlePageChange={handlePageChange}
|
pagination={pagination}
|
||||||
pagination={pagination}
|
startNumber={pagination.currentPage * NUMBER_OF_OFFERS_IN_PAGE + 1}
|
||||||
startNumber={pagination.currentPage * NUMBER_OF_OFFERS_IN_PAGE + 1}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ export enum YOE_CATEGORY {
|
|||||||
SENIOR = 3,
|
SENIOR = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OfferTableTabOptions = [
|
export const OfferTableYoeOptions = [
|
||||||
{
|
{
|
||||||
label: 'Fresh Grad (0-2 YOE)',
|
label: 'Fresh Grad (0-2 YOE)',
|
||||||
value: YOE_CATEGORY.ENTRY,
|
value: YOE_CATEGORY.ENTRY,
|
||||||
|
26
apps/portal/src/components/shared/Container.tsx
Normal file
26
apps/portal/src/components/shared/Container.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
variant?: 'narrow' | 'normal';
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function Container({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
variant = 'normal',
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'mx-auto px-4 sm:px-6 lg:px-8',
|
||||||
|
variant === 'normal' && 'max-w-7xl',
|
||||||
|
variant === 'narrow' && 'max-w-6xl',
|
||||||
|
className,
|
||||||
|
)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -5,10 +5,14 @@ import { Banner } from '@tih/ui';
|
|||||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||||
import OffersTable from '~/components/offers/table/OffersTable';
|
import OffersTable from '~/components/offers/table/OffersTable';
|
||||||
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
||||||
|
import Container from '~/components/shared/Container';
|
||||||
|
import type { JobTitleType } from '~/components/shared/JobTitles';
|
||||||
import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead';
|
import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead';
|
||||||
|
|
||||||
export default function OffersHomePage() {
|
export default function OffersHomePage() {
|
||||||
const [jobTitleFilter, setjobTitleFilter] = useState('software-engineer');
|
const [jobTitleFilter, setJobTitleFilter] = useState<JobTitleType | ''>(
|
||||||
|
'software-engineer',
|
||||||
|
);
|
||||||
const [companyFilter, setCompanyFilter] = useState('');
|
const [companyFilter, setCompanyFilter] = useState('');
|
||||||
const { event: gaEvent } = useGoogleAnalytics();
|
const { event: gaEvent } = useGoogleAnalytics();
|
||||||
|
|
||||||
@ -42,14 +46,14 @@ export default function OffersHomePage() {
|
|||||||
textSize="inherit"
|
textSize="inherit"
|
||||||
onSelect={(option) => {
|
onSelect={(option) => {
|
||||||
if (option) {
|
if (option) {
|
||||||
setjobTitleFilter(option.value);
|
setJobTitleFilter(option.value as JobTitleType);
|
||||||
gaEvent({
|
gaEvent({
|
||||||
action: `offers.table_filter_job_title_${option.value}`,
|
action: `offers.table_filter_job_title_${option.value}`,
|
||||||
category: 'engagement',
|
category: 'engagement',
|
||||||
label: 'Filter by job title',
|
label: 'Filter by job title',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setjobTitleFilter('');
|
setJobTitleFilter('');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -74,12 +78,12 @@ export default function OffersHomePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center bg-white pb-20 pt-10">
|
<Container className="pb-20 pt-10">
|
||||||
<OffersTable
|
<OffersTable
|
||||||
companyFilter={companyFilter}
|
companyFilter={companyFilter}
|
||||||
jobTitleFilter={jobTitleFilter}
|
jobTitleFilter={jobTitleFilter}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Container>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,8 @@ export default function Banner({ children, size = 'md', onHide }: Props) {
|
|||||||
size === 'xs' && 'text-xs',
|
size === 'xs' && 'text-xs',
|
||||||
)}>
|
)}>
|
||||||
<div className="mx-auto max-w-7xl py-2 px-3 sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-7xl py-2 px-3 sm:px-6 lg:px-8">
|
||||||
<div className="pr-16 sm:px-16 sm:text-center">
|
<div
|
||||||
|
className={clsx('text-center sm:px-16', onHide != null && 'pr-16')}>
|
||||||
<p className="font-medium text-white">{children}</p>
|
<p className="font-medium text-white">{children}</p>
|
||||||
</div>
|
</div>
|
||||||
{onHide != null && (
|
{onHide != null && (
|
||||||
|
Reference in New Issue
Block a user