[offers][feat] add table loading status, refactor table (#368)

pull/369/head
Zhang Ziqing 2 years ago committed by GitHub
parent 7d15aa43cf
commit 0eb4f3fc5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,18 +1,19 @@
import { useFormContext, useWatch } from 'react-hook-form';
import { Collapsible, RadioList } from '@tih/ui';
import FormRadioList from './components/FormRadioList';
import FormSelect from './components/FormSelect';
import FormTextInput from './components/FormTextInput';
import {
companyOptions,
educationFieldOptions,
educationLevelOptions,
locationOptions,
titleOptions,
} from '../constants';
import { JobType } from '../types';
import { CURRENCY_OPTIONS } from '../../../utils/offers/currency/CurrencyEnum';
} from '~/components/offers/constants';
import FormRadioList from '~/components/offers/forms/components/FormRadioList';
import FormSelect from '~/components/offers/forms/components/FormSelect';
import FormTextInput from '~/components/offers/forms/components/FormTextInput';
import { JobType } from '~/components/offers/types';
import { CURRENCY_OPTIONS } from '~/utils/offers/currency/CurrencyEnum';
function YoeSection() {
const { register } = useFormContext();

@ -1,7 +1,6 @@
import { useState } from 'react';
import { UserCircleIcon } from '@heroicons/react/20/solid';
import { HorizontalDivider, Tabs } from '~/../../../packages/ui/dist';
import { HorizontalDivider, Tabs } from '@tih/ui';
const tabs = [
{

@ -1,8 +1,7 @@
import type { ComponentProps, ForwardedRef } from 'react';
import { forwardRef } from 'react';
import type { UseFormRegisterReturn } from 'react-hook-form';
import { TextArea } from '~/../../../packages/ui/dist';
import { TextArea } from '@tih/ui';
type TextAreaProps = ComponentProps<typeof TextArea>;

@ -8,10 +8,9 @@ import {
} from '@heroicons/react/24/outline';
import { Button, Dialog, Spinner, Tabs } from '@tih/ui';
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
import type { BackgroundCard } from '~/components/offers/types';
import ProfilePhotoHolder from './ProfilePhotoHolder';
type ProfileHeaderProps = Readonly<{
background?: BackgroundCard;
handleDelete: () => void;

@ -0,0 +1,32 @@
import Link from 'next/link';
import type { OfferTableRowData } from '~/components/offers/table/types';
export type OfferTableRowProps = Readonly<{ row: OfferTableRowData }>;
export default function OfferTableRow({
row: { company, date, id, profileId, salary, title, yoe },
}: OfferTableRowProps) {
return (
<tr
key={id}
className="border-b bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-600">
<th
className="whitespace-nowrap py-4 px-6 font-medium text-gray-900 dark:text-white"
scope="row">
{company}
</th>
<td className="py-4 px-6">{title}</td>
<td className="py-4 px-6">{yoe}</td>
<td className="py-4 px-6">{salary}</td>
<td className="py-4 px-6">{date}</td>
<td className="space-x-4 py-4 px-6">
<Link
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
href={`/offers/profile/${profileId}`}>
View Profile
</Link>
</td>
</tr>
);
}

@ -1,54 +1,34 @@
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { HorizontalDivider, Pagination, Select, Tabs } from '@tih/ui';
import { HorizontalDivider, Select, Spinner, Tabs } from '@tih/ui';
import OffersTablePagination from '~/components/offers/table/OffersTablePagination';
import type {
OfferTableRowData,
PaginationType,
} from '~/components/offers/table/types';
import { YOE_CATEGORY } from '~/components/offers/table/types';
import CurrencySelector from '~/utils/offers/currency/CurrencySelector';
import { formatDate } from '~/utils/offers/time';
import { trpc } from '~/utils/trpc';
type OfferTableRow = {
company: string;
date: string;
id: string;
profileId: string;
salary: number | undefined;
title: string;
yoe: number;
};
// To be changed to backend enum
// eslint-disable-next-line no-shadow
enum YOE_CATEGORY {
INTERN = 0,
ENTRY = 1,
MID = 2,
SENIOR = 3,
}
import OffersRow from './OffersRow';
type OffersTableProps = {
const NUMBER_OF_OFFERS_IN_PAGE = 10;
export type OffersTableProps = Readonly<{
companyFilter: string;
jobTitleFilter: string;
};
type Pagination = {
currentPage: number;
numOfItems: number;
numOfPages: number;
totalItems: number;
};
const NUMBER_OF_OFFERS_IN_PAGE = 10;
}>;
export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
const [currency, setCurrency] = useState('SGD'); // TODO
const [currency, setCurrency] = useState('SGD'); // TODO: Detect location
const [selectedTab, setSelectedTab] = useState(YOE_CATEGORY.ENTRY);
const [pagination, setPagination] = useState<Pagination>({
const [pagination, setPagination] = useState<PaginationType>({
currentPage: 1,
numOfItems: 1,
numOfPages: 0,
totalItems: 0,
});
const [offers, setOffers] = useState<Array<OfferTableRow>>([]);
const [offers, setOffers] = useState<Array<OfferTableRowData>>([]);
useEffect(() => {
setPagination({
@ -58,7 +38,7 @@ export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
totalItems: 0,
});
}, [selectedTab]);
trpc.useQuery(
const offersQuery = trpc.useQuery(
[
'offers.list',
{
@ -166,7 +146,7 @@ export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
'Company',
'Title',
'YOE',
'TC/year',
selectedTab === YOE_CATEGORY.INTERN ? 'Monthly Salary' : 'TC/year',
'Date offered',
'Actions',
].map((header) => (
@ -183,97 +163,37 @@ export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
setPagination({ ...pagination, currentPage: currPage });
};
function renderRow({
company,
title,
yoe,
salary,
date,
profileId,
id,
}: OfferTableRow) {
return (
<tr
key={id}
className="border-b bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-600">
<th
className="whitespace-nowrap py-4 px-6 font-medium text-gray-900 dark:text-white"
scope="row">
{company}
</th>
<td className="py-4 px-6">{title}</td>
<td className="py-4 px-6">{yoe}</td>
<td className="py-4 px-6">{salary}</td>
<td className="py-4 px-6">{date}</td>
<td className="space-x-4 py-4 px-6">
{/* <a
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
onClick={() => handleClickViewProfile(profileId)}>
View Profile
</a> */}
<Link
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
href={`/offers/profile/${profileId}`}>
View Profile
</Link>
{/* <a
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
href="#">
Comment
</a> */}
</td>
</tr>
);
}
function renderPagination() {
return (
<nav
aria-label="Table navigation"
className="flex items-center justify-between p-4">
<span className="text-sm font-normal text-gray-500 dark:text-gray-400">
Showing
<span className="font-semibold text-gray-900 dark:text-white">
{` ${
(pagination.currentPage - 1) * NUMBER_OF_OFFERS_IN_PAGE + 1
} - ${
(pagination.currentPage - 1) * NUMBER_OF_OFFERS_IN_PAGE +
offers.length
} `}
</span>
{`of `}
<span className="font-semibold text-gray-900 dark:text-white">
{pagination.totalItems}
</span>
</span>
<Pagination
current={pagination.currentPage}
end={pagination.numOfPages}
label="Pagination"
pagePadding={1}
start={1}
onSelect={(currPage) => {
handlePageChange(currPage);
}}
/>
</nav>
);
}
return (
<div className="w-5/6">
{renderTabs()}
<HorizontalDivider />
<div className="relative w-full overflow-x-auto shadow-md sm:rounded-lg">
{renderFilters()}
<table className="w-full text-left text-sm text-gray-500 dark:text-gray-400">
{renderHeader()}
<tbody>
{offers.map((offer: OfferTableRow) => renderRow(offer))}
</tbody>
</table>
{renderPagination()}
{offersQuery.isLoading ? (
<div className="col-span-10 pt-4">
<Spinner display="block" size="lg" />
</div>
) : (
<table className="w-full text-left text-sm text-gray-500 dark:text-gray-400">
{renderHeader()}
<tbody>
{offers.map((offer) => (
<OffersRow key={offer.id} row={offer} />
))}
</tbody>
</table>
)}
<OffersTablePagination
endNumber={
(pagination.currentPage - 1) * NUMBER_OF_OFFERS_IN_PAGE +
offers.length
}
handlePageChange={handlePageChange}
pagination={pagination}
startNumber={
(pagination.currentPage - 1) * NUMBER_OF_OFFERS_IN_PAGE + 1
}
/>
</div>
</div>
);

@ -0,0 +1,44 @@
import { Pagination } from '@tih/ui';
import type { PaginationType } from '~/components/offers/table/types';
type OffersTablePaginationProps = Readonly<{
endNumber: number;
handlePageChange: (page: number) => void;
pagination: PaginationType;
startNumber: number;
}>;
export default function OffersTablePagination({
endNumber,
pagination,
startNumber,
handlePageChange,
}: OffersTablePaginationProps) {
return (
<nav
aria-label="Table navigation"
className="flex items-center justify-between p-4">
<span className="text-sm font-normal text-gray-500 dark:text-gray-400">
Showing
<span className="font-semibold text-gray-900 dark:text-white">
{` ${startNumber} - ${endNumber} `}
</span>
{`of `}
<span className="font-semibold text-gray-900 dark:text-white">
{pagination.totalItems}
</span>
</span>
<Pagination
current={pagination.currentPage}
end={pagination.numOfPages}
label="Pagination"
pagePadding={1}
start={1}
onSelect={(currPage) => {
handlePageChange(currPage);
}}
/>
</nav>
);
}

@ -0,0 +1,24 @@
export type OfferTableRowData = {
company: string;
date: string;
id: string;
profileId: string;
salary: number | undefined;
title: string;
yoe: number;
};
// eslint-disable-next-line no-shadow
export enum YOE_CATEGORY {
INTERN = 0,
ENTRY = 1,
MID = 2,
SENIOR = 3,
}
export type PaginationType = {
currentPage: number;
numOfItems: number;
numOfPages: number;
totalItems: number;
};

@ -1,7 +1,6 @@
import { Fragment, useState } from 'react';
import { Dialog, Transition } from '@headlessui/react';
import { HorizontalDivider } from '~/../../../packages/ui/dist';
import { HorizontalDivider } from '@tih/ui';
import type { ContributeQuestionFormProps } from './ContributeQuestionForm';
import ContributeQuestionForm from './ContributeQuestionForm';

@ -1,8 +1,8 @@
import { useState } from 'react';
import { Select } from '@tih/ui';
import OffersTable from '~/components/offers/OffersTable';
import OffersTitle from '~/components/offers/OffersTitle';
import OffersTable from '~/components/offers/table/OffersTable';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
export default function OffersHomePage() {

@ -2,15 +2,14 @@ import Error from 'next/error';
import { useRouter } from 'next/router';
import { useState } from 'react';
import ProfileComments from '~/components/offers/profile/ProfileComments';
import ProfileDetails from '~/components/offers/profile/ProfileDetails';
import ProfileHeader from '~/components/offers/profile/ProfileHeader';
import type { OfferEntity } from '~/components/offers/types';
import type { BackgroundCard } from '~/components/offers/types';
import { formatDate } from '~/utils/offers/time';
import { trpc } from '~/utils/trpc';
import ProfileComments from '../../../components/offers/profile/ProfileComments';
import ProfileDetails from '../../../components/offers/profile/ProfileDetails';
export default function OfferProfile() {
const ErrorPage = (
<Error statusCode={404} title="Requested profile does not exist." />

Loading…
Cancel
Save