Skip to content

Commit f9cf66b

Browse files
authored
Merge pull request #25 from DevOps-Cloud-Team5/SCRUM-17
allow attendence to be set and unset, clean up schedule page
2 parents 9a78494 + 232ddcf commit f9cf66b

File tree

5 files changed

+215
-29
lines changed

5 files changed

+215
-29
lines changed

src/pages/course/index.tsx

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { useState, useEffect } from "react";
2+
import RootPage from "../root";
3+
import Container from "@mui/material/Container";
4+
import Typography from "@mui/material/Typography";
5+
import Cookies from "js-cookie";
6+
import "./profile.css"; // Import CSS file for additional styling
7+
8+
import { backend_get, deleteAuthCookies } from "../../utils";
9+
import { useNavigate, useParams } from "react-router-dom";
10+
import { JwtPayload, jwtDecode } from "jwt-decode";
11+
import { CookieJWT } from "../../types/common";
12+
13+
const Profile = () => {
14+
const navigate = useNavigate();
15+
const { id } = useParams();
16+
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+
});
25+
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();
39+
};
40+
41+
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+
};
59+
60+
fetchUserProfile();
61+
}, [navigate]);
62+
63+
return (
64+
<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">
73+
<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}
84+
</Typography>
85+
</div>
86+
</div>
87+
</Container>
88+
</RootPage>
89+
);
90+
};
91+
92+
export default Profile;

src/pages/course/profile.css

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* profile.css */
2+
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 {
16+
background: rgba(
17+
255,
18+
255,
19+
255,
20+
0.1
21+
); /* Semi-transparent white for profile background */
22+
backdrop-filter: blur(5px); /* Blur effect for the background */
23+
border-radius: 15px;
24+
display: flex;
25+
align-items: center;
26+
padding: 2em;
27+
margin: 1em;
28+
gap: 1em;
29+
}
30+
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 */
37+
}
38+
39+
.profile-info {
40+
color: #fff; /* Light text for readability */
41+
}
42+
43+
.profile-info h1,
44+
.profile-info p {
45+
margin: 0.3em 0; /* Add some vertical spacing between text elements */
46+
}
47+
48+
.profile-info h1 {
49+
color: #29b6f6; /* A brighter color for the user's name */
50+
font-size: 1.5em;
51+
}
52+
53+
.profile-info p {
54+
font-size: 1em;
55+
color: #bbb; /* Slightly dimmed color for less important text */
56+
}
57+
58+
/* You can add more styles or customize the colors as you see fit */

src/pages/profile/profile.css

