feat(tables): implement sortable columns across various table components
- Added TableSortProvider to manage sorting state in AdminsTable, CmsTable, CompaniesTable, CustomersTable, TendersTable, and CompanyCategoriesTable. - Enhanced TableHead component to support sortable columns with visual indicators for sort direction. - Introduced columnSortKeys and columnDefaultOrder mappings in respective table presenters to define sortable fields and default sorting behavior. - Updated useCompanyListPresenter, useCmsTablePresenter, useCustomerListPresenter, useTenderListPresenter, and useCompanyCategoriesPresenter to handle sorting logic and state management. - Improved user experience by allowing dynamic sorting of table data based on user interactions.
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableSortProvider,
|
||||
} from "@/components/ui/table";
|
||||
import TableSkeleton from "@/components/ui/TableSkeleton";
|
||||
import { AdminStatus } from "@/constants/enums";
|
||||
@@ -41,6 +42,11 @@ const AdminsTable = () => {
|
||||
isChangeStatusModalOpen,
|
||||
setIsChangeStatusModalOpen,
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
changeStatus,
|
||||
currentUser,
|
||||
setStatus,
|
||||
@@ -86,18 +92,29 @@ const AdminsTable = () => {
|
||||
onClearAll={clearAllFilters}
|
||||
/>
|
||||
<div ref={tableSectionRef}>
|
||||
<TableSortProvider
|
||||
sortKey={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onSortChange={handleSortChange}
|
||||
>
|
||||
<Table data-cy="admins-table">
|
||||
<TableHeader>
|
||||
<TableRow className="border-none uppercase [&>th]:text-start">
|
||||
{columns.map((column, index) => (
|
||||
<TableHead
|
||||
key={column}
|
||||
colSpan={100}
|
||||
data-cy={`admins-table-header-${index}`}
|
||||
>
|
||||
{column}
|
||||
</TableHead>
|
||||
))}
|
||||
{columns.map((column, index) => {
|
||||
const sortKey = columnSortKeys[column];
|
||||
return (
|
||||
<TableHead
|
||||
key={column}
|
||||
colSpan={100}
|
||||
sortable={Boolean(sortKey)}
|
||||
sortKey={sortKey}
|
||||
defaultSortOrder={columnDefaultOrder[column]}
|
||||
data-cy={`admins-table-header-${index}`}
|
||||
>
|
||||
{column}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -202,6 +219,7 @@ const AdminsTable = () => {
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</TableSortProvider>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isChangeStatusModalOpen}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { SortDirection } from "@/components/ui/tableSortContext";
|
||||
import { apiDefaultParams } from "@/constants/Api_Params";
|
||||
import { AdminStatus } from "@/constants/enums";
|
||||
import {
|
||||
@@ -10,7 +11,11 @@ import {
|
||||
} from "@/hooks/queries";
|
||||
import { IUser, TChangeAdminStatusCredentials } from "@/lib/api";
|
||||
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||
import { deleteEmptyKeys, getPaginatedRowNumber } from "@/utils/shared";
|
||||
import {
|
||||
buildQueryString,
|
||||
deleteEmptyKeys,
|
||||
getPaginatedRowNumber,
|
||||
} from "@/utils/shared";
|
||||
import gsap from "gsap";
|
||||
import { useIsMutating } from "@tanstack/react-query";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
@@ -225,12 +230,49 @@ export const useAdminsPresenter = () => {
|
||||
"actions",
|
||||
];
|
||||
|
||||
/** Column label → BSON `sort_by` field. */
|
||||
const columnSortKeys: Record<string, string> = {
|
||||
"full name": "full_name",
|
||||
email: "email",
|
||||
username: "username",
|
||||
status: "status",
|
||||
};
|
||||
|
||||
/** First-click direction per column. */
|
||||
const columnDefaultOrder: Record<string, SortDirection> = {
|
||||
"full name": "asc",
|
||||
email: "asc",
|
||||
username: "asc",
|
||||
};
|
||||
|
||||
const sortBy = (params.sort_by as string) ?? "created_at";
|
||||
const sortOrder = (params.sort_order as SortDirection) ?? "desc";
|
||||
|
||||
const handleSortChange = useCallback(
|
||||
(key: string, order: SortDirection) => {
|
||||
const newParams = buildFirstPageParams({
|
||||
...params,
|
||||
sort_by: key,
|
||||
sort_order: order,
|
||||
});
|
||||
const cleanedParams = deleteEmptyKeys(newParams);
|
||||
const queryString = buildQueryString(cleanedParams);
|
||||
router.push(`${pathName}?${queryString}`);
|
||||
},
|
||||
[buildFirstPageParams, params, pathName, router],
|
||||
);
|
||||
|
||||
return {
|
||||
allUsers: usersList,
|
||||
usersList,
|
||||
setFilterValue,
|
||||
metadata,
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
isPending,
|
||||
onConfirmDelete,
|
||||
isModalOpen,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableSortProvider,
|
||||
} from "@/components/ui/table";
|
||||
import TableSkeleton from "@/components/ui/TableSkeleton";
|
||||
import { _TooltipDefaultParams } from "@/constants/tooltip";
|
||||
@@ -22,6 +23,11 @@ import useCmsTablePresenter from "./useCmsTablePresenter";
|
||||
const CmsTable = () => {
|
||||
const {
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
data,
|
||||
isPending,
|
||||
isModalOpen,
|
||||
@@ -55,14 +61,28 @@ const CmsTable = () => {
|
||||
onClearAll={clearAllFilters}
|
||||
/>
|
||||
<div ref={tableSectionRef}>
|
||||
<TableSortProvider
|
||||
sortKey={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onSortChange={handleSortChange}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-none uppercase [&>th]:text-start">
|
||||
{columns.map((column) => (
|
||||
<TableHead key={column} colSpan={100}>
|
||||
{column}
|
||||
</TableHead>
|
||||
))}
|
||||
{columns.map((column) => {
|
||||
const sortKey = columnSortKeys[column];
|
||||
return (
|
||||
<TableHead
|
||||
key={column}
|
||||
colSpan={100}
|
||||
sortable={Boolean(sortKey)}
|
||||
sortKey={sortKey}
|
||||
defaultSortOrder={columnDefaultOrder[column]}
|
||||
>
|
||||
{column}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{isPending ? (
|
||||
@@ -130,6 +150,7 @@ const CmsTable = () => {
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</TableSortProvider>
|
||||
</div>
|
||||
{isModalOpen && (
|
||||
<ConfirmationModal
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
import { SortDirection } from "@/components/ui/tableSortContext";
|
||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||
import { useDeleteCms, useGetCmsList } from "@/hooks/queries/useCmsQueries";
|
||||
import { TCms } from "@/lib/api/types/TCms";
|
||||
import { deleteEmptyKeys } from "@/utils/shared";
|
||||
import { buildQueryString, deleteEmptyKeys } from "@/utils/shared";
|
||||
import { useIsMutating } from "@tanstack/react-query";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
@@ -67,8 +68,45 @@ const useCmsTablePresenter = () => {
|
||||
if (!currentCms?.id) return;
|
||||
deleteCms(currentCms.id);
|
||||
};
|
||||
|
||||
/** Column label → BSON `sort_by` field. */
|
||||
const columnSortKeys: Record<string, string> = {
|
||||
key: "key",
|
||||
"created at": "created_at",
|
||||
};
|
||||
|
||||
/** First-click direction per column. */
|
||||
const columnDefaultOrder: Record<string, SortDirection> = {
|
||||
key: "asc",
|
||||
"created at": "desc",
|
||||
};
|
||||
|
||||
const sortBy = (params.sort_by as string) ?? "created_at";
|
||||
const sortOrder = (params.sort_order as SortDirection) ?? "desc";
|
||||
|
||||
const handleSortChange = useCallback(
|
||||
(key: string, order: SortDirection) => {
|
||||
const next: Record<string, any> = {
|
||||
...params,
|
||||
sort_by: key,
|
||||
sort_order: order,
|
||||
offset: 0,
|
||||
};
|
||||
delete next.cursor;
|
||||
const cleanedParams = deleteEmptyKeys(next);
|
||||
const queryString = buildQueryString(cleanedParams);
|
||||
router.push(`${pathname}?${queryString}`);
|
||||
},
|
||||
[params, pathname, router],
|
||||
);
|
||||
|
||||
return {
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
isPending,
|
||||
data,
|
||||
isModalOpen,
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableSortProvider,
|
||||
} from "@/components/ui/table";
|
||||
import TableSkeleton from "@/components/ui/TableSkeleton";
|
||||
import { _TooltipDefaultParams } from "@/constants/tooltip";
|
||||
@@ -42,6 +43,11 @@ const CompanyCategoriesTable = () => {
|
||||
watch,
|
||||
setFilterValue,
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
isMutating,
|
||||
metadata,
|
||||
shouldShowPagination,
|
||||
@@ -70,15 +76,28 @@ const CompanyCategoriesTable = () => {
|
||||
onClearAll={clearAllFilters}
|
||||
/>
|
||||
<div ref={tableSectionRef}>
|
||||
<TableSortProvider
|
||||
sortKey={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onSortChange={handleSortChange}
|
||||
>
|
||||
<Table data-cy="company-categories-table">
|
||||
<TableHeader>
|
||||
<TableRow className="border-none uppercase [&>th]:text-start">
|
||||
<TableHead colSpan={100}>Row</TableHead>
|
||||
<TableHead colSpan={100}>Title</TableHead>
|
||||
<TableHead colSpan={100}>Description</TableHead>
|
||||
<TableHead colSpan={100}>Created at</TableHead>
|
||||
<TableHead colSpan={100}>Published</TableHead>
|
||||
<TableHead colSpan={100}>Actions</TableHead>
|
||||
{columns.map((column) => {
|
||||
const sortKey = columnSortKeys[column];
|
||||
return (
|
||||
<TableHead
|
||||
key={column}
|
||||
colSpan={100}
|
||||
sortable={Boolean(sortKey)}
|
||||
sortKey={sortKey}
|
||||
defaultSortOrder={columnDefaultOrder[column]}
|
||||
>
|
||||
{column}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{isLoading ? (
|
||||
@@ -172,6 +191,7 @@ const CompanyCategoriesTable = () => {
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</TableSortProvider>
|
||||
</div>
|
||||
{isModalOpen && (
|
||||
<ConfirmationModal
|
||||
|
||||
+41
-1
@@ -1,4 +1,5 @@
|
||||
"use client";
|
||||
import { SortDirection } from "@/components/ui/tableSortContext";
|
||||
import { apiDefaultParams } from "@/constants/Api_Params";
|
||||
import {
|
||||
useCompanyCategoriesQuery,
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||
import { TCompanyCategory } from "@/lib/api";
|
||||
import { deleteEmptyKeys } from "@/utils/shared";
|
||||
import { buildQueryString, deleteEmptyKeys } from "@/utils/shared";
|
||||
import { useIsMutating } from "@tanstack/react-query";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useOptimistic, useRef, useState } from "react";
|
||||
@@ -108,6 +109,40 @@ const useCompanyCategoriesPresenter = () => {
|
||||
"Published",
|
||||
"Actions",
|
||||
];
|
||||
|
||||
/** Column label → BSON `sort_by` field. */
|
||||
const columnSortKeys: Record<string, string> = {
|
||||
Title: "name",
|
||||
Description: "description",
|
||||
"Created at": "created_at",
|
||||
Published: "published",
|
||||
};
|
||||
|
||||
/** First-click direction per column. */
|
||||
const columnDefaultOrder: Record<string, SortDirection> = {
|
||||
Title: "asc",
|
||||
Description: "asc",
|
||||
"Created at": "desc",
|
||||
Published: "desc",
|
||||
};
|
||||
|
||||
const sortBy = (params.sort_by as string) ?? "created_at";
|
||||
const sortOrder = (params.sort_order as SortDirection) ?? "desc";
|
||||
|
||||
const handleSortChange = useCallback(
|
||||
(key: string, order: SortDirection) => {
|
||||
const newParams = buildFirstPageParams({
|
||||
...params,
|
||||
sort_by: key,
|
||||
sort_order: order,
|
||||
});
|
||||
const cleanedParams = deleteEmptyKeys(newParams);
|
||||
const queryString = buildQueryString(cleanedParams);
|
||||
router.push(`${pathName}?${queryString}`);
|
||||
},
|
||||
[buildFirstPageParams, params, pathName, router],
|
||||
);
|
||||
|
||||
return {
|
||||
categories,
|
||||
optimisticCategories,
|
||||
@@ -119,6 +154,11 @@ const useCompanyCategoriesPresenter = () => {
|
||||
pathName,
|
||||
router,
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
params,
|
||||
setParams,
|
||||
currentCategory,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableSortProvider,
|
||||
} from "@/components/ui/table";
|
||||
import TableSkeleton from "@/components/ui/TableSkeleton";
|
||||
import { _TooltipDefaultParams } from "@/constants/tooltip";
|
||||
@@ -55,6 +56,11 @@ const CompaniesTable = () => {
|
||||
isPending,
|
||||
pathName,
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
allCompanies,
|
||||
setIsModalOpen,
|
||||
router,
|
||||
@@ -93,18 +99,29 @@ const CompaniesTable = () => {
|
||||
onClearAll={clearAllFilters}
|
||||
/>
|
||||
<div ref={tableSectionRef}>
|
||||
<TableSortProvider
|
||||
sortKey={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onSortChange={handleSortChange}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{columns.map((col) => (
|
||||
<TableHead
|
||||
colSpan={100}
|
||||
className="text-start uppercase"
|
||||
key={col}
|
||||
>
|
||||
{col}
|
||||
</TableHead>
|
||||
))}
|
||||
{columns.map((col) => {
|
||||
const sortKey = columnSortKeys[col];
|
||||
return (
|
||||
<TableHead
|
||||
colSpan={100}
|
||||
className="text-start uppercase"
|
||||
key={col}
|
||||
sortable={Boolean(sortKey)}
|
||||
sortKey={sortKey}
|
||||
defaultSortOrder={columnDefaultOrder[col]}
|
||||
>
|
||||
{col}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{isPending ? (
|
||||
@@ -267,6 +284,7 @@ const CompaniesTable = () => {
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</TableSortProvider>
|
||||
</div>
|
||||
{isModalOpen && (
|
||||
<ConfirmationModal
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import { ConfirmationModalProps } from "@/components/ui/ConfirmationModal";
|
||||
import { SortDirection } from "@/components/ui/tableSortContext";
|
||||
import { apiDefaultParams } from "@/constants/Api_Params";
|
||||
import {
|
||||
useCompaniesQuery,
|
||||
@@ -8,7 +9,10 @@ import {
|
||||
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||
import { TCompany } from "@/lib/api";
|
||||
import { deleteEmptyKeys } from "@/utils/shared";
|
||||
import {
|
||||
buildQueryString,
|
||||
deleteEmptyKeys,
|
||||
} from "@/utils/shared";
|
||||
import { useIsMutating } from "@tanstack/react-query";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
@@ -104,6 +108,48 @@ export const useCompanyListPresenter = () => {
|
||||
deleteCompany(currentCompany.id);
|
||||
}
|
||||
};
|
||||
|
||||
/** Column label → BSON `sort_by` field. */
|
||||
const columnSortKeys: Record<string, string> = {
|
||||
name: "name",
|
||||
email: "email",
|
||||
phone: "phone",
|
||||
country: "address.country",
|
||||
state: "address.state",
|
||||
language: "language",
|
||||
currency: "currency",
|
||||
"employee count": "employee_count",
|
||||
};
|
||||
|
||||
/** First-click direction per column. */
|
||||
const columnDefaultOrder: Record<string, SortDirection> = {
|
||||
name: "asc",
|
||||
email: "asc",
|
||||
phone: "asc",
|
||||
country: "asc",
|
||||
state: "asc",
|
||||
language: "asc",
|
||||
currency: "asc",
|
||||
"employee count": "desc",
|
||||
};
|
||||
|
||||
const sortBy = (params.sort_by as string) ?? "created_at";
|
||||
const sortOrder = (params.sort_order as SortDirection) ?? "desc";
|
||||
|
||||
const handleSortChange = useCallback(
|
||||
(key: string, order: SortDirection) => {
|
||||
const newParams = buildFirstPageParams({
|
||||
...params,
|
||||
sort_by: key,
|
||||
sort_order: order,
|
||||
});
|
||||
const cleanedParams = deleteEmptyKeys(newParams);
|
||||
const queryString = buildQueryString(cleanedParams);
|
||||
router.push(`${pathName}?${queryString}`);
|
||||
},
|
||||
[buildFirstPageParams, params, pathName, router],
|
||||
);
|
||||
|
||||
const allCompanies = data?.data.companies ?? [];
|
||||
const metadata = data?.meta;
|
||||
return {
|
||||
@@ -111,6 +157,11 @@ export const useCompanyListPresenter = () => {
|
||||
onConfirmDelete,
|
||||
metadata,
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
pathName,
|
||||
currentCompany,
|
||||
modalConfig,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableSortProvider,
|
||||
} from "@/components/ui/table";
|
||||
import TableSkeleton from "@/components/ui/TableSkeleton";
|
||||
import { _TooltipDefaultParams } from "@/constants/tooltip";
|
||||
@@ -38,6 +39,11 @@ const CustomersTable = () => {
|
||||
pathName,
|
||||
router,
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
allCustomers,
|
||||
metadata,
|
||||
isModalOpen,
|
||||
@@ -84,18 +90,29 @@ const CustomersTable = () => {
|
||||
onClearAll={clearAllFilters}
|
||||
/>
|
||||
<div ref={tableSectionRef}>
|
||||
<TableSortProvider
|
||||
sortKey={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onSortChange={handleSortChange}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{columns.map((col) => (
|
||||
<TableHead
|
||||
colSpan={100}
|
||||
className="text-start font-extrabold uppercase"
|
||||
key={col}
|
||||
>
|
||||
{col}
|
||||
</TableHead>
|
||||
))}
|
||||
{columns.map((col) => {
|
||||
const sortKey = columnSortKeys[col];
|
||||
return (
|
||||
<TableHead
|
||||
colSpan={100}
|
||||
className="text-start font-extrabold uppercase"
|
||||
key={col}
|
||||
sortable={Boolean(sortKey)}
|
||||
sortKey={sortKey}
|
||||
defaultSortOrder={columnDefaultOrder[col]}
|
||||
>
|
||||
{col}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
{isPending ? (
|
||||
@@ -266,6 +283,7 @@ const CustomersTable = () => {
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</TableSortProvider>
|
||||
</div>
|
||||
<IsVisible condition={shouldShowPagination}>
|
||||
<Pagination
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { SortDirection } from "@/components/ui/tableSortContext";
|
||||
import {
|
||||
useAssignCompany,
|
||||
useCompanyFullList,
|
||||
@@ -11,7 +12,7 @@ import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||
import { TAssignCustomerToCompanyCredentials, TCustomer } from "@/lib/api";
|
||||
import { apiDefaultParams } from "@/constants/Api_Params";
|
||||
import { deleteEmptyKeys } from "@/utils/shared";
|
||||
import { buildQueryString, deleteEmptyKeys } from "@/utils/shared";
|
||||
import { useIsMutating } from "@tanstack/react-query";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
@@ -144,6 +145,42 @@ const useCustomerListPresenter = () => {
|
||||
}
|
||||
};
|
||||
|
||||
/** Column label → BSON `sort_by` field. */
|
||||
const columnSortKeys: Record<string, string> = {
|
||||
"full name": "full_name",
|
||||
email: "email",
|
||||
"created at": "created_at",
|
||||
type: "type",
|
||||
status: "status",
|
||||
role: "role",
|
||||
};
|
||||
|
||||
/** First-click direction per column. */
|
||||
const columnDefaultOrder: Record<string, SortDirection> = {
|
||||
"full name": "asc",
|
||||
email: "asc",
|
||||
type: "asc",
|
||||
role: "asc",
|
||||
"created at": "desc",
|
||||
};
|
||||
|
||||
const sortBy = (params.sort_by as string) ?? "created_at";
|
||||
const sortOrder = (params.sort_order as SortDirection) ?? "desc";
|
||||
|
||||
const handleSortChange = useCallback(
|
||||
(key: string, order: SortDirection) => {
|
||||
const newParams = buildFirstPageParams({
|
||||
...params,
|
||||
sort_by: key,
|
||||
sort_order: order,
|
||||
});
|
||||
const cleanedParams = deleteEmptyKeys(newParams);
|
||||
const queryString = buildQueryString(cleanedParams);
|
||||
router.push(`${pathName}?${queryString}`);
|
||||
},
|
||||
[buildFirstPageParams, params, pathName, router],
|
||||
);
|
||||
|
||||
const customers = data?.data?.customers ?? [];
|
||||
const shouldShowPagination =
|
||||
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
||||
@@ -153,6 +190,11 @@ const useCustomerListPresenter = () => {
|
||||
metadata: data?.meta,
|
||||
currentCustomer,
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
isPending,
|
||||
setCurrentCustomer,
|
||||
selectedCompanies,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableSortProvider,
|
||||
} from "@/components/ui/table";
|
||||
import IsVisible from "@/components/ui/IsVisible";
|
||||
import ListHeader from "@/components/ui/ListHeader";
|
||||
@@ -25,6 +26,11 @@ import useTenderListPresenter from "./useTenderListPresenter";
|
||||
const TendersTable = () => {
|
||||
const {
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
filterRegister,
|
||||
handleFilterSubmit,
|
||||
search,
|
||||
@@ -67,14 +73,28 @@ const TendersTable = () => {
|
||||
onClearAll={clearAllFilters}
|
||||
/>
|
||||
<div ref={tableSectionRef}>
|
||||
<TableSortProvider
|
||||
sortKey={sortBy}
|
||||
sortOrder={sortOrder}
|
||||
onSortChange={handleSortChange}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-none uppercase [&>th]:text-start">
|
||||
{columns.map((column) => (
|
||||
<TableHead key={column} colSpan={100}>
|
||||
{column}
|
||||
</TableHead>
|
||||
))}
|
||||
{columns.map((column) => {
|
||||
const sortKey = columnSortKeys[column];
|
||||
return (
|
||||
<TableHead
|
||||
key={column}
|
||||
colSpan={100}
|
||||
sortable={Boolean(sortKey)}
|
||||
sortKey={sortKey}
|
||||
defaultSortOrder={columnDefaultOrder[column]}
|
||||
>
|
||||
{column}
|
||||
</TableHead>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -153,6 +173,7 @@ const TendersTable = () => {
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</TableSortProvider>
|
||||
</div>
|
||||
<IsVisible condition={shouldShowPagination}>
|
||||
<Pagination
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import { ConfirmationModalProps } from "@/components/ui/ConfirmationModal";
|
||||
import { SortDirection } from "@/components/ui/tableSortContext";
|
||||
import { apiDefaultParams } from "@/constants/Api_Params";
|
||||
import { useTendersQuery } from "@/hooks/queries";
|
||||
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||
@@ -506,9 +507,50 @@ const useTenderListPresenter = () => {
|
||||
"status",
|
||||
"actions",
|
||||
];
|
||||
|
||||
/** Column label → BSON `sort_by` field (see bp-panel-list-sorting.md). */
|
||||
const columnSortKeys: Record<string, string> = {
|
||||
title: "title",
|
||||
"country code": "country_code",
|
||||
"publication date": "publication_date",
|
||||
"submission deadline": "submission_deadline",
|
||||
"tender deadline": "tender_deadline",
|
||||
"created at": "created_at",
|
||||
status: "status",
|
||||
};
|
||||
|
||||
/** First-click direction per column, following the BP sorting UX guide. */
|
||||
const columnDefaultOrder: Record<string, SortDirection> = {
|
||||
title: "asc",
|
||||
"submission deadline": "asc",
|
||||
"tender deadline": "asc",
|
||||
};
|
||||
|
||||
const sortBy = (params.sort_by as string) ?? "created_at";
|
||||
const sortOrder = (params.sort_order as SortDirection) ?? "desc";
|
||||
|
||||
const handleSortChange = useCallback(
|
||||
(key: string, order: SortDirection) => {
|
||||
const newParams = buildFirstPageParams({
|
||||
...params,
|
||||
sort_by: key,
|
||||
sort_order: order,
|
||||
});
|
||||
const cleanedParams = deleteEmptyKeys(newParams);
|
||||
const queryString = buildQueryString(cleanedParams);
|
||||
router.push(`${pathName}?${queryString}`);
|
||||
},
|
||||
[buildFirstPageParams, params, pathName, router],
|
||||
);
|
||||
|
||||
return {
|
||||
currentTender,
|
||||
columns,
|
||||
columnSortKeys,
|
||||
columnDefaultOrder,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
handleSortChange,
|
||||
setCurrentTender,
|
||||
router,
|
||||
allTenders: data,
|
||||
|
||||
Reference in New Issue
Block a user