Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed the ability to save answer of question when type is Open Answer #3148

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
8 changes: 8 additions & 0 deletions src/components/question-editor/QuestionEditor.constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ListIcon from '@mui/icons-material/List'
import ShortTextIcon from '@mui/icons-material/ShortText'

import { SizeEnum, QuestionTypesEnum } from '~/types'
import { emptyField } from '~/utils/validations/common'

export const sortQuestions = [
{
Expand All @@ -28,3 +29,10 @@ export const determineQuestionType = (type: QuestionTypesEnum) => {
const isSingleChoice = type === sortQuestions[2].value
return { isMultipleChoice, isOpenAnswer, isSingleChoice }
}

export const validateOpenAnswer = (value: string) => {
if (value.length === 0) {
return emptyField({ value: value }) ?? ''
}
return ''
}
7 changes: 7 additions & 0 deletions src/components/question-editor/QuestionEditor.styles.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { theme } from '~/styles/app-theme/custom-mui.styles'
import { commonShadow } from '~/styles/app-theme/custom-shadows'

const divider = {
Expand Down Expand Up @@ -66,6 +67,12 @@ export const styles = {
justifyContent: 'space-between',
alignItems: 'center'
},
openAnswer: {
mt: theme.spacing(1)
},
emptyAnswer: {
color: 'primary.300'
},
iconWrapper: {
display: 'flex',
padding: '8px',
Expand Down
119 changes: 79 additions & 40 deletions src/components/question-editor/QuestionEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,20 @@ import Button from '~scss-components/button/Button'
import AppSelect from '~/components/app-select/AppSelect'
import {
determineQuestionType,
sortQuestions
sortQuestions,
validateOpenAnswer
} from '~/components/question-editor/QuestionEditor.constants'

import { styles } from '~/components/question-editor/QuestionEditor.styles'
import {
QuestionForm,
SizeEnum,
TextFieldVariantEnum,
QuestionFormAnswer
QuestionFormAnswer,
UseFormErrors
} from '~/types'
import { Typography } from '@mui/material'
import { spliceSx } from '~/utils/helper-functions'

interface QuestionEditorProps {
data: QuestionForm
Expand All @@ -46,8 +50,9 @@ interface QuestionEditorProps {
onSave?: () => Promise<void>
loading?: boolean
isQuizQuestion?: boolean
handleErrors: (key: keyof QuestionForm, error: string) => void
errors: UseFormErrors<QuestionForm>
}

const QuestionEditor: FC<QuestionEditorProps> = ({
data,
handleInputChange,
Expand All @@ -56,12 +61,14 @@ const QuestionEditor: FC<QuestionEditorProps> = ({
onEdit,
onSave,
loading,
isQuizQuestion
isQuizQuestion,
handleErrors,
errors
}) => {
const { t } = useTranslation()
const { openMenu, renderMenu, closeMenu } = useMenu()

const { type, text, answers, openAnswer } = data
const { type, text, answers } = data
const { isMultipleChoice, isOpenAnswer, isSingleChoice } =
determineQuestionType(type)

Expand Down Expand Up @@ -114,17 +121,17 @@ const QuestionEditor: FC<QuestionEditorProps> = ({

const addNewOneAnswer = (event: MouseEvent<HTMLInputElement>) => {
event.preventDefault()
if (!isEmptyAnswer) {
const addAnswer = [
...data.answers,
{
id: data.answers.length,
text: '',
isCorrect: false
}
]
handleNonInputValueChange('answers', addAnswer)
}
if (isEmptyAnswer) return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add braces here, please


handleNonInputValueChange('answers', [
...data.answers,
{
id: data.answers.length,
text: '',
isCorrect: isOpenAnswer,
isEditing: isOpenAnswer || undefined
ShadowOfTheSpace marked this conversation as resolved.
Show resolved Hide resolved
}
])
}

const deleteRadioButton = (id: number) => {
Expand Down Expand Up @@ -169,6 +176,31 @@ const QuestionEditor: FC<QuestionEditorProps> = ({
</Box>
))

const openAnswerOptions = answersWithId.map((item) => (
<Box key={item.id} sx={spliceSx(styles.answer, styles.openAnswer)}>
{item.isEditing ? (
<AppTextField
errorMsg={t(errors.answers)}
fullWidth
label={t('questionPage.answer')}
onBlur={() => handleBlur(item.id)}
onChange={(e) => onChangeInput(e, item.id)}
value={item.text}
variant={TextFieldVariantEnum.Outlined}
/>
) : (
<InputBase
fullWidth
onChange={(e) => onChangeInput(e, item.id)}
placeholder={t('questionPage.writeYourAnswer')}
value={item.text}
/>
)}
<IconButton onClick={() => deleteRadioButton(item.id)}>
<CloseIcon fontSize={SizeEnum.Small} />
</IconButton>
</Box>
))
const showMoreMenu = renderMenu(
<MenuItem onClick={onAction}>
<Box sx={styles.editIconWrapper}>
Expand All @@ -178,7 +210,18 @@ const QuestionEditor: FC<QuestionEditorProps> = ({
</MenuItem>
)

const isButtonVisible = text && (isOpenAnswer ? openAnswer : answers[0]?.text)
const handleBlur = (index: number) => {
const updatedAnswers = [...answers]
const { text } = updatedAnswers[index]

if (text.trim()) {
updatedAnswers[index].isEditing = false
handleNonInputValueChange('answers', updatedAnswers)
} else {
handleErrors('answers', validateOpenAnswer(text))
}
}
const isButtonVisible = text
ShadowOfTheSpace marked this conversation as resolved.
Show resolved Hide resolved

return (
<Box sx={styles.editorBlock}>
Expand Down Expand Up @@ -216,36 +259,32 @@ const QuestionEditor: FC<QuestionEditorProps> = ({

{isSingleChoice && <RadioGroup sx={styles.group}>{options}</RadioGroup>}

{!isOpenAnswer && (
<Box
data-testid='addNewAnswerBtn'
onClick={addNewOneAnswer}
sx={styles.addRadio(isEmptyAnswer)}
>
{isOpenAnswer && openAnswerOptions}

<Box
data-testid='addNewAnswerBtn'
onClick={addNewOneAnswer}
sx={
isOpenAnswer
? spliceSx(styles.addRadio(isEmptyAnswer), styles.openAnswer)
: styles.addRadio(isEmptyAnswer)
}
>
{isOpenAnswer ? (
<Typography color={isEmptyAnswer ? 'primary.300' : ''}>
ShadowOfTheSpace marked this conversation as resolved.
Show resolved Hide resolved
{t('questionPage.addNewOne')}
</Typography>
) : (
<FormControlLabel
checked={false}
control={isMultipleChoice ? <Checkbox /> : <RadioButton label='' />}
disabled={isEmptyAnswer}
label={t('questionPage.addNewOne')}
value={0}
/>
<AddIcon
fontSize={SizeEnum.Small}
sx={styles.addIcon(isEmptyAnswer)}
/>
</Box>
)}

{isOpenAnswer && (
<AppTextField
fullWidth
label={t('questionPage.answer')}
onChange={handleInputChange('openAnswer')}
value={openAnswer}
variant={TextFieldVariantEnum.Outlined}
/>
)}

)}
<AddIcon fontSize={SizeEnum.Small} sx={styles.addIcon(isEmptyAnswer)} />
</Box>
{onCancel && onSave && (
<>
<Divider sx={styles.buttonsDivider} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
useCallback,
FC,
useState,
useEffect,
Dispatch,
SetStateAction
} from 'react'
import React from 'react'
import Box from '@mui/material/Box'
ShadowOfTheSpace marked this conversation as resolved.
Show resolved Hide resolved

import { useModalContext } from '~/context/modal-context'
Expand All @@ -32,25 +25,25 @@ import { getErrorKey } from '~/utils/get-error-key'

interface CreateOrEditQuizQuestionProps {
question?: Question
setQuestions: Dispatch<SetStateAction<Question[]>>
setQuestions: React.Dispatch<React.SetStateAction<Question[]>>
onCancel: () => void
}

const CreateOrEditQuizQuestion: FC<CreateOrEditQuizQuestionProps> = ({
const CreateOrEditQuizQuestion: React.FC<CreateOrEditQuizQuestionProps> = ({
question,
setQuestions,
onCancel
}) => {
const dispatch = useAppDispatch()
const [isNewQuestion, setIsNewQuestion] = useState<boolean>(!!question)
const [isNewQuestion, setIsNewQuestion] = React.useState<boolean>(!!question)
const { openModal, closeModal } = useModalContext()

const createQuestionService = useCallback(
const createQuestionService = React.useCallback(
(data?: QuestionForm) => ResourceService.createQuestion(data),
[]
)

const updateQuestionService = useCallback(
const updateQuestionService = React.useCallback(
(params?: UpdateQuestionParams) => ResourceService.updateQuestion(params),
ShadowOfTheSpace marked this conversation as resolved.
Show resolved Hide resolved
[]
)
Expand Down Expand Up @@ -112,9 +105,16 @@ const CreateOrEditQuizQuestion: FC<CreateOrEditQuizQuestionProps> = ({
onResponseError
})

const { data, handleInputChange, handleNonInputValueChange, handleSubmit } =
useForm<QuestionForm>({ initialValues: initialValues(question) })

const {
data,
handleInputChange,
handleNonInputValueChange,
handleSubmit,
handleErrors,
errors
} = useForm<QuestionForm>({
initialValues: initialValues(question)
})
const onCloseCreation = () => {
closeModal()
onCancel()
Expand All @@ -127,13 +127,30 @@ const CreateOrEditQuizQuestion: FC<CreateOrEditQuizQuestionProps> = ({
closeModal()
}

const onCreateQuestion = async () => {
await createQuestion(data)
}
const onCreateQuestion = React.useCallback(async () => {
const updatedData = data.openAnswer
? {
...data,
answers: [
...data.answers,
{
text: data.openAnswer,
isCorrect: true,
id: data.answers.length
}
],
openAnswer: ''
}
: data

const onUpdateQuestion = async () => {
question && (await updateQuestion({ ...data, id: question._id }))
}
await createQuestion(updatedData)
}, [data, createQuestion])

const onUpdateQuestion = React.useCallback(async () => {
if (question) {
await updateQuestion({ ...data, id: question._id })
}
}, [question, data, updateQuestion])

const onOpenCreateQuestionModal = () => {
openModal({
Expand All @@ -147,15 +164,17 @@ const CreateOrEditQuizQuestion: FC<CreateOrEditQuizQuestionProps> = ({
})
}

useEffect(() => {
React.useEffect(() => {
!question && onOpenCreateQuestionModal()
ShadowOfTheSpace marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to not use eslint disablers in our project

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it isn't mine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but If you're working on the pr you should fix all of the issues coming with it. In my opinion at least...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to do my best

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just add useCallback to the onOpenCreateQuestionModal function declaration. This will stabilize the reference for that function, and useEffect will run only if question or onOpenCreateQuestionModal are changed.

}, [question])

return isNewQuestion ? (
<Box component={ComponentEnum.Form} onSubmit={handleSubmit}>
<QuestionEditor
data={data}
errors={errors}
handleErrors={handleErrors}
handleInputChange={handleInputChange}
handleNonInputValueChange={handleNonInputValueChange}
isQuizQuestion
Expand Down
3 changes: 2 additions & 1 deletion src/containers/quiz/question-answer/Answer.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export const styles = {
borderRadius: 2,
display: 'flex',
alignItems: 'center',
bgcolor
bgcolor,
height: isOpenAnswer ? '48px' : undefined
}
},
label: {
Expand Down
4 changes: 2 additions & 2 deletions src/containers/quiz/quiz-question/Question.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const QuizQuestion: FC<QuizQuestionProps> = ({
shouldShowAnswersCorrectness &&
(isCorrect ? <CheckIcon sx={iconStyles} /> : <CloseIcon sx={iconStyles} />)

const showCorrectAnswers = shouldShowCorrectAnswers && !isOpenAnswer
const showCorrectAnswers = shouldShowCorrectAnswers
ShadowOfTheSpace marked this conversation as resolved.
Show resolved Hide resolved

const correctAnswersList =
showCorrectAnswers &&
Expand Down Expand Up @@ -135,7 +135,7 @@ const QuizQuestion: FC<QuizQuestionProps> = ({

const answersBlock = isOpenAnswer ? (
<Answer
isCorrect
isCorrect={isCorrect}
ShadowOfTheSpace marked this conversation as resolved.
Show resolved Hide resolved
isEditable={isEditable}
ShadowOfTheSpace marked this conversation as resolved.
Show resolved Hide resolved
label={question.text}
onTextInputChange={handleInputChange}
Expand Down
6 changes: 5 additions & 1 deletion src/pages/create-or-edit-question/CreateOrEditQuestion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ const CreateOrEditQuestion = () => {
handleDataChange,
handleInputChange,
handleNonInputValueChange,
handleSubmit
handleSubmit,
handleErrors,
errors
} = useForm<CreateOrEditQuestionForm>({
initialValues: initialValues,
onSubmit: async () => {
Expand Down Expand Up @@ -203,6 +205,8 @@ const CreateOrEditQuestion = () => {
<Divider sx={styles.mainDivider} />
<QuestionEditor
data={data}
errors={errors}
handleErrors={handleErrors}
handleInputChange={handleInputChange}
handleNonInputValueChange={handleNonInputValueChange}
/>
Expand Down
Loading
Loading