-14
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,3 @@
1-
/* profile.css */
2-
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-
151
.profile {
162
background: rgba(
173
255,

src/pages/schedule/index.tsx

+62-13
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ import moment from "moment";
1212
import { Button, Divider, capitalize } from "@mui/material";
1313
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
1414
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
15+
import IndeterminateCheckBoxIcon from '@mui/icons-material/IndeterminateCheckBox';
16+
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
17+
import CheckBoxIcon from '@mui/icons-material/CheckBox';
1518
import "./schedule.css";
1619

20+
// Set monday to the first day of the week
21+
moment.updateLocale('en', { week: { dow : 1, }});
22+
1723
const AttendancePage: React.FC = () => {
1824
const navigate = useNavigate();
1925
const { response, error, loading, sendRequest } = useAxiosRequest<Empty, ScheduleLecture[]>();
@@ -46,21 +52,29 @@ const AttendancePage: React.FC = () => {
4652
}
4753
}, [response]);
4854

49-
const setStudentAttendence = (lecture_id : number) => {
55+
const setStudentAttendence = (lecture_id : number, attended : boolean) => {
5056
const new_lectures = lectures?.map(
5157
(lectures : ScheduleLecture[]) =>
5258
lectures.map(
53-
(lecture : ScheduleLecture) => (lecture.id == lecture_id ? { ...lecture, attended_student: true } : lecture))
59+
(lecture : ScheduleLecture) => (lecture.id == lecture_id ? { ...lecture, attended_student: attended } : lecture))
5460
)
5561
setLectures(new_lectures)
5662
}
5763

5864
const handleAttendanceChange =
59-
(lecture_id: number) =>
65+
(lecture: ScheduleLecture) =>
6066
(event: React.ChangeEvent<HTMLInputElement>) => {
61-
backend_post(`lecture/${lecture_id}/student_att`, "", true)
67+
var url = `lecture/${lecture.id}/student_set_att`
68+
var attended = true
69+
70+
if (lecture.attended_student == true) {
71+
url = `lecture/${lecture.id}/student_unset_att`
72+
attended = false
73+
}
74+
75+
backend_post(url, "", true)
6276
.then((resp) => {
63-
if (resp.status == 200) setStudentAttendence(lecture_id)
77+
if (resp.status == 200) setStudentAttendence(lecture.id, attended)
6478
}
6579
)
6680
.catch((error) => console.log(error));
@@ -84,6 +98,18 @@ const AttendancePage: React.FC = () => {
8498
return moment(Date.parse(time)).format("HH:mm")
8599
}
86100

101+
const getCheckboxColor = (lecture : ScheduleLecture, attended_box : boolean) => {
102+
if(lecture.attended_student && lecture.attended_teacher) return "green"
103+
if (attended_box == false) return "rgb(224, 6, 31)"
104+
return "white"
105+
}
106+
107+
// Get date of a specific weekday in the current week
108+
const getDateDay = (dayIndex : number) => {
109+
const new_date = schedule_date.weekday(dayIndex)
110+
return new_date.date()
111+
}
112+
87113
return (
88114
<RootPage>
89115
<Container
@@ -113,29 +139,52 @@ const AttendancePage: React.FC = () => {
113139
{lectures?.map((day : ScheduleLecture[], dayIndex) => (
114140
<div key={dayConvert[dayIndex]} style={{ background: alternatingColor[dayIndex%2], paddingTop: "0", marginTop: "0" }}>
115141
<h2 style={{ paddingLeft: "2%", paddingTop: "1%", paddingBottom: "0", marginBottom: "0", marginTop: "0" }}>
116-
{dayConvert[dayIndex]}
142+
{`${getDateDay(dayIndex)} ${dayConvert[dayIndex]}`}
117143
</h2>
118144

119145
{(day.length != 0) ? <Divider variant="middle" style={{ marginTop: "1.5%" }} /> : null}
120146

121147
<List>
122148
{day.map((lecture, lectureIndex) => (
123149
<ListItem key={lecture.id} style={{ marginTop: "-0.5%", marginBottom: "-1%" }}>
124-
<div style={{ marginRight: "3%" }}>
150+
<div style={{ marginRight: "1%" }}>
125151
<ListItemText primary={convertToScheduleTime(lecture.start_time)} />
126152
<ListItemText primary={convertToScheduleTime(lecture.end_time)} />
127153
</div>
128-
<ListItemText primary={lecture.course} secondary={capitalize(lecture.lecture_type)}/>
154+
<Button
155+
variant="string"
156+
color="inherit"
157+
disableTouchRipple
158+
onClick={() => navigate(`/course/${lecture.course}`)
159+
}
160+
sx={{
161+
textTransform: 'none',
162+
textAlign: 'left', "&.MuiButtonBase-root:hover": {
163+
bgcolor: "transparent",
164+
textDecoration: "underline"
165+
}
166+
}}
167+
>
168+
<ListItemText primary={lecture.course_name} secondary={capitalize(lecture.lecture_type)}/>
169+
</Button>
170+
171+
<ListItemText primary={""}/>
129172

130173
<Checkbox
131-
checked={lecture.attended_student}
132-
onChange={handleAttendanceChange(lecture.id)}
133-
sx={{ "& .MuiSvgIcon-root": { color: "white" } }}
174+
indeterminateIcon={<CheckBoxOutlineBlankIcon />}
175+
icon={<IndeterminateCheckBoxIcon />}
176+
indeterminate={lecture.attended_student == null}
177+
checked={lecture.attended_student == true}
178+
onChange={handleAttendanceChange(lecture)}
179+
sx={{ "& .MuiSvgIcon-root": { color: getCheckboxColor(lecture, lecture.attended_student) } }}
134180
/>
135181
<Checkbox
136-
checked={lecture.attended_teacher}
182+
indeterminateIcon={<CheckBoxOutlineBlankIcon />}
183+
icon={<IndeterminateCheckBoxIcon />}
184+
indeterminate={lecture.attended_teacher == null}
185+
checked={lecture.attended_teacher == true}
137186
disabled={true}
138-
sx={{ "& .MuiSvgIcon-root": { color: "white" } }}
187+
sx={{ "& .MuiSvgIcon-root": { color: getCheckboxColor(lecture, lecture.attended_teacher) } }}
139188
/>
140189
</ListItem>
141190
))

src/types/common.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ export interface ScheduleLecture {
1616
start_time: string,
1717
end_time: string,
1818
lecture_type: string,
19+
course_name: string,
1920
course: number,
20-
attended_student: boolean,
21-
attended_teacher: boolean
21+
attended_student: boolean | null,
22+
attended_teacher: boolean | null
2223
}
2324

2425
export interface User {

0 commit comments

Comments
 (0)