Skip to content

Commit

Permalink
refactor(quiz): decomposing question form into multiple components
Browse files Browse the repository at this point in the history
  • Loading branch information
ManhLinhVu-ext54629 committed Dec 6, 2024
1 parent 80ed333 commit 9f3fd6b
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 142 deletions.
136 changes: 136 additions & 0 deletions frontend/src/components/question/QuestionForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import type { QuizQuestion } from '../../model/quiz-question.ts'
import { type Accessor, type Component, createMemo, createSignal, For, Show } from 'solid-js'
import { preventDefault } from '../../helpers.ts'
import * as QuestionService from '../../services/QuizQuestionService.ts'
import { isMultipleAnswersCorrect, type MultipleAnswerResult } from '../../services/QuizQuestionService.ts'
import { transformObjectToArray } from '../../utils/transformObjectToArray.ts'
import { Explanation, QuestionExplanation } from './explanation/Explanation.tsx'
import { Feedback } from './feedback/Feedback.tsx'

export const QuestionForm = ({
id,
question,
answers,
explanations,
correctAnswers,
questionExplanation,
}: QuizQuestion) => {
const [selectedAnswer, setSelectedAnswer] = createSignal<number | null>(null)
const [selectedAnswers, setSelectedAnswers] = createSignal<{ [key: string]: boolean } | Record<string, boolean>>({})
const [isAnswerCorrect, setIsAnswerCorrect] = createSignal(false)
//const [setExplanation] = createSignal<string | ''>('')
const [explanationIdx, setExplanationIdx] = createSignal<number | null>(null)
const [answersRequiringFeedback, setAnswersRequiringFeedback] = createSignal<number[]>([])

const [submitted, setSubmitted] = createSignal(false)

const isMultiple = correctAnswers.length > 1

const submit = preventDefault(async () => {
const selectedAnswerIdx = selectedAnswer()
if (selectedAnswerIdx === null) return
QuestionService.isAnswerCorrect(id, selectedAnswerIdx).then(isCorrect => {
setSubmitted(true)
setIsAnswerCorrect(isCorrect)
//setExplanation(explanations[selectedAnswerIdx])
setExplanationIdx(selectedAnswerIdx)
})
})

const submitMultiple = preventDefault(async () => {
if (Object.keys(selectedAnswers()).length === 0) return

const payload = transformObjectToArray(selectedAnswers())

isMultipleAnswersCorrect(id, payload).then((result: MultipleAnswerResult) => {
setSubmitted(true)

setIsAnswerCorrect(result.questionAnsweredCorrectly)
setAnswersRequiringFeedback(result.answersRequiringFeedback)
})
})

const selectAnswer = (answerIdx: number) => () => {
setSelectedAnswer(answerIdx)
}

const handleCheckboxChange = (event: InputEvent) => {
const { name, checked } = event.target as HTMLInputElement
setSelectedAnswers(prevState => ({
...prevState,
[name]: checked,
}))
}

type AnswerProps = {
answer: string // Adjust type based on the actual answer object
idx: number
explanation: string
isFeedbackRequired: Accessor<boolean>
}

const Answer: Component<AnswerProps> = ({ answer, idx, explanation, isFeedbackRequired }) => {
const answerId: string = `answer-${idx}`

if (isMultiple) {
return (
<li class="answerOption">
<input
type={'checkbox'}
name={`${idx}`}
id={answerId}
value={answer}
checked={selectedAnswers()?.[idx]}
onInput={handleCheckboxChange}
/>
<label for={answerId}>
{answer}
<Show
when={submitted() && isFeedbackRequired()}
children={Explanation(false, explanation)}
keyed
/>
</label>
</li>
)
}

return (
<li>
<input type={'radio'} name={'answer'} id={answerId} value={answer} onClick={selectAnswer(idx)} />
<label for={answerId}>
{answer}
<Show
when={explanationIdx() === idx}
children={Explanation(isAnswerCorrect(), explanation)}
keyed
/>
</label>
</li>
)
}

return (
<form onSubmit={isMultiple ? submitMultiple : submit}>
<h1>{question}</h1>
<ul>
<For each={answers}>
{(answer, idx) => {
const isFeedbackRequired = createMemo(() => answersRequiringFeedback().some(id => id === idx()))
return (
<Answer
answer={answer}
idx={idx()}
explanation={explanations[idx()]}
isFeedbackRequired={isFeedbackRequired}
/>
)
}}
</For>
</ul>
<input type="submit" value={'Submit'} />
<Show when={submitted()} children={Feedback(isAnswerCorrect())} keyed />
<Show when={submitted()} children={QuestionExplanation(questionExplanation)} />
</form>
)
}
14 changes: 14 additions & 0 deletions frontend/src/components/question/explanation/Explanation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const Explanation = (correct: boolean, explanation: string) => {
return (
<span>
{' '}
<span class={correct ? 'greenSpan' : 'redSpan'}>{correct ? 'Correct!' : 'Incorrect!'}</span> <br />
{'Explanation: '}
<span class="explanation">{explanation}</span>
</span>
)
}

export const QuestionExplanation = (questionExplanation: string) => (
<p class="questionExplanation">{questionExplanation}</p>
)
1 change: 1 addition & 0 deletions frontend/src/components/question/feedback/Feedback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const Feedback = (correct: boolean) => <p class="feedback">{correct ? 'Correct!' : 'Incorrect!'}</p>
25 changes: 25 additions & 0 deletions frontend/src/components/question/questionForm.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ul {
list-style-type: none;
padding: 0;
margin: 1em 0;

li input[type="radio"] {
margin-right: 0.5em;
}
}

