Skip to content

Commit

Permalink
Merge pull request #33 from DakshChan/feature/IBS-35-upcomingInterviews
Browse files Browse the repository at this point in the history
[IBS-35] Upcoming Interviews Page
  • Loading branch information
ryu-kyu authored Jul 26, 2023
2 parents 94ed6a4 + 20652dc commit 0d30301
Show file tree
Hide file tree
Showing 15 changed files with 1,864 additions and 594 deletions.
108 changes: 66 additions & 42 deletions backend/module/interview/staff/change.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,74 @@ const client = require("../../../setup/db");
const helpers = require("../../../utilities/helpers");

router.put("/", (req, res) => {
if (res.locals["task"] === "") {
res.status(400).json({ message: "The task is missing or invalid." });
return;
}
if (res.locals["task"] === "") {
res.status(400).json({ message: "The task is missing or invalid." });
return;
}

let temp_set = helpers.interview_data_set_new(req.body, 3);
let set = temp_set["set"];
let set_data = temp_set["data"];
let set_data_id = temp_set["data_id"];
if (set === "") {
res.status(400).json({ message: "There is nothing to change." });
return;
}
let temp_set = helpers.interview_data_set_new(req.body, 3);
let set = temp_set["set"];
let set_data = temp_set["data"];
let set_data_id = temp_set["data_id"];
if (set === "") {
res.status(400).json({ message: "There is nothing to change." });
return;
}

let temp_filter = helpers.interview_data_filter(req.body, set_data_id, false, res.locals["username"]);
let filter = temp_filter["filter"];
let filter_data = temp_filter["data"];
if (filter === "") {
res.status(400).json({ message: "Pleaes add at least one condition." });
return;
}
let temp_filter = helpers.interview_data_filter(
req.body,
set_data_id,
false,
res.locals["username"]
);
let filter = temp_filter["filter"];
let filter_data = temp_filter["data"];
if (filter === "") {
res.status(400).json({ message: "Please add at least one condition." });
return;
}

if (req.body["force"] === true || req.body["force"] === "true") {
var sql_change = "UPDATE course_" + res.locals["course_id"] + ".interview SET" + set.substring(0, set.length - 1) + " WHERE task = ($1) AND host = ($2)" + filter;
} else {
var sql_change = "UPDATE course_" + res.locals["course_id"] + ".interview SET" + set.substring(0, set.length - 1) + " WHERE task = ($1) AND host = ($2) AND group_id IS NULL" + filter;
}
if (req.body["force"] === true || req.body["force"] === "true") {
var sql_change =
"UPDATE course_" +
res.locals["course_id"] +
".interview SET" +
set.substring(0, set.length - 1) +
" WHERE task = ($1) AND host = ($2)" +
filter;
} else {
var sql_change =
"UPDATE course_" +
res.locals["course_id"] +
".interview SET" +
set.substring(0, set.length - 1) +
" WHERE task = ($1) AND host = ($2) AND group_id IS NULL" +
filter;
}

client.query(sql_change, [res.locals["task"], res.locals["username"]].concat(set_data).concat(filter_data), (err, pgRes) => {
if (err) {
if (err.code === "23505") {
res.status(409).json({ message: "You have another interview at the same time." });
} else {
res.status(404).json({ message: "Unknown error." });
}
} else if (pgRes.rowCount <= 1) {
let message = pgRes.rowCount + " interview has been changed.";
res.status(200).json({ message: message });
} else {
let message = pgRes.rowCount + " interviews have been changed.";
res.status(200).json({ message: message });
}
});
client.query(
sql_change,
[res.locals["task"], res.locals["username"]]
.concat(set_data)
.concat(filter_data),
(err, pgRes) => {
if (err) {
if (err.code === "23505") {
res
.status(409)
.json({ message: "You have another interview at the same time." });
} else {
res.status(404).json({ message: "Unknown error." });
}
} else if (pgRes.rowCount <= 1) {
let message = pgRes.rowCount + " interview has been changed.";
res.status(200).json({ message: message });
} else {
let message = pgRes.rowCount + " interviews have been changed.";
res.status(200).json({ message: message });
}
}
);
});

})

