Skip to content

Commit 06dd34e

Browse files
Jeffreycarolynzhang18
Jeffrey
authored andcommitted
add useTable hook, custom table component and refactor manage user page
1 parent a2a5a65 commit 06dd34e

File tree

3 files changed

+219
-83
lines changed

3 files changed

+219
-83
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// takes in data and displays them in the component
2+
import React from "react";
3+
import {
4+
Table,
5+
TableContainer,
6+
TableBody,
7+
TableFooter,
8+
TableHead,
9+
Paper,
10+
} from "@mui/material";
11+
12+
interface ICustomTable<T> {
13+
data: T[];
14+
renderRow: (row: T) => React.ReactNode;
15+
renderHead: () => React.ReactNode;
16+
renderFooter: () => React.ReactNode;
17+
getKey: (row: T) => string | number;
18+
}
19+
20+
export default function CustomTable<T>(props: ICustomTable<T>) {
21+
const { data, renderRow, renderHead, renderFooter, getKey } = props;
22+
return (
23+
<TableContainer component={Paper}>
24+
<Table>
25+
<TableHead>{renderHead()}</TableHead>
26+
<TableBody>
27+
{data.map((row: T) => (
28+
<React.Fragment key={getKey(row)}>{renderRow(row)}</React.Fragment>
29+
))}
30+
</TableBody>
31+
<TableFooter>{renderFooter()}</TableFooter>
32+
</Table>
33+
</TableContainer>
34+
);
35+
}

frontend/src/components/pages/ManageUserPage.tsx

+116-83
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,140 @@
1-
import React, { useState, useEffect } from "react";
1+
import React, { useState, useEffect, useCallback } from "react";
22
import MenuItem from "@mui/material/MenuItem";
33
import Select, { SelectChangeEvent } from "@mui/material/Select";
44
import InputLabel from "@mui/material/InputLabel";
55
import {
6-
TableContainer,
7-
Paper,
8-
TableBody,
96
TableRow,
107
TableCell,
11-
TableFooter,
128
TablePagination,
13-
TableHead,
149
Stack,
1510
FormControl,
1611
} from "@mui/material";
1712
import TablePaginationActions from "@mui/material/TablePagination/TablePaginationActions";
18-
import { Table } from "react-bootstrap";
13+
import CustomTable from "../common/table/CustomTable";
14+
import useTable from "../../hooks/useTable";
1915
import { Role } from "../../types/AuthTypes";
2016
import UserAPIClient from "../../APIClients/UserAPIClient";
2117
import { User } from "../../types/UserTypes";
2218

