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

Manage users page #2133

Merged
merged 20 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
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