module.exports = router;
module.exports = router;
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@fullcalendar/interaction": "^6.0.1",
"@fullcalendar/react": "^6.0.1",
"@mui/icons-material": "^5.11.9",
"@mui/lab": "^5.0.0-alpha.137",
"@mui/material": "^5.11.10",
"@mui/styles": "^5.11.12",
"@mui/utils": "5.12.0",
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import StudentInterviewPage from './components/Page/Student/StudentInterviewPage
import StudentTaskPage from './components/Page/Student/StudentTaskPage';
import StudentDetailsPage from './components/Page/Student/StudentDetailsPage';
import TaTaskPage from './components/Page/Ta/TaTaskPage';
import TaInterviewPage from './components/Page/Ta/TaInterviewPage';
import InterviewPage from './components/Page/Staff/InterviewPage';
import AdminPage from './components/Page/Admin/AdminPage';
import { ThemeProvider } from '@mui/material/styles';
import 'react-toastify/dist/ReactToastify.css';
Expand Down Expand Up @@ -70,7 +70,7 @@ function App() {
<Route path="/ta/course/:course_id/task" element={<TaTaskPage />}></Route>
<Route
path="/ta/course/:course_id/task/:task/interview"
element={<TaInterviewPage />}
element={<InterviewPage />}
></Route>
<Route
path="/instructor/course/:course_id/task"
Expand Down Expand Up @@ -100,6 +100,10 @@ function App() {
path="/instructor/course/:course_id/task/:task_id/mark"
element={<InstructorTaskMarksPage />}
/>
<Route
path="/instructor/course/:course_id/task/:task/interview"
element={<InterviewPage />}
></Route>
<Route
path="/instructor/course/:course_id/impersonate"
element={<InstructorImpersonate />}
Expand Down
178 changes: 177 additions & 1 deletion frontend/src/api/staff_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,175 @@ let deleteTaskGroup = async (courseId, taskGroupId) => {
}
};

const checkGroup = async (courseId, groupId) => {
const token = sessionStorage.getItem('token');
const role = findRoleInCourse(courseId);

const config = {
headers: { Authorization: `Bearer ${token}` }
};

if (role === 'instructor' || role === 'ta') {
const url = `${process.env.REACT_APP_API_URL}/${role}/course/${courseId}/group/check?group_id=${groupId}`;
try {
return await axios.get(url, config);
} catch (err) {
return err.response;
}
} else return null;
};

/**
* Get array of interview objects for staff
* @param courseId current course ID
* @param taskId current task ID
* @returns {Promise<axios.AxiosResponse<any>|*|null>}
*/
const getAllInterviews = async (courseId, taskId) => {
const token = sessionStorage.getItem('token');
const role = findRoleInCourse(courseId);

const axiosConfig = {
headers: { Authorization: `Bearer ${token}` }
};

if (role === 'instructor' || role === 'ta') {
const url = `${process.env.REACT_APP_API_URL}/${role}/course/${courseId}/interview/all?task=${taskId}`;
try {
return await axios.get(url, axiosConfig);
} catch (err) {
return err.response;
}
} else {
// insufficient access
return null;
}
};

/**
* Schedule an interview
* @param course_id current course ID
* @param curr_task current task ID
* @param length length specified in minutes
* @param time starting time
* @param location location: "Online" or room
* @returns {Promise<axios.AxiosResponse<any>|*>}
*/
let scheduleInterview = async (course_id, curr_task, length, time, location) => {
let token = sessionStorage.getItem('token');
const role = findRoleInCourse(course_id);

const data = {
task: curr_task,
length: length.toString(),
time: time.toString(),
location: location
};

if (role === 'instructor' || role === 'ta') {
const url = `${process.env.REACT_APP_API_URL}/${role}/course/${course_id}/interview/schedule`;
try {
return await axios.post(url, data, { headers: { Authorization: `Bearer ${token}` } });
} catch (err) {
return err.response;
}
} else return null;
};

/**
* Delete interview from course/task
* @param course_id current course ID
* @param curr_task current task ID
* @param id interview ID
* @returns {Promise<axios.AxiosResponse<any>|*|null>}
*/
let deleteInterview = async (course_id, curr_task, id) => {
let token = sessionStorage.getItem('token');
const role = findRoleInCourse(course_id);

let config = {
data: { task: curr_task, interview_id: id.toString() },
headers: { Authorization: `Bearer ${token}` }
};

if (role === 'instructor' || role === 'ta') {
const url = `${process.env.REACT_APP_API_URL}/${role}/course/${course_id}/interview/delete`;
try {
return await axios.delete(url, config);
} catch (err) {
return err.response;
}
} else return null;
};

/**
* Given the arguments (3rd and 4th have optional fields... i.e. can have null/undefined values),
* call backend API to change interviews from corresponding "old" fields to "new" (set_) fields.
* @param courseId current course id
* @param task current task id
* @param set_time new time for interview
* @param set_group_id new group id for interview
* @param set_length new length for interview
* @param set_location new location for interview
* @param set_note new note for interview
* @param set_cancelled new cancelled field for interview
* @param interview_id old interview id
* @param booked old booked field for interview
* @param time old (starting) time for interview
* @param date old (starting) date for interview
* @param group_id old group id for interview
* @param length old interview length
* @param location old location for interview
* @param note old note for interview
* @param cancelled old cancelled field for interview
* @returns {Promise<axios.AxiosResponse<any>|*>}
*/
const changeInterview = async (
courseId,
task,
{ set_time, set_group_id, set_length, set_location, set_note, set_cancelled },
{ interview_id, booked, time, date, group_id, length, location, note, cancelled }
) => {
let token = sessionStorage.getItem('token');

let config = {
data: {
task,
set_time,
set_group_id,
set_length,
set_location,
set_note,
set_cancelled,
interview_id,
booked,
time,
date,
group_id,
length,
location,
note,
cancelled
},
headers: { Authorization: `Bearer ${token}` }
};

// delete all undefined/null fields from config.data
config.data = Object.fromEntries(
Object.entries(config.data).filter(([key, value]) => value != null)
);

const role = findRoleInCourse(courseId);
if (role === 'instructor' || role === 'ta') {
const url = `${process.env.REACT_APP_API_URL}/${role}/course/${courseId}/interview/change`;
try {
return await axios.put(url, config);
} catch (err) {
return err.response;
}
} else return null;
};

const StaffApi = {
get_students_in_course,
getAllMarks,
Expand All @@ -374,7 +543,14 @@ const StaffApi = {

collectAllSubmissionsForTask,
collectOneSubmission,
downloadSubmissions
downloadSubmissions,

checkGroup,

getAllInterviews,
scheduleInterview,
deleteInterview,
changeInterview
};

export default StaffApi;
Loading

0 comments on commit 0d30301

Please sign in to comment.