diff --git a/packages/frontend/public/img/checkPencil.webp b/packages/frontend/public/img/checkPencil.webp new file mode 100644 index 0000000..b2f56f8 Binary files /dev/null and b/packages/frontend/public/img/checkPencil.webp differ diff --git a/packages/frontend/public/img/outPencil.webp b/packages/frontend/public/img/outPencil.webp new file mode 100644 index 0000000..7955ee8 Binary files /dev/null and b/packages/frontend/public/img/outPencil.webp differ diff --git a/packages/frontend/public/img/xmark.webp b/packages/frontend/public/img/xmark.webp new file mode 100644 index 0000000..1dcd98b Binary files /dev/null and b/packages/frontend/public/img/xmark.webp differ diff --git a/packages/frontend/src/components/practice/PracticeModal.tsx b/packages/frontend/src/components/practice/PracticeModal.tsx new file mode 100644 index 0000000..ef18567 --- /dev/null +++ b/packages/frontend/src/components/practice/PracticeModal.tsx @@ -0,0 +1,107 @@ +import { PracticeModalProps } from '@/types/PracticeModalProps'; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import styled from 'styled-components'; + +const ModalOverlay = styled.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.3); + display: flex; + justify-content: center; + align-items: center; +`; + +const ModalContent = styled.div` + display: flex; + flex-direction: column; + gap: 7px; + align-items: center; + background: #fff; + width: 417px; + padding: 20px; + border-radius: 8px; +`; +const XmarkImg = styled.img` + width: 16px; + height: 16px; + margin-left: auto; +`; + +const ModalBox = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex: 1; +`; + +const PencilImg = styled.img` + width: 110px; + height: 110px; +`; + +const Title = styled.h1` + font-weight: 600; + font-size: 25px; +`; + +const SubTitle = styled.h2` + font-weight: 600; + font-size: 16px; + color: #939393; +`; + +const Btn = styled.button<{ $color: string }>` + width: 150px; + height: 47px; + margin-top: 15px; + border-radius: 20px; + background-color: ${(props) => props.$color}; + color: #fff; + font-weight: bold; + font-size: 20px; +`; + +function PracticeModal({ + type, + setIsOpen, + img_path, + color, + title, + sub_title, + btn_text, +}: PracticeModalProps) { + const navigate = useNavigate(); + return ( + <> + + + + + + {title} + {sub_title} + { + if (type === 'check') { + navigate('/result'); + } else if (type === 'out') { + navigate('/main'); + } + }} + $color={color} + > + {btn_text} + + + + + + ); +} + +export default PracticeModal; diff --git a/packages/frontend/src/pages/Practice.tsx b/packages/frontend/src/pages/Practice.tsx index da77ae0..13e0322 100644 --- a/packages/frontend/src/pages/Practice.tsx +++ b/packages/frontend/src/pages/Practice.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; -import { useNavigate } from 'react-router-dom'; import { useAppDispatch, useAppSelector } from '../redux/hook'; import { selectChoice } from '../redux/_reducers/choices'; import { fetchGetProblem } from '@/redux/_reducers/problem'; import Loading from './Loading'; +import PracticeModal from '@/components/practice/PracticeModal'; const Wrapper = styled.div` position: relative; @@ -179,7 +179,6 @@ function Practice() { : setIsNextDisabled(false); }, [questionIndex, isLoading]); - const navigate = useNavigate(); const problemdata = data.questions[questionIndex]; const problem = problemdata.content; const choices = problemdata.choice; @@ -195,24 +194,74 @@ function Practice() { ); // 선택했던 답의 index 번호 const currentChoiceIndex = currentChoice?.choiceIndex; + + const extracAnswer = (s: string): string => { + // 정규 표현식을 사용하여 "()"와 같은 패턴을 찾습니다. + const pattern = /\((\w+)\)/; + const match = s.match(pattern); + if (match) { + // 정규 표현식에 일치하는 그룹을 추출하여 리턴합니다. + return match[1]; + } else { + return ''; + } + }; + // choice를 클릭했을때 문제 번호와 답, 답의 index 저장 const clickChoice = (answer: string, i: number) => { + const exAnswer = extracAnswer(answer); // 마지막 문제가 아닐때 if (questionIndex !== lastIndex) { - dispatch(selectChoice({ questionIndex, answer, choiceIndex: i })); + dispatch( + selectChoice({ questionIndex, answer: exAnswer, choiceIndex: i }), + ); setQuestionIndex((prev) => prev + 1); + } else { + dispatch( + selectChoice({ questionIndex, answer: exAnswer, choiceIndex: i }), + ); + setIsOpenCheckModal(true); } }; + // 마지막 문제 클릭시 나오는 모달창의 isopen + const [isOpenCheckModal, setIsOpenCheckModal] = useState(false); + // 홈 버튼을 눌렀을때 나오는 모달창의 isopen + const [isOpenOutModal, setIsOpenOutModal] = useState(false); + return ( {isLoading ? ( ) : ( <> + {isOpenCheckModal && ( + { + setIsOpenCheckModal(false); + }} + img_path="/img/checkPencil.webp" + color="#35DE73" + title="마지막 문제입니다." + sub_title="답안지를 제출하겠습니까?" + btn_text="제출하기" + /> + )} + {isOpenOutModal && ( + setIsOpenOutModal(false)} + img_path="/img/outPencil.webp" + color="#FF5573" + title="이대로 나갈 건가요?" + sub_title="풀었던 문제들은 저장되지 않아요." + btn_text="나가기" + /> + )} { - navigate('/main'); + setIsOpenOutModal(true); }} src={`/img/homewhite.webp`} /> diff --git a/packages/frontend/src/pages/practicedata.json b/packages/frontend/src/pages/practicedata.json deleted file mode 100644 index ff04193..0000000 --- a/packages/frontend/src/pages/practicedata.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "id": 7, - "title": "실전1", - "filename": "2024-02-11-토익실전문제 1~13회.xlsx", - "created_at": "2024-02-11T13:20:40.625Z", - "is_public": false, - "updated_at": null, - "questions": [ - { - "content": "The community center provides residents with a ------- of courses in arts and crafts.", - "choice": "(A) showing (B) prospect (C) variety (D) consequence", - "answer": "C", - "created_at": "2024-02-11T13:20:40.625Z", - "updated_at": null, - "translation": "지역 문화 센터에서는 주민들에게 다양한 미술 공예 강좌를 제공한다.", - "id": "b6182d3d-e293-47e8-b9b2-3d2786d5303c", - "question_number": 1, - "toeic_id": 7 - }, - { - "content": "Users of the Zwisher line of kitchen appliances will ------- from the many conveniences they provide.", - "choice": "(A) improvise (B) benefit (C) follow (D) transform", - "answer": "B", - "created_at": "2024-02-11T13:20:40.625Z", - "updated_at": null, - "translation": "Zwisher사 주방용품 라인 사용자들은 그것이 제공하는 많은 편리함으로부터 혜택을 볼 것이다.", - "id": "559357b5-78c3-4764-9765-c9c5f5b635f1", - "question_number": 2, - "toeic_id": 7 - }, - { - "content": "Children are not allowed to attend the festival on their own and must be ------- by an adult.", - "choice": "(A) appeared (B) required (C) succeeded (D) accompanied", - "answer": "D", - "created_at": "2024-02-11T13:20:40.625Z", - "updated_at": null, - "translation": "아이들은 혼자서 축제에 참여하는 것이 허락되지 않으며 반드시 어른을 동반해야 한다.", - "id": "f010d9d4-a5ac-4a16-a274-13dabfa8150e", - "question_number": 3, - "toeic_id": 7 - }, - { - "content": "Participating customers will be asked to ------- what they think of the company’s products on a survey form.", - "choice": "(A) manage (B) demand (C) adopt (D) indicate", - "answer": "D", - "created_at": "2024-02-11T13:20:40.625Z", - "updated_at": null, - "translation": "참여하는 고객들은 그 회사 제품에 대해 어떻게 생각하는지를 설문지에 나타내도록 요청받을 것이다.", - "id": "142ec342-fe36-4464-ab0b-2687ee451185", - "question_number": 4, - "toeic_id": 7 - }, - { - "content": "The museum’s current ------- features displays of ancient artifacts discovered at a historical site in Turkey last year.", - "choice": "(A) audience (B) exhibition (C) subscription (D) announcement", - "answer": "B", - "created_at": "2024-02-11T13:20:40.625Z", - "updated_at": null, - "translation": "박물관의 현재 전시회는 지난해 터키에 있는 한 유적지에서 발견된 고대 유물들의 전시를 특징으로 한다.", - "id": "b844eea5-e691-4420-a7ff-f3a7c49983e8", - "question_number": 5, - "toeic_id": 7 - }, - { - "content": "Online companies have an ------- over traditional retail stores because they spend less on maintenance.", - "choice": "(A) admission (B) influence (C) advantage (D) experience", - "answer": "C", - "created_at": "2024-02-11T13:20:40.625Z", - "updated_at": null, - "translation": "온라인 회사들은 보수 관리에 더 적게 지출하므로 전통적인 소매상점보다 유리하다.", - "id": "2cec9c95-ba2d-49fa-966f-49eefcce0b44", - "question_number": 6, - "toeic_id": 7 - }, - { - "content": "Employees who wish to ------- how the new policy might affect them should consult their supervisors.", - "choice": "(A) enable (B) clarify (C) contain (D) inform", - "answer": "B", - "created_at": "2024-02-11T13:20:40.625Z", - "updated_at": null, - "translation": "새로운 정책이 그들에게 어떤 영향을 미칠지를 명확하게 하고자 하는 직원들은 상사에게 물어봐야 한다.", - "id": "dc9b880b-9edb-4d51-983d-890e695f668d", - "question_number": 7, - "toeic_id": 7 - }, - { - "content": "As part of a special -------, Stomps Gym is discounting its membership fee for new users.", - "choice": "(A) offer (B) notice (C) charge (D) warranty", - "answer": "A", - "created_at": "2024-02-11T13:20:40.625Z", - "updated_at": null, - "translation": "특별 제공의 일환으로, Stomps 체육관은 신규 이용자들에게 가입비를 할인해 주고 있다.", - "id": "eda5f60c-e079-46ff-89de-52b8a42d7037", - "question_number": 8, - "toeic_id": 7 - } - ] -} diff --git a/packages/frontend/src/types/PracticeModalProps.ts b/packages/frontend/src/types/PracticeModalProps.ts new file mode 100644 index 0000000..c96b138 --- /dev/null +++ b/packages/frontend/src/types/PracticeModalProps.ts @@ -0,0 +1,9 @@ +export type PracticeModalProps = { + type: 'check' | 'out'; + setIsOpen: () => void; + img_path: string; + color: string; + title: string; + sub_title: string; + btn_text: string; +};