Skip to content

Commit d20ced5

Browse files
authored
Merge pull request #29 from DevOps-Cloud-Team5/SCRUM-90-course-page
90% finish course page, and enrollment and disenrollment, add course specific schedule
2 parents e0be0af + 12d0e4a commit d20ced5

File tree

10 files changed

+586
-248
lines changed

10 files changed

+586
-248
lines changed

src/pages/course/profile.css src/pages/course/course.css

+11-26
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,12 @@
1-
/* profile.css */
1+
/* course.css */
22

3-
/* body {
4-
font-family: 'Arial', sans-serif;
5-
background-color: #000;
6-
color: #fff;
7-
margin: 0;
8-
padding: 0;
9-
display: flex;
10-
align-items: center;
11-
justify-content: center;
12-
min-height: 100vh;
13-
} */
14-
15-
.profile {
3+
.course {
164
background: rgba(
175
255,
186
255,
197
255,
208
0.1
21-
); /* Semi-transparent white for profile background */
9+
); /* Semi-transparent white for course background */
2210
backdrop-filter: blur(5px); /* Blur effect for the background */
2311
border-radius: 15px;
2412
display: flex;
@@ -28,29 +16,26 @@
2816
gap: 1em;
2917
}
3018

31-
.avatar {
32-
width: 100px;
33-
height: 100px;
34-
border-radius: 50%;
35-
border: 3px solid #fff; /* White border for the avatar */
36-
box-shadow: 0 0 15px rgba(255, 255, 255, 0.3); /* subtle glow to the avatar */
19+
.course-info {
20+
display: flex;
21+
justify-content: space-between;
3722
}
3823

39-
.profile-info {
24+
.course-data {
4025
color: #fff; /* Light text for readability */
4126
}
4227

43-
.profile-info h1,
44-
.profile-info p {
28+
.course-data h1,
29+
.course-data p {
4530
margin: 0.3em 0; /* Add some vertical spacing between text elements */
4631
}
4732

48-
.profile-info h1 {
33+
.course-data h1 {
4934
color: #29b6f6; /* A brighter color for the user's name */
5035
font-size: 1.5em;
5136
}
5237

53-
.profile-info p {
38+
.course-data p {
5439
font-size: 1em;
5540
color: #bbb; /* Slightly dimmed color for less important text */
5641
}

src/pages/course/index.tsx

+239-63
Original file line numberDiff line numberDiff line change
@@ -3,90 +3,266 @@ import RootPage from "../root";
33
import Container from "@mui/material/Container";
44
import Typography from "@mui/material/Typography";
55
import Cookies from "js-cookie";
6-
import "./profile.css"; // Import CSS file for additional styling
6+
import "./course.css"; // Import CSS file for additional styling
77

8-
import { backend_get, deleteAuthCookies } from "../../utils";
8+
import {
9+
IsStudent,
10+
IsAdmin,
11+
backend_get,
12+
deleteAuthCookies,
13+
useAxiosRequest,
14+
backend_post
15+
} from "../../utils";
916
import { useNavigate, useParams } from "react-router-dom";
1017
import { JwtPayload, jwtDecode } from "jwt-decode";
11-
import { CookieJWT } from "../../types/common";
18+
import {
19+
CookieJWT,
20+
Empty,
21+
FullCourse,
22+
FullCourseUser
23+
} from "../../types/common";
24+
import { Avatar, Button, IconButton, capitalize, styled } from "@mui/material";
25+
import MoreVertIcon from "@mui/icons-material/MoreVert";
26+
import { render } from "@testing-library/react";
1227

13-
const Profile = () => {
28+
const Course = () => {
29+
const { response, error, loading, sendRequest } = useAxiosRequest<
30+
Empty,
31+
FullCourse
32+
>();
1433
const navigate = useNavigate();
1534
const { id } = useParams();
1635

17-
const [profileData, setProfileData] = useState({
18-
username: "",
19-
email: "",
20-
role: "",
21-
first_name: "",
22-
last_name: "",
23-
avatarUrl: "https://randomuser.me/api/portraits/men/5.jpg"
24-
});
36+
const [courseData, setCourseData] = useState<FullCourse>();
2537

26-
const getProfileData = async () => {
27-
let username = "";
28-
if (id != undefined) username = id;
29-
else {
30-
const jwt_token = Cookies.get("token_access");
31-
if (jwt_token == undefined) return { code: "missing access token" };
32-
const decoded: CookieJWT = jwtDecode(jwt_token);
33-
if (!("username" in decoded))
34-
return { code: "broken access token" };
35-
username = decoded["username"];
36-
}
37-
const resp = await backend_get("user/get/" + username, true);
38-
return resp.json();
38+
const getCourseData = () => {
39+
sendRequest({
40+
method: "GET",
41+
route: `/course/get/${id}`,
42+
useJWT: true
43+
});
3944
};
4045

4146
useEffect(() => {
42-
const fetchUserProfile = async () => {
43-
try {
44-
const profile = await getProfileData(); // Assuming getUserProfile returns user profile data
45-
if ("code" in profile) {
46-
deleteAuthCookies();
47-
navigate("/login");
48-
navigate(0);
49-
return;
50-
}
51-
// profile[0]["avaterUrl"] =
52-
// "https://randomuser.me/api/portraits/men/5.jpg";
53-
setProfileData(profile);
54-
} catch (error) {
55-
console.error("Error fetching profile:", error);
56-
// Show error on frontend
57-
}
58-
};
47+
getCourseData();
48+
}, [sendRequest]);
5949

60-
fetchUserProfile();
61-
}, [navigate]);
50+
useEffect(() => {
51+
if (response) setCourseData(response);
52+
}, [response]);
53+
54+
const alternatingColor = ["#424242", "#595959"];
55+
56+
const StyledTable = styled("table")({
57+
borderCollapse: "collapse",
58+
width: "100%",
59+
"& th, & tr": {
60+
padding: "8px",
61+
borderBottom: "1px solid #ddd",
62+
textAlign: "left"
63+
},
64+
"& td": {
65+
padding: "8px",
66+
textAlign: "left"
67+
},
68+
"& th": {
69+
fontWeight: "bold" // Add bold font weight to header cells if needed
70+
},
71+
"& .type-column": {
72+
width: "10%", // Adjust the width of the actions column
73+
textAlign: "left" // Align content to the left
74+
},
75+
"& .actions-column": {
76+
width: "5%", // Adjust the width of the actions column
77+
textAlign: "left" // Align content to the left
78+
},
79+
"& .avatar-column": {
80+
width: "5%", // Adjust the width of the avatar column
81+
textAlign: "left" // Align content to the left
82+
},
83+
"& .actions-icon": {
84+
display: "flex",
85+
justifyContent: "center",
86+
alignItems: "center"
87+
}
88+
});
89+
90+
const handleProfileClick = (username: string) => {
91+
navigate(`/profile/${username}`);
92+
};
93+
94+
const showAttendenceStats = () => {
95+
return (
96+
IsStudent() &&
97+
courseData?.attended != -1 &&
98+
courseData?.missed != -1
99+
);
100+
};
101+
102+
const handleEnrollment = (enroll: boolean) => {
103+
let url = "course/enroll/" + id;
104+
if (!enroll) url = "course/disenroll/" + id;
105+
backend_post(url, "", true).then((resp) => {
106+
if (resp.status == 200) {
107+
getCourseData();
108+
}
109+
});
110+
};
62111

63112
return (
64113
<RootPage>
65-
<Container component="main" maxWidth="xs">
66-
<div className="profile">
67-
<img
68-
className="avatar"
69-
src={profileData.avatarUrl}
70-
alt="User Avatar"
71-
/>
72-
<div className="profile-info">
114+
<Container component="main" className="course" maxWidth="md">
115+
<div className="course-info">
116+
<div className="course-data">
73117
<Typography component="h1" variant="h5">
74-
{profileData.first_name} {profileData.last_name}
75-
</Typography>
76-
<Typography component="p" variant="body1">
77-
Username: {profileData.username}
78-
</Typography>
79-
<Typography component="p" variant="body1">
80-
Email: {profileData.email}
81-
</Typography>
82-
<Typography component="p" variant="body1">
83-
Role: {profileData.role}
118+
{courseData?.course_name}
84119
</Typography>
120+
<p>
121+
<span className="label">Teachers:</span>
122+
{courseData?.num_teachers}
123+
</p>
124+
<p>
125+
<span className="label">Students:</span>
126+
{courseData?.num_students}
127+
</p>
128+
{showAttendenceStats() ? (
129+
<>
130+
<p>
131+
<span className="label">
132+
Lectures Attended:
133+
</span>
134+
{courseData?.attended}
135+
</p>
136+
<p>
137+
<span className="label">
138+
Lectures Missed:
139+
</span>
140+
{courseData?.missed}
141+
</p>
142+
</>
143+
) : null}
144+
</div>
145+
146+
<div
147+
style={{
148+
display: "flex",
149+
flexDirection: "column",
150+
marginTop: "2%",
151+
marginRight: "5%"
152+
}}
153+
>
154+
{!IsAdmin() ? (
155+
courseData?.enrolled == true ? (
156+
<Button
157+
variant="contained"
158+
onClick={() => {
159+
handleEnrollment(false);
160+
}}
161+
style={{
162+
marginBottom: "5%",
163+
textTransform: "none"
164+
}}
165+
sx={{
166+
"&.MuiButton-root:hover": {
167+
bgcolor: "red"
168+
}
169+
}}
170+
>
171+
Enrolled
172+
</Button>
173+
) : (
174+
<Button
175+
variant="contained"
176+
onClick={() => {
177+
handleEnrollment(true);
178+
}}
179+
style={{
180+
marginBottom: "5%",
181+
textTransform: "none"
182+
}}
183+
>
184+
Enroll
185+
</Button>
186+
)
187+
) : null}
188+
189+
<Button
190+
variant="contained"
191+
style={{ textTransform: "none" }}
192+
onClick={() => navigate(`/course/${id}/schedule`)}
193+
>
194+
Schedule
195+
</Button>
85196
</div>
86197
</div>
198+
199+
<div style={{ marginTop: "7%" }}>
200+
<Typography component="h1" variant="h5">
201+
Enrolled People
202+
</Typography>
203+
204+
<StyledTable>
205+
<thead>
206+
<tr>
207+
<th className="avatar-column"></th>
208+
<th>Name</th>
209+
<th className="type-column">Role</th>
210+
{IsAdmin() ? (
211+
<th className="actions-column">Actions</th>
212+
) : null}
213+
</tr>
214+
</thead>
215+
216+
<tbody>
217+
{courseData?.users.map(
218+
(user: FullCourseUser, index: number) => (
219+
<tr
220+
key={user.username}
221+
style={{
222+
backgroundColor:
223+
alternatingColor[index % 2]
224+
}}
225+
>
226+
<td className="avatar-column">
227+
<Avatar
228+
alt={`${user.first_name} ${user.last_name}`}
229+
/>
230+
</td>
231+
<td>
232+
<Button
233+
style={{
234+
color: "white",
235+
textTransform: "none",
236+
fontSize: "1em"
237+
}}
238+
onClick={() =>
239+
handleProfileClick(
240+
user.username
241+
)
242+
}
243+
>
244+
{`${user.first_name} ${user.last_name}`}
245+
</Button>
246+
</td>
247+
<td style={{ fontSize: "1em" }}>
248+
{capitalize(user.role)}
249+
</td>
250+
{IsAdmin() ? (
251+
<td className="actions-icon">
252+
<IconButton>
253+
<MoreVertIcon />
254+
</IconButton>
255+
</td>
256+
) : null}
257+
</tr>
258+
)
259+
)}
260+
</tbody>
261+
</StyledTable>
262+
</div>
87263
</Container>
88264
</RootPage>
89265
);
90266
};
91267

92-
export default Profile;
268+
export default Course;

0 commit comments

Comments
 (0)