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 LectureCreatePage = lazy(async () => await import('./pages/LectureCreatePage'));
|
||||
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([
|
||||
{
|
||||
@ -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;
|
||||
|
||||
if (accessToken) {
|
||||
console.log(accessToken);
|
||||
config.headers.Authorization = `${accessToken}`;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user