Skip to content

Commit

Permalink
add useTable hook, custom table component and refactor manage user page
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeffrey authored and carolynzhang18 committed Oct 1, 2024
1 parent a2a5a65 commit 06dd34e
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 83 deletions.
35 changes: 35 additions & 0 deletions frontend/src/components/common/table/CustomTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// takes in data and displays them in the component
import React from "react";
import {
Table,
TableContainer,
TableBody,
TableFooter,
TableHead,
Paper,
} from "@mui/material";

interface ICustomTable<T> {
data: T[];
renderRow: (row: T) => React.ReactNode;
renderHead: () => React.ReactNode;
renderFooter: () => React.ReactNode;
getKey: (row: T) => string | number;
}

export default function CustomTable<T>(props: ICustomTable<T>) {
const { data, renderRow, renderHead, renderFooter, getKey } = props;
return (
<TableContainer component={Paper}>
<Table>
<TableHead>{renderHead()}</TableHead>
<TableBody>
{data.map((row: T) => (
<React.Fragment key={getKey(row)}>{renderRow(row)}</React.Fragment>
))}
</TableBody>
<TableFooter>{renderFooter()}</TableFooter>
</Table>
</TableContainer>
);
}
199 changes: 116 additions & 83 deletions frontend/src/components/pages/ManageUserPage.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,140 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback } from "react";
import MenuItem from "@mui/material/MenuItem";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import InputLabel from "@mui/material/InputLabel";
import {
TableContainer,
Paper,
TableBody,
TableRow,
TableCell,
TableFooter,
TablePagination,
TableHead,
Stack,
FormControl,
} from "@mui/material";
import TablePaginationActions from "@mui/material/TablePagination/TablePaginationActions";
import { Table } from "react-bootstrap";
import CustomTable from "../common/table/CustomTable";
import useTable from "../../hooks/useTable";
import { Role } from "../../types/AuthTypes";
import UserAPIClient from "../../APIClients/UserAPIClient";
import { User } from "../../types/UserTypes";

const ManageUser = (): React.ReactElement => {
const [role, setRole] = useState<Role>("Administrator");
const [users, setUsers] = useState<User[]>([]);
const [page, setPage] = useState(1);
const [usersPerPage, setUsersPerPage] = useState(10);

const {
paginatedData,
handleChangePage,
handleChangeRowsPerPage,
pageSize,
page,
setSortField,
setSortAscending,
sortField,
} = useTable({
data: users,
});

useEffect(() => {
async function getUsers() {
const fetchedUsers = await UserAPIClient.getUsersByRole(role);
setUsers(fetchedUsers);
setPage(0);
handleChangePage(0);
}
getUsers();
}, [role]);

Check warning on line 43 in frontend/src/components/pages/ManageUserPage.tsx

View workflow job for this annotation

GitHub Actions / run-lint

React Hook useEffect has a missing dependency: 'handleChangePage'. Either include it or remove the dependency array

const emptyRows =
page > 0 ? Math.max(0, (1 + page) * usersPerPage - users.length) : 0;
const setSortFieldAndSortDirection = useCallback(
(selectedSortField: keyof User) => {
if (selectedSortField === sortField) {
setSortAscending((prev) => !prev);
} else {
setSortField(selectedSortField);
setSortAscending(true);
}
},
[setSortField, setSortAscending, sortField],
);

const changePage = useCallback(
(event: React.MouseEvent<HTMLButtonElement> | null, newPage: number) => {
handleChangePage(newPage);
},
[handleChangePage],
);

const changeRowsPerPage = useCallback(
(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
handleChangeRowsPerPage(parseInt(event.target.value, 10));
handleChangePage(0);
},
[handleChangePage, handleChangeRowsPerPage],
);

const renderHead = useCallback(() => {
return (
<TableRow>
<TableCell onClick={() => setSortFieldAndSortDirection("email")}>
Email
</TableCell>
<TableCell
align="right"
onClick={() => setSortFieldAndSortDirection("firstName")}
>
First Name
</TableCell>
<TableCell
align="right"
onClick={() => setSortFieldAndSortDirection("lastName")}
>
Last Name
</TableCell>
</TableRow>
);
}, [setSortFieldAndSortDirection]);

const renderRow = useCallback((user: User) => {
return (
<TableRow style={{ height: 53 }}>
<TableCell component="th" scope="row">
{user.email}
</TableCell>
<TableCell style={{ width: 160 }} align="right">
{user.firstName}
</TableCell>
<TableCell style={{ width: 160 }} align="right">
{user.lastName}
</TableCell>
</TableRow>
);
}, []);

const handleChangePage = (
event: React.MouseEvent<HTMLButtonElement> | null,
newPage: number,
) => {
setPage(newPage);
};
const renderFooter = useCallback(() => {
return (
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, { label: "All", value: -1 }]}
colSpan={3}
count={users.length}
rowsPerPage={pageSize}
page={page}
slotProps={{
select: {
inputProps: {
"aria-label": "users per page",
},
native: true,
},
}}
onPageChange={changePage}
onRowsPerPageChange={changeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
);
}, [changePage, changeRowsPerPage, page, pageSize, users.length]);

