diff --git a/public/assets/icons/info.png b/public/assets/icons/info.png new file mode 100644 index 0000000..e0626be Binary files /dev/null and b/public/assets/icons/info.png differ diff --git a/src/App.tsx b/src/App.tsx index c0ba75c..6f070c2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,7 @@ import Questionnaire from "pages/EditQuestionnaire/Questionnaire"; import Courses from "pages/Courses/Course"; import CourseEditor from "pages/Courses/CourseEditor"; import { loadCourseInstructorDataAndInstitutions } from "pages/Courses/CourseUtil"; +import StudentTasks from "./pages/StudentTasks/StudentTasks"; import TA from "pages/TA/TA"; import TAEditor from "pages/TA/TAEditor"; import { loadTAs } from "pages/TA/TAUtil"; @@ -47,6 +48,10 @@ function App() { { path: "login", element: }, { path: "logout", element: } /> }, { path: "edit-questionnaire", element: } /> }, + { + path: "student_tasks", + element: } leastPrivilegeRole={ROLE.STUDENT} />, + }, { path: "assignments/edit/:id/createteams", element: , @@ -274,4 +279,4 @@ function App() { return ; } -export default App; +export default App; \ No newline at end of file diff --git a/src/pages/StudentTasks/StudentTaskDetail.tsx b/src/pages/StudentTasks/StudentTaskDetail.tsx new file mode 100644 index 0000000..aff3053 --- /dev/null +++ b/src/pages/StudentTasks/StudentTaskDetail.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { useParams } from 'react-router-dom'; + +const StudentTaskDetail: React.FC = () => { + const { id } = useParams(); // To grab the ID from the URL + + return ( +
+

Assignment Title: {id}

+ {/* Details about the assignment could be fetched and displayed here */} +
+ ); +}; + +export default StudentTaskDetail; \ No newline at end of file diff --git a/src/pages/StudentTasks/StudentTasks.module.css b/src/pages/StudentTasks/StudentTasks.module.css new file mode 100644 index 0000000..165eb27 --- /dev/null +++ b/src/pages/StudentTasks/StudentTasks.module.css @@ -0,0 +1,210 @@ +/* Base container styling */ +.container { + font-family: Arial, sans-serif; + margin: 20px; +} + +/* Header 1 styling */ +h1 { + text-align: left; + padding-top: 5px; + padding-left: 15px; + margin-bottom: 0px; + padding-bottom: 0px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.428571429; + color: #333333; +} + +/* Table base styling */ +table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +/* Table, th, and td shared border styling */ +table, +th, +td { + border: 1px solid #ddd; +} + +/* Table header styling */ +th { + background-color: #f8f8f8; + color: #333; + text-align: left; + padding: 8px; +} + +/* Table data cell styling */ +td { + padding: 8px; + text-align: left; +} + +/* First child of th and td border styling */ +th:first-child, +td:first-child { + border-left: none; +} + +/* Last child of th and td border styling */ +th:last-child, +td:last-child { + border-right: none; +} + +/* Table row striping for even rows */ +tr:nth-child(even) { + background-color: #f2f2f2; +} + +/* Badge info icon and publishing rights checkbox styling */ +.badge-info-icon, +.publishing-rights-checkbox { + cursor: pointer; + margin-left: 5px; +} + +/* Checked state styling for publishing rights checkbox */ +.publishing-rights-checkbox:checked { + accent-color: #009688; +} + +/* Table header row font styling */ +thead tr { + font-weight: bold; +} + +/* Status indicator icons styling */ +.status-indicator { + color: #009688; + margin-left: 5px; +} + +/* Disabled state styling for checkboxes */ +input[type="checkbox"][disabled] { + opacity: 0.6; + cursor: not-allowed; +} + +/* Information icon styling in table */ +.info-icon { + font-style: normal; + color: #017bff; + cursor: help; +} + +/* Side information section styling */ +.side-info { + color: #333; + padding: 10px 0; +} + +/* Number styling in side information section */ +.side-info .number { + font-weight: bold; + color: #d9534f; /* Red color for the number badge */ +} + +/* New styles for the links based on the uploaded image */ +table td { + padding: 8px; /* Padding specified in your screenshot */ + max-width: 100px; /* Adjust the width as needed for your design */ + white-space: normal; + padding: 8px; + line-height: 1.428571429; + vertical-align: top; + border-top: 1px solid #ddd; +} + +/* Table data cell link styling */ +table td a { + color: #986633; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + padding: 0px 1px 2px; + display: block; + margin: 10px 0; + text-decoration: none; + font-weight: bold; + border-radius: 4px; + transition: box-shadow 0.2s ease-in-out, background-color 0.3s; +} + +/* Table data cell link hover and focus styling */ +table td a:hover, table td a:focus { + color: #337ab7; + text-decoration: underline; +} + +/* Page layout styling */ +.pageLayout { + display: flex; + margin: 16px; +} + +/* Sidebar styling */ +.sidebar { + width: 250px; + margin-right: 20px; + padding-top: 20px; +} + +/* Main content area styling */ +.mainContent { + flex-grow: 1; + overflow: hidden; +} + +/* Header below pageLayout styling */ +.header { + margin-bottom: 20px; +} + +/* Tasks table styling */ +.tasksTable { + width: 100%; +} + +/* Page assignments styling */ +.assignments-page { + font-family: 'Arial', sans-serif; +} + +/* Title in assignments page styling */ +.assignments-title { + color: #333; + text-align: left; + padding: 20px; + font-size: 24px; +} + +/* Footer section styling */ +.footer { + display: flex; + justify-content: center; + padding: 20px 0; +} + +/* Footer link styling */ +.footerLink { + margin: 0 10px; + color: #986633; + text-decoration: none; +} + +/* Footer link hover styling */ +.footerLink:hover { + color: #000000; + text-decoration: underline; +} + +/* Center checkbox in table data cell styling */ +.centerCheckbox { + text-align: center; + vertical-align: middle; +} diff --git a/src/pages/StudentTasks/StudentTasks.tsx b/src/pages/StudentTasks/StudentTasks.tsx new file mode 100644 index 0000000..38f9336 --- /dev/null +++ b/src/pages/StudentTasks/StudentTasks.tsx @@ -0,0 +1,142 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { useNavigate, Link } from 'react-router-dom'; +import { RootState } from '../../store/store'; +import useAPI from 'hooks/useAPI'; +import styles from './StudentTasks.module.css'; +import StudentTasksBox from './StudentTasksBox'; +import testData from './testData.json'; + +// Define the types for a single task and the associated course +type Task = { + id: number; + assignment: string; + course: string; + topic: string; + currentStage: string; + reviewGrade: string | { comment: string }; + badges: string | boolean; + stageDeadline: string; + publishingRights: boolean; +}; + +// Empty props for FC as no props are needed currently +type Props = {}; + +// Main functional component for Student Tasks +const StudentTasks: React.FC = () => { + // State and hooks initialization + const participantTasks = testData.participantTasks; + const currentUserId = testData.current_user_id; + const auth = useSelector((state: RootState) => state.authentication); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + // State to hold tasks + const [tasks, setTasks] = useState([]); + const exampleDuties = testData.dueTasks; + const taskRevisions = testData.revisions; + const studentsTeamedWith = testData.studentsTeamedWith; + + // Effect to process tasks on component mount or update + useEffect(() => { + if (participantTasks) { + const filteredParticipantTasks = participantTasks.filter(task => task.participant_id === currentUserId); + + const mergedTasks = filteredParticipantTasks.map(task => { + return { + id: task.id, + assignment: task.assignment, + course: task.course, + topic: task.topic || '-', + currentStage: task.current_stage || 'Pending', + reviewGrade: task.review_grade || 'N/A', + badges: task.badges || '', + stageDeadline: task.stage_deadline || 'No deadline', + publishingRights: task.publishing_rights || false + }; + }); + setTasks(mergedTasks); + } + }, [participantTasks]); + + // Callback to toggle publishing rights + const togglePublishingRights = useCallback((id: number) => { + setTasks(prevTasks => prevTasks.map(task => + task.id === id ? {...task, publishingRights: !task.publishingRights} : task + )); + }, []); + + const showBadges = tasks.some(task => task.badges); + + // Component render method + return ( +
+

Assignments

+
+ +
+ + + + + + + + + {showBadges && } + + + + + + {tasks.map((task) => ( + + + + + + + {showBadges && } + + + + ))} + +
AssignmentCourseTopicCurrent StageReview GradeBadges + Stage Deadline + Info + + Publishing Rights + Info +
{task.assignment}{task.course}{task.topic}{task.currentStage} + {task.reviewGrade === "N/A" ? "NA" : + Review Grade + } + {task.badges}{task.stageDeadline} + togglePublishingRights(task.id)} + /> +
+
+
+ + {/* Footer section */} +
+ Help + Papers on Expertiza +
+
+ ); +}; + +// Export the component for use in other parts of the application +export default StudentTasks; diff --git a/src/pages/StudentTasks/StudentTasksBox.module.css b/src/pages/StudentTasks/StudentTasksBox.module.css new file mode 100644 index 0000000..3db98a7 --- /dev/null +++ b/src/pages/StudentTasks/StudentTasksBox.module.css @@ -0,0 +1,120 @@ +/* Stripes for odd rows in a table within .student-tasks */ +.student-tasks .table-striped>tbody>tr:nth-of-type(odd)>td, +.student-tasks .table-striped>tbody>tr:nth-of-type(odd)>th { + background-color: #ffffff; + --bs-table-bg-type: none +} + +/* Stripes for even rows in a table within .student-tasks */ +.student-tasks .table-striped>tbody>tr:nth-of-type(even)>td, +.student-tasks .table-striped>tbody>tr:nth-of-type(even)>th { + background-color: #f2f2f2; + --bs-table-bg-type: none; +} + +/* Styling for task boxes */ +.taskbox { + padding: 5px; + margin-bottom: 39px; + border: 1px dashed #999999; + float: left; + font-size: 12px; + background: none repeat scroll 0pt 0pt #fafaea; + width: 100%; +} + +/* Styling for the task number indicator */ +.tasknum { + color: #FFFFFF; + background-color: #B73204; +} + +/* Styling for the revision number indicator */ +.revnum { + color: #FFFFFF; + background-color: #999999; +} + +/* Styling for notification links */ +.notification a { + color:#0066CC +} + +/* Layout for the page containing the student tasks */ +.pageLayout { + display: flex; + margin: 16px; +} + +/* Fixed-width sidebar styling */ +.sidebar { + width: 200px; /* Width of the sidebar */ + margin-right: 20px; /* Spacing between sidebar and main content */ +} + +/* Main content area that grows to fill the space */ +.mainContent { + flex-grow: 1; + overflow: hidden; /* In case the content is too wide */ +} + +/* Styling for the page header */ +.header { + margin-bottom: 20px; /* Space below the header */ +} + +/* Full-width table styling */ +.tasksTable { + width: 100%; /* Full width of the main content area */ + /* Add more styling for your table */ +} + +/* Margin styling for sections */ +.section { + margin-bottom: 20px; /* Space between sections */ +} + +/* Header styling within sections */ +.section-header { + font-size: 18px; /* Larger font size for visibility */ + font-weight: bold; /* Bold text for section headers */ + color: #333; /* Darker text for better readability */ + margin-bottom: 10px; /* Space below section header */ +} + +/* Styling for individual items within a section */ +.section-item { + margin-left: 20px; /* Indent for items in the list */ + margin-bottom: 5px; /* Space between items */ + color: #555; /* Slightly lighter text for items */ +} + +/* Standard badge styling */ +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0; + padding: 1; + background-color: #a52a2a; + color: white; +} + +/* Grey badge variant */ +.greyBadge{ + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0; + background-color: rgb(159, 156, 156); + color: white; +} diff --git a/src/pages/StudentTasks/StudentTasksBox.tsx b/src/pages/StudentTasks/StudentTasksBox.tsx new file mode 100644 index 0000000..6da727f --- /dev/null +++ b/src/pages/StudentTasks/StudentTasksBox.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import styles from './StudentTasksBox.module.css'; + + type DueTask = { + + }; + + type Revision = { + name: string; + dueDate: string; + }; + + //Students teamed with data structure + type StudentsTeamedWith = { + [semester: string]: string[]; + }; + + // interface for Student task box data + interface StudentTasksBoxProps { + dueTasks: DueTask[]; + revisions: Revision[]; + studentsTeamedWith: StudentsTeamedWith; + } + + const StudentTasksBox: React.FC = ({ dueTasks, revisions, studentsTeamedWith }) => { + + let totalStudents = 0; + for (const semester in studentsTeamedWith) { + totalStudents += studentsTeamedWith[semester].length; + } + + // Function to calculate the number of days left until the due date + const calculateDaysLeft = (dueDate: string) => { + const today = new Date(); + const due = new Date(dueDate); + const timeDiff = due.getTime() - today.getTime(); + const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24)); + return daysDiff > 0 ? daysDiff : 0; + }; + + // Find the revisions that have not done yet based on the due date + const revisedTasks = revisions.filter(revisions => calculateDaysLeft(revisions.dueDate) > 0); + +// HTML for student task box + return ( +
+
+ 0   + Tasks not yet started +
+ + {/* Revisions section (remains empty since revisions array is empty) */} +
+ {revisedTasks.length}   + Revisions + {revisedTasks.map((task, index) => { + const daysLeft = calculateDaysLeft(task.dueDate); + return ( +
+ » {task.name} ({daysLeft} day{daysLeft !== 1 ? 's' : ''} left) +
+ ); + })} +
+ + + {/* Students who have teamed with you section */} +
+ {totalStudents}   + Students who have teamed with you +
+ {Object.entries(studentsTeamedWith).map(([semester, students], index) => ( +
+ {semester} + {students.length} + {students.map((student, studentIndex) => ( +
+ » {student} +
+ ))} +
+ ))} +
+ ); +}; + +// Exporting the component for use in other parts of the application +export default StudentTasksBox; diff --git a/src/pages/StudentTasks/testData.json b/src/pages/StudentTasks/testData.json new file mode 100644 index 0000000..d068855 --- /dev/null +++ b/src/pages/StudentTasks/testData.json @@ -0,0 +1,170 @@ + +{ + "participantTasks": [ + { + "id": 1, + "assignment": "Program 1", + "course": "CSC 517", + "topic": "Ruby", + "current_stage": "In progress", + "review_grade": { + "comment": "40/40" + }, + "badges": false, + "stage_deadline": "2024-02-26 12:00:00 -0500", + "publishing_rights": true, + "participant_id": 1 + }, + { + "id": 2, + "assignment": "Program 2", + "course": "CSC 517", + "topic": "Rails", + "current_stage": "In progress", + "review_grade": "N/A", + "badges": false, + "stage_deadline": "2024-02-26 12:00:00 -0500", + "publishing_rights": false, + "participant_id": 2 + }, + { + "id": 3, + "assignment": "Program 3", + "course": "CSC 517", + "topic": "Git", + "current_stage": "In progress", + "review_grade": "N/A", + "badges": false, + "stage_deadline": "2024-02-26 12:00:00 -0500", + "publishing_rights": false, + "participant_id": 1 + }, + { + "id": 4, + "assignment": "Program 4", + "course": "CSC 517", + "topic": "Reimplementation Backend", + "current_stage": "In progress", + "review_grade": "120/120", + "badges": false, + "stage_deadline": "2024-02-26 12:00:00 -0500", + "publishing_rights": true, + "participant_id": 1 + }, + { + "id": 5, + "assignment": "Program 5", + "course": "CSC 517", + "topic": "Reimplementation Frontend", + "current_stage": "Finished", + "review_grade": "N/A", + "badges": false, + "stage_deadline": "2024-02-26 12:00:00 -0500", + "publishing_rights": false, + "participant_id": 1 + }, + { + "id": 6, + "assignment": "OSS Program 1", + "course": "CSC 517", + "topic": "Reimplementation Frontend", + "current_stage": "Finished", + "review_grade": "N/A", + "badges": false, + "stage_deadline": "2024-02-26 12:00:00 -0500", + "publishing_rights": false, + "participant_id": 1 + }, + { + "id": 7, + "assignment": "OSS Program 2", + "course": "CSC 517", + "topic": "Reimplementation Frontend", + "current_stage": "Finished", + "review_grade": "N/A", + "badges": false, + "stage_deadline": "2024-02-26 12:00:00 -0500", + "publishing_rights": false, + "participant_id": 1 + }, + { + "id": 8, + "assignment": "Program 5", + "course": "CSC 517", + "topic": "Reimplementation Frontend", + "current_stage": "Finished", + "review_grade": "N/A", + "badges": false, + "stage_deadline": "2024-02-26 12:00:00 -0500", + "publishing_rights": true, + "participant_id": 1 + }, + { + "id": 9, + "assignment": "Program 5", + "course": "CSC 517", + "topic": "Reimplementation Frontend", + "current_stage": "Finished", + "review_grade": "N/A", + "badges": false, + "stage_deadline": "2024-02-26 12:00:00 -0500", + "publishing_rights": true, + "participant_id": 1 + } + ], + "courses": [ + { + "id": 1, + "name": "Course3", + "directory_path": "/path/to/course_files", + "info": null, + "private": false, + "created_at": "2024-04-21T23:46:38.633Z", + "updated_at": "2024-04-21T23:46:38.633Z", + "instructor_id": 2, + "institution_id": 1 + }, + { + "id": 4, + "name": "Course4", + "directory_path": "/path/to/course_files", + "info": null, + "private": false, + "created_at": "2024-04-21T23:46:38.633Z", + "updated_at": "2024-04-21T23:46:38.633Z", + "instructor_id": 2, + "institution_id": 1 + } + + ], + + "dueTasks": [ + + ], + "revisions": [ + { + "name": "Program 1", + "dueDate": "2024-03-18" + }, + { + "name": "Program 2", + "dueDate": "2024-03-18" + }, + { + "name": "OSS project", + "dueDate": "2024-03-18" + }, + { + "name": "OSS project 2", + "dueDate": "2024-03-25" + }, + { + "name": "OSS project 3", + "dueDate": "2024-04-28" + } + ], + "studentsTeamedWith": { + "CSC/ECE 517, Fall 2024": ["teammate one", "teammate two", "teammate three", "teammate four","teammate five", "teammate six", "teammate seven"] + }, + "current_user_id": 1 +} \ No newline at end of file