Skip to content

Commit 5dd8cea

Browse files
authored
Merge pull request #89 from CS3219-AY2324S1/88-allow-master-to-view-normal-users
add normal users table
2 parents 3801480 + 334ab18 commit 5dd8cea

File tree

7 files changed

+206
-6
lines changed

7 files changed

+206
-6
lines changed

UserService/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
handleUpdateUser,
99
handleCreateAdminUser,
1010
handleGetAdminUsers,
11+
handleGetNormalUsers
1112
} from "./user/user.controller";
1213
const app = express();
1314
const port = 3004;
@@ -16,6 +17,7 @@ app.use(express.json());
1617

1718
app.post("/user", handleCreateUser);
1819
app.get("/adminusers", handleGetAdminUsers);
20+
app.get("/normalusers", handleGetNormalUsers);
1921
app.post("/useradmin", handleCreateAdminUser);
2022
app.get("/user", handleGetUser);
2123
app.put("/user", handleUpdateUser);

UserService/src/user/user.controller.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getAdminUsers,
88
getUser,
99
updateUser,
10+
getNormalUsers
1011
} from "./user.service";
1112

1213
export async function handleCreateUser(req: Request, res: Response) {
@@ -62,6 +63,17 @@ export async function handleGetAdminUsers(req: Request, res: Response) {
6263
}
6364
}
6465

