feat: QuizsetEditPage 기능 완성 및 스타일 일부 추가

This commit is contained in:
정기영 2024-08-05 17:50:00 +09:00
parent 0a9639e7fb
commit 0980d5d0fc
8 changed files with 164 additions and 77 deletions

View File

@ -1,17 +1,13 @@
import { Link } from 'react-router-dom';
import styles from './ClassCard.module.css';
export default function ClassCard({ img, path, children }) {
export default function ClassCard({ path, children }) {
return (
<Link
to={path}
className={styles.card}
>
<img
src={img}
alt="강의 이미지"
className={styles.thumbnail}
/>
<div className={styles.thumbnail} />
<div>{children}</div>
</Link>
);

View File

@ -2,10 +2,12 @@ import { useState } from 'react';
import styles from './QuizCard.module.css';
export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
// TODO:
const [question, setQuestion] = useState(quiz.question || '');
const [answer, setAnswer] = useState(quiz.answer || '');
const [choices, setChoices] = useState(quiz.choices || []);
const [image, setImage] = useState(quiz.image || null);
const [imagePreview, setImagePreview] = useState(quiz.image || null);
const handleChoiceChange = (num, content) => {
const updatedChoices = choices.map((choice) => (choice.num === num ? { ...choice, content } : choice));
@ -34,15 +36,38 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
const file = e.target.files[0] ?? null;
setImage(file);
updateQuiz(quiz.id, { ...quiz, question, answer, choices, image: file });
if (file) {
const fileReader = new FileReader();
fileReader.onloadend = () => {
setImagePreview(fileReader.result);
};
fileReader.readAsDataURL(file);
} else {
setImagePreview(null);
}
};
return (
<div className={styles.card}>
<div className={styles.header}>
<span>퀴즈 생성 카드</span>
<span onClick={() => deleteQuiz(quiz.id)}>X</span> {/* id를 기반으로 삭제 */}
<button onClick={() => deleteQuiz(quiz.id)}>X</button>
</div>
<label>질문</label>
<label>퀴즈 이미지</label>
<input
type="file"
accept=".png, .jpg, .jpeg"
onChange={handleFileChange}
/>
{imagePreview && (
<img
src={imagePreview}
alt="Preview"
className={styles.imagePreview}
/>
)}
<label className={styles.label}>질문</label>
<input
type="text"
value={question}
@ -50,9 +75,10 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
setQuestion(e.target.value);
updateQuiz(quiz.id, { ...quiz, question: e.target.value, answer, choices, image });
}}
className={styles.input}
placeholder="질문 내용을 입력하세요"
/>
<label>정답</label>
<label className={styles.label}>정답</label>
<input
type="text"
value={answer}
@ -60,6 +86,7 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
setAnswer(e.target.value);
updateQuiz(quiz.id, { ...quiz, question, answer: e.target.value, choices, image });
}}
className={styles.input}
placeholder="정답을 입력하세요"
/>
<div>
@ -69,14 +96,14 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
<button
type="button"
onClick={handleAddChoice}
className={styles.button}
className={`${styles.button} ${styles.add}`}
>
선택지 추가하기
</button>
<button
type="button"
onClick={handlePopChoice}
className={styles.removeButton}
className={`${styles.button} ${styles.remove}`}
>
선택지 줄이기
</button>
@ -92,12 +119,6 @@ export default function QuizCard({ quiz, updateQuiz, deleteQuiz }) {
/>
</div>
))}
<label>퀴즈 이미지</label>
<input
type="file"
accept=".png, .jpg, .jpeg"
onChange={handleFileChange}
/>
</div>
);
}

View File

