Skip to content

Commit 35cff02

Browse files
committed
add admin table
1 parent 1383951 commit 35cff02

File tree

9 files changed

+218
-10
lines changed

9 files changed

+218
-10
lines changed

UserService/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import cors from "cors";
77
// import { Socket, Server } from 'socket.io';
88
// import { Server as ServerHttp } from 'http';
99

10-
import { handleCreateUser, handleGetUser, handleUpdateUser, handleCreateAdminUser } from "./user/user.controller";
10+
import { handleCreateUser, handleGetUser, handleUpdateUser, handleCreateAdminUser, handleGetAdminUsers } from "./user/user.controller";
1111
const app = express();
1212
const port = 3004;
1313
app.use(cors());
@@ -24,6 +24,7 @@ app.get("/", (req, res) => {
2424
// const db = getFirestore();
2525

2626
app.post("/user", handleCreateUser);
27+
app.get("/adminusers", handleGetAdminUsers);
2728
app.post("/useradmin", handleCreateAdminUser);
2829
app.get("/user", handleGetUser);
2930
app.put("/user", handleUpdateUser);

UserService/src/user/user.controller.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Request, Response } from "express";
22

3-
import { createAdminUser, createUser, getUser, updateUser } from "./user.service";
3+
import { createAdminUser, createUser, getAdminUsers, getUser, updateUser } from "./user.service";
44

55
export async function handleCreateUser(req: Request, res: Response) {
66
try {
@@ -43,6 +43,17 @@ export async function handleGetUser(req: Request, res: Response) {
4343
}
4444
}
4545

46+
export async function handleGetAdminUsers(req: Request, res: Response) {
47+
try {
48+
console.log(`getting admin users`);
49+
const result = await getAdminUsers();
50+
res.status(200).send(result);
51+
} catch (error) {
52+
console.error(error);
53+
res.status(500).send(error);
54+
}
55+
}
56+
4657
export async function handleUpdateUser(req: Request, res: Response) {
4758
try {
4859
const email = req.body.params.email;

UserService/src/user/user.service.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { initializeApp } from "firebase/app";
2-
import { doc, getDoc, getFirestore, setDoc, updateDoc } from "firebase/firestore";
2+
import { doc, getDoc, getFirestore, setDoc, updateDoc, where, query, collection, getDocs } from "firebase/firestore";
33
import { firebaseConfig } from "../firebase/firebase.config";
44

55
initializeApp(firebaseConfig);
@@ -80,13 +80,42 @@ export async function getUser(email: string): Promise<User> {
8080
}
8181
}
8282

83+
export async function getAdminUsers(): Promise<User[]> {
84+
try {
85+
const q = query(collection(db, "users"), where("role", "==", "admin"));
86+
const querySnapshot = await getDocs(q);
87+
let adminsArray: (User)[];
88+
adminsArray = []
89+
querySnapshot.forEach((doc) => {
90+
const user = doc.data()
91+
adminsArray = adminsArray.concat([{
92+
email: user.email,
93+
name: user.name ? user.name : undefined,
94+
year: user.year ? user.year : undefined,
95+
major: user.major ? user.major : undefined,
96+
role: user.role,
97+
completed: user.completed,
98+
}])
99+
})
100+
101+
if (querySnapshot) {
102+
return Promise.resolve(adminsArray);
103+
104+
}
105+
return Promise.reject("no such user");
106+
} catch (error) {
107+
return Promise.reject(error);
108+
}
109+
}
110+
83111
export async function updateUser(email: string, params: any): Promise<User> {
84112
try {
85113
const document = doc(db, "users", email);
86114
await updateDoc(document, {
87115
name: params.name,
88116
year: params.year,
89-
major: params.major
117+
major: params.major,
118+
role: params.role
90119
})
91120
const data = await getDoc(doc(db, "users", email));
92121
const user = data.data();

frontend/src/api/user/data.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { UserModel, Values } from "./model";
55
export const createUser = (email: string) =>
66
UserHttpClient.post<UserModel>("/user", { email: email });
77

8+
export const getAllAdminUsers = () => UserHttpClient.get<UserModel[]>("/adminusers");
9+
810
export const createAdminUser = (email: string) =>
911
UserHttpClient.post<UserModel>("/useradmin", { email: email });
1012

frontend/src/api/user/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ export interface UserModel {
88
}
99

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

frontend/src/components/Navbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import MenuIcon from "@mui/icons-material/Menu";
1616
import AdbIcon from "@mui/icons-material/Adb";
1717
import { useAuth } from "../auth/auth.context";
1818
import { useNavigate } from "react-router-dom";
19+
import { useData } from "../data/data.context";
1920

2021
const pages = ["Questions"];
2122
const authPages = [
@@ -58,7 +59,6 @@ export default function Navbar() {
5859
{ name: "Logout", onclick: logout },
5960
];
6061
if (user?.role == 'maintainer') {
61-
// settings = settings.concat({name: "Create", onclick: test})
6262
settings = settings.concat({name: "Create Admin", onclick: () => navigate("/createadmin", { replace: true })})
6363
}
6464

frontend/src/data/data.context.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +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";
45

56
interface Response {
67
type: "success" | "error" | undefined;
@@ -19,6 +20,15 @@ interface Question {
1920
examples: Example[];
2021
}
2122

23+
interface AdminUser {
24+
email: string;
25+
name?: string;
26+
year?: string;
27+
major?: string;
28+
role: string;
29+
completed: number;
30+
}
31+
2232
export interface Example {
2333
text: string;
2434
image: string;
@@ -28,7 +38,9 @@ interface DataContextData {
2838
loading: boolean;
2939
response: Response;
3040
questions: Question[];
31-
getQuestions: () => void;
41+
getQuestions: () => void;
42+
adminUsers: AdminUser[];
43+
getAdminUsers: () => void;
3244
// getExamples: (id: string) => void;
3345
}
3446

@@ -46,13 +58,16 @@ const DataContext = createContext<DataContextData>({
4658
response: emptyResponse,
4759
questions: [],
4860
getQuestions: () => undefined,
61+
adminUsers: [],
62+
getAdminUsers: () => undefined,
4963
// getExamples: (id: string) => undefined,
5064
});
5165

5266
export function DataContextProvider({ children }: DataContextProviderProps) {
5367
const [loading, setLoading] = useState(false);
5468
const [response, setResponse] = useState<Response>(emptyResponse);
5569
const [questions, setQuestions] = useState<Question[]>([]);
70+
const [adminUsers, setAdminUsers] = useState<AdminUser[]>([]);
5671

5772
const getQuestions = async () => {
5873
try {
@@ -75,10 +90,29 @@ export function DataContextProvider({ children }: DataContextProviderProps) {
7590
}
7691
};
7792

93+
const getAdminUsers = async () => {
94+
try {
95+
setLoading(true);
96+
const result = await (await getAllAdminUsers()).data;
97+
setLoading(false);
98+
setAdminUsers(result);
99+
setResponse({
100+
type: "success",
101+
message: "successfully retrieved admin users",
102+
});
103+
} catch (e) {
104+
setLoading(false);
105+
setResponse({
106+
type: "error",
107+
message: e,
108+
});
109+
}
110+
};
111+
78112
const dataContextProviderValue = useMemo(
79-
() => ({ loading, response, questions, getQuestions }),
113+
() => ({ loading, response, questions, getQuestions, adminUsers, getAdminUsers }),
80114
//eslint-disable-next-line react-hooks/exhaustive-deps
81-
[loading, response, questions]
115+
[loading, response, questions, adminUsers]
82116
);
83117

84118
return (

frontend/src/pages/profile.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import ProfileField from "../components/ProfileField";
1515
import { useAuth } from "../auth/auth.context";
1616
import { Values } from "../components/DropDownOrTextField";
1717
import { updateUser } from "../api/user";
18+
import AdminUsersTable from "../components/AdminUsersTable";
1819

1920
export default function Profile() {
2021
const { user, setUser } = useAuth();
@@ -24,6 +25,7 @@ export default function Profile() {
2425
name: user?.name ? user?.name : "",
2526
year: user?.year ? user?.year : "",
2627
major: user?.major ? user.major : "",
28+
role: user?.role ? user.role : "",
2729
});
2830

2931
const profileFields = [
@@ -36,7 +38,7 @@ export default function Profile() {
3638
const toggleEdit = async () => {
3739
if (edit) {
3840
try {
39-
const response = await updateUser(value);
41+
await updateUser(value);
4042
setUser(value)
4143
} catch (e) {
4244
console.log(e)
@@ -77,6 +79,7 @@ export default function Profile() {
7779
))}
7880
</Grid>
7981
</Grid>
82+
{user?.role == 'maintainer' && <AdminUsersTable/>}
8083
</Container>
8184
</Box>
8285
);

0 commit comments

Comments
 (0)