66+
export async function handleGetNormalUsers(req: Request, res: Response) {
67+
try {
68+
console.log(`getting normal users`);
69+
const result = await getNormalUsers();
70+
res.status(200).send(result);
71+
} catch (error) {
72+
console.error(error);
73+
res.status(500).send(error);
74+
}
75+
}
76+
6577
export async function handleUpdateUser(req: Request, res: Response) {
6678
try {
6779
const email = req.body.params.email;

UserService/src/user/user.service.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,34 @@ export async function getAdminUsers(): Promise<User[]> {
131131
}
132132
}
133133

134+
export async function getNormalUsers(): Promise<User[]> {
135+
try {
136+
const q = query(collection(db, "users"), where("role", "==", "user"));
137+
const querySnapshot = await getDocs(q);
138+
let userArray: (User)[];
139+
userArray = []
140+
querySnapshot.forEach((doc) => {
141+
const user = doc.data()
142+
userArray = userArray.concat([{
143+
email: user.email,
144+
name: user.name ? user.name : undefined,
145+
year: user.year ? user.year : undefined,
146+
major: user.major ? user.major : undefined,
147+
role: user.role,
148+
completed: user.completed,
149+
}])
150+
})
151+
152+
if (querySnapshot) {
153+
return Promise.resolve(userArray);
154+
155+
}
156+
return Promise.reject("no such user");
157+
} catch (error) {
158+
return Promise.reject(error);
159+
}
160+
}
161+
134162
export async function updateUser(email: string, params: any): Promise<User> {
135163
try {
136164
const document = doc(db, "users", email);

frontend/src/api/user/data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const createUser = (email: string, token: string) =>
77
export const getAllAdminUsers = () =>
88
UserHttpClient.get<UserModel[]>("/adminusers");
99

10+
export const getAllNormalUsers = () => UserHttpClient.get<UserModel[]>("/normalusers");
1011
export const createAdminUser = (email: string, token: string) =>
1112
UserHttpClient.post<UserModel>("/useradmin", { email: email, token: token });
1213

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Select, MenuItem, TablePagination, SelectChangeEvent, Grid, Typography, Button } from '@mui/material';
3+
import { useData } from '../data/data.context';
4+
import { updateUser } from '../api/user';
5+
6+
interface NormalUser {
7+
email: string;
8+
name?: string;
9+
year?: string;
10+
major?: string;
11+
role: string;
12+
completed: number;
13+
}
14+
15+
const ITEMS_PER_PAGE_OPTIONS = [5, 10]; // Number of items to display per page
16+
17+
const NormalUsersTable: React.FC = () => {
18+
const [normalUsersData, setNormalUsers] = useState<NormalUser[]>([]);
19+
const [currentPage, setCurrentPage] = useState<number>(0);
20+
const [itemsPerPage, setItemsPerPage] = useState<number>(ITEMS_PER_PAGE_OPTIONS[0]);
21+
const { normalUsers, getNormalUsers } = useData();
22+
23+
useEffect(() => {
24+
async function getUsers() {
25+
getNormalUsers();
26+
}
27+
getUsers();
28+
// eslint-disable-next-line react-hooks/exhaustive-deps
29+
}, []);
30+
31+
useEffect(() => {
32+
setNormalUsers(normalUsers);
33+
console.log('check')
34+
}, [normalUsers]);
35+
36+
const handlePageChange = (event: unknown, newPage: number) => {
37+
setCurrentPage(newPage);
38+
};
39+
40+
const handleChangeItemsPerPage = (
41+
event: SelectChangeEvent<unknown>,
42+
) => {
43+
setItemsPerPage(event.target.value as number);
44+
setCurrentPage(0);
45+
};
46+
47+
const giveAdmin = async (user: NormalUser, index: number) => {
48+
await updateUser({
49+
email: user?.email ? user?.email : "",
50+
name: user?.name ? user?.name : "",
51+
year: user?.year ? user?.year : "",
52+
major: user?.major ? user.major : "",
53+
role: "admin",
54+
completed: user?.completed ? user.completed : 0,
55+
})
56+
setNormalUsers(normalUsersData.filter((e, i) => i !== index));
57+
}
58+
59+
const indexOfLastUser = (currentPage + 1) * itemsPerPage;
60+
const indexOfFirstUser = indexOfLastUser - itemsPerPage;
61+
const currentUsers = normalUsersData.slice(indexOfFirstUser, indexOfLastUser);
62+
63+
return (
64+
<><div style={{ maxHeight: '400px', overflowY: 'auto', width: '100%' }}>
65+
66+
<TableContainer component={Paper} style={{ margin: '10px', padding: '10px' }}>
67+
<Grid container>
68+
<Grid item xs={3}>
69+
<Typography fontWeight={600}>Normal Users:</Typography>
70+
</Grid>
71+
</Grid>
72+
<Table style={{ minWidth: 650, fontSize: '14px' }}>
73+
<TableHead>
74+
<TableRow >
75+
<TableCell>Email</TableCell>
76+
<TableCell>Name</TableCell>
77+
<TableCell>Year</TableCell>
78+
<TableCell>Major</TableCell>
79+
{/* <TableCell>Role</TableCell> */}
80+
{/* <TableCell>Completed</TableCell> */}
81+
<TableCell>Grant Privileges</TableCell>
82+
</TableRow>
83+
</TableHead>
84+
<TableBody>
85+
{currentUsers.map((user:NormalUser, index:number) => (
86+
<TableRow key={index}>
87+
<TableCell>{user.email}</TableCell>
88+
<TableCell>{user.name}</TableCell>
89+
<TableCell>{user.year}</TableCell>
90+
<TableCell>{user.major}</TableCell>
91+
<TableCell>
92+
<Button onClick={() => giveAdmin(user, index)}>
93+
Confirm
94+
</Button>
95+
</TableCell>
96+
97+
{/* <TableCell>{user.role}</TableCell> */}
98+
{/* <TableCell>{user.completed}</TableCell> */}
99+
</TableRow>
100+
))}
101+
</TableBody>
102+
</Table>
103+
</TableContainer>
104+
</div><div style={{ display: 'flex', alignItems: 'center' }}>
105+
<Select
106+
value={itemsPerPage}
107+
onChange={handleChangeItemsPerPage}
108+
style={{ marginTop: '10px' }}
109+
>
110+
{ITEMS_PER_PAGE_OPTIONS.map((option) => (
111+
<MenuItem key={option} value={option}>
112+
{`${option} per page`}
113+
</MenuItem>
114+
))}
115+
</Select>
116+
117+
<TablePagination
118+
rowsPerPageOptions={[]}
119+
component="div"
120+
count={normalUsersData.length}
121+
rowsPerPage={itemsPerPage}
122+
page={currentPage}
123+
onPageChange={handlePageChange} />
124+
</div></>
125+
126+
);
127+
};
128+
129+
export default NormalUsersTable;

frontend/src/data/data.context.tsx

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createContext, ReactNode, useContext, useMemo, useState } from "react";
22

33
import { getAllQuestions } from "../api/questions/data";
4-
import { getAllAdminUsers } from "../api/user";
4+
import { getAllAdminUsers, getAllNormalUsers } from "../api/user";
55
import { useAuth } from "../auth/auth.context";
66

77
interface Response {
@@ -21,7 +21,7 @@ interface Question {
2121
examples: Example[];
2222
}
2323

24-
export interface AdminUser {
24+
interface User {
2525
email: string;
2626
name?: string;
2727
year?: string;
@@ -40,9 +40,11 @@ interface DataContextData {
4040
loading: boolean;
4141
response: Response;
4242
questions: Question[];
43-
getQuestions: () => void;
44-
adminUsers: AdminUser[];
43+
getQuestions: () => void;
44+
adminUsers: User[];
4545
getAdminUsers: () => void;
46+
normalUsers: User[];
47+
getNormalUsers: () => void;
4648
// getExamples: (id: string) => void;
4749
}
4850

@@ -62,6 +64,8 @@ const DataContext = createContext<DataContextData>({
6264
getQuestions: () => undefined,
6365
adminUsers: [],
6466
getAdminUsers: () => undefined,
67+
normalUsers: [],
68+
getNormalUsers: () => undefined,
6569
// getExamples: (id: string) => undefined,
6670
});
6771

@@ -70,7 +74,8 @@ export function DataContextProvider({ children }: DataContextProviderProps) {
7074
const [loading, setLoading] = useState(false);
7175
const [response, setResponse] = useState<Response>(emptyResponse);
7276
const [questions, setQuestions] = useState<Question[]>([]);
73-
const [adminUsers, setAdminUsers] = useState<AdminUser[]>([]);
77+
const [adminUsers, setAdminUsers] = useState<User[]>([]);
78+
const [normalUsers, setNormalUsers] = useState<User[]>([]);
7479

7580
const getQuestions = async () => {
7681
try {
@@ -116,6 +121,25 @@ export function DataContextProvider({ children }: DataContextProviderProps) {
116121
}
117122
};
118123

124+
const getNormalUsers = async () => {
125+
try {
126+
setLoading(true);
127+
const result = await (await getAllNormalUsers()).data;
128+
setLoading(false);
129+
setNormalUsers(result);
130+
setResponse({
131+
type: "success",
132+
message: "successfully retrieved normal users",
133+
});
134+
} catch (e) {
135+
setLoading(false);
136+
setResponse({
137+
type: "error",
138+
message: e,
139+
});
140+
}
141+
};
142+
119143
const dataContextProviderValue = useMemo(
120144
() => ({
121145
loading,
@@ -124,9 +148,11 @@ export function DataContextProvider({ children }: DataContextProviderProps) {
124148
getQuestions,
125149
adminUsers,
126150
getAdminUsers,
151+
normalUsers,
152+
getNormalUsers
127153
}),
128154
//eslint-disable-next-line react-hooks/exhaustive-deps
129-
[loading, response, questions, adminUsers]
155+
[loading, response, questions, adminUsers, normalUsers]
130156
);
131157

132158
return (

frontend/src/pages/profile.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Values } from "../components/DropDownOrTextField";
1616
import { updateUser } from "../api/user";
1717
import AdminUsersTable from "../components/AdminUsersTable";
1818
import DeleteButtonModal from "../components/UserService/DeleteButton";
19+
import NormalUsersTable from "../components/NormalUsersTable";
1920

2021
export default function Profile() {
2122
const { user, setUser } = useAuth();
@@ -82,6 +83,7 @@ export default function Profile() {
8283
</Grid>
8384
<DeleteButtonModal />
8485
{user?.role === "master" && <AdminUsersTable />}
86+
{user?.role === "master" && <NormalUsersTable />}
8587
</Container>
8688
</Box>
8789
);

0 commit comments

Comments
 (0)