.explanation {
color: dodgerblue;
}

.greenSpan {
color: green;
}

.redSpan {
color: red;
}

.debugBorder {
border: 1px solid red;
}
146 changes: 4 additions & 142 deletions frontend/src/quiz.tsx
Original file line number Diff line number Diff line change
@@ -1,149 +1,11 @@
import './quiz.scss'

import { type Component, createSignal, For, onMount, Show, type Accessor, createMemo } from 'solid-js'
import { createSignal, onMount, Show } from 'solid-js'
import { useParams } from '@solidjs/router'

import type { QuizQuestion } from 'model/quiz-question.ts'
import { preventDefault } from 'helpers.ts'
import { transformObjectToArray } from './utils/transformObjectToArray.ts'
import { getQuestion, isMultipleAnswersCorrect, type MultipleAnswerResult } from './services/QuizQuestionService.ts'
import * as QuestionService from './services/QuizQuestionService.ts'

const Feedback = (correct: boolean) => <p class="feedback">{correct ? 'Correct!' : 'Incorrect!'}</p>

const Explanation = (correct: boolean, explanation: string) => {
return (
<span>
{' '}
<span class={correct ? 'greenSpan' : 'redSpan'}>{correct ? 'Correct!' : 'Incorrect!'}</span> <br />
{'Explanation: '}
<span class="explanation">{explanation}</span>
</span>
)
}

const QuestionExplanation = (questionExplanation: string) => <p class="questionExplanation">{questionExplanation}</p>

const Question = ({ id, question, answers, explanations, correctAnswers, questionExplanation }: QuizQuestion) => {
const [selectedAnswer, setSelectedAnswer] = createSignal<number | null>(null)
const [selectedAnswers, setSelectedAnswers] = createSignal<{ [key: string]: boolean } | Record<string, boolean>>({})
const [isAnswerCorrect, setIsAnswerCorrect] = createSignal(false)
//const [setExplanation] = createSignal<string | ''>('')
const [explanationIdx, setExplanationIdx] = createSignal<number | null>(null)
const [answersRequiringFeedback, setAnswersRequiringFeedback] = createSignal<number[]>([])

const [submitted, setSubmitted] = createSignal(false)

const isMultiple = correctAnswers.length > 1

const submit = preventDefault(async () => {
const selectedAnswerIdx = selectedAnswer()
if (selectedAnswerIdx === null) return
QuestionService.isAnswerCorrect(id, selectedAnswerIdx).then(isCorrect => {
setSubmitted(true)
setIsAnswerCorrect(isCorrect)
//setExplanation(explanations[selectedAnswerIdx])
setExplanationIdx(selectedAnswerIdx)
})
})

const submitMultiple = preventDefault(async () => {
if (Object.keys(selectedAnswers()).length === 0) return

const payload = transformObjectToArray(selectedAnswers())

isMultipleAnswersCorrect(id, payload).then((result: MultipleAnswerResult) => {
setSubmitted(true)

setIsAnswerCorrect(result.questionAnsweredCorrectly)
setAnswersRequiringFeedback(result.answersRequiringFeedback)
})
})

const selectAnswer = (answerIdx: number) => () => {
setSelectedAnswer(answerIdx)
}

const handleCheckboxChange = (event: InputEvent) => {
const { name, checked } = event.target as HTMLInputElement
setSelectedAnswers(prevState => ({
...prevState,
[name]: checked,
}))
}

type AnswerProps = {
answer: string // Adjust type based on the actual answer object
idx: number
explanation: string
isFeedbackRequired: Accessor<boolean>
}

const Answer: Component<AnswerProps> = ({ answer, idx, explanation, isFeedbackRequired }) => {
const answerId: string = `answer-${idx}`

if (isMultiple) {
return (
<li class="answerOption">
<input
type={'checkbox'}
name={`${idx}`}
id={answerId}
value={answer}
checked={selectedAnswers()?.[idx]}
onInput={handleCheckboxChange}
/>
<label for={answerId}>
{answer}
<Show
when={submitted() && isFeedbackRequired()}
children={Explanation(false, explanation)}
keyed
/>
</label>
</li>
)
}

return (
<li>
<input type={'radio'} name={'answer'} id={answerId} value={answer} onClick={selectAnswer(idx)} />
<label for={answerId}>
{answer}
<Show
when={explanationIdx() === idx}
children={Explanation(isAnswerCorrect(), explanation)}
keyed
/>
</label>
</li>
)
}

return (
<form onSubmit={isMultiple ? submitMultiple : submit}>
<h1>{question}</h1>
<ul>
<For each={answers}>
{(answer, idx) => {
const isFeedbackRequired = createMemo(() => answersRequiringFeedback().some(id => id === idx()))
return (
<Answer
answer={answer}
idx={idx()}
explanation={explanations[idx()]}
isFeedbackRequired={isFeedbackRequired}
/>
)
}}
</For>
</ul>
<input type="submit" value={'Submit'} />
<Show when={submitted()} children={Feedback(isAnswerCorrect())} keyed />
<Show when={submitted()} children={QuestionExplanation(questionExplanation)} />
</form>
)
}
import { getQuestion } from './services/QuizQuestionService.ts'
import { QuestionForm } from './components/question/QuestionForm.tsx'

export const Quiz = () => {
const params = useParams()
Expand All @@ -153,5 +15,5 @@ export const Quiz = () => {

onMount(async () => setQuizQuestion(await getQuestion(questionId)))

return <Show when={quizQuestion()} children={Question} keyed />
return <Show when={quizQuestion()} children={QuestionForm} keyed />
}

0 comments on commit 9f3fd6b

Please sign in to comment.