@ -1,7 +1,7 @@
.card {
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 10px 20px;
padding: 16px 12px;
width: 400px;
display: flex;
flex-direction: column;
@ -14,20 +14,46 @@
justify-content: space-between;
}
.label {
color: var(--text-color);
font-size: 14px;
line-height: 1.4;
font-weight: 400;
margin-bottom: 4px;
}
.imagePreview {
display: block;
max-width: 100%;
height: auto;
margin: 10px 0;
border-radius: 8px;
}
.input {
padding: 14px;
background: var(--background);
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 14px;
line-height: 1.4;
font-weight: 400;
}
.input::placeholder {
color: var(--text-color-tertiary);
}
.buttonsWrapper {
display: flex;
flex-direction: row;
gap: 8px;
}
.removeButton {
.button {
display: flex;
align-items: center;
padding: 12px 16px;
border: 1px solid var(--accent-color);
background-color: var(--accent-color);
color: var(--on-primary);
stroke: var(--on-primary);
font-size: 16px;
line-height: 1.4;
font-weight: 700;
@ -36,18 +62,16 @@
cursor: pointer;
}
.button {
display: flex;
align-items: center;
padding: 12px 16px;
.add {
border: 1px solid var(--primary-color);
background-color: var(--primary-color);
color: var(--on-primary);
stroke: var(--on-primary);
font-size: 16px;
line-height: 1.4;
font-weight: 700;
align-self: end;
border-radius: 8px;
cursor: pointer;
}
.remove {
border: 1px solid var(--blue100);
background-color: var(--blue100);
color: var(--info-color);
stroke: var(--info-color);
}

View File

@ -52,33 +52,37 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit, initialV
className={styles.form}
onSubmit={(e) => onSubmit(e, title, quizzes)}
>
<label className={styles.label}>퀴즈셋 제목</label>
<input
className={styles.input}
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="퀴즈셋 제목을 입력해주세요"
/>
{quizzes.map((quiz) => (
<QuizCard
key={quiz.id}
quiz={quiz}
updateQuiz={updateQuiz}
deleteQuiz={deleteQuiz}
/>
))}
<button
type="button"
onClick={handleAddQuiz}
className={styles.button}
>
퀴즈 추가하기
</button>
<div className={styles.grid}>
{quizzes.map((quiz) => (
<QuizCard
key={quiz.id}
quiz={quiz}
updateQuiz={updateQuiz}
deleteQuiz={deleteQuiz}
/>
))}
<button
type="button"
onClick={handleAddQuiz}
className={`${styles.button} ${styles.add}`}
>
퀴즈 추가하기
</button>
</div>
<button
type="submit"
className={styles.button}
className={styles.createButton}
>
<EditIcon />
<div>제출</div>
<EditIcon className={styles.edit} />
<div>퀴즈 생성하기</div>
</button>
</form>
</div>

View File

@ -32,43 +32,82 @@
font-weight: 800;
}
.heading {
font-size: 24px;
line-height: 1.2;
font-weight: 700;
}
.form {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
gap: 20px;
gap: 8px;
}
.removeButton {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
border: 1px solid var(--accent-color);
background-color: var(--accent-color);
color: var(--on-primary);
stroke: var(--on-primary);
.label {
color: var(--text-color);
font-size: 16px;
line-height: 1.4;
font-weight: 700;
align-self: end;
font-weight: 400;
margin-bottom: 8px;
}
.input {
padding: 20px;
background: var(--background);
border: 1px solid var(--border-color);
border-radius: 8px;
cursor: pointer;
font-size: 20px;
line-height: 1.2;
font-weight: 500;
}
.grid {
display: flex;
flex-direction: column;
gap: 20px;
align-items: start;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
}
.button {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
font-size: 16px;
line-height: 1.4;
font-weight: 700;
border-radius: 8px;
cursor: pointer;
}
.add {
border: 1px solid var(--primary-color);
background-color: var(--primary-color);
color: var(--on-primary);
stroke: var(--on-primary);
font-size: 16px;
line-height: 1.4;
font-weight: 700;
align-self: end;
}
.createButton {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-radius: 8px;
cursor: pointer;
font-size: 24px;
line-height: 1.2;
font-weight: 700;
border: 1px solid var(--accent-color);
background-color: var(--accent-color);
color: var(--on-primary);
stroke: var(--on-primary);
}
.edit {
font-size: 32px;
}

View File

@ -3,7 +3,7 @@ import { API_URL } from '../../constants';
export function useQuizsetDelete() {
const quizsetDelete = (quizsetId) => {
return instance.delete(`${API_URL}/quiz/teacher/${quizsetId}`);
return instance.delete(`${API_URL}/quiz/${quizsetId}`);
};
return { quizsetDelete };

View File

@ -9,17 +9,21 @@ export default function QuizsetEditPage() {
const location = useLocation();
const initialValue = location.state.initialValue;
const { quizsetEdit } = useQuizsetEdit();
console.log(initialValue);
const handleSubmit = async (e, title, quizzes) => {
e.preventDefault();
console.log(quizzes);
const images = [];
const quizContents = [];
quizzes.forEach((quiz) => {
const { image, ...quizData } = quiz;
images.push(image);
quizContents.push(quizData);
if (quizData.id > initialValue.quizzes[initialValue.quizzes.length - 1].id) {
const { question, answer, choices } = quizData;
quizContents.push({ question, answer, choices });
} else {
quizContents.push(quizData);
}
});
const quizsetObject = {
@ -27,7 +31,6 @@ export default function QuizsetEditPage() {
title,
quizzes: quizContents,
};
console.log(quizsetObject);
const formData = new FormData();
formData.append('quizSetUpdateRequest', new Blob([JSON.stringify(quizsetObject)], { type: 'application/json' }));

View File

@ -8,15 +8,15 @@ export default function QuizsetWritePage() {
const handleSubmit = async (e, title, quizzes) => {
e.preventDefault();
console.log(quizzes);
const images = [];
const quizContents = [];
quizzes.forEach((quiz) => {
const { image, ...quizData } = quiz;
// eslint-disable-next-line no-unused-vars
const { image, question, answer, choices } = quiz;
images.push(image);
quizContents.push(quizData);
quizContents.push({ question, answer, choices });
});
const quizsetObject = {