From 649c2cea37a7bf1946cf147a925690e66217c3e1 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Tue, 1 Apr 2025 08:53:07 -0400 Subject: [PATCH 1/4] added swap radio<->cb behavior for buttons --- .../forms/assignments/multipleChoiceModal.tsx | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/multipleChoiceModal.tsx b/devU-client/src/components/pages/forms/assignments/multipleChoiceModal.tsx index 25f8d841..065612a0 100644 --- a/devU-client/src/components/pages/forms/assignments/multipleChoiceModal.tsx +++ b/devU-client/src/components/pages/forms/assignments/multipleChoiceModal.tsx @@ -19,10 +19,11 @@ const MultipleChoiceModal = ({ open, onClose }: Props) => { const [formData, setFormData] = useState({ title: '', maxScore: '', - correctAnswer: new Map(), + correctAnswer: new Map(), // did this with just a string in assignmentDetailPage.tsx but if it aint broke... that said if this does break reference that approach numCorrect: 0, regex: false }); + const [boxType, setBoxType] = useState("checkbox") const submittable = () => { if (!formData.title || !formData.maxScore || formData.numCorrect == 0) { return false } @@ -108,6 +109,31 @@ const MultipleChoiceModal = ({ open, onClose }: Props) => { }) } + const switchBoxType = (e: React.ChangeEvent) => { + const newState = e.target.checked + if (newState === true) { + setBoxType('radio') + } else { + setBoxType('checkbox') + } + console.log("peeing") + var inputs = document.getElementsByTagName('input') // reset correctAnswer when you do this + + for (var i = 0; i < inputs.length; i++) { + console.log(inputs[i]) + if (inputs[i].name == 'correct') { + inputs[i].checked = false; + } + } + setFormData(prevState => { + const correctAnswers = new Map (prevState.correctAnswer) + for (let key in correctAnswers){ + correctAnswers.set(key, false) + } + return { ...prevState, correctAnswer: correctAnswers, numCorrect: 0} + }) + } + return (
@@ -119,36 +145,38 @@ const MultipleChoiceModal = ({ open, onClose }: Props) => {
- - +
- +
- +
- +
- +
- +
+ +
Date: Tue, 1 Apr 2025 12:49:33 -0400 Subject: [PATCH 2/4] finished behavior for radio/checkbox, works on both pages where displayed --- .../listItems/assignmentProblemListItem.scss | 47 ++++++++-- .../listItems/assignmentProblemListItem.tsx | 26 +++++- .../assignments/assignmentDetailPage.tsx | 7 +- .../assignments/assignmentUpdatePage.tsx | 12 +-- .../forms/assignments/multipleChoiceModal.tsx | 87 +++++++++++-------- 5 files changed, 124 insertions(+), 55 deletions(-) diff --git a/devU-client/src/components/listItems/assignmentProblemListItem.scss b/devU-client/src/components/listItems/assignmentProblemListItem.scss index 901655fc..3fdca7e3 100644 --- a/devU-client/src/components/listItems/assignmentProblemListItem.scss +++ b/devU-client/src/components/listItems/assignmentProblemListItem.scss @@ -31,6 +31,7 @@ margin-bottom: 5px; cursor: pointer; width: fit-content; + input { position: absolute; opacity: 0; @@ -38,8 +39,7 @@ width: 0; } - - .checkbox { + .radio { position: absolute; transition: all .1s ease; top: 3px; @@ -52,27 +52,62 @@ margin-left: 0; } + - .checkbox::after { + .radio::after { width: 12px; height: 12px; border-radius: 100%; content: ""; position: absolute; display: none; + } + + .checkbox { + position: absolute; + transition: all .1s ease; + top: 3px; + left: 0; + height: 18px; + width: 18px; + background-color: $background; + border: 1px solid #999; + border-radius: 5px; + margin-left: 0; + } + + .checkboxCheck { + width: 15px; + height: 15px; + top: 2px; + left: 2px; + border-radius: 5px; + color: white; + position: absolute; } input:checked { + ~ .radio { + background-color: $primary; + border: 1px solid $primary; + } + ~ .checkbox{ - background-color: $primary; - border: 1px solid $primary; + background-color: $primary; + border: 1px solid $primary; } - ~ .checkbox::after { + + ~ .radio::after { display: block; border: 3px solid #fff; } + ~ .checkboxCheck{ + display: block; + } + } + &:last-of-type{ margin-bottom: 0; } diff --git a/devU-client/src/components/listItems/assignmentProblemListItem.tsx b/devU-client/src/components/listItems/assignmentProblemListItem.tsx index a2b811c8..33f9f166 100644 --- a/devU-client/src/components/listItems/assignmentProblemListItem.tsx +++ b/devU-client/src/components/listItems/assignmentProblemListItem.tsx @@ -4,6 +4,7 @@ import RequestService from 'services/request.service' import {AssignmentProblem, NonContainerAutoGrader} from 'devu-shared-modules' import styles from './assignmentProblemListItem.scss' +import FaIcon from 'components/shared/icons/faIcon' type Props = { problem: AssignmentProblem @@ -58,7 +59,7 @@ const AssignmentProblemListItem = ({problem, handleChange, disabled}: Props) => />
)} - else if(type == "MCQ") { + else if(type == "MCQ-mult") { const options = meta.options if (!options){ return
@@ -68,13 +69,32 @@ const AssignmentProblemListItem = ({problem, handleChange, disabled}: Props) =>

{problem.problemName}

{Object.keys(options).map((key : string) => ( ))} + ) + } else if(type == "MCQ-single") { + const options = meta.options + if (!options){ + return
+ } + return ( +
+

{problem.problemName}

+ {Object.keys(options).map((key : string) => ( + ))}
) } diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 2866b4bd..6e414363 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -103,7 +103,7 @@ const AssignmentDetailPage = () => { const value = e.target.value; const key = e.target.id; - if (type === 'checkbox') { // behavior for multiple choic questions + if (type === 'checkbox') { // behavior for multiple choice - multiple answer questions const newState = e.target.checked; setFormData(prevState => { @@ -120,12 +120,13 @@ const AssignmentDetailPage = () => { [key]: res }; }); - - } else { + } + else { setFormData(prevState => ({ ...prevState, [key]: value })); + console.log(formData) } }; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index 7173c189..5fc81159 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { ExpressValidationError, Assignment, AssignmentProblem, NonContainerAutoGrader, ContainerAutoGrader } from 'devu-shared-modules' +import { ExpressValidationError, Assignment, AssignmentProblem, NonContainerAutoGrader } from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' import { useHistory, useParams } from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -36,7 +36,7 @@ const AssignmentUpdatePage = () => { const [nonContainerAutograders, setNonContainerAutograders] = useState([]) const [containerAutoGraderModal, setContainerAutoGraderModal] = useState(false); const handleCloseContainerAutoGraderModal = () => setContainerAutoGraderModal(false); - const [containerAutograders, setContainerAutograders] = useState([]) + //const [containerAutograders, setContainerAutograders] = useState([]) @@ -131,7 +131,7 @@ const AssignmentUpdatePage = () => { useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { setFormData(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`).then((res) => { setAssignmentProblems(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders`).then((res) => { setNonContainerAutograders(res) })}, []) - useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`).then((res) => { setContainerAutograders(res) })}, []) + //useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`).then((res) => { setContainerAutograders(res) })}, []) /*useEffect(() => {RequestService.get(`/api/course/${courseId}/categories/`).then((res) => { setCategories(res) }).finally(convertToOptions)}, []) @@ -393,10 +393,10 @@ const AssignmentUpdatePage = () => { {nonContainerAutograders.length != 0 && nonContainerAutograders.map((nonContainerAutograder) => (
{nonContainerAutograder.question} - Non-Code Grader
))} - {containerAutograders.length != 0 && containerAutograders.map((containerAutograder) => (
+ {/* {containerAutograders.length != 0 && containerAutograders.map((containerAutograder) => (
{containerAutograder.autogradingImage} - - Code Grader
))} - {nonContainerAutograders.length == 0 && containerAutograders.length == 0 &&
No graders yet
} + Code Grader
))}*/} + {nonContainerAutograders.length == 0 &&
No graders yet
} {/*ADD BACK CAG.length===0 check here when CAG endpoint fixed */}

Problems

{assignmentProblems.length != 0 ? (assignmentProblems.map((problem) => ( diff --git a/devU-client/src/components/pages/forms/assignments/multipleChoiceModal.tsx b/devU-client/src/components/pages/forms/assignments/multipleChoiceModal.tsx index 065612a0..9f8292f2 100644 --- a/devU-client/src/components/pages/forms/assignments/multipleChoiceModal.tsx +++ b/devU-client/src/components/pages/forms/assignments/multipleChoiceModal.tsx @@ -17,30 +17,19 @@ const MultipleChoiceModal = ({ open, onClose }: Props) => { const { courseId } = useParams<{ courseId: string }>() const [options, setOptions] = useState({}) const [formData, setFormData] = useState({ + type: 'MCQ-mult', title: '', maxScore: '', - correctAnswer: new Map(), // did this with just a string in assignmentDetailPage.tsx but if it aint broke... that said if this does break reference that approach - numCorrect: 0, + correctAnswer: '', regex: false }); const [boxType, setBoxType] = useState("checkbox") const submittable = () => { - if (!formData.title || !formData.maxScore || formData.numCorrect == 0) { return false } + if (!formData.title || !formData.maxScore || formData.correctAnswer.length <= 0) { return false } else { return true } } - const createCorrectString = () => { - var correctString = '' - formData.correctAnswer.forEach((val, key ) => { - if (val === true){ - correctString += key - } - }) - correctString = correctString.split('').sort().join('') // makes selecting answers in any order correct - return(correctString) - } - const handleSubmit = () => { // early return if form not fully filled out if (!submittable) { return } @@ -54,10 +43,10 @@ const MultipleChoiceModal = ({ open, onClose }: Props) => { const graderFormData = { assignmentId: parseInt(assignmentId), question: formData.title, - correctString: createCorrectString(), + correctString: formData.correctAnswer, score: Number(formData.maxScore), isRegex: formData.regex, - metadata: {type: "MCQ", options: options} + metadata: {type: formData.type, options: options} } RequestService.post(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`, problemFormData) @@ -79,10 +68,23 @@ const MultipleChoiceModal = ({ open, onClose }: Props) => { setAlert({ autoDelete: false, type: 'error', message }) }) - console.log(graderFormData) - // close modal - onClose(); + closeModal() + } + + const resetData = () => { // reset data whenever question submitted/hits error, so data is not carried over, shows erroneous messages + setFormData({ + type: 'MCQ-mult', + title: '', + maxScore: '', + correctAnswer: '', + regex: false}) + setBoxType('checkbox') + } + + const closeModal = () => { + resetData() + onClose() } const handleChange = (e: React.ChangeEvent) => { @@ -101,44 +103,55 @@ const MultipleChoiceModal = ({ open, onClose }: Props) => { const handleCorrectAnswerChange = (e: React.ChangeEvent) => { const value = e.target.id - const newState = e.target.checked - setFormData(prevState => { - const correctAnswers = new Map (prevState.correctAnswer) - correctAnswers.set(value, newState) - return { ...prevState, correctAnswer: correctAnswers, numCorrect: formData.numCorrect + (newState ? 1 : -1)} - }) + if (formData.type === "MCQ-mult"){ + const checkState = e.target.checked + setFormData(prevState => { + const currentValue = prevState.correctAnswer || ""; + let res = ''; + if (checkState === true) { // if the box is checked, we want that as a new answer + res = currentValue + value + } else { // otherwise remove it from the string to make it incorrect + res = currentValue.replace(value, "") + } + res = res.split('').sort().join('') // makes selecting answers in any order correct + return { + ...prevState, + correctAnswer: res + }; + }); + } else if (formData.type === "MCQ-single") { + setFormData(prevState => ({...prevState, correctAnswer: value})) // only one answer is accepted + } else { + setAlert({ autoDelete: false, type: 'error', message: "Unknown Type" }) + } + } const switchBoxType = (e: React.ChangeEvent) => { const newState = e.target.checked - if (newState === true) { + if (newState === true) { // if box checked, then we're only accepting one answer, switch inputs to radio. setBoxType('radio') + setFormData(prevState => ({...prevState, type: "MCQ-single"})) } else { setBoxType('checkbox') + setFormData(prevState => ({...prevState, type: "MCQ-mult"})) } - console.log("peeing") + var inputs = document.getElementsByTagName('input') // reset correctAnswer when you do this for (var i = 0; i < inputs.length; i++) { - console.log(inputs[i]) if (inputs[i].name == 'correct') { inputs[i].checked = false; } } - setFormData(prevState => { - const correctAnswers = new Map (prevState.correctAnswer) - for (let key in correctAnswers){ - correctAnswers.set(key, false) - } - return { ...prevState, correctAnswer: correctAnswers, numCorrect: 0} - }) + setFormData(prevState => ({...prevState, correctAnswer: ''})) } return ( - +
-
From 5bd000e3c891329319ef4651f214f1d0dde1c55e Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Tue, 1 Apr 2025 14:48:23 -0400 Subject: [PATCH 3/4] fixed close modal errors and added a createdAt check to distinguish identically named NCAG questions until meta is added to assignmentproblem --- .../listItems/assignmentProblemListItem.tsx | 14 ++++++++------ .../pages/forms/assignments/codeProblemModal.tsx | 15 +++++++++++++-- .../forms/assignments/multipleChoiceModal.tsx | 7 ++++++- .../pages/forms/assignments/textProblemModal.tsx | 15 ++++++++++++++- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/devU-client/src/components/listItems/assignmentProblemListItem.tsx b/devU-client/src/components/listItems/assignmentProblemListItem.tsx index 33f9f166..08055de1 100644 --- a/devU-client/src/components/listItems/assignmentProblemListItem.tsx +++ b/devU-client/src/components/listItems/assignmentProblemListItem.tsx @@ -24,7 +24,7 @@ const AssignmentProblemListItem = ({problem, handleChange, disabled}: Props) => const getMeta = () => { if (ncags && ncags.length > 0){ - const ncag = ncags.find(ncag => ncag.question == problem.problemName) + const ncag = ncags.find(ncag => ((ncag.question == problem.problemName) && (ncag.createdAt === problem.createdAt))) // currently checking against createdAt since if two non-code questions have the same name they can be confused otherwise, can be removed once meta string added to assignemntproblem if (!ncag || !ncag.metadata) { return undefined } @@ -41,13 +41,15 @@ const AssignmentProblemListItem = ({problem, handleChange, disabled}: Props) => if (!meta || !meta.type){ return (
+
File Input Problems are not done yet pending backend changes! :D
) } - + const type = meta.type if (type == "Text") { return (
+
{type}

{problem.problemName}

id={problem.problemName} />
- )} - else if(type == "MCQ-mult") { + )} else if(type == "MCQ-mult") { const options = meta.options if (!options){ return
} return (
+
{type}

{problem.problemName}

{Object.keys(options).map((key : string) => (