feat: QuizSet Create / Read
This commit is contained in:
parent
45f63e4819
commit
561f91481c
@ -26,6 +26,8 @@ const PasswordChangePage = lazy(async () => await import('./pages/PasswordChange
|
|||||||
const LearningLecturesPage = lazy(async () => await import('./pages/LearningLecturesPage'));
|
const LearningLecturesPage = lazy(async () => await import('./pages/LearningLecturesPage'));
|
||||||
const LectureCreatePage = lazy(async () => await import('./pages/LectureCreatePage'));
|
const LectureCreatePage = lazy(async () => await import('./pages/LectureCreatePage'));
|
||||||
const LectureEditPage = lazy(async () => await import('./pages/LectureEditPage'));
|
const LectureEditPage = lazy(async () => await import('./pages/LectureEditPage'));
|
||||||
|
const QuizsetListPage = lazy(async () => await import('./pages/QuizsetListPage'));
|
||||||
|
const QuizsetWritePage = lazy(async () => await import('./pages/QuizsetWritePage'));
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -114,6 +116,19 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'quiz',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
element: <QuizsetListPage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'write',
|
||||||
|
element: <QuizsetWritePage />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
79
frontend/src/components/QuizForm/QuizCard.jsx
Normal file
79
frontend/src/components/QuizForm/QuizCard.jsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import styles from './QuizCard.module.css';
|
||||||
|
|
||||||
|
export default function QuizCard({ quiz, index, updateQuiz }) {
|
||||||
|
const [question, setQuestion] = useState(quiz.question || '');
|
||||||
|
const [answer, setAnswer] = useState(quiz.answer || '');
|
||||||
|
const [choices, setChoices] = useState(quiz.choices || []);
|
||||||
|
|
||||||
|
const handleChoiceChange = (num, content) => {
|
||||||
|
const updatedChoices = choices.map((choice) => (choice.num === num ? { ...choice, content } : choice));
|
||||||
|
setChoices(updatedChoices);
|
||||||
|
updateQuiz(index, { question, answer, choices: updatedChoices });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddChoice = () => {
|
||||||
|
if (choices.length < 4) {
|
||||||
|
const newChoice = { num: choices.length + 1, content: '' };
|
||||||
|
const updatedChoices = [...choices, newChoice];
|
||||||
|
setChoices(updatedChoices);
|
||||||
|
updateQuiz(index, { question, answer, choices: updatedChoices });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePopChoice = () => {
|
||||||
|
if (choices.length > 0) {
|
||||||
|
const updatedChoices = choices.slice(0, -1);
|
||||||
|
setChoices(updatedChoices);
|
||||||
|
updateQuiz(index, { question, answer, choices: updatedChoices });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.card}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={question}
|
||||||
|
onChange={(e) => {
|
||||||
|
setQuestion(e.target.value);
|
||||||
|
updateQuiz(index, { question: e.target.value, answer, choices });
|
||||||
|
}}
|
||||||
|
placeholder="질문 내용을 입력하세요"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={answer}
|
||||||
|
onChange={(e) => {
|
||||||
|
setAnswer(e.target.value);
|
||||||
|
updateQuiz(index, { question, answer: e.target.value, choices });
|
||||||
|
}}
|
||||||
|
placeholder="정답을 입력하세요"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<span>Tip: 선택지를 넣지 않는다면 단답형 문제가 됩니다</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleAddChoice}
|
||||||
|
>
|
||||||
|
선택지 추가하기
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handlePopChoice}
|
||||||
|
>
|
||||||
|
선택지 줄이기
|
||||||
|
</button>
|
||||||
|
{choices.map?.((choice, idx) => (
|
||||||
|
<div key={idx}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={choice.content}
|
||||||
|
onChange={(e) => handleChoiceChange(choice.num, e.target.value)}
|
||||||
|
placeholder={`Choice ${choice.num}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
8
frontend/src/components/QuizForm/QuizCard.module.css
Normal file
8
frontend/src/components/QuizForm/QuizCard.module.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.card {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 8px;
|
||||||
|
width: 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
52
frontend/src/components/QuizForm/QuizsetForm.jsx
Normal file
52
frontend/src/components/QuizForm/QuizsetForm.jsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import QuizCard from './QuizCard';
|
||||||
|
import styles from './QuizsetForm.module.css';
|
||||||
|
|
||||||
|
export default function QuizsetForm({ onSubmit }) {
|
||||||
|
// TODO: 디자인 만들기 및 스타일 적용
|
||||||
|
const [title, setTitle] = useState('');
|
||||||
|
const [quizzes, setQuizzes] = useState([]);
|
||||||
|
|
||||||
|
const handleAddQuiz = () => {
|
||||||
|
setQuizzes([...quizzes, { question: '', answer: '', choices: [] }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateQuiz = (index, updatedQuiz) => {
|
||||||
|
const updatedQuizzes = quizzes.map((quiz, i) => (i === index ? updatedQuiz : quiz));
|
||||||
|
setQuizzes(updatedQuizzes);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className={styles.form}
|
||||||
|
onSubmit={(e) => onSubmit(e, title, quizzes)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
placeholder="퀴즈셋 제목을 입력해주세요"
|
||||||
|
/>
|
||||||
|
{quizzes.map((quiz, index) => (
|
||||||
|
<QuizCard
|
||||||
|
key={index}
|
||||||
|
quiz={quiz}
|
||||||
|
index={index}
|
||||||
|
updateQuiz={updateQuiz}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleAddQuiz}
|
||||||
|
>
|
||||||
|
퀴즈 추가하기
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => onSubmit(e, title, quizzes)}
|
||||||
|
>
|
||||||
|
퀴즈셋 저장하기
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
6
frontend/src/components/QuizForm/QuizsetForm.module.css
Normal file
6
frontend/src/components/QuizForm/QuizsetForm.module.css
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
2
frontend/src/components/QuizForm/index.js
Normal file
2
frontend/src/components/QuizForm/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as QuizCard } from './QuizCard';
|
||||||
|
export { default as QuizsetForm } from './QuizsetForm';
|
10
frontend/src/hooks/api/useQuizsetDetail.js
Normal file
10
frontend/src/hooks/api/useQuizsetDetail.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
|
import instance from '../../utils/axios/instance';
|
||||||
|
import { API_URL } from '../../constants';
|
||||||
|
|
||||||
|
export function useQuizsetDetail(id) {
|
||||||
|
return useSuspenseQuery({
|
||||||
|
queryKey: ['quizset', id],
|
||||||
|
queryFn: () => instance.get(`${API_URL}/quiz/${id}`),
|
||||||
|
});
|
||||||
|
}
|
14
frontend/src/hooks/api/useQuizsetWrite.js
Normal file
14
frontend/src/hooks/api/useQuizsetWrite.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import instance from '../../utils/axios/instance';
|
||||||
|
import { API_URL } from '../../constants';
|
||||||
|
|
||||||
|
export function useQuizsetWrite() {
|
||||||
|
const quizsetWrite = (formData) => {
|
||||||
|
return instance.post(`${API_URL}/quiz`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { quizsetWrite };
|
||||||
|
}
|
10
frontend/src/hooks/api/useQuizsets.js
Normal file
10
frontend/src/hooks/api/useQuizsets.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import instance from '../../utils/axios/instance';
|
||||||
|
import { API_URL } from '../../constants';
|
||||||
|
|
||||||
|
export function useQuizsets() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['quizsetList'],
|
||||||
|
queryFn: () => instance.get(`${API_URL}/quiz`),
|
||||||
|
});
|
||||||
|
}
|
15
frontend/src/pages/QuizsetDetailPage/QuizsetDetailPage.jsx
Normal file
15
frontend/src/pages/QuizsetDetailPage/QuizsetDetailPage.jsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { useQuizsetDetail } from '../../hooks/api/useQuizsetDetail';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
// import useBoundStore from '../../store';
|
||||||
|
|
||||||
|
export default function QuizsetListPage() {
|
||||||
|
const { lectureId } = useParams();
|
||||||
|
const { data } = useQuizsetDetail(lectureId);
|
||||||
|
const quizset = data?.data ?? [];
|
||||||
|
console.log(quizset);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>디테일일{lectureId}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
1
frontend/src/pages/QuizsetDetailPage/index.js
Normal file
1
frontend/src/pages/QuizsetDetailPage/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './QuizsetDetailPage';
|
27
frontend/src/pages/QuizsetListPage/QuizsetListPage.jsx
Normal file
27
frontend/src/pages/QuizsetListPage/QuizsetListPage.jsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { ArticleLink } from '../../components/ArticleLink';
|
||||||
|
import ArticleBoard from '../../components/ArticleBoard/ArticleBoard';
|
||||||
|
import { useQuizsets } from '../../hooks/api/useQuizsets';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
// import useBoundStore from '../../store';
|
||||||
|
|
||||||
|
export default function QuizsetListPage() {
|
||||||
|
const { lectureId } = useParams();
|
||||||
|
const { data } = useQuizsets(lectureId);
|
||||||
|
const quizsets = data?.data ?? [];
|
||||||
|
// const userType = useBoundStore((state) => state.userType);
|
||||||
|
console.log(quizsets);
|
||||||
|
return (
|
||||||
|
<ArticleBoard
|
||||||
|
title="퀴즈 목록"
|
||||||
|
canCreate={true}
|
||||||
|
>
|
||||||
|
{quizsets.map?.((quizset) => (
|
||||||
|
<ArticleLink
|
||||||
|
key={`${quizset.quizSetId}`}
|
||||||
|
title={quizset.title}
|
||||||
|
to={`${quizset.quizSetId}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ArticleBoard>
|
||||||
|
);
|
||||||
|
}
|
1
frontend/src/pages/QuizsetListPage/index.js
Normal file
1
frontend/src/pages/QuizsetListPage/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './QuizsetListPage';
|
34
frontend/src/pages/QuizsetWritePage/QuizsetWritePage.jsx
Normal file
34
frontend/src/pages/QuizsetWritePage/QuizsetWritePage.jsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { QuizsetForm } from '../../components/QuizForm';
|
||||||
|
import { useQuizsetWrite } from '../../hooks/api/useQuizsetWrite';
|
||||||
|
|
||||||
|
export default function QuizsetWritePage() {
|
||||||
|
// TODO: lecture에서 이미지 전송 성공 후 해당 방법으로 이미지 파일 입력
|
||||||
|
const { quizsetWrite } = useQuizsetWrite();
|
||||||
|
const handleSubmit = async (e, title, quizzes) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log(title, quizzes);
|
||||||
|
const quizsetObject = {
|
||||||
|
title,
|
||||||
|
quizzes,
|
||||||
|
};
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('quizSetCreateRequest', new Blob([JSON.stringify(quizsetObject)], { type: 'application/json' }));
|
||||||
|
|
||||||
|
const response = await quizsetWrite(formData);
|
||||||
|
console.log(response);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>퀴즈 쓰기</div>
|
||||||
|
<QuizsetForm onSubmit={handleSubmit} />
|
||||||
|
<div>
|
||||||
|
<label>퀴즈 이미지</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".png, .jpg, .jpeg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
1
frontend/src/pages/QuizsetWritePage/index.js
Normal file
1
frontend/src/pages/QuizsetWritePage/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './QuizsetWritePage';
|
@ -15,6 +15,7 @@ instance.interceptors.request.use((config) => {
|
|||||||
const accessToken = useBoundStore.getState().token;
|
const accessToken = useBoundStore.getState().token;
|
||||||
|
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
|
console.log(accessToken);
|
||||||
config.headers.Authorization = `${accessToken}`;
|
config.headers.Authorization = `${accessToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user