const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
setUsersPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const getKey = useCallback((user: User) => {
return user.id;
}, []);

return (
<Stack direction="column" justifyContent="center" margin="2rem" spacing={2}>
Expand All @@ -69,65 +154,13 @@ const ManageUser = (): React.ReactElement => {
<MenuItem value="Learner">Learner</MenuItem>
</Select>
</FormControl>
<TableContainer component={Paper}>
<Table aria-label="User grouped by role table">
<TableHead>
<TableRow>
<TableCell>Email</TableCell>
<TableCell align="right">First Name</TableCell>
<TableCell align="right">Last Name</TableCell>
</TableRow>
</TableHead>
<TableBody>
{(usersPerPage > 0
? users.slice(
page * usersPerPage,
page * usersPerPage + usersPerPage,
)
: users
).map((user) => (
<TableRow key={user.id} style={{ height: 53 }}>
<TableCell component="th" scope="row">
{user.email}
</TableCell>
<TableCell style={{ width: 160 }} align="right">
{user.firstName}
</TableCell>
<TableCell style={{ width: 160 }} align="right">
{user.lastName}
</TableCell>
</TableRow>
))}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, { label: "All", value: -1 }]}
colSpan={3}
count={users.length}
rowsPerPage={usersPerPage}
page={page}
slotProps={{
select: {
inputProps: {
"aria-label": "users per page",
},
native: true,
},
}}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
</TableFooter>
</Table>
</TableContainer>
<CustomTable
data={paginatedData}
renderRow={renderRow}
renderHead={renderHead}
renderFooter={renderFooter}
getKey={getKey}
/>
</Stack>
);
};
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/hooks/useTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useState, useMemo } from "react";

// want to take in data and be able to paginate, sort and filter the data

interface UseTableOptions<T> {
data: T[];
initialPageSize?: number;
initialSortField?: keyof T;
initialAscendingOrder?: boolean;
filterFunction?: (item: T) => boolean;
}

export default function useTable<T>({
data,
initialPageSize = 10,
initialSortField,
initialAscendingOrder = true,
filterFunction = () => true,
}: UseTableOptions<T>) {
const [page, setPage] = useState(0);
const [pageSize, setPageSize] = useState(initialPageSize);
const [sortField, setSortField] = useState<keyof T | undefined>(
initialSortField,
);
const [sortAscending, setSortAscending] = useState(initialAscendingOrder);

// first we filter the data, then we sort it, then paginate it
const filteredData = useMemo(() => {
return data.filter((item: T) => filterFunction(item));
}, [data, filterFunction]);

const sortedData = useMemo(() => {
if (!sortField) return filteredData;
return filteredData.sort((a, b) => {
const aValue = a[sortField];
const bValue = b[sortField];

if (initialAscendingOrder) return aValue > bValue ? 1 : -1;
return aValue < bValue ? 1 : -1;
});
}, [filteredData, sortField, initialAscendingOrder]);

const paginatedData = useMemo(() => {
return sortedData.slice(page * pageSize, (1 + page) * pageSize);
}, [page, pageSize, sortedData]);

const handleChangePage = (newPage: number) => {
setPage(newPage);
};

const handleChangeRowsPerPage = (rowsPerPage: number) => {
setPageSize(rowsPerPage);
setPage(0);
};

return {
paginatedData,
handleChangePage,
handleChangeRowsPerPage,
setSortField,
setSortAscending,
setPageSize,
sortField,
sortAscending,
pageSize,
page,
};
}

0 comments on commit 06dd34e

Please sign in to comment.