From 2cfbe46f613a5f276737006893e9befa282b7ac5 Mon Sep 17 00:00:00 2001 From: drsuneamer Date: Fri, 24 Nov 2023 18:33:16 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20(=EA=B0=9D=EA=B4=80=EC=8B=9D)=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EB=93=B1=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/atoms/etc/Multiple.tsx | 28 ++-- .../molecules/CreateQuizCategory.tsx | 17 ++- .../organisms/createquiz/CreateMultiple.tsx | 69 ++++++++-- .../template/common/CreateModal.tsx | 33 +++-- .../template/createquiz/CreateQuizBody.tsx | 122 ++++++++++++++++-- frontend/src/recoil/newquiz.ts | 16 +++ 6 files changed, 235 insertions(+), 50 deletions(-) create mode 100644 frontend/src/recoil/newquiz.ts diff --git a/frontend/src/components/atoms/etc/Multiple.tsx b/frontend/src/components/atoms/etc/Multiple.tsx index 5539328..5745a0d 100644 --- a/frontend/src/components/atoms/etc/Multiple.tsx +++ b/frontend/src/components/atoms/etc/Multiple.tsx @@ -1,9 +1,18 @@ import styled from "@emotion/styled"; interface Props { - num: number; + num: string; + selected: string; func: (e: React.MouseEvent) => void; } + +const Div = styled.div` + .selected { + background-color: #d6e7e2; + color: #5bb8a1; + } +`; + const Button = styled.button` font-family: "Press Start 2P"; border-radius: 10px; @@ -14,10 +23,7 @@ const Button = styled.button` border: none; color: white; margin: 5px 10px 5px 0px; - &:focus { - background-color: #d6e7e2; - color: #5bb8a1; - } + &:hover { cursor: pointer; background-color: #25b097; @@ -31,8 +37,14 @@ const Button = styled.button` */ export default function Multiple(props: Props) { return ( - +
+ +
); } diff --git a/frontend/src/components/molecules/CreateQuizCategory.tsx b/frontend/src/components/molecules/CreateQuizCategory.tsx index 238b231..aa6cae8 100644 --- a/frontend/src/components/molecules/CreateQuizCategory.tsx +++ b/frontend/src/components/molecules/CreateQuizCategory.tsx @@ -1,8 +1,7 @@ -import typeState from "@/recoil/type"; -import categoryState from "@/recoil/category"; import styled from "@emotion/styled"; import { useRecoilState } from "recoil"; import Dropdown from "../atoms/etc/Dropdown"; +import newQuizState from "@/recoil/newquiz"; const Container = styled.div` font-family: "DungGeunMo"; @@ -26,9 +25,9 @@ const Text2 = styled.div` */ export default function CreateQuizCategory() { const types = [ - { value: "multiple", name: "객관식" }, - { value: "short-answer", name: "주관식" }, - { value: "essay", name: "서술형" }, + { value: "객관식", name: "객관식" }, + { value: "주관식", name: "주관식" }, + { value: "서술형", name: "서술형" }, ]; const categories = [ { value: "네트워크", name: "네트워크" }, @@ -38,14 +37,14 @@ export default function CreateQuizCategory() { { value: "앱", name: "앱" }, ]; - const [type, setType] = useRecoilState(typeState); + const [newQuiz, setNewQuiz] = useRecoilState(newQuizState); + const handleType = (e: React.ChangeEvent) => { - setType(e.target.value); + setNewQuiz((prev) => ({ ...prev, type: e.target.value })); }; - const [category, setCategory] = useRecoilState(categoryState); const handleCategory = (e: React.ChangeEvent) => { - setCategory(e.target.value); + setNewQuiz((prev) => ({ ...prev, category: e.target.value })); }; return ( diff --git a/frontend/src/components/organisms/createquiz/CreateMultiple.tsx b/frontend/src/components/organisms/createquiz/CreateMultiple.tsx index 7270082..5e811c3 100644 --- a/frontend/src/components/organisms/createquiz/CreateMultiple.tsx +++ b/frontend/src/components/organisms/createquiz/CreateMultiple.tsx @@ -2,6 +2,7 @@ import Multiple from "@/components/atoms/etc/Multiple"; import LargeInput from "@/components/atoms/inputs/LargeInput"; import SmallInput from "@/components/atoms/inputs/SmallInput"; import multipleAnswerState from "@/recoil/multipleanswer"; +import newQuizState from "@/recoil/newquiz"; import styled from "@emotion/styled"; import { useRecoilState } from "recoil"; @@ -28,47 +29,89 @@ const Title = styled.div` * @returns */ export default function CreateMultiple() { - const [answer, setAnswer] = useRecoilState(multipleAnswerState); + const [newQuiz, setNewQuiz] = useRecoilState(newQuizState); + // console.log("curr: ", newQuiz); const handleAnswer = (e: any) => { - setAnswer(e.target.value); + setNewQuiz((prev) => ({ ...prev, answer: e.target.value })); + }; + + const handleTitle = (e: any) => { + setNewQuiz((prev) => ({ ...prev, title: e.target.value })); + }; + + const handleQuestion = (e: any) => { + setNewQuiz((prev) => ({ ...prev, question: e.target.value })); + }; + + const handleOption = (v: any, index: number) => { + setNewQuiz((prevData) => { + return { + ...prevData, + options: [...prevData.options.slice(0, index), v, ...prevData.options.slice(index + 1)], + }; + }); + }; + + const handleDescription = (e: any) => { + setNewQuiz((prev) => ({ ...prev, description: e.target.value })); }; return ( 제목 - + 문제 - + 1 - + { + handleOption(e.target.value, 0); + }} + /> 2 - + { + handleOption(e.target.value, 1); + }} + /> 3 - + { + handleOption(e.target.value, 2); + }} + /> 4 - + { + handleOption(e.target.value, 3); + }} + /> 정답  - - - - + + + + 해설 - + ); diff --git a/frontend/src/components/template/common/CreateModal.tsx b/frontend/src/components/template/common/CreateModal.tsx index 63b2151..e6ec2c6 100644 --- a/frontend/src/components/template/common/CreateModal.tsx +++ b/frontend/src/components/template/common/CreateModal.tsx @@ -1,6 +1,9 @@ import styled from "@emotion/styled"; import router from "next/router"; import { useEffect } from "react"; +import api from "@/interceptor"; +import newQuizState from "@/recoil/newquiz"; +import { useRecoilState } from "recoil"; // 모달 창 뒷배경 export const SearchModalBox = styled.div` @@ -42,7 +45,11 @@ export const SearchModalContent = styled.div` font-size: 1.7em; } > div:nth-of-type(4) { - margin-top: 10px; + margin-top: 15px; + font-size: 1em; + } + > div:nth-of-type(5) { + margin-top: 5px; font-size: 1em; } > div { @@ -53,7 +60,7 @@ export const SearchModalContent = styled.div` border: none; width: 180px; margin-top: 20px; - padding: 0.4rem 0.6rem; + padding: 0.2rem 0.6rem; font-size: 0.9rem; margin-right: 10px; border-radius: 5px; @@ -88,9 +95,20 @@ export default function CreateModal(props: Props) { }; }, []); - const onSubmit = () => { - router.push("/"); - }; + const [newQuiz, setNewQuiz] = useRecoilState(newQuizState); + + const baseURL = process.env.NEXT_PUBLIC_API_BASE_URL; + + function onSubmit() { + api + .post(`${baseURL}/api/problem/pending`, newQuiz) + .then((res) => { + console.log(res); + }) + .catch((e) => { + console.log(e); + }); + } return ( @@ -98,9 +116,8 @@ export default function CreateModal(props: Props) {
X
알림!
제출한 문제는 관리자 승인 이후 등록됩니다.
-
- 과정 중 수정사항이 발견될 경우, 문제 내용이 변경될 수 있습니다. -
+
문제 내용과 카테고리가 정확히 등록되었는지 다시 한 번 확인해주세요.
+
과정 중 수정사항이 발견될 경우, 문제 내용이 변경될 수 있습니다.
diff --git a/frontend/src/components/template/createquiz/CreateQuizBody.tsx b/frontend/src/components/template/createquiz/CreateQuizBody.tsx index fc01baa..2cf7d41 100644 --- a/frontend/src/components/template/createquiz/CreateQuizBody.tsx +++ b/frontend/src/components/template/createquiz/CreateQuizBody.tsx @@ -1,14 +1,20 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import styled from "@emotion/styled"; import CreateQuizCategory from "../../molecules/CreateQuizCategory"; import { useRecoilState } from "recoil"; -import typeState from "../../../recoil/type"; -import categoryState from "@/recoil/category"; +import newQuizState from "@/recoil/newquiz"; import CreateMultiple from "@/components/organisms/createquiz/CreateMultiple"; import CreateShortAnswer from "@/components/organisms/createquiz/CreateShortAnswer"; import CreateEssay from "@/components/organisms/createquiz/CreateEssay"; import LargeButton from "@/components/atoms/buttons/LargeButton"; import CreateModal from "@/components/template/common/CreateModal"; +import api from "@/interceptor"; +import { useRouter } from "next/router"; +import swal from "sweetalert"; + +interface Props { + stat?: String; +} const Div = styled.div` color: white; @@ -18,32 +24,124 @@ const Div = styled.div` margin-left: 20px; margin-right: 20px; margin-bottom: 20px; + + .row { + display: flex; + justify-content: space-between; + } `; /** * 문제 작성 template: 객관식/주관식/서술형 컴포넌트 구분 * @returns */ -export default function CreateQuizBody() { - const [type, setType] = useRecoilState(typeState); +export default function CreateQuizBody(props: Props) { + const router = useRouter(); + const baseURL = process.env.NEXT_PUBLIC_API_BASE_URL; + const { id } = router.query; + const [newQuiz, setNewQuiz] = useRecoilState(newQuizState); + const [authorId, setAuthorId] = useState(0); - // 삭제 모달 관리 + useEffect(() => { + if (props.stat === "update" && id) { + api + .get(`${baseURL}/api/problem/pending/${id}`) + .then((res) => { + const problem = res.data.data.problem; + setAuthorId(res.data.data.problem.authorId); + setNewQuiz((prev) => ({ ...prev, title: problem.title })); + setNewQuiz((prev) => ({ ...prev, category: problem.category })); + setNewQuiz((prev) => ({ ...prev, answer: problem.answer })); + setNewQuiz((prev) => ({ ...prev, description: problem.description })); + setNewQuiz((prev) => ({ ...prev, type: problem.type })); + setNewQuiz((prev) => ({ ...prev, question: problem.question })); + setNewQuiz((prev) => ({ ...prev, options: problem.options })); + }) + .catch(() => { + // 없는 문제의 경우 404 + router.push("/error"); + }); + } + }, [props.stat, id]); + + // 모달 관리 const [showModal, setShowModal] = useState(false); - const clickModal = () => setShowModal(!showModal); + function clickModal() { + // 유효성 검사 + if (newQuiz.title === "") { + alert("제목을 입력하세요."); + } else if (newQuiz.question === "") { + alert("문제를 입력하세요."); + } else if (newQuiz.answer === "") { + alert("정답을 입력하세요."); + } else if (newQuiz.description === "") { + alert("해설을 입력하세요."); + } else { + setShowModal(!showModal); + } + } + + // 답안 제출 + function onSubmit() {} + + function onAccept() { + api + .post(`${baseURL}/api/problem/pending/authorization`, { + pendingProblemId: id, + title: newQuiz.title, + category: newQuiz.category, + type: newQuiz.type, + question: newQuiz.question, + options: newQuiz.options, + answer: newQuiz.answer, + description: newQuiz.description, + authorId: authorId, + }) + .then(() => { + router.push("/admin"); + }) + .catch(() => { + swal("일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요.", { + icon: "error", + }); + }); + } + + // 문제 반려 + function onReject() { + api + .put(`${baseURL}/api/problem/pending/authorization/${id}`) + .then((res) => { + router.push("/admin"); + }) + .catch((e) => { + swal("일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요.", { + icon: "error", + }); + }); + } return (
- {type == "multiple" ? ( + {newQuiz.type == "객관식" ? ( - ) : type === "short-answer" ? ( + ) : newQuiz.type === "주관식" ? ( ) : ( )} -
- -
+ + {props.stat === "update" ? ( +
+ + +
+ ) : ( +
+ +
+ )} {showModal && }
diff --git a/frontend/src/recoil/newquiz.ts b/frontend/src/recoil/newquiz.ts new file mode 100644 index 0000000..9815af3 --- /dev/null +++ b/frontend/src/recoil/newquiz.ts @@ -0,0 +1,16 @@ +import { atom } from "recoil"; + +const newQuizState = atom({ + key: "newQuizState ", + default: { + title: "", + category: "네트워크", + type: "객관식", + question: "", + options: ["", "", "", ""], + answer: "", + description: "", + }, +}); + +export default newQuizState; From 97ffd585ed553fb1925a29f111884256df23ac80 Mon Sep 17 00:00:00 2001 From: drsuneamer Date: Fri, 24 Nov 2023 18:33:48 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20(=EA=B0=9D=EA=B4=80=EC=8B=9D)=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=93=B1=EB=A1=9D=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=8A=B9=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/atoms/buttons/LargeButton.tsx | 27 ++++++++++++++++++- .../components/atoms/inputs/LargeInput.tsx | 11 ++++++-- .../components/atoms/inputs/SmallInput.tsx | 9 +++++-- frontend/src/pages/admin.tsx | 22 +++++++++++++++ frontend/src/pages/pending/[id].tsx | 11 ++++++++ 5 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 frontend/src/pages/admin.tsx create mode 100644 frontend/src/pages/pending/[id].tsx diff --git a/frontend/src/components/atoms/buttons/LargeButton.tsx b/frontend/src/components/atoms/buttons/LargeButton.tsx index 60272aa..ccf685c 100644 --- a/frontend/src/components/atoms/buttons/LargeButton.tsx +++ b/frontend/src/components/atoms/buttons/LargeButton.tsx @@ -2,8 +2,27 @@ import styled from "@emotion/styled"; interface Props { label: string; + type?: string; + onClick: () => void; } +const Div = styled.div` + .half { + width: 47vw; + } + + .red { + background-color: #ef7474; + width: 47vw; + &:hover { + background-color: #cc6e6e; + } + } + + .full { + } +`; + const Button = styled.button` margin-top: 10px; font-family: "DungGeunMo"; @@ -26,5 +45,11 @@ const Button = styled.button` * @returns */ export default function LargeButton(props: Props) { - return ; + return ( +
+ +
+ ); } diff --git a/frontend/src/components/atoms/inputs/LargeInput.tsx b/frontend/src/components/atoms/inputs/LargeInput.tsx index f4b26c0..bd2b310 100644 --- a/frontend/src/components/atoms/inputs/LargeInput.tsx +++ b/frontend/src/components/atoms/inputs/LargeInput.tsx @@ -1,4 +1,11 @@ import styled from "@emotion/styled"; +import { ChangeEvent } from "react"; + +interface Props { + value: string; + onChange: any; + // onChange: (event: ChangeEvent) => void; +} const Textarea = styled.textarea` border-radius: 10px; @@ -18,6 +25,6 @@ const Textarea = styled.textarea` } `; -export default function LargeInput() { - return