Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add table column sort #1182

Merged
merged 1 commit into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/renderer/api/cadt/v1/projects/projects.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface GetProjectsParams {
page: number;
orgUid?: string;
search?: string;
order?: string;
}

interface GetProjectsResponse {
Expand All @@ -18,7 +19,7 @@ interface GetProjectsResponse {
const projectsApi = cadtApi.injectEndpoints({
endpoints: (builder) => ({
getProjects: builder.query<GetProjectsResponse, GetProjectsParams>({
query: ({ page, orgUid, search }: GetProjectsParams) => {
query: ({ page, orgUid, search, order }: GetProjectsParams) => {
// Initialize the params object with page and limit
const params: GetProjectsParams & {limit: number} = { page, limit: 10 };

Expand All @@ -30,6 +31,10 @@ const projectsApi = cadtApi.injectEndpoints({
params.search = search.replace(/[^a-zA-Z0-9 _.-]+/, '');
}

if (order) {
params.order = order;
}

return {
url: `${host}/v1/projects`,
params, // Use the constructed params object
Expand Down
7 changes: 6 additions & 1 deletion src/renderer/api/cadt/v1/units/units.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface GetUnitsParams {
page: number;
orgUid?: string;
search?: string;
order?: string;
}

interface GetUnitsResponse {
Expand All @@ -17,7 +18,7 @@ interface GetUnitsResponse {
const unitsApi = cadtApi.injectEndpoints({
endpoints: (builder) => ({
getUnits: builder.query<GetUnitsResponse, GetUnitsParams>({
query: ({ page, orgUid, search }: GetUnitsParams) => {
query: ({ page, orgUid, search, order }: GetUnitsParams) => {
// Initialize the params object with page and limit
const params: GetUnitsParams & {limit: number} = { page, limit: 10 };

Expand All @@ -29,6 +30,10 @@ const unitsApi = cadtApi.injectEndpoints({
params.search = search.replace(/[^a-zA-Z0-9 _.-]+/, '');
}

if (order) {
params.order = order;
}

return {
url: `${host}/v1/units`,
params, // Use the constructed params object
Expand Down
10 changes: 9 additions & 1 deletion src/renderer/components/blocks/tables/ProjectsListTable.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React, { useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import { DebouncedFunc }from 'lodash'
import { DebouncedFunc } from 'lodash';
import { DataTable, PageCounter, Pagination } from '@/components';

interface TableProps {
data: any[];
isLoading: boolean;
currentPage: number;
onPageChange: DebouncedFunc<(page: any) => void>;
setOrder?: (sort: string) => void;
order?: string;
totalPages: number;
totalCount: number;
}
Expand All @@ -17,9 +19,13 @@ const ProjectsListTable: React.FC<TableProps> = ({
isLoading,
currentPage,
onPageChange,
setOrder,
order,
totalPages,
totalCount,
}) => {


const columns = useMemo(
() => [
{
Expand Down Expand Up @@ -74,6 +80,8 @@ const ProjectsListTable: React.FC<TableProps> = ({
<div className="relative">
<DataTable
columns={columns}
onChangeOrder={setOrder}
order={order}
data={data}
primaryKey="warehouseProjectId"
isLoading={isLoading}
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/components/blocks/tables/UnitsListTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface TableProps {
isLoading: boolean;
currentPage: number;
onPageChange: DebouncedFunc<(page: any) => void>;
setOrder?: (sort: string) => void;
order?: string;
totalPages: number;
totalCount: number;
}
Expand All @@ -17,6 +19,8 @@ const UnitsListTable: React.FC<TableProps> = ({
isLoading,
currentPage,
onPageChange,
setOrder,
order,
totalPages,
totalCount,
}) => {
Expand Down Expand Up @@ -75,6 +79,8 @@ const UnitsListTable: React.FC<TableProps> = ({
<DataTable
columns={columns}
data={data}
onChangeOrder={setOrder}
order={order}
primaryKey="warehouseUnitId"
isLoading={isLoading}
footer={
Expand Down
112 changes: 66 additions & 46 deletions src/renderer/components/layout/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FormattedMessage } from 'react-intl';
import { Tooltip } from '@/components';
import SimpleBar from 'simplebar-react';
import 'simplebar/dist/simplebar.min.css';
import { AiOutlineSortAscending, AiOutlineSortDescending } from 'react-icons/ai';

interface Column {
key: string;
Expand All @@ -20,19 +21,21 @@ interface DataTableProps {
data: any[];
isLoading?: boolean;
onRowClick?: (row: any) => void;
onChangeOrder?: (column: string) => void;
order?: string;
footer?: JSX.Element | null;
}

const DataTable: React.FC<DataTableProps> =
({
columns,
primaryKey = 'id',
data,
isLoading = false,
onRowClick = noop,
footer = null,
}) => {

const DataTable: React.FC<DataTableProps> = ({
columns,
primaryKey = 'id',
data,
isLoading = false,
onRowClick = noop,
onChangeOrder,
order,
footer = null,
}) => {
if (isLoading) {
return null;
}
Expand Down Expand Up @@ -80,44 +83,61 @@ const DataTable: React.FC<DataTableProps> =
style={{ height: data.length > 5 ? 'calc(100vh - 265px)' : 'auto' }}
>
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0 z-10">
<tr>
{columns.map((column) => (
<th
key={column.key}
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
>
{column.renderHeader ? column.renderHeader(column) : column.title}
</th>
))}
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{data?.length > 0 &&
data.map((row, index) => (
<tr
key={row[primaryKey]}
onClick={() => onRowClick(row)}
className={
index % 2 === 0
? 'bg-gray-50 dark:bg-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
: 'bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700'
}
>
{columns.map((column) => (
<td key={`${column.key}-${row[primaryKey]}`} className="px-6 py-4 whitespace-normal">
<div className="text-gray-600 dark:text-white">
{column.render ? (
column.render(row)
) : (
<div className="truncate" style={{ maxWidth: '300px' }}>
<Tooltip content={row[column.key]}>{row[column.key]}</Tooltip>
<tr>
{columns.map((column) => {
const isActive = order?.startsWith(column.key);
return (
<th
key={column.key}
style={{ cursor: onChangeOrder ? 'pointer' : 'default' }}
className={`px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${
isActive
? 'bg-gray-200 dark:bg-gray-600 text-gray-800 dark:text-white'
: 'text-gray-500 dark:text-gray-400'
}`}
onClick={() => onChangeOrder && onChangeOrder(column.key)}
>
<div className="flex items-center justify-between">
<span>{column.renderHeader ? column.renderHeader(column) : column.title}</span>
{order?.includes(column.key) && (
<div style={{ width: '24px', height: '24px', display: 'inline-block' }}>
{order.includes('ASC') && <AiOutlineSortAscending className="w-6 h-6" />}
{order.includes('DESC') && <AiOutlineSortDescending className="w-6 h-6" />}
</div>
)}
</div>
</td>
))}
</tr>
))}
</th>
);
})}
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{data?.length > 0 &&
data.map((row, index) => (
<tr
key={row[primaryKey]}
onClick={() => onRowClick(row)}
className={
index % 2 === 0
? 'bg-gray-50 dark:bg-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700'
: 'bg-white dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700'
}
>
{columns.map((column) => (
<td key={`${column.key}-${row[primaryKey]}`} className="px-6 py-4 whitespace-normal">
<div className="text-gray-600 dark:text-white">
{column.render ? (
column.render(row)
) : (
<div className="truncate" style={{ maxWidth: '300px' }}>
<Tooltip content={row[column.key]}>{row[column.key]}</Tooltip>
</div>
)}
</div>
</td>
))}
</tr>
))}
</tbody>
</table>
</SimpleBar>
Expand All @@ -135,4 +155,4 @@ const DataTable: React.FC<DataTableProps> =
);
};

export { DataTable };
export { DataTable };
3 changes: 2 additions & 1 deletion src/renderer/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './useUrlHash';
export * from './useQueryParamState';
export * from './useQueryParamState';
export * from './useColumnOrder';
40 changes: 40 additions & 0 deletions src/renderer/hooks/useColumnOrder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useCallback } from 'react';

/**
* A custom hook to manage table column sorting order logic.
* It provides a function to update the order based on user interaction.
* The order cycles through 'ASC', 'DESC', and no order ('') when the same column is clicked.
* Clicking a new column sets the order to 'ASC'.
*
* @param {string} order The current sorting order.
* @param {Function} setOrder The function to update the sorting order.
* @returns {Function} The function to handle updating the order based on column clicks.
*/
function useColumnOrderHandler(order, setOrder) {
const handleSetOrder = useCallback((column) => {
const currentColumn = order.split(':')[0];
const currentDirection = order.split(':')[1];

if (currentColumn === column) {
// Cycle through 'ASC', 'DESC', and no order ('')
switch (currentDirection) {
case 'ASC':
setOrder(`${column}:DESC`);
break;
case 'DESC':
setOrder('');
break;
default:
setOrder(`${column}:ASC`);
break;
}
} else {
// Default to ascending order for a new column
setOrder(`${column}:ASC`);
}
}, [order, setOrder]);

return handleSetOrder;
}

export { useColumnOrderHandler };
2 changes: 1 addition & 1 deletion src/renderer/hooks/useQueryParamState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const useQueryParamState: QueryParamState<string> = (name, defaultValue = '') =>
const location = useLocation();

const setQueryStringParameter = useCallback(
(value: string) => {
(value?: string) => {
const params = new URLSearchParams(window.location.search || window.location.hash.replace(/#.*\?/, ""));

if (_.isNil(value) || value === '') {
Expand Down
9 changes: 7 additions & 2 deletions src/renderer/pages/ProjectsList/ProjectsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback } from 'react';
import { useGetProjectsQuery } from '@/api';
import { useQueryParamState } from '@/hooks';
import { useQueryParamState, useColumnOrderHandler } from '@/hooks';
import { debounce } from 'lodash';
import {
OrganizationSelector,
Expand All @@ -15,13 +15,15 @@ const ProjectsList: React.FC = () => {
const [currentPage, setCurrentPage] = useQueryParamState('page', '1');
const [orgUid, setOrgUid] = useQueryParamState('orgUid', undefined);
const [search, setSearch] = useQueryParamState('search', undefined);
const [order, setOrder] = useQueryParamState('order', undefined);
const handleSetOrder = useColumnOrderHandler(order, setOrder);

const {
data: projectsData,
isLoading: projectsLoading,
isFetching: projectsFetching,
error: projectsError,
} = useGetProjectsQuery({ page: Number(currentPage), orgUid, search });
} = useGetProjectsQuery({ page: Number(currentPage), orgUid, search, order });

const handlePageChange = useCallback(
debounce((page) => setCurrentPage(page), 800),
Expand All @@ -42,6 +44,7 @@ const ProjectsList: React.FC = () => {
[setSearch, debounce],
);


if (projectsLoading) {
return <SkeletonTable />;
}
Expand Down Expand Up @@ -70,6 +73,8 @@ const ProjectsList: React.FC = () => {
isLoading={projectsLoading}
currentPage={Number(currentPage)}
onPageChange={handlePageChange}
setOrder={handleSetOrder}
order={order}
totalPages={projectsData.pageCount}
totalCount={projectsData.pageCount * 10}
/>
Expand Down
8 changes: 6 additions & 2 deletions src/renderer/pages/UnitsList/UnitsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback } from 'react';
import { useGetUnitsQuery } from '@/api';
import { useQueryParamState } from '@/hooks';
import { useQueryParamState, useColumnOrderHandler } from '@/hooks';
import { debounce } from 'lodash';
import {
OrganizationSelector,
Expand All @@ -15,13 +15,15 @@ const UnitsList: React.FC = () => {
const [currentPage, setCurrentPage] = useQueryParamState('page', '1');
const [orgUid, setOrgUid] = useQueryParamState('orgUid', undefined);
const [search, setSearch] = useQueryParamState('search', undefined);
const [order, setOrder] = useQueryParamState('order', undefined);
const handleSetOrder = useColumnOrderHandler(order, setOrder);

const {
data: unitsData,
isLoading: unitsLoading,
isFetching: unitsFetching,
error: unitsError,
} = useGetUnitsQuery({ page: Number(currentPage), orgUid, search });
} = useGetUnitsQuery({ page: Number(currentPage), orgUid, search, order });

const handlePageChange = useCallback(
debounce((page) => setCurrentPage(page), 800),
Expand Down Expand Up @@ -70,6 +72,8 @@ const UnitsList: React.FC = () => {
isLoading={unitsLoading}
currentPage={Number(currentPage)}
onPageChange={handlePageChange}
setOrder={handleSetOrder}
order={order}
totalPages={unitsData.pageCount}
totalCount={unitsData.pageCount * 10}
/>
Expand Down
Loading