diff --git a/src/apis/learning.ts b/src/apis/learning.ts index e693be3..278e4fa 100644 --- a/src/apis/learning.ts +++ b/src/apis/learning.ts @@ -20,7 +20,7 @@ export const getQuizAndAnswer = async (languageTaleId: number) => { const access = LocalStorage.getItem("access"); const authAxios = getAuthAxios(access); const response = await authAxios.get(`${baseURL}/quiz/${languageTaleId}`); - return response.data; + return response.data.data; } catch (error) { throw error; } diff --git a/src/components/common/selectOption/SelectOptionList.tsx b/src/components/common/selectOption/SelectOptionList.tsx index 525fbc0..0020187 100644 --- a/src/components/common/selectOption/SelectOptionList.tsx +++ b/src/components/common/selectOption/SelectOptionList.tsx @@ -30,15 +30,15 @@ const SelectOptionList = (props: SelectListProps) => { } }; - const handleSelect = (value: string | number | null) => { - props.setter(value); + const handleSelect = (text: string | number | null) => { + props.setter(text); }; return ( - {props.selectList.map((element) => ( + {props.selectList.map((element, id) => ( { +const ChoiceQuiz = ({ + setter, + data, + isQuizGraded, +}: ChoiceQuizProps) => { const [select, setSelect] = useState(null); + useEffect(() => { + setter(null); + setSelect(null); + }, [data]); + const handleOptionChange = (text: string | number | null) => { setSelect(text as string); setter(text); - console.log(select); }; return ( - Love는 무슨 뜻인가요? + {data.question} ({ - text: option.text, - value: option.text, - state: - currentStep === 2 - ? option.text === select - ? option.text === "사랑" - ? "correct" - : "wrong" - : option.text === "사랑" - ? "correct" - : "default" - : option.text === select - ? "selected" - : "default", + selectList={data.choiceList.map((option, id) => ({ + text: option.sunji, + value: id + 1, + state: isQuizGraded + ? id + 1 === select + ? option.isCorrect === 1 + ? "wrong" + : "correct" + : option.isCorrect === 0 + ? "correct" + : "default" + : id + 1 === select + ? "selected" + : "default", }))} - setter={currentStep === 1 ? handleOptionChange : () => {}} + setter={handleOptionChange} /> diff --git a/src/components/learn/EssayQuiz.tsx b/src/components/learn/EssayQuiz.tsx index f02526e..f1af03a 100644 --- a/src/components/learn/EssayQuiz.tsx +++ b/src/components/learn/EssayQuiz.tsx @@ -1,9 +1,13 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import * as S from "./learn.styled"; import SelectBtn from "@components/common/selectOption/SelectBtn"; import { EssayQuizProps } from "@type/learning"; -const EssayQuiz = ({ setter, currentStep, answer }: EssayQuizProps) => { +const EssayQuiz = ({ + setter, + data, + isQuizGraded, +}: EssayQuizProps) => { const [inputValue, setInputValue] = useState(""); const handleChange = (event: React.ChangeEvent) => { @@ -12,11 +16,16 @@ const EssayQuiz = ({ setter, currentStep, answer }: EssayQuizProps) => { setter(trimmedValue); }; + useEffect(() => { + setter(null); + setInputValue(""); + }, [data]); + return ( - Love는 무슨 뜻인가요? + {data.question} - {currentStep !== 4 ? ( + {!isQuizGraded ? ( { <> {}} /> - {inputValue !== answer && ( + {inputValue !== "답" && ( {}} diff --git a/src/components/learn/SentenceQuiz.tsx b/src/components/learn/SentenceQuiz.tsx index 768b04b..1c7c37f 100644 --- a/src/components/learn/SentenceQuiz.tsx +++ b/src/components/learn/SentenceQuiz.tsx @@ -11,7 +11,7 @@ const shuffleArray = (array: Array<{ order: number; word: string }>) => { return shuffled; }; -const SentenceQuiz = ({ setter, currentStep, quizData }: SentenceQuizProps) => { +const SentenceQuiz = ({ setter, data, isQuizGraded }: SentenceQuizProps) => { const [selectedWords, setSelectedWords] = useState< Array<{ order: number; word: string }> >([]); @@ -20,8 +20,9 @@ const SentenceQuiz = ({ setter, currentStep, quizData }: SentenceQuizProps) => { >([]); useEffect(() => { - setShuffledWords(shuffleArray(quizData.sequenceList)); - }, []); + setSelectedWords([]); + setShuffledWords(shuffleArray(data.sequenceList)); + }, [data]); const handleWordClick = (wordObj: { order: number; word: string }) => { setSelectedWords((prev) => { @@ -35,9 +36,9 @@ const SentenceQuiz = ({ setter, currentStep, quizData }: SentenceQuizProps) => { }; const isCorrectOrder = () => { - if (selectedWords.length !== quizData.sequenceList.length) return false; + if (selectedWords.length !== data.sequenceList.length) return false; return selectedWords.every( - (wordObj, index) => wordObj.order === quizData.sequenceList[index].order + (wordObj, index) => wordObj.order === data.sequenceList[index].order ); }; @@ -49,9 +50,7 @@ const SentenceQuiz = ({ setter, currentStep, quizData }: SentenceQuizProps) => { return ( handleWordClick(wordObj) : () => {} - } + onClick={!isQuizGraded ? () => handleWordClick(wordObj) : () => {}} $isSelected={!!isSelected} > {wordObj.word} @@ -61,7 +60,7 @@ const SentenceQuiz = ({ setter, currentStep, quizData }: SentenceQuizProps) => { }; useEffect(() => { - if (selectedWords.length === quizData.sequenceList.length) { + if (selectedWords.length === data.sequenceList.length) { const completedSentence = selectedWords .map((wordObj) => wordObj.word) .join(" "); @@ -74,28 +73,26 @@ const SentenceQuiz = ({ setter, currentStep, quizData }: SentenceQuizProps) => { return ( - {currentStep !== 6 + {!isQuizGraded ? "문장을 배열해볼까요?" : isCorrectOrder() ? "정답입니다!" : "오답입니다"} - {quizData.question} + {data.question} {selectedWords.length === 0 ? "단어를 선택해주세요" : selectedWords.map((wordObj) => wordObj.word).join(" ")} - {currentStep === 6 && !isCorrectOrder() && ( + {isQuizGraded && !isCorrectOrder() && ( {"정답: " + - quizData.sequenceList.map((wordObj) => wordObj.word).join(" ")} + data.sequenceList.map((wordObj) => wordObj.word).join(" ")} )} diff --git a/src/components/learn/TaleLearn.tsx b/src/components/learn/TaleLearn.tsx index 5a766c9..3fbca2e 100644 --- a/src/components/learn/TaleLearn.tsx +++ b/src/components/learn/TaleLearn.tsx @@ -2,60 +2,113 @@ import FinishScreen from "@components/common/FinishScreen"; import NextBtn from "@components/common/NextBtn"; import ProgressBar from "@components/common/progressBar/ProgressBar"; import useLearning from "@hooks/useLearning"; -import LearnTaleKeys from "./LearnTaleKeys"; import ChoiceQuiz from "./ChoiceQuiz"; import EssayQuiz from "./EssayQuiz"; import SentenceQuiz from "./SentenceQuiz"; import { Wrapper } from "./learn.styled"; +import { + EssayQuestions, + MultipleChoices, + QuizData, + SentenceArrangements, +} from "@type/learning"; +import { QUIZ_STAGES, QuizType } from "@utils/constants/QuizStage"; + +interface TaleLearnProps { + quizData?: QuizData; +} + +const TaleLearn = ({ quizData }: TaleLearnProps) => { + if (!quizData) { + return; + } + + const totalSteps = quizData.totalSteps; -const TaleLearn = () => { const { setChoice, - choice, setSentence, - essay, setEssay, currentStep, isLastStep, isNextBtnActive, handleNextStep, - } = useLearning(); + getCurrentQuizType, + isQuizGraded, + } = useLearning(quizData); + + const progressPercentage = (currentStep / (totalSteps - 1)) * 100; - const progressPercentage = - currentStep ===0 - ? 5 - : (currentStep)*16.7 + const getCurrentQuiz = () => { + if ( + currentStep < + QUIZ_STAGES[QuizType.MultipleChoice].end(quizData.multipleChoices.length) + ) { + return quizData.multipleChoices[currentStep]; + } else if ( + currentStep < + QUIZ_STAGES[QuizType.Essay].end( + quizData.multipleChoices.length, + quizData.essayQuestions.length + ) + ) { + return quizData.essayQuestions[ + currentStep - quizData.multipleChoices.length + ]; + } else { + return quizData.sentenceArrangements[ + currentStep - + QUIZ_STAGES[QuizType.Essay].end( + quizData.multipleChoices.length, + quizData.essayQuestions.length + ) + ]; + } + }; + + const isMultipleChoice = (quiz: any): quiz is MultipleChoices => + "choiceList" in quiz; + const isEssayQuestion = (quiz: any): quiz is EssayQuestions => + "answer" in quiz; + const isSentenceArrangement = (quiz: any): quiz is SentenceArrangements => + "sequenceList" in quiz; + + const currentQuiz = getCurrentQuiz(); + const currentQuizType = getCurrentQuizType(currentStep); return ( <> - {currentStep < 7 ? ( + {currentStep < totalSteps ? ( - {currentStep === 0 && } - {(currentStep === 1 || currentStep === 2) && ( - - )} - {(currentStep === 3 || currentStep === 4) && choice && ( - - )} - {(currentStep === 5 || currentStep === 6) && choice && essay && ( - + {/* {currentStep === 0 && } */} + {currentQuiz && currentQuiz.question && ( + <> + {currentQuizType === QuizType.MultipleChoice && + isMultipleChoice(currentQuiz) && ( + + )} + {currentQuizType === QuizType.Essay && + isEssayQuestion(currentQuiz) && ( + + )} + {currentQuizType === QuizType.SentenceArrangement && + isSentenceArrangement(currentQuiz) && ( + + )} + )} ` font-size: 2rem; font-weight: 400; cursor: pointer; - transition: background-color 0.3s ease; `; export const ResultSentence = styled.div<{ $isCorrect: boolean }>` diff --git a/src/hooks/useLearning.ts b/src/hooks/useLearning.ts index 8eb64c6..eb53c33 100644 --- a/src/hooks/useLearning.ts +++ b/src/hooks/useLearning.ts @@ -1,42 +1,73 @@ +import { QuizData } from "@type/learning"; +import { QUIZ_STAGES, QuizType } from "@utils/constants/QuizStage"; import { useState, useEffect } from "react"; -const useLearning = () => { +const useLearning = (quizData: QuizData) => { const [choice, setChoice] = useState(null); const [essay, setEssay] = useState(null); const [sentence, setSentence] = useState(null); const [currentStep, setCurrentStep] = useState(0); - const [isStepCompleted, setIsStepCompleted] = useState([ - true, - false, - true, - false, - true, - false, - true, - ]); + const [isStepCompleted, setIsStepCompleted] = useState( + Array(quizData.totalSteps).fill(false) + ); + const [isQuizGraded, setIsQuizGraded] = useState(false); const handleNextStep = async () => { - setCurrentStep((prev) => prev + 1); + if (isQuizGraded) { + setCurrentStep((prev) => prev + 1); + setIsQuizGraded(false); + } else { + setIsQuizGraded(true); + } + }; + + const getCurrentQuizType = (step: number): QuizType => { + if ( + step < + QUIZ_STAGES[QuizType.MultipleChoice].end(quizData.multipleChoices.length) + ) { + return QuizType.MultipleChoice; + } else if ( + step < + QUIZ_STAGES[QuizType.Essay].end( + quizData.multipleChoices.length, + quizData.essayQuestions.length + ) + ) { + return QuizType.Essay; + } else { + return QuizType.SentenceArrangement; + } }; const checkStepCompletion = () => { const stepsCompletion = [...isStepCompleted]; stepsCompletion[0] = true; - stepsCompletion[0] = true; - stepsCompletion[1] = !!choice; - stepsCompletion[3] = !!essay; - stepsCompletion[5] = sentence !== null; - setIsStepCompleted(stepsCompletion); + const currentQuizType = getCurrentQuizType(currentStep); + + switch (currentQuizType) { + case QuizType.MultipleChoice: + stepsCompletion[currentStep] = !!choice; + break; + case QuizType.Essay: + stepsCompletion[currentStep] = !!essay; + break; + case QuizType.SentenceArrangement: + stepsCompletion[currentStep] = sentence !== null; + break; + default: + break; + } setIsStepCompleted(stepsCompletion); }; useEffect(() => { checkStepCompletion(); - }, [choice, essay, sentence]); + }, [choice, essay, sentence, currentStep]); - const isLastStep = currentStep === 6; + const isLastStep = currentStep === quizData.totalSteps - 1; const isNextBtnActive = isStepCompleted[currentStep]; return { @@ -52,6 +83,8 @@ const useLearning = () => { isLastStep, isNextBtnActive, handleNextStep, + getCurrentQuizType, + isQuizGraded, }; }; diff --git a/src/pages/TaleLearnPage.tsx b/src/pages/TaleLearnPage.tsx index 0a5da59..6ff4599 100644 --- a/src/pages/TaleLearnPage.tsx +++ b/src/pages/TaleLearnPage.tsx @@ -1,30 +1,40 @@ +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; import { getQuizAndAnswer } from "@apis/learning"; import Header from "@components/common/header/Header"; import TaleLearn from "@components/learn/TaleLearn"; -import { useEffect } from "react"; -import { useParams } from "react-router-dom"; const TaleLearnPage = () => { + const [quizData, setQuizData] = useState(); const { id } = useParams(); - const taleId = Number(id); + useEffect(() => { const getQuiz = async (taleId: number) => { - // const quiz = await createQuiz({ - // taleId: taleId, - // languageId: 1, - // learningLevel: "2000", - // }); - // console.log(quiz); const response = await getQuizAndAnswer(taleId); - console.log(response); + if (response) { + // 테스트용 + response.multipleChoices = response.multipleChoices.slice(0, 4); + response.essayQuestions = response.essayQuestions.slice(0, 4); + response.sentenceArrangements = response.sentenceArrangements.slice( + 0, + 2 + ); + + response.totalSteps = + response.multipleChoices.length + + response.essayQuestions.length + + response.sentenceArrangements.length; + setQuizData(response); + } }; - if (id) getQuiz(taleId); + if (id) getQuiz(Number(id)); }, [id]); + return ( <>
- + ); }; diff --git a/src/type/learning.d.ts b/src/type/learning.d.ts index 186366c..b320479 100644 --- a/src/type/learning.d.ts +++ b/src/type/learning.d.ts @@ -4,27 +4,36 @@ export interface LearningInfoData { learningLevel: "1000" | "2000" | "3000" | "4000" | "5000"; } -interface ChoiceQuizProps { - setter: (value: string | number | null) => void; - currentStep: number; +export interface QuizData { + essayQuestions: EssayQuestions[]; + multipleChoices: MultipleChoices[]; + sentenceArrangements: SentenceArrangements[]; + totalSteps: number; } - -interface EssayQuizProps { - setter: (value: string | number | null) => void; - currentStep: number; +export interface EssayQuestions { + question: string; answer: string; } -interface SentenceQuizProps { - setter: (value: string | number | null) => void; - currentStep: number; - quizData: SentenceQuizData; +export interface MultipleChoices { + question: string; + choiceList: Array<{ ids: number; sunji: string; isCorrect: number }>; } -interface SentenceQuizData { +export interface SentenceArrangements { question: string; sequenceList: Array<{ order: number; word: string }>; } -interface SpeakPracticeProps { +interface BaseQuizProps { + setter: (value: string | number | null) => void; + data: T; + isQuizGraded: boolean; +} + +export type ChoiceQuizProps = BaseQuizProps; +export type EssayQuizProps = BaseQuizProps; +export type SentenceQuizProps = BaseQuizProps; + +export interface SpeakPracticeProps { title: string; text1: string; text2: string; diff --git a/src/utils/constants/QuizStage.ts b/src/utils/constants/QuizStage.ts new file mode 100644 index 0000000..9fa30b7 --- /dev/null +++ b/src/utils/constants/QuizStage.ts @@ -0,0 +1,27 @@ +export enum QuizType { + MultipleChoice = "MULTIPLE_CHOICE", + Essay = "ESSAY", + SentenceArrangement = "SENTENCE_ARRANGEMENT", +} + +export const QUIZ_STAGES = { + [QuizType.MultipleChoice]: { + start: 0, + end: (multipleChoicesLength: number) => multipleChoicesLength, + }, + [QuizType.Essay]: { + start: (multipleChoicesLength: number) => multipleChoicesLength, + end: (multipleChoicesLength: number, essayQuestionsLength: number) => + multipleChoicesLength + essayQuestionsLength, + }, + [QuizType.SentenceArrangement]: { + start: (multipleChoicesLength: number, essayQuestionsLength: number) => + multipleChoicesLength + essayQuestionsLength, + end: ( + multipleChoicesLength: number, + essayQuestionsLength: number, + sentenceArrangementsLength: number + ) => + multipleChoicesLength + essayQuestionsLength + sentenceArrangementsLength, + }, +};