mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2026-04-07 21:07:59 +08:00
[offers][fix] improve table UX
This commit is contained in:
@@ -61,7 +61,7 @@ function ProfileJewel() {
|
||||
<Menu.Button className="focus:ring-primary-500 flex rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-offset-2">
|
||||
<span className="sr-only">Open user menu</span>
|
||||
{session?.user?.image == null ? (
|
||||
<span>Render some icon</span>
|
||||
<span>TODO: Render some icon</span>
|
||||
) : (
|
||||
<img
|
||||
alt={session?.user?.email ?? session?.user?.name ?? ''}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function OfferTableRow({
|
||||
<tr key={id} className="divide-x divide-slate-200 border-b bg-white">
|
||||
<td className="space-y-0.5 py-4 px-4" scope="row">
|
||||
<div className="font-medium">{company.name}</div>
|
||||
<div className="text-xs text-slate-400">
|
||||
<div className="text-xs text-slate-500">
|
||||
{location.cityName} ({location.countryCode})
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { JobType } from '@prisma/client';
|
||||
import { DropdownMenu, Spinner, useToast } from '@tih/ui';
|
||||
|
||||
@@ -23,13 +23,15 @@ import OffersRow from './OffersRow';
|
||||
|
||||
import type { DashboardOffer, GetOffersResponse, Paging } from '~/types/offers';
|
||||
|
||||
const NUMBER_OF_OFFERS_IN_PAGE = 10;
|
||||
const NUMBER_OF_OFFERS_PER_PAGE = 20;
|
||||
|
||||
export type OffersTableProps = Readonly<{
|
||||
companyFilter: string;
|
||||
companyName?: string;
|
||||
countryFilter: string;
|
||||
jobTitleFilter: string;
|
||||
}>;
|
||||
|
||||
export default function OffersTable({
|
||||
countryFilter,
|
||||
companyName,
|
||||
@@ -101,15 +103,16 @@ export default function OffersTable({
|
||||
pathname,
|
||||
]);
|
||||
|
||||
const topRef = useRef<HTMLDivElement>(null);
|
||||
const { showToast } = useToast();
|
||||
trpc.useQuery(
|
||||
const { isLoading: isResultsLoading } = trpc.useQuery(
|
||||
[
|
||||
'offers.list',
|
||||
{
|
||||
companyId: companyFilter,
|
||||
countryId: countryFilter,
|
||||
currency,
|
||||
limit: NUMBER_OF_OFFERS_IN_PAGE,
|
||||
limit: NUMBER_OF_OFFERS_PER_PAGE,
|
||||
offset: pagination.currentPage,
|
||||
sortBy: selectedSortBy ?? '-monthYearReceived',
|
||||
title: jobTitleFilter,
|
||||
@@ -257,17 +260,27 @@ export default function OffersTable({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative w-full border border-slate-200">
|
||||
{renderFilters()}
|
||||
<div className="relative w-full divide-y divide-slate-200 border border-slate-200 bg-white">
|
||||
<div ref={topRef}>{renderFilters()}</div>
|
||||
<OffersTablePagination
|
||||
endNumber={
|
||||
pagination.currentPage * NUMBER_OF_OFFERS_PER_PAGE + offers.length
|
||||
}
|
||||
handlePageChange={handlePageChange}
|
||||
isInitialFetch={isLoading}
|
||||
isLoading={isResultsLoading}
|
||||
pagination={pagination}
|
||||
startNumber={pagination.currentPage * NUMBER_OF_OFFERS_PER_PAGE + 1}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<div className="col-span-10 py-32">
|
||||
<Spinner display="block" size="lg" />
|
||||
</div>
|
||||
) : (
|
||||
<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-xs text-slate-700 sm:text-sm md:text-base">
|
||||
<table className="w-full divide-y divide-slate-200 text-left text-xs text-slate-700 sm:text-sm">
|
||||
{renderHeader()}
|
||||
<tbody>
|
||||
<tbody className="divide-y divide-slate-200">
|
||||
{offers.map((offer) => (
|
||||
<OffersRow key={offer.id} jobType={jobType} row={offer} />
|
||||
))}
|
||||
@@ -283,11 +296,18 @@ export default function OffersTable({
|
||||
)}
|
||||
<OffersTablePagination
|
||||
endNumber={
|
||||
pagination.currentPage * NUMBER_OF_OFFERS_IN_PAGE + offers.length
|
||||
pagination.currentPage * NUMBER_OF_OFFERS_PER_PAGE + offers.length
|
||||
}
|
||||
handlePageChange={handlePageChange}
|
||||
handlePageChange={(number) => {
|
||||
topRef?.current?.scrollIntoView({
|
||||
block: 'start',
|
||||
});
|
||||
handlePageChange(number);
|
||||
}}
|
||||
isInitialFetch={isLoading}
|
||||
isLoading={isResultsLoading}
|
||||
pagination={pagination}
|
||||
startNumber={pagination.currentPage * NUMBER_OF_OFFERS_IN_PAGE + 1}
|
||||
startNumber={pagination.currentPage * NUMBER_OF_OFFERS_PER_PAGE + 1}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,37 +1,51 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Pagination } from '@tih/ui';
|
||||
import { Pagination, Spinner } from '@tih/ui';
|
||||
|
||||
import type { Paging } from '~/types/offers';
|
||||
|
||||
type OffersTablePaginationProps = Readonly<{
|
||||
endNumber: number;
|
||||
handlePageChange: (page: number) => void;
|
||||
isInitialFetch?: boolean;
|
||||
isLoading?: boolean;
|
||||
pagination: Paging;
|
||||
startNumber: number;
|
||||
}>;
|
||||
|
||||
export default function OffersTablePagination({
|
||||
isInitialFetch,
|
||||
isLoading,
|
||||
endNumber,
|
||||
pagination,
|
||||
startNumber,
|
||||
handlePageChange,
|
||||
}: OffersTablePaginationProps) {
|
||||
const [screenWidth, setScreenWidth] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setScreenWidth(window.innerWidth);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<nav aria-label="Table navigation" className="p-4">
|
||||
<div className="flex grid grid-cols-1 items-center md:grid-cols-2">
|
||||
<div className="mb-2 text-sm font-normal text-slate-500 md:mb-0">
|
||||
Showing
|
||||
<span className="font-semibold text-slate-900">
|
||||
{` ${endNumber > 0 ? startNumber : 0} - ${endNumber} `}
|
||||
</span>
|
||||
{`of `}
|
||||
<span className="font-semibold text-slate-900">
|
||||
{pagination.totalItems}
|
||||
</span>
|
||||
<nav aria-label="Offers Pagination" className="py-3 px-4">
|
||||
<div className="grid grid-cols-1 items-center gap-2 md:grid-cols-2">
|
||||
<div>
|
||||
{!isInitialFetch && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="text-sm text-slate-500">
|
||||
Showing
|
||||
<span className="font-semibold text-slate-900">
|
||||
{` ${endNumber > 0 ? startNumber : 0} - ${endNumber} `}
|
||||
</span>
|
||||
{`of `}
|
||||
<span className="font-semibold text-slate-900">
|
||||
{pagination.totalItems}
|
||||
</span>{' '}
|
||||
results
|
||||
</div>
|
||||
{isLoading && <Spinner size="xs" />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex md:justify-end">
|
||||
<Pagination
|
||||
|
||||
Reference in New Issue
Block a user