Skip to content

Commit

Permalink
feat(frontend): add table component and manage users page (#2133)
Browse files Browse the repository at this point in the history
* feat(package): tanstack table install

* feat(routes): manage-users route add

* feat(assetModules): icon add

* feat(css): remove counter button from input

* fix(paginationType): place paginationType to common

* feat(table): table radix component add

* fix(user): update ts type

* fix(pagination): update pagination type

* fix(table): update styles

* feat(dataTable): DataTable components add

* feat(user): update user role func add

* fix(searchBar): fix style

* feat(assetModules): add icon

* feat(manageUsers): manage users table & role update functionality add

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(manageUsers): update row column

* fix(lock): fix broken lock file

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
NSUWAL123 and pre-commit-ci[bot] authored Jan 31, 2025
1 parent 94b1965 commit 25f6ff6
Show file tree
Hide file tree
Showing 22 changed files with 715 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@radix-ui/react-switch": "^1.1.0",
"@reduxjs/toolkit": "^2.2.7",
"@sentry/react": "^8.21.0",
"@tanstack/react-table": "^8.20.6",
"@tiptap/extension-bullet-list": "^2.4.0",
"@tiptap/extension-document": "^2.4.0",
"@tiptap/extension-heading": "^2.4.0",
Expand Down
22 changes: 22 additions & 0 deletions src/frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src/frontend/src/api/HomeService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import axios from 'axios';
import { AppDispatch } from '@/store/Store';
import { HomeActions } from '@/store/slices/HomeSlice';
import { homeProjectPaginationTypes, projectType } from '@/models/home/homeModel';
import { projectType } from '@/models/home/homeModel';
import { paginationType } from '@/store/types/ICommon';

export const HomeSummaryService = (url: string) => {
return async (dispatch: AppDispatch) => {
Expand All @@ -11,7 +12,7 @@ export const HomeSummaryService = (url: string) => {
try {
const fetchHomeData = await axios.get(url);
const projectSummaries: projectType[] = fetchHomeData.data.results;
const paginationResp: homeProjectPaginationTypes = fetchHomeData.data.pagination;
const paginationResp: paginationType = fetchHomeData.data.pagination;
dispatch(HomeActions.SetHomeProjectPagination(paginationResp));
dispatch(HomeActions.SetHomeProjectSummary(projectSummaries));
dispatch(HomeActions.HomeProjectLoading(false));
Expand Down
41 changes: 39 additions & 2 deletions src/frontend/src/api/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ import axios, { AxiosResponse } from 'axios';
import { AppDispatch } from '@/store/Store';
import { UserActions } from '@/store/slices/UserSlice';
import { userType } from '@/models/user/userModel';
import { paginationType } from '@/store/types/ICommon';
import { CommonActions } from '@/store/slices/CommonSlice';

export const GetUserListService = (url: string) => {
export const GetUserListService = (url: string, params: { page: number; results_per_page: number; search: string }) => {
return async (dispatch: AppDispatch) => {
dispatch(UserActions.SetUserListLoading(true));

const getUserList = async (url: string) => {
try {
const response: AxiosResponse<userType[]> = await axios.get(url);
const response: AxiosResponse<{ results: userType[]; pagination: paginationType }> = await axios.get(url, {
params,
});
dispatch(UserActions.SetUserList(response.data));
} catch (error) {
} finally {
Expand All @@ -20,3 +24,36 @@ export const GetUserListService = (url: string) => {
await getUserList(url);
};
};

export const UpdateUserRole = (url: string, payload: { role: 'READ_ONLY' | 'ADMIN' | 'MAPPER' }) => {
return async (dispatch: AppDispatch) => {
const updateUserRole = async (url: string) => {
dispatch(UserActions.SetUpdateUserRoleLoading(true));
try {
const response: AxiosResponse<userType> = await axios.patch(url, payload);
dispatch(UserActions.UpdateUserList(response.data));
dispatch(
CommonActions.SetSnackBar({
open: true,
message: `Updated ${response.data.username}'s role to ${response.data.role} successfully`,
variant: 'success',
duration: 2000,
}),
);
} catch (error) {
dispatch(
CommonActions.SetSnackBar({
open: true,
message: 'Failed to update user role',
variant: 'error',
duration: 2000,
}),
);
} finally {
dispatch(UserActions.SetUpdateUserRoleLoading(false));
}
};

await updateUserRole(url);
};
};
82 changes: 82 additions & 0 deletions src/frontend/src/components/RadixComponents/Table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import * as React from 'react';

import { cn } from '@/utilfunctions/shadcn';

const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
({ className, ...props }, ref) => (
<div>
<table
ref={ref}
className={cn('fmtm-table-container fmtm-relative fmtm-w-full fmtm-overflow-y-auto fmtm-rounded-lg', className)}
{...props}
/>
</div>
),
);
Table.displayName = 'Table';

const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => (
<thead
ref={ref}
className={cn(
'fmtm-sticky fmtm-top-0 fmtm-z-10 fmtm-border-b-[#EAECF0] fmtm-bg-grey-200 [&_tr]:fmtm-border-b',
className,
)}
{...props}
/>
),
);
TableHeader.displayName = 'TableHeader';

const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => <tbody ref={ref} className={cn('fmtm-bg-white', className)} {...props} />,
);
TableBody.displayName = 'TableBody';

const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
({ className, ...props }, ref) => (
<tr ref={ref} className={cn('data-[state=selected]:-fmtm-bg-muted fmtm-border-b', className)} {...props} />
),
);
TableRow.displayName = 'TableRow';

const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'fmtm-whitespace-nowrap fmtm-px-4 fmtm-py-3 [&:not(:first-child):not(:last-child)]:fmtm-border-x-[1px] fmtm-border-white fmtm-text-grey-800 [&:has([role=checkbox])]:fmtm-pr-0 ',
className,
)}
{...props}
/>
),
);
TableHead.displayName = 'TableHead';

const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
'fmtm-h-11 fmtm-px-4 fmtm-py-3 fmtm-text-grey-800 fmtm-text-[15px] [&:has([role=checkbox])]:fmtm-pr-0',
className,
)}
{...props}
/>
),
);
TableCell.displayName = 'TableCell';