2319
const ManageUser = (): React.ReactElement => {
2420
const [role, setRole] = useState<Role>("Administrator");
2521
const [users, setUsers] = useState<User[]>([]);
26-
const [page, setPage] = useState(1);
27-
const [usersPerPage, setUsersPerPage] = useState(10);
22+
23+
const {
24+
paginatedData,
25+
handleChangePage,
26+
handleChangeRowsPerPage,
27+
pageSize,
28+
page,
29+
setSortField,
30+
setSortAscending,
31+
sortField,
32+
} = useTable({
33+
data: users,
34+
});
35+
2836
useEffect(() => {
2937
async function getUsers() {
3038
const fetchedUsers = await UserAPIClient.getUsersByRole(role);
3139
setUsers(fetchedUsers);
32-
setPage(0);
40+
handleChangePage(0);
3341
}
3442
getUsers();
3543
}, [role]);
3644

37-
const emptyRows =
38-
page > 0 ? Math.max(0, (1 + page) * usersPerPage - users.length) : 0;
45+
const setSortFieldAndSortDirection = useCallback(
46+
(selectedSortField: keyof User) => {
47+
if (selectedSortField === sortField) {
48+
setSortAscending((prev) => !prev);
49+
} else {
50+
setSortField(selectedSortField);
51+
setSortAscending(true);
52+
}
53+
},
54+
[setSortField, setSortAscending, sortField],
55+
);
56+
57+
const changePage = useCallback(
58+
(event: React.MouseEvent<HTMLButtonElement> | null, newPage: number) => {
59+
handleChangePage(newPage);
60+
},
61+
[handleChangePage],
62+
);
63+
64+
const changeRowsPerPage = useCallback(
65+
(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
66+
handleChangeRowsPerPage(parseInt(event.target.value, 10));
67+
handleChangePage(0);
68+
},
69+
[handleChangePage, handleChangeRowsPerPage],
70+
);
71+
72+
const renderHead = useCallback(() => {
73+
return (
74+
<TableRow>
75+
<TableCell onClick={() => setSortFieldAndSortDirection("email")}>
76+
Email
77+
</TableCell>
78+
<TableCell
79+
align="right"
80+
onClick={() => setSortFieldAndSortDirection("firstName")}
81+
>
82+
First Name
83+
</TableCell>
84+
<TableCell
85+
align="right"
86+
onClick={() => setSortFieldAndSortDirection("lastName")}
87+
>
88+
Last Name
89+
</TableCell>
90+
</TableRow>
91+
);
92+
}, [setSortFieldAndSortDirection]);
93+
94+
const renderRow = useCallback((user: User) => {
95+
return (
96+
<TableRow style={{ height: 53 }}>
97+
<TableCell component="th" scope="row">
98+
{user.email}
99+
</TableCell>
100+
<TableCell style={{ width: 160 }} align="right">
101+
{user.firstName}
102+
</TableCell>
103+
<TableCell style={{ width: 160 }} align="right">
104+
{user.lastName}
105+
</TableCell>
106+
</TableRow>
107+
);
108+
}, []);
39109

40-
const handleChangePage = (
41-
event: React.MouseEvent<HTMLButtonElement> | null,
42-
newPage: number,
43-
) => {
44-
setPage(newPage);
45-
};
110+
const renderFooter = useCallback(() => {
111+
return (
112+
<TableRow>
113+
<TablePagination
114+
rowsPerPageOptions={[5, 10, 25, { label: "All", value: -1 }]}
115+
colSpan={3}
116+
count={users.length}
117+
rowsPerPage={pageSize}
118+
page={page}
119+
slotProps={{
120+
select: {
121+
inputProps: {
122+
"aria-label": "users per page",
123+
},
124+
native: true,
125+
},
126+
}}
127+
onPageChange={changePage}
128+
onRowsPerPageChange={changeRowsPerPage}
129+
ActionsComponent={TablePaginationActions}
130+
/>
131+
</TableRow>
132+
);
133+
}, [changePage, changeRowsPerPage, page, pageSize, users.length]);
46134

47-
const handleChangeRowsPerPage = (
48-
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
49-
) => {
50-
setUsersPerPage(parseInt(event.target.value, 10));
51-
setPage(0);
52-
};
135+
const getKey = useCallback((user: User) => {
136+
return user.id;
137+
}, []);
53138

54139
return (
55140
<Stack direction="column" justifyContent="center" margin="2rem" spacing={2}>
@@ -69,65 +154,13 @@ const ManageUser = (): React.ReactElement => {
69154
<MenuItem value="Learner">Learner</MenuItem>
70155
</Select>
71156
</FormControl>
72-
<TableContainer component={Paper}>
73-
<Table aria-label="User grouped by role table">
74-
<TableHead>
75-
<TableRow>
76-
<TableCell>Email</TableCell>
77-
<TableCell align="right">First Name</TableCell>
78-
<TableCell align="right">Last Name</TableCell>
79-
</TableRow>
80-
</TableHead>
81-
<TableBody>
82-
{(usersPerPage > 0
83-
? users.slice(
84-
page * usersPerPage,
85-
page * usersPerPage + usersPerPage,
86-
)
87-
: users
88-
).map((user) => (
89-
<TableRow key={user.id} style={{ height: 53 }}>
90-
<TableCell component="th" scope="row">
91-
{user.email}
92-
</TableCell>
93-
<TableCell style={{ width: 160 }} align="right">
94-
{user.firstName}
95-
</TableCell>
96-
<TableCell style={{ width: 160 }} align="right">
97-
{user.lastName}
98-
</TableCell>
99-
</TableRow>
100-
))}
101-
{emptyRows > 0 && (
102-
<TableRow style={{ height: 53 * emptyRows }}>
103-
<TableCell colSpan={6} />
104-
</TableRow>
105-
)}
106-
</TableBody>
107-
<TableFooter>
108-
<TableRow>
109-
<TablePagination
110-
rowsPerPageOptions={[5, 10, 25, { label: "All", value: -1 }]}
111-
colSpan={3}
112-
count={users.length}
113-
rowsPerPage={usersPerPage}
114-
page={page}
115-
slotProps={{
116-
select: {
117-
inputProps: {
118-
"aria-label": "users per page",
119-
},
120-
native: true,
121-
},
122-
}}
123-
onPageChange={handleChangePage}
124-
onRowsPerPageChange={handleChangeRowsPerPage}
125-
ActionsComponent={TablePaginationActions}
126-
/>
127-
</TableRow>
128-
</TableFooter>
129-
</Table>
130-
</TableContainer>
157+
<CustomTable
158+
data={paginatedData}
159+
renderRow={renderRow}
160+
renderHead={renderHead}
161+
renderFooter={renderFooter}
162+
getKey={getKey}
163+
/>
131164
</Stack>
132165
);
133166
};

frontend/src/hooks/useTable.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useState, useMemo } from "react";
2+
3+
// want to take in data and be able to paginate, sort and filter the data
4+
5+
interface UseTableOptions<T> {
6+
data: T[];
7+
initialPageSize?: number;
8+
initialSortField?: keyof T;
9+
initialAscendingOrder?: boolean;
10+
filterFunction?: (item: T) => boolean;
11+
}
12+
13+
export default function useTable<T>({
14+
data,
15+
initialPageSize = 10,
16+
initialSortField,
17+
initialAscendingOrder = true,
18+
filterFunction = () => true,
19+
}: UseTableOptions<T>) {
20+
const [page, setPage] = useState(0);
21+
const [pageSize, setPageSize] = useState(initialPageSize);
22+
const [sortField, setSortField] = useState<keyof T | undefined>(
23+
initialSortField,
24+
);
25+
const [sortAscending, setSortAscending] = useState(initialAscendingOrder);
26+
27+
// first we filter the data, then we sort it, then paginate it
28+
const filteredData = useMemo(() => {
29+
return data.filter((item: T) => filterFunction(item));
30+
}, [data, filterFunction]);
31+
32+
const sortedData = useMemo(() => {
33+
if (!sortField) return filteredData;
34+
return filteredData.sort((a, b) => {
35+
const aValue = a[sortField];
36+
const bValue = b[sortField];
37+
38+
if (initialAscendingOrder) return aValue > bValue ? 1 : -1;
39+
return aValue < bValue ? 1 : -1;
40+
});
41+
}, [filteredData, sortField, initialAscendingOrder]);
42+
43+
const paginatedData = useMemo(() => {
44+
return sortedData.slice(page * pageSize, (1 + page) * pageSize);
45+
}, [page, pageSize, sortedData]);
46+
47+
const handleChangePage = (newPage: number) => {
48+
setPage(newPage);
49+
};
50+
51+
const handleChangeRowsPerPage = (rowsPerPage: number) => {
52+
setPageSize(rowsPerPage);
53+
setPage(0);
54+
};
55+
56+
return {
57+
paginatedData,
58+
handleChangePage,
59+
handleChangeRowsPerPage,
60+
setSortField,
61+
setSortAscending,
62+
setPageSize,
63+
sortField,
64+
sortAscending,
65+
pageSize,
66+
page,
67+
};
68+
}

0 commit comments

Comments
 (0)