diff --git a/frontend/src/App.js b/frontend/src/App.js index 013f2e3b..96ee3686 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -25,6 +25,7 @@ import ThemeSettings from './layouts/full-layout/customizer/ThemeSettings'; import { useSelector } from 'react-redux'; import RTL from './layouts/full-layout/customizer/RTL'; import StudentListPage from './components/Page/Student/StudentListPage'; +import TaskGroupListPage from './components/General/TaskGroupList/TaskGroupListPage'; import SubmitMarks from './components/Page/Instructor/SubmitMarks'; import TaskGroupPage from './components/Page/Instructor/TaskGroupPage'; @@ -74,7 +75,14 @@ function App() { } + > + + + } /> + } diff --git a/frontend/src/Constants/roles.js b/frontend/src/Constants/roles.js new file mode 100644 index 00000000..79fd281b --- /dev/null +++ b/frontend/src/Constants/roles.js @@ -0,0 +1,5 @@ +export const ROLE = "role"; +export const STUDENT = "student" +export const TA = "ta" +export const INSTRUCTOR = "instructor" +export const ADMIN = "admin" diff --git a/frontend/src/api/instructor_api.js b/frontend/src/api/instructor_api.js index 629dc198..dfd2c1cc 100644 --- a/frontend/src/api/instructor_api.js +++ b/frontend/src/api/instructor_api.js @@ -127,19 +127,67 @@ let submitMark = async (courseId, task, criteria, username, mark) => { // } // }; +let allTasks = async (course_id) => { + let token = sessionStorage.getItem("token"); + + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + + try { + return await axios.get(process.env.REACT_APP_API_URL + "/instructor/course/" + course_id + "/task/all", config); + } catch (err) { + return err.response; + } +}; + +let allGroups = async (course_id, task) => { + let token = sessionStorage.getItem("token") + + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + + try { + return await axios.get(process.env.REACT_APP_API_URL + "/instructor/course/" + course_id + "/group/all?task=" + task, config); + } catch (err) { + return err.response; + } +} + +let taskGroups = async (course_id, task) => { + /** + * Gets all groups for a particular task within a course. + */ + let token = sessionStorage.getItem("token"); + + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + + try { + return await axios.get(process.env.REACT_APP_API_URL + "/instructor/course/" + course_id + "/group/all?task=" + task, config); + } catch (err) { + return err.response; + } +} + + let InstructorApi = { - // Task related - all_tasks, - impersonate, - submitMark - // - // // Group related - // check_group, - // - // // Interview related - // all_interviews, - // schedule_interview, - // delete_interview, + //Course related + allTasks, + // Task related + impersonate, + taskGroups, + // + // // Group related + // check_group, + allGroups, + // + // // Interview related + // all_interviews, + // schedule_interview, + // delete_interview, }; export default InstructorApi; diff --git a/frontend/src/api/ta_api.js b/frontend/src/api/ta_api.js index cc983d9b..38a68b45 100644 --- a/frontend/src/api/ta_api.js +++ b/frontend/src/api/ta_api.js @@ -28,6 +28,20 @@ let check_group = async (course_id, group_id) => { } }; +let allGroups = async (course_id, task) => { + let token = sessionStorage.getItem("token") + + let config = { + headers: { Authorization: `Bearer ${token}` } + }; + + try { + return await axios.get(process.env.REACT_APP_API_URL + "/ta/course/" + course_id + "/group/all?task=" + task, config); + } catch (err) { + return err.response; + } +} + let all_interviews = async (course_id, curr_task) => { let token = sessionStorage.getItem("token"); @@ -83,6 +97,7 @@ let TaApi = { // Group related check_group, + allGroups, // Interview related all_interviews, diff --git a/frontend/src/components/General/TaskGroupList/TaskGroupCard.jsx b/frontend/src/components/General/TaskGroupList/TaskGroupCard.jsx new file mode 100644 index 00000000..9875a17e --- /dev/null +++ b/frontend/src/components/General/TaskGroupList/TaskGroupCard.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Card, CardActionArea, CardContent, CardMedia, Typography } from '@mui/material'; +import { Link } from 'react-router-dom'; +import HomeCardLink from './HomeCardLink'; + +const Homecard = ({ data }) => { + const role = data.role === undefined || data.role === 'student' ? '' : data.role; + const staffRoles = ['admin', 'ta', 'instructor']; + + const coursePageLink = (role ? '/' + role : '') + '/course/' + data.course_id + '/task'; + const courseStudentListPageLink = `/course/${data.course_id}/student-list`; + + return ( + + + + + + {data.course_code} + +
+ + {data.course_session.replaceAll('_', ' ')} + + + {data?.role?.charAt(0)?.toUpperCase() + data?.role?.slice(1)} + +
+
+
+ {staffRoles.includes(data.role) && ( + + )} +
+ ); +}; + +export default Homecard; diff --git a/frontend/src/components/General/TaskGroupList/TaskGroupList.jsx b/frontend/src/components/General/TaskGroupList/TaskGroupList.jsx new file mode 100644 index 00000000..1eeef721 --- /dev/null +++ b/frontend/src/components/General/TaskGroupList/TaskGroupList.jsx @@ -0,0 +1,38 @@ +import React, { Children } from 'react'; +import BaseCard from '../../FlexyMainComponents/base-card/BaseCard'; +import InstructorApi from '../../../api/instructor_api'; +import { useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router'; +import { Grid } from '@mui/material'; + +const TaskGroupList = ({ courseId, task }) => { + let groups = []; + InstructorApi.allGroups(courseId, task) + .then((res) => { + res.groups.forEach((group) => { + groups.push({ id: group.id, members: group.users }); + }); + }) + .catch((err) => { + console.log(err); + }); + + return ( + + {groups.map((group) => ( + + ))} + + ); +}; + +export default TaskGroupList; diff --git a/frontend/src/components/General/TaskGroupList/TaskGroupListPage.jsx b/frontend/src/components/General/TaskGroupList/TaskGroupListPage.jsx new file mode 100644 index 00000000..b7c8a04d --- /dev/null +++ b/frontend/src/components/General/TaskGroupList/TaskGroupListPage.jsx @@ -0,0 +1,106 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import InstructorApi from '../../../api/instructor_api'; +import { Grid } from '@mui/material'; +import { TextField } from '@mui/material'; +import Autocomplete from '@mui/material/Autocomplete'; +import { getCourses } from '../../../../utilities/courses'; +import NavBar from '../../Module/Navigation/NavBar'; +import { makeStyles } from '@mui/styles'; +import TaskGroupList from './TaskGroupList'; +import { getTasks } from '../../../../utilities/tasks'; + +const useStyles = makeStyles({ + container: { + display: 'flex', + flexWrap: 'wrap', + alignItems: 'flex-start', + justifyContent: 'flex-start', + margin: '16px', + padding: '16px' + }, + + dropdown: { + display: 'inline-block' + } +}); + +const TaskGroupListPage = () => { + const classes = useStyles(); + const courseOptions = getCourses(); + + const [course, setCourse] = useState(''); + const [courseId, setCourseId] = useState(null); + const [task, setTask] = useState(''); + const [taskOptions, setTaskOptions] = useState([]); + + const handleCourseChange = (option) => { + setCourse(option.label); + setCourseId(option.course_id); + }; + + useEffect(() => { + if (course) { + const tasks = getTasks(); + setTaskOptions(tasks); + } + }, [course]); + + return ( + + + +
+
+ ( + + )} + options={courseOptions} + onChange={(event, value) => handleCourseChange(value)} + /> +
+ {task && ( +
+ ( + + )} + onChange={(event, value) => setTask(value)} + /> +
+ )} +
+ + + +
+ ); +}; + +export default TaskGroupListPage; diff --git a/frontend/utilities/courses.js b/frontend/utilities/courses.js new file mode 100644 index 00000000..518d9db4 --- /dev/null +++ b/frontend/utilities/courses.js @@ -0,0 +1,42 @@ +export const getCourses = (roles = []) => { + /** Gets all courses that a user belongs to with respect to the roles that they are in. + * + * @param roles An optional array of roles to validate whether a user belongs to a course + * with a given role. + * + * @returns An array of courses that the user belongs to: [{courseLabel: CSC108, courseId: 1}] + */ + const lowerCaseRoles = roles.map((role) => role.toLowerCase()); + const courses = JSON.parse(sessionStorage.getItem('roles')); + let courseOptions = []; + + courses.forEach((course) => { + // Filter for role specific courses + if (lowerCaseRoles && lowerCaseRoles.includes(course.role.toLowerCase())) { + courseOptions.push({ label: course.course_code, course_id: course.course_id }); + } else { + courseOptions.push({ label: course.course_code, course_id: course.course_id }); + } + }); + return courseOptions; +}; + +export const getCourseIdFromName = (courseName) => { + /** + * Gets the course id associated with courseName + * + * @param courseName: The name of the course whos id to find + * + * @returns An int representing the course id or null if none exist. + */ + + const courses = getCourses(); + console.log(courseName); + + courses.forEach((course) => { + if (courseName.toLowerCase() === course.label.toLowerCase()) { + return course.course_id; + } + }); + return null; +}; diff --git a/frontend/utilities/tasks.js b/frontend/utilities/tasks.js new file mode 100644 index 00000000..b5713501 --- /dev/null +++ b/frontend/utilities/tasks.js @@ -0,0 +1,22 @@ +import InstructorApi from '../src/api/instructor_api'; +export const getTasks = (courseId) => { + /** Gets all tasks in a course + * + * @param courseId The id of a course + * + * @return An array of objects containing the course code and id: [{courseCode: 'CSC108', courseId: '1'}] + */ + + let tasks = []; + InstructorApi.allTasks(courseId) + .then((res) => { + res.task.forEach((task) => { + tasks.push({ label: task.task }); + }); + }) + .catch((err) => { + console.log(err); + }); + + return tasks; +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..9cf0bffb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "IBS", + "lockfileVersion": 2, + "requires": true, + "packages": {} +}