From 9fc765ccc3e38df3e0947aba870fedf085295b02 Mon Sep 17 00:00:00 2001 From: SameepK Date: Thu, 3 Apr 2025 00:40:34 -0400 Subject: [PATCH 1/4] added the attendance page --- .../pages/Multiplechoice/matchingTable.scss | 217 ++++++++++++++++++ .../pages/Multiplechoice/matchingTable.tsx | 111 +++++++++ 2 files changed, 328 insertions(+) create mode 100644 devU-client/src/components/pages/Multiplechoice/matchingTable.scss create mode 100644 devU-client/src/components/pages/Multiplechoice/matchingTable.tsx diff --git a/devU-client/src/components/pages/Multiplechoice/matchingTable.scss b/devU-client/src/components/pages/Multiplechoice/matchingTable.scss new file mode 100644 index 00000000..ad89511a --- /dev/null +++ b/devU-client/src/components/pages/Multiplechoice/matchingTable.scss @@ -0,0 +1,217 @@ +@import 'variables'; + +.matching-table-container { + width: 100%; + margin: 1.5rem 0; + overflow-x: auto; + border-radius: 10px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + background: var(--background); +} + +.matching-table { + width: 100%; + border-collapse: collapse; + font-family: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif; + color: var(--text-color); + + thead { + background-color: var(--purple); + + th { + padding: 12px 16px; + text-align: left; + font-weight: 600; + color: white; + font-size: 1rem; + + &:first-child { + border-top-left-radius: 10px; + } + + &:last-child { + border-top-right-radius: 10px; + } + } + } + + tbody { + tr { + border-bottom: 1px solid var(--grey-lightest); + transition: background-color 0.2s ease; + + &:nth-child(even) { + background-color: var(--table-row-even); + } + + &:nth-child(odd) { + background-color: var(--table-row-odd); + } + + &:hover { + background-color: var(--list-item-background-hover); + } + + &:last-child { + border-bottom: none; + } + + &.correct { + background-color: rgba(48, 96, 37, 0.1); + } + + &.incorrect { + background-color: rgba(138, 38, 38, 0.1); + } + } + + td { + padding: 12px 16px; + vertical-align: middle; + } + } + + .prompt-column { + width: 45%; + } + + .response-column { + width: 35%; + } + + .status-column { + width: 20%; + } + + .prompt-cell { + font-weight: 500; + } + + .response-cell { + .answer-select { + width: 100%; + padding: 8px 12px; + border-radius: 5px; + border: 1px solid var(--input-field-label); + background-color: var(--input-field-background); + color: var(--text-color); + font-family: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif; + font-size: 0.95rem; + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23555555' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + background-size: 16px; + padding-right: 30px; + + &:focus { + outline: none; + border-color: var(--purple); + box-shadow: 0 0 0 2px rgba(82, 70, 138, 0.1); + } + } + + .selected-answer { + padding: 6px 10px; + background-color: var(--grey-lightest); + border-radius: 5px; + font-size: 0.95rem; + display: inline-block; + max-width: 100%; + word-break: break-word; + } + } + + .status-cell { + .status-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 50%; + margin-right: 8px; + font-weight: bold; + + &.correct { + background-color: rgba(48, 96, 37, 0.2); + color: var(--green); + } + + &.incorrect { + background-color: rgba(138, 38, 38, 0.2); + color: var(--red); + } + } + + .correct-answer { + display: block; + font-size: 0.85rem; + color: var(--text-color-secondary); + margin-top: 4px; + margin-left: 32px; + } + + .status-unanswered { + color: var(--text-color-secondary); + font-style: italic; + font-size: 0.9rem; + } + } +} + +/* Responsive adjustments */ +@media screen and (max-width: 768px) { + .matching-table { + thead th, tbody td { + padding: 10px 12px; + font-size: 0.9rem; + } + + .status-column, .status-cell { + display: none; + } + + .prompt-column { + width: 50%; + } + + .response-column { + width: 50%; + } + } +} + +/* Dark mode adjustments */ +body.dark-mode { + .matching-table { + .answer-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23c5c5c5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); + } + + tbody tr { + &.correct { + background-color: rgba(176, 238, 162, 0.1); + } + + &.incorrect { + background-color: rgba(255, 163, 163, 0.1); + } + } + + .status-cell { + .status-icon { + &.correct { + background-color: rgba(176, 238, 162, 0.2); + color: var(--green-lighter); + } + + &.incorrect { + background-color: rgba(255, 163, 163, 0.2); + color: var(--red-lighter); + } + } + } + } +} \ No newline at end of file diff --git a/devU-client/src/components/pages/Multiplechoice/matchingTable.tsx b/devU-client/src/components/pages/Multiplechoice/matchingTable.tsx new file mode 100644 index 00000000..5f9fdd1d --- /dev/null +++ b/devU-client/src/components/pages/Multiplechoice/matchingTable.tsx @@ -0,0 +1,111 @@ +import React, { useState, useEffect } from 'react'; +import './matchingTable.scss'; + +interface MatchItem { + id: string; + prompt: string; + options: string[]; + correctAnswer: string; + studentAnswer: string; +} + +interface MatchingTableProps { + items: MatchItem[]; + isInstructorView?: boolean; + onAnswerChange?: (id: string, answer: string) => void; + readOnly?: boolean; +} + +const MatchingTable: React.FC = ({ + items = [], + isInstructorView = false, + onAnswerChange, + readOnly = false +}) => { + const [matchItems, setMatchItems] = useState(items); + const allOptions = [...new Set(items.flatMap(item => item.options))]; + + useEffect(() => { + setMatchItems(items); + }, [items]); + + const handleSelectionChange = (id: string, selectedValue: string) => { + const updatedItems = matchItems.map(item => + item.id === id ? { ...item, studentAnswer: selectedValue } : item + ); + + setMatchItems(updatedItems); + + if (onAnswerChange) { + onAnswerChange(id, selectedValue); + } + }; + + const isCorrect = (item: MatchItem): boolean => { + return item.studentAnswer === item.correctAnswer; + }; + + const getStatusClass = (item: MatchItem): string => { + if (!isInstructorView || !item.studentAnswer) return ''; + return isCorrect(item) ? 'correct' : 'incorrect'; + }; + + return ( +
+ + + + + + {isInstructorView && } + + + + {matchItems.map((item) => ( + + + + {isInstructorView && ( + + )} + + ))} + +
PromptResponseStatus
{item.prompt} + {readOnly ? ( +
{item.studentAnswer || 'Not answered'}
+ ) : ( + + )} +
+ {item.studentAnswer ? ( + isCorrect(item) ? ( + + ) : ( + <> + + + Correct: {item.correctAnswer} + + + ) + ) : ( + Not answered + )} +
+
+ ); +}; + +export default MatchingTable; \ No newline at end of file From 64bccffa7304ac539eff1e196e9eddbff1a62e92 Mon Sep 17 00:00:00 2001 From: SameepK Date: Thu, 3 Apr 2025 00:41:02 -0400 Subject: [PATCH 2/4] added the attendance page. --- .../src/components/authenticatedRouter.tsx | 5 + .../pages/Attendence/attendancePage.scss | 138 ++++++++++++++++++ .../pages/Attendence/attendancePage.tsx | 97 ++++++++++++ .../forms/assignments/AddProblemModal.scss | 102 +++++++++++++ .../forms/assignments/AddProblemModal.tsx | 51 ++++--- 5 files changed, 371 insertions(+), 22 deletions(-) create mode 100644 devU-client/src/components/pages/Attendence/attendancePage.scss create mode 100644 devU-client/src/components/pages/Attendence/attendancePage.tsx create mode 100644 devU-client/src/components/pages/forms/assignments/AddProblemModal.scss diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index 8913409d..7927737f 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -22,6 +22,8 @@ import InstructorSubmissionspage from "./pages/submissions/InstructorSubmissions import SubmissionFileView from './pages/submissions/submissionFileView' import UserCoursesListPage from "./pages/listPages/courses/coursesListPage"; import JoinCoursePage from "./pages/listPages/joinwithcodepage"; +import attendancePage from './pages/Attendence/attendancePage' +import matchingTable from './pages/Multiplechoice/matchingTable' import WebhookURLForm from './pages/webhookURLForm' @@ -47,6 +49,9 @@ const AuthenticatedRouter = () => ( + + + diff --git a/devU-client/src/components/pages/Attendence/attendancePage.scss b/devU-client/src/components/pages/Attendence/attendancePage.scss new file mode 100644 index 00000000..6551a99d --- /dev/null +++ b/devU-client/src/components/pages/Attendence/attendancePage.scss @@ -0,0 +1,138 @@ +@import 'variables'; + + + + +h2 { + text-align: center; +} + +p { + text-align: center; + margin-left: 0; +} + +.pageWrapper { + padding: 0 100px; +} + + +.form { + background-color: $list-item-background; + border-radius: 20px; + padding: 30px; + width: 50%; + height: fit-content; +} + +.form>h2 { + margin-top: 0; +} + +.datepickerContainer { + width: 100%; + text-align: center; + display: flex; + justify-content: space-between; + gap: 10px; +} + +.header { + color: $text-color; + display: flex; + align-items: center; +} + + +.fileName { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: flex; + justify-content: center; + align-items: center; +} + +.fileRemovalButton { + background: none; + border: none; + color: $text-color; + cursor: pointer; + font-size: 0.9em; + + + &:hover { + text-decoration: dashed; + color: red; + } +} + +.formRow { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 15px; + + label { + font-weight: 600; + font-size: 0.9rem; + } + + input, select, textarea { + padding: 10px; + border-radius: 8px; + border: 1px solid #ccc; + font-family: inherit; + font-size: 0.95rem; + } +} + +.code-input-row { + display: flex; + gap: 10px; + + input { + flex: 1; + } + + .btnPrimary { + white-space: nowrap; + padding: 8px 15px; + font-weight: 600; + } +} + + +@media (max-width: 1115px) { + .pageWrapper { + padding: 0 50px; + } +} + +@media (max-width: 1015px) { + .pageWrapper { + padding: 0 100px; + } + + .flex { + flex-direction: column; + align-items: center; + } + + .form { + width: 100%; + } +} + +@media (max-width: 670px) { + .pageWrapper { + padding: 0 50px; + } +} + +@media (max-width: 560px) { + .datepickerContainer { + flex-direction: column; + gap: 15px; + } +} \ No newline at end of file diff --git a/devU-client/src/components/pages/Attendence/attendancePage.tsx b/devU-client/src/components/pages/Attendence/attendancePage.tsx new file mode 100644 index 00000000..30b51d78 --- /dev/null +++ b/devU-client/src/components/pages/Attendence/attendancePage.tsx @@ -0,0 +1,97 @@ +import React, { useState } from 'react'; +import Modal from 'components/shared/layouts/modal'; +import './attendancePage.scss'; // using same styles as assignment modal + +interface Props { + open: boolean; + onClose: () => void; +} + +const InstructorAttendanceModal: React.FC = ({ open, onClose }) => { + const [course, setCourse] = useState(''); + const [date, setDate] = useState(''); + const [code, setCode] = useState(''); + const [duration, setDuration] = useState('15'); + const [description, setDescription] = useState(''); + + const handleGenerateCode = () => { + const randomCode = Math.random().toString(36).substring(2, 7).toUpperCase(); + setCode(randomCode); + }; + + const handleSubmit = () => { + const attendanceData = { course, date, code, duration, description }; + console.log('Submitting attendance:', attendanceData); + onClose(); // close modal after submit + }; + + return ( + +
+
+ + +
+ +
+ + setDate(e.target.value)} + required + /> +
+ +
+ +
+ setCode(e.target.value.toUpperCase())} + placeholder="Enter or generate a code" + required + /> + +
+
+ +
+ + setDuration(e.target.value)} + required + /> +
+ +
+ +