feat(backoffice): users page integration

- Get users data from API
- Set up server-side pagination and match URL params
- Hide filter that doesn't exist in back-end
This commit is contained in:
Hafid Nur
2025-12-09 21:29:53 +07:00
parent 254a2f154d
commit 7fc43d0f09
5 changed files with 355 additions and 124 deletions
+10 -7
View File
@@ -10,13 +10,18 @@ const ADMIN_BASE_URL = '/admin';
// Admin Users
export const getAdminUsers = async (params?: {
page?: number;
limit?: number;
per_page?: number;
search?: string;
city?: string;
is_admin?: boolean;
}) => {
const response = await api.get<TAdminUsersResponse>(
`${ADMIN_BASE_URL}/users`,
{ params }
{
params: {
...params,
is_admin: params?.is_admin ?? false,
},
}
);
return response.data;
};
@@ -24,10 +29,8 @@ export const getAdminUsers = async (params?: {
// Admin Teams
export const getAdminTeams = async (params?: {
page?: number;
limit?: number;
per_page?: number;
search?: string;
city?: string;
visibility?: string;
}) => {
const response = await api.get<TAdminTeamsResponse>(
`${ADMIN_BASE_URL}/teams`,
@@ -39,7 +42,7 @@ export const getAdminTeams = async (params?: {
// Admin Submissions
export const getAdminSubmissions = async (params?: {
page?: number;
limit?: number;
per_page?: number;
search?: string;
status?: string;
}) => {
+132 -2
View File
@@ -23,6 +23,11 @@ interface DataTableProps<T extends RowData> {
columns?: ColumnDef<T, unknown>[];
pageSize?: number;
className?: string;
// server-side pagination props
manualPagination?: boolean;
pageCount?: number;
currentPage?: number;
onPageChange?: (page: number) => void;
}
export const DataTable = <T extends RowData>({
@@ -31,6 +36,10 @@ export const DataTable = <T extends RowData>({
columns = [],
pageSize = 9,
className,
manualPagination = false,
pageCount,
currentPage = 1,
onPageChange,
}: DataTableProps<T>) => {
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
@@ -75,10 +84,20 @@ export const DataTable = <T extends RowData>({
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
// server-side pagination config
manualPagination,
pageCount: manualPagination ? pageCount : undefined,
};
return config;
}, [memoizedData, memoizedColumns, pagination, sorting]);
}, [
memoizedData,
memoizedColumns,
pagination,
sorting,
manualPagination,
pageCount,
]);
// Prefer external table instance if provided; otherwise create an internal one
const internalTable = useReactTable(tableConfig);
@@ -166,7 +185,118 @@ export const DataTable = <T extends RowData>({
</tbody>
</table>
</div>
<Pagination table={t} />
{manualPagination && onPageChange && pageCount ? (
// Server-side pagination controls with numbered pages
<div className="flex items-center justify-center gap-10">
<button
className="disabled:opacity-50 cursor-pointer"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
aria-label="Previous page"
>
<svg
className="w-4 h-4 text-neutral-800"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</button>
<div className="flex gap-4 items-baseline">
{pageCount <= 8 ? (
// Show all pages if 8 or fewer
Array.from({ length: pageCount }, (_, index) => (
<button
key={index}
className={`size-[30px] py-2 flex items-center justify-center rounded-md cursor-pointer ${
currentPage === index + 1
? 'bg-primary-500 text-white'
: 'bg-primary-100 hover:bg-primary-200'
}`}
onClick={() => onPageChange(index + 1)}
>
{index + 1}
</button>
))
) : (
// Show ellipsis for many pages
<>
<button
onClick={() => onPageChange(1)}
className={`size-[30px] py-2 flex items-center justify-center rounded-md cursor-pointer ${
currentPage === 1
? 'bg-primary-500 text-white'
: 'bg-primary-100 hover:bg-primary-200'
}`}
>
1
</button>
{currentPage > 3 && <span>...</span>}
{Array.from(
{ length: 5 },
(_, index) => currentPage - 2 + index
)
.filter((page) => page > 1 && page < pageCount)
.map((page) => (
<button
key={page}
onClick={() => onPageChange(page)}
className={`size-[30px] py-2 flex items-center justify-center rounded-md cursor-pointer ${
currentPage === page
? 'bg-primary-500 text-white'
: 'bg-primary-100 hover:bg-primary-200'
}`}
>
{page}
</button>
))}
{currentPage < pageCount - 2 && <span>...</span>}
<button
onClick={() => onPageChange(pageCount)}
className={`size-[30px] py-2 flex items-center justify-center rounded-md cursor-pointer ${
currentPage === pageCount
? 'bg-primary-500 text-white'
: 'bg-primary-100 hover:bg-primary-200'
}`}
>
{pageCount}
</button>
</>
)}
</div>
<button
className="disabled:opacity-50 cursor-pointer"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === pageCount}
aria-label="Next page"
>
<svg
className="w-4 h-4 text-neutral-800"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</button>
</div>
) : (
// Client-side pagination (default)
<Pagination table={t} />
)}
</div>
);
};