Skip to content

Commit

Permalink
Implemented create or edit answer component (#1119)
Browse files Browse the repository at this point in the history
* Implemented create or edit answer component

* Fixed code smell

* Moved typy to types

* Fixed comments

* Changed name component
  • Loading branch information
Yurenko authored Sep 10, 2023
1 parent ae49688 commit 41a73bb
Show file tree
Hide file tree
Showing 13 changed files with 480 additions and 9 deletions.
23 changes: 23 additions & 0 deletions src/components/question-editor/QuestionEditor.constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'
import NotesIcon from '@mui/icons-material/Notes'
import RuleIcon from '@mui/icons-material/Rule'

import { SizeEnum } from '~/types'

export const sortQuestions = [
{
icon: <CheckCircleOutlineIcon fontSize={SizeEnum.Small} />,
title: 'questionPage.questionType.multipleChoice',
value: 'multipleChoice'
},
{
icon: <NotesIcon fontSize={SizeEnum.Small} />,
title: 'questionPage.questionType.openAnswer',
value: 'openAnswer'
},
{
icon: <RuleIcon fontSize={SizeEnum.Small} />,
title: 'questionPage.questionType.oneAnswer',
value: 'oneAnswer'
}
]
66 changes: 66 additions & 0 deletions src/components/question-editor/QuestionEditor.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { commonShadow } from '~/styles/app-theme/custom-shadows'

export const styles = {
root: {
display: 'flex',
padding: '16px 24px',
flexDirection: 'column',
alignItems: 'flex-start',
borderRadius: '6px',
boxShadow: commonShadow
},
questionHeader: {
width: '100%',
display: 'flex',
justifyContent: 'space-between'
},
group: {
width: '100%'
},
options: {
display: 'flex',
alignItems: 'center',
gap: '16px'
},
divider: {
alignSelf: 'stretch',
mb: '24px'
},
inputItem: {
color: 'basic.black',
width: '100%',
mb: '8px',
'.MuiFormControlLabel-label': {
width: '100%'
}
},
answer: {
width: '100%',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
},
iconWrapper: {
display: 'flex',
padding: '8px',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '4px',
backgroundColor: 'basic.grey'
},
selectContainer: {
'.MuiOutlinedInput-notchedOutline': { border: 0 }
},
addRadio: (isEmptyAnswer: boolean) => ({
display: 'flex',
alignItems: 'center',
color: 'primary.600',
cursor: isEmptyAnswer ? 'auto' : 'pointer',
'& label': {
mr: '8px'
}
}),
addIcon: (isEmptyAnswer: boolean) => ({
color: isEmptyAnswer ? 'primary.300' : 'primary.700'
})
}
194 changes: 194 additions & 0 deletions src/components/question-editor/QuestionEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { ChangeEvent, MouseEvent, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Box from '@mui/material/Box'
import Divider from '@mui/material/Divider'
import IconButton from '@mui/material/IconButton'
import DeleteIcon from '@mui/icons-material/Delete'
import CloseIcon from '@mui/icons-material/Close'
import AddIcon from '@mui/icons-material/Add'
import Checkbox from '@mui/material/Checkbox'
import FormControlLabel from '@mui/material/FormControlLabel'
import FormGroup from '@mui/material/FormGroup'
import InputBase from '@mui/material/InputBase'
import Radio from '@mui/material/Radio'
import RadioGroup from '@mui/material/RadioGroup'

import AppTextField from '~/components/app-text-field/AppTextField'
import AppSelect from '~/components/app-select/AppSelect'

import { styles } from '~/components/question-editor/QuestionEditor.styles'
import { sortQuestions } from '~/components/question-editor/QuestionEditor.constants'
import { Answer, SizeEnum, TextFieldVariantEnum } from '~/types'

const QuestionEditor = () => {
const [questionType, setQuestionType] = useState(sortQuestions[0])
const [question, setQuestion] = useState<string>('')
const [answers, setAnswers] = useState<Answer[]>([])
const [answer, setAnswer] = useState<string>('')

const { t } = useTranslation()

const isMultipleChoice = questionType.value === sortQuestions[0].value
const isOpenAnswer = questionType.value === sortQuestions[1].value
const isSingleChoice = questionType.value === sortQuestions[2].value
const isEmptyAnswer = answers[answers.length - 1]?.text === ''

const setTypeValue = (value: string) => {
const questionOption = sortQuestions.find((item) => item.value === value)

setQuestionType(questionOption ?? sortQuestions[0])
}

const handleQuestion = (event: ChangeEvent<HTMLInputElement>) => {
setQuestion(event.target.value)
}

const sortOptions = sortQuestions.map(({ icon, title, value }) => ({
title: t(title),
value,
icon
}))

const handleOptionChange = (index: number, checked: boolean) => {
const updatedAnswers = [...answers]

if (isMultipleChoice) {
updatedAnswers[index].isCorrect = checked
} else if (isSingleChoice) {
updatedAnswers.forEach((answer, i) => {
answer.isCorrect = i === index
})
}

setAnswers(updatedAnswers)
}

const onChangeInput = (
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
index: number
) => {
const currentValue = event.target.value
setAnswers((prevAnswers) => {
const updatedAnswers = [...prevAnswers]
updatedAnswers[index] = {
...updatedAnswers[index],
text: currentValue
}
return updatedAnswers
})
}

const addNewOneAnswer = (event: MouseEvent<HTMLInputElement>) => {
event.preventDefault()
if (!isEmptyAnswer) {
setAnswers((prev) => [
...prev,
{
id: answers.length,
text: '',
isCorrect: false
}
])
}
}

const deleteRadioButton = (id: number) => {
const updatedAnswers = answers.filter((item) => item.id !== id)
setAnswers(updatedAnswers)
}

const handleTypeChange = (value: string) => {
setAnswers((prevAnswers) =>
prevAnswers.map((answer) => ({ ...answer, isCorrect: false }))
)
setTypeValue(value)
}

const handleAnswer = (event: ChangeEvent<HTMLInputElement>) =>
setAnswer(event.target.value)

const options = answers.map((item) => (
<Box key={item.id} sx={styles.answer}>
<FormControlLabel
checked={item.isCorrect}
control={isMultipleChoice ? <Checkbox /> : <Radio />}
label={
<InputBase
fullWidth
onChange={(e) => onChangeInput(e, item.id)}
placeholder={t('questionPage.writeYourAnswer')}
value={item.text}
/>
}
onChange={(_, checked) => handleOptionChange(item.id, checked)}
sx={styles.inputItem}
value={item.id}
/>
<IconButton onClick={() => deleteRadioButton(item.id)}>
<CloseIcon fontSize={SizeEnum.Small} />
</IconButton>
</Box>
))

return (
<Box sx={styles.root}>
<Box sx={styles.questionHeader}>
<Box sx={styles.options}>
<Box sx={styles.iconWrapper}>{questionType.icon}</Box>
<AppSelect
fields={sortOptions}
setValue={handleTypeChange}
sx={styles.selectContainer}
value={questionType.value}
/>
</Box>

<IconButton>
<DeleteIcon />
</IconButton>
</Box>

<Divider sx={styles.divider} />

<AppTextField
fullWidth
label={t('questionPage.question')}
onChange={handleQuestion}
value={question}
variant={TextFieldVariantEnum.Outlined}
/>

{isMultipleChoice && <FormGroup sx={styles.group}>{options}</FormGroup>}

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

{!isOpenAnswer && (
<Box onClick={addNewOneAnswer} sx={styles.addRadio(isEmptyAnswer)}>
<FormControlLabel
checked={false}
control={isMultipleChoice ? <Checkbox /> : <Radio />}
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={handleAnswer}
value={answer}
variant={TextFieldVariantEnum.Outlined}
/>
)}
</Box>
)
}

export default QuestionEditor
4 changes: 3 additions & 1 deletion src/constants/translations/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import myOffersPage from './my-offers-page.json'
import myResourcesPage from './my-resources-page.json'
import chatPage from './chat.json'
import lesson from './lesson.json'
import questionPage from './question-page.json'

const en = {
translations: {
Expand Down Expand Up @@ -72,7 +73,8 @@ const en = {
myOffersPage,
myResourcesPage,
chatPage,
lesson
lesson,
questionPage
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/constants/translations/en/question-page.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"question": "Question",
"answer": "Answer",
"questionType": {
"multipleChoice": "Multiple Choice",
"openAnswer": "Open Answer",
"oneAnswer": "One Answer"
},
"addNewOne": "Add new one",
"writeYourAnswer": "Write your answer"
}
4 changes: 3 additions & 1 deletion src/constants/translations/ua/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import myOffersPage from './my-offers-page.json'
import myResourcesPage from './my-resources-page.json'
import chatPage from './chat.json'
import lesson from './lesson.json'
import questionPage from './question-page.json'

const ua = {
translations: {
Expand All @@ -46,7 +47,8 @@ const ua = {
myOffersPage,
myResourcesPage,
chatPage,
lesson
lesson,
questionPage
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/constants/translations/ua/question-page.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"question": "Питання",
"answer": "Відповідь",
"questionType": {
"multipleChoice": "Множинний вибір",
"openAnswer": "Відкрита відповідь",
"oneAnswer": "Одна відповідь"
},
"addNewOne": "Добавити ще одне питання",
"writeYourAnswer": "Напишіть свою відповідь"
}
3 changes: 2 additions & 1 deletion src/types/common/enums/common.enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export enum TypographyVariantEnum {
}

export enum TextFieldVariantEnum {
Standard = 'standard'
Standard = 'standard',
Outlined = 'outlined'
}

export enum DrawerVariantEnum {
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from '~/types/attachment/attachment.index'
export * from '~/types/my-attachments/myAttachments.index'
export * from '~/types/quizzes/quizzes.index'
export * from '~/types/lesson/lesson.index'
export * from '~/types/questions/questions.index'
5 changes: 5 additions & 0 deletions src/types/questions/interfaces/questions.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Answer {
id: number
text: string
isCorrect: boolean
}
1 change: 1 addition & 0 deletions src/types/questions/questions.index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '~/types/questions/interfaces/questions.interface'
12 changes: 6 additions & 6 deletions src/types/quizzes/interfaces/quizzes.interface.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { RequestParams, CommonEntityFields, UserResponse } from '~/types'
import {
RequestParams,
CommonEntityFields,
UserResponse,
Answer
} from '~/types'

export interface QuizzesParams extends RequestParams {
title: string
}

export interface Answer {
text: string
isCorrect: boolean
}

export interface QuestionWithAnswers {
question: string
answers: Answer[]
Expand Down
Loading

0 comments on commit 41a73bb

Please sign in to comment.