const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
({ className, ...props }, ref) => <tfoot ref={ref} className={cn('fmtm-font-medium', className)} {...props} />,
);
TableFooter.displayName = 'TableFooter';

const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
({ className, ...props }, ref) => <caption ref={ref} className={cn('fmtm-mt-4', className)} {...props} />,
);
TableCaption.displayName = 'TableCaption';

export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React from 'react';
import { useState } from 'react';
import type { Table } from '@tanstack/react-table';
import clsx from 'clsx';
import AssetModules from '@/shared/AssetModules';
import { ColumnData } from '..';
import usePagination, { DOTS } from './usePagination';

interface IPaginationProps {
totalCount: number;
siblingCount?: number;
currentPage: number;
pageSize: number;
isLoading: boolean;
table: Table<ColumnData>;
className?: string;
}

export default function DataTablePagination({
totalCount,
siblingCount = 1,
currentPage,
pageSize,
isLoading,
table,
className,
}: IPaginationProps) {
const [currentPageState, setCurrentPageState] = useState<number | string>(currentPage);

const paginationRange = usePagination({
currentPage,
totalCount,
siblingCount,
pageSize,
});
return (
<div
className={clsx(
'fmtm-bottom-0 fmtm-flex fmtm-items-center fmtm-justify-between fmtm-flex-col sm:fmtm-flex-row fmtm-bg-white fmtm-p-2 sm:fmtm-p-3 fmtm-shadow-black fmtm-shadow-2xl fmtm-z-50 fmtm-gap-1',
className,
)}
>
<p>
Showing {table.getRowCount()} of {totalCount} results
</p>

<div className="fmtm-flex fmtm-gap-6">
{/* Go to page */}
<div className="fmtm-flex fmtm-flex-1 fmtm-items-center fmtm-justify-end fmtm-gap-2 fmtm-md:pr-6">
<p className=" fmtm-whitespace-nowrap fmtm-text-grey-800">Go to Page</p>
<input
type="number"
min={1}
disabled={isLoading}
defaultValue={currentPageState}
value={currentPageState}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
const lastPage = table.getPageCount();
setCurrentPageState(Number(e.target.value) > lastPage ? lastPage : e.target.value);
table.setPageIndex(page);
}}
className="fmtm-outline-none fmtm-border fmtm-border-[#D0D5DD] fmtm-rounded-lg fmtm-w-8 fmtm-h-8 fmtm-p-1"
/>
</div>

{/* pagination-numbers */}
{paginationRange.length > 1 && (
<div className="fmtm-flex fmtm-items-center fmtm-gap-2 fmtm-overflow-x-auto fmtm-max-sm:w-[100%] fmtm-max-sm:justify-center">
{/* previous button */}
<button
disabled={!table.getCanPreviousPage()}
onClick={() => {
setCurrentPageState((prev) => Number(prev) - 1);
table.previousPage();
}}
className={`${!table.getCanPreviousPage() && 'fmtm-cursor-not-allowed'} hover:fmtm-bg-red-50 fmtm-rounded-full fmtm-duration-100 hover:fmtm-text-red-600`}
>
<AssetModules.ChevronLeftIcon />
</button>

{/* Page Numbers */}
{paginationRange.map((pageNumber) => {
if (pageNumber === DOTS) {
return (
<span key={pageNumber} className="fmtm-body-sm-regular fmtm-text-black-600">
&#8230;
</span>
);
}
return (
<div
className={`fmtm-grid fmtm-h-8 fmtm-cursor-pointer fmtm-place-items-center fmtm-px-3 600 fmtm-transition-colors hover:fmtm-text-primary-900 fmtm-border-b ${
currentPage === pageNumber ? 'fmtm-border-[#989898]' : 'fmtm-border-white'
}`}
key={pageNumber}
onClick={() => {
setCurrentPageState(pageNumber);
table.setPageIndex(+pageNumber - 1);
}}
>
<p className={`${currentPage === pageNumber ? 'fmtm-font-bold fmtm-text-[#212121]' : ''}`}>
{pageNumber}
</p>
</div>
);
})}
{/* next button */}
<button
disabled={!table.getCanNextPage()}
onClick={() => {
setCurrentPageState((prev) => Number(prev) + 1);
table.nextPage();
}}
className={`${!table.getCanNextPage() && 'fmtm-cursor-not-allowed'} hover:fmtm-bg-red-50 fmtm-rounded-full fmtm-duration-100 hover:fmtm-text-red-600`}
>
<AssetModules.ChevronRightIcon />
</button>
</div>
)}
</div>
</div>
);
}
Loading

0 comments on commit 25f6ff6

Please sign in to comment.