diff --git a/frontend/src/components/common/table/CustomTable.tsx b/frontend/src/components/common/table/CustomTable.tsx new file mode 100644 index 00000000..f2940c28 --- /dev/null +++ b/frontend/src/components/common/table/CustomTable.tsx @@ -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 { + data: T[]; + renderRow: (row: T) => React.ReactNode; + renderHead: () => React.ReactNode; + renderFooter: () => React.ReactNode; + getKey: (row: T) => string | number; +} + +export default function CustomTable(props: ICustomTable) { + const { data, renderRow, renderHead, renderFooter, getKey } = props; + return ( + + + {renderHead()} + + {data.map((row: T) => ( + {renderRow(row)} + ))} + + {renderFooter()} +
+
+ ); +} diff --git a/frontend/src/components/pages/ManageUserPage.tsx b/frontend/src/components/pages/ManageUserPage.tsx index 70d93549..60054094 100644 --- a/frontend/src/components/pages/ManageUserPage.tsx +++ b/frontend/src/components/pages/ManageUserPage.tsx @@ -1,21 +1,17 @@ -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"; @@ -23,33 +19,122 @@ import { User } from "../../types/UserTypes"; const ManageUser = (): React.ReactElement => { const [role, setRole] = useState("Administrator"); const [users, setUsers] = useState([]); - 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]); + }, [role, handleChangePage]); + + 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 | null, newPage: number) => { + handleChangePage(newPage); + }, + [handleChangePage], + ); + + const changeRowsPerPage = useCallback( + (event: React.ChangeEvent) => { + handleChangeRowsPerPage(parseInt(event.target.value, 10)); + handleChangePage(0); + }, + [handleChangePage, handleChangeRowsPerPage], + ); + + const renderHead = useCallback(() => { + return ( + + setSortFieldAndSortDirection("email")}> + Email + + setSortFieldAndSortDirection("firstName")} + > + First Name + + setSortFieldAndSortDirection("lastName")} + > + Last Name + + + ); + }, [setSortFieldAndSortDirection]); - const emptyRows = - page > 0 ? Math.max(0, (1 + page) * usersPerPage - users.length) : 0; + const renderRow = useCallback((user: User) => { + return ( + + + {user.email} + + + {user.firstName} + + + {user.lastName} + + + ); + }, []); - const handleChangePage = ( - event: React.MouseEvent | null, - newPage: number, - ) => { - setPage(newPage); - }; + const renderFooter = useCallback(() => { + return ( + + + + ); + }, [changePage, changeRowsPerPage, page, pageSize, users.length]); - const handleChangeRowsPerPage = ( - event: React.ChangeEvent, - ) => { - setUsersPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; + const getKey = useCallback((user: User) => { + return user.id; + }, []); return ( @@ -69,65 +154,13 @@ const ManageUser = (): React.ReactElement => { Learner - - - - - Email - First Name - Last Name - - - - {(usersPerPage > 0 - ? users.slice( - page * usersPerPage, - page * usersPerPage + usersPerPage, - ) - : users - ).map((user) => ( - - - {user.email} - - - {user.firstName} - - - {user.lastName} - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - - - -
-
+
); }; diff --git a/frontend/src/components/pages/ViewHelpRequestsPage.tsx b/frontend/src/components/pages/ViewHelpRequestsPage.tsx index bcbd75a1..1dbc5f39 100644 --- a/frontend/src/components/pages/ViewHelpRequestsPage.tsx +++ b/frontend/src/components/pages/ViewHelpRequestsPage.tsx @@ -1,15 +1,10 @@ /* eslint-disable no-underscore-dangle */ -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { - TableContainer, - Paper, - TableHead, TableRow, TableCell, - TableBody, TableFooter, TablePagination, - Table, Breadcrumbs, Link, Checkbox, @@ -20,12 +15,37 @@ import { useFacilitator } from "../../hooks/useUser"; import HelpRequestAPIClient from "../../APIClients/HelpRequestAPIClient"; import { HelpRequest } from "../../types/HelpRequestType"; import { formatDate } from "../../utils/DateUtils"; +import CustomTable from "../common/table/CustomTable"; +import useTable from "../../hooks/useTable"; const ViewHelpRequestsPage = (): React.ReactElement => { const facilitator = useFacilitator(); const [helpRequests, setHelpRequests] = useState([]); - const [page, setPage] = useState(0); - const [helpRequestsPerPage, setHelpRequestsPerPage] = useState(10); + + const { + paginatedData, + handleChangePage, + handleChangeRowsPerPage, + pageSize, + page, + } = useTable({ + data: helpRequests, + }); + + const changePage = useCallback( + (event: React.MouseEvent | null, newPage: number) => { + handleChangePage(newPage); + }, + [handleChangePage], + ); + + const changeRowsPerPage = useCallback( + (event: React.ChangeEvent) => { + handleChangeRowsPerPage(parseInt(event.target.value, 10)); + handleChangePage(0); + }, + [handleChangePage, handleChangeRowsPerPage], + ); useEffect(() => { const fetchHelpRequests = async () => { @@ -36,25 +56,6 @@ const ViewHelpRequestsPage = (): React.ReactElement => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const emptyRows = - page > 0 - ? Math.max(0, (1 + page) * helpRequestsPerPage - helpRequests.length) - : 0; - - const handleChangePage = ( - event: React.MouseEvent | null, - newPage: number, - ) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = ( - event: React.ChangeEvent, - ) => { - setHelpRequestsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; - const markHelpRequestCompleted = async (helpRequest: HelpRequest) => { const data = await HelpRequestAPIClient.markHelpRequestCompleted( helpRequest.id, @@ -69,112 +70,115 @@ const ViewHelpRequestsPage = (): React.ReactElement => { } }; + const renderHead = useCallback(() => { + return ( + + Completed + Request Id + Learner + Message + Location (Unit / Module / Page) + Date + + ); + }, []); + + const renderRow = useCallback((helpRequest: HelpRequest) => { + return ( + + + markHelpRequestCompleted(helpRequest)} + /> + + + + {helpRequest.id} + + + + {helpRequest.learner.firstName} {helpRequest.learner.lastName} + + {helpRequest.message} + + + + {helpRequest.unit.title} + + + {helpRequest.module.title} + + + {helpRequest.page.title} + + + + + {formatDate(helpRequest.createdAt)} + + + ); + }, []); + + const renderFooter = useCallback(() => { + return ( + + + + + + ); + }, [changePage, changeRowsPerPage, helpRequests.length, page, pageSize]); + + const getKey = useCallback((helpRequest: HelpRequest) => { + return helpRequest.id; + }, []); + return (
- - - - - Completed - Request Id - Learner - Message - Location (Unit / Module / Page) - Date - - - - {(helpRequestsPerPage > 0 - ? helpRequests.slice( - page * helpRequestsPerPage, - page * helpRequestsPerPage + helpRequestsPerPage, - ) - : helpRequests - ).map((helpRequest) => ( - - - markHelpRequestCompleted(helpRequest)} - /> - - - - {helpRequest.id} - - - - {helpRequest.learner.firstName} {helpRequest.learner.lastName} - - - {helpRequest.message} - - - - - {helpRequest.unit.title} - - - {helpRequest.module.title} - - - {helpRequest.page.title} - - - - - {formatDate(helpRequest.createdAt)} - - - ))} - {emptyRows > 0 && ( - - - - )} - - - - - - -
-
+
); }; diff --git a/frontend/src/hooks/useTable.ts b/frontend/src/hooks/useTable.ts new file mode 100644 index 00000000..61cb7e70 --- /dev/null +++ b/frontend/src/hooks/useTable.ts @@ -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 { + data: T[]; + initialPageSize?: number; + initialSortField?: keyof T; + initialAscendingOrder?: boolean; + filterFunction?: (item: T) => boolean; +} + +export default function useTable({ + data, + initialPageSize = 10, + initialSortField, + initialAscendingOrder = true, + filterFunction = () => true, +}: UseTableOptions) { + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(initialPageSize); + const [sortField, setSortField] = useState( + 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, + }; +}