fix: 게시판 부분 maxLength 추가
This commit is contained in:
parent
591cfa1e30
commit
1a0b020ec5
@ -2,23 +2,42 @@ import styles from './ArticleDetailAnswerInput.module.css';
|
||||
import { useAnswerWrite } from '../../../../hooks/api/useAnswerWrite';
|
||||
import { useAnswerEdit } from '../../../../hooks/api/useAnswerEdit';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useState } from 'react';
|
||||
import { useRef, useEffect, useState } from 'react';
|
||||
import SendIcon from '/src/assets/icons/send.svg?react';
|
||||
export default function ArticleDetailAnswerInput({ onSubmit, initialAnswer, isEditing = false }) {
|
||||
// TODO: 우선 Textarea로 댓글 수정. 필요시 Input으로 다시 변경
|
||||
const { answerWrite } = useAnswerWrite();
|
||||
const { answerEdit } = useAnswerEdit();
|
||||
const { questionId } = useParams();
|
||||
const [newAnswer, setNewAnswer] = useState(initialAnswer);
|
||||
const [answer, setAnswer] = useState(initialAnswer);
|
||||
|
||||
const textareaRef = useRef(null);
|
||||
|
||||
const adjustTextareaHeight = () => {
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.style.height = 'auto';
|
||||
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
adjustTextareaHeight();
|
||||
}, [answer]);
|
||||
|
||||
const handleInput = (e) => {
|
||||
const { value } = e.target;
|
||||
setAnswer(value);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
console.log(isEditing);
|
||||
if (isEditing) {
|
||||
await answerEdit(questionId, newAnswer);
|
||||
await answerEdit(questionId, answer);
|
||||
} else {
|
||||
await answerWrite(questionId, newAnswer);
|
||||
await answerWrite(questionId, answer);
|
||||
}
|
||||
onSubmit(newAnswer);
|
||||
onSubmit(answer);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -26,14 +45,16 @@ export default function ArticleDetailAnswerInput({ onSubmit, initialAnswer, isEd
|
||||
onSubmit={handleSubmit}
|
||||
className={styles.answer}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
maxLength={255}
|
||||
value={newAnswer}
|
||||
onChange={(e) => setNewAnswer(e.target.value)}
|
||||
<textarea
|
||||
maxLength={1000}
|
||||
value={answer}
|
||||
onChange={handleInput}
|
||||
ref={textareaRef}
|
||||
placeholder="답변 작성하기"
|
||||
className={styles.input}
|
||||
/>
|
||||
{answer.length > 950 && <div className={styles.textLength}>{answer.length} / 1000</div>}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className={styles.button}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styles from './CreateArticle.module.css';
|
||||
import EditIcon from '/src/assets/icons/edit.svg?react';
|
||||
@ -7,12 +7,22 @@ import BackIcon from '/src/assets/icons/back.svg?react';
|
||||
export default function CreateArticle({ topic, title, onSubmit }) {
|
||||
const [articleTitle, setArticleTitle] = useState('');
|
||||
const [articleContent, setArticleContent] = useState('');
|
||||
const [textAreaHeight, setTextAreaHeight] = useState('auto');
|
||||
const textareaRef = useRef(null);
|
||||
|
||||
const adjustTextareaHeight = () => {
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.style.height = 'auto';
|
||||
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
adjustTextareaHeight();
|
||||
}, [articleContent]);
|
||||
|
||||
const handleInput = (e) => {
|
||||
const { value, scrollHeight } = e.target;
|
||||
const { value } = e.target;
|
||||
setArticleContent(value);
|
||||
setTextAreaHeight(scrollHeight + 'px');
|
||||
};
|
||||
|
||||
return (
|
||||
@ -35,22 +45,26 @@ export default function CreateArticle({ topic, title, onSubmit }) {
|
||||
<label className={styles.label}>제목</label>
|
||||
<input
|
||||
type="text"
|
||||
maxLength={255}
|
||||
maxLength={200}
|
||||
className={styles.titleInput}
|
||||
placeholder="제목을 입력하세요"
|
||||
value={articleTitle}
|
||||
onChange={(e) => setArticleTitle(e.target.value)}
|
||||
/>
|
||||
{articleTitle.length > 190 && <div className={styles.textLength}>{articleTitle.length} / 200</div>}
|
||||
</div>
|
||||
<div className={styles.fieldWrapper}>
|
||||
<label className={styles.label}>내용</label>
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className={styles.contentInput}
|
||||
placeholder="내용을 입력하세요"
|
||||
value={articleContent}
|
||||
maxLength={1000}
|
||||
onChange={handleInput}
|
||||
style={{ height: textAreaHeight, overflow: 'hidden' }}
|
||||
style={{ overflow: 'hidden' }}
|
||||
></textarea>
|
||||
{articleContent.length > 950 && <div className={styles.textLength}>{articleContent.length} / 1000</div>}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -108,3 +108,9 @@
|
||||
cursor: not-allowed;
|
||||
stroke: var(--text-color-tertiary);
|
||||
}
|
||||
|
||||
.textLength {
|
||||
margin-top: 4px;
|
||||
align-self: end;
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@ -9,10 +9,6 @@ export default function EditArticle({ topic, title, prevTitle, prevContent, onSu
|
||||
const [articleContent, setArticleContent] = useState(prevContent);
|
||||
const textAreaRef = useRef(null);
|
||||
|
||||
// useEffect(() => {
|
||||
// adjustTextAreaHeight();
|
||||
// }, []);
|
||||
|
||||
useEffect(() => {
|
||||
adjustTextAreaHeight();
|
||||
}, [articleContent]);
|
||||
@ -48,22 +44,25 @@ export default function EditArticle({ topic, title, prevTitle, prevContent, onSu
|
||||
<label className={styles.label}>제목</label>
|
||||
<input
|
||||
type="text"
|
||||
maxLength={255}
|
||||
maxLength={200}
|
||||
className={styles.titleInput}
|
||||
placeholder="제목을 입력하세요"
|
||||
value={articleTitle}
|
||||
onChange={(e) => setArticleTitle(e.target.value)}
|
||||
/>
|
||||
{articleTitle.length > 190 && <div className={styles.textLength}>{articleTitle.length} / 200</div>}
|
||||
</div>
|
||||
<div className={styles.fieldWrapper}>
|
||||
<label className={styles.label}>내용</label>
|
||||
<textarea
|
||||
ref={textAreaRef}
|
||||
className={styles.contentInput}
|
||||
maxLength={1000}
|
||||
placeholder="내용을 입력하세요"
|
||||
value={articleContent}
|
||||
onChange={handleInput}
|
||||
></textarea>
|
||||
{articleContent.length > 950 && <div className={styles.textLength}>{articleContent.length} / 1000</div>}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -108,3 +108,9 @@
|
||||
cursor: not-allowed;
|
||||
stroke: var(--text-color-tertiary);
|
||||
}
|
||||
|
||||
.textLength {
|
||||
margin-top: 4px;
|
||||
align-self: end;
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styles from './EditFreeboard.module.css';
|
||||
import EditIcon from '/src/assets/icons/edit.svg?react';
|
||||
@ -7,11 +7,21 @@ import BackIcon from '/src/assets/icons/back.svg?react';
|
||||
export default function EditFreeboard({ topic, title, prevContent, prevTitle, onSubmit }) {
|
||||
const [articleTitle, setArticleTitle] = useState(prevTitle);
|
||||
const [articleContent, setArticleContent] = useState(prevContent);
|
||||
const textAreaRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
adjustTextAreaHeight();
|
||||
}, [articleContent]);
|
||||
|
||||
const adjustTextAreaHeight = () => {
|
||||
if (textAreaRef.current) {
|
||||
textAreaRef.current.style.height = 'auto';
|
||||
textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`;
|
||||
}
|
||||
};
|
||||
|
||||
const handleInput = (e) => {
|
||||
setArticleContent(e.target.value);
|
||||
e.target.style.height = 'auto';
|
||||
e.target.style.height = e.target.scrollHeight + 'px';
|
||||
};
|
||||
|
||||
return (
|
||||
@ -34,21 +44,25 @@ export default function EditFreeboard({ topic, title, prevContent, prevTitle, on
|
||||
<label className={styles.label}>제목</label>
|
||||
<input
|
||||
type="text"
|
||||
maxLength={255}
|
||||
maxLength={200}
|
||||
className={styles.titleInput}
|
||||
placeholder={'제목을 입력하세요'}
|
||||
value={articleTitle}
|
||||
onChange={(e) => setArticleTitle(e.target.value)}
|
||||
/>
|
||||
{articleTitle.length > 190 && <div className={styles.textLength}>{articleTitle.length} / 200</div>}
|
||||
</div>
|
||||
<div className={styles.fieldWrapper}>
|
||||
<label className={styles.label}>내용</label>
|
||||
<textarea
|
||||
className={styles.contentInput}
|
||||
placeholder="내용을 입력하세요"
|
||||
ref={textAreaRef}
|
||||
maxLength={1000}
|
||||
value={articleContent}
|
||||
onChange={handleInput}
|
||||
></textarea>
|
||||
{articleContent.length > 950 && <div className={styles.textLength}>{articleContent.length} / 1000</div>}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -108,3 +108,9 @@
|
||||
cursor: not-allowed;
|
||||
stroke: var(--text-color-tertiary);
|
||||
}
|
||||
|
||||
.textLength {
|
||||
margin-top: 4px;
|
||||
align-self: end;
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useRef, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styles from './EditQna.module.css';
|
||||
import EditIcon from '/src/assets/icons/edit.svg?react';
|
||||
@ -7,11 +7,21 @@ import BackIcon from '/src/assets/icons/back.svg?react';
|
||||
export default function EditQna({ topic, title, prevContent, prevTitle, onSubmit }) {
|
||||
const [articleTitle, setArticleTitle] = useState(prevTitle);
|
||||
const [articleContent, setArticleContent] = useState(prevContent);
|
||||
const textAreaRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
adjustTextAreaHeight();
|
||||
}, [articleContent]);
|
||||
|
||||
const adjustTextAreaHeight = () => {
|
||||
if (textAreaRef.current) {
|
||||
textAreaRef.current.style.height = 'auto';
|
||||
textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`;
|
||||
}
|
||||
};
|
||||
|
||||
const handleInput = (e) => {
|
||||
setArticleContent(e.target.value);
|
||||
e.target.style.height = 'auto';
|
||||
e.target.style.height = e.target.scrollHeight + 'px';
|
||||
};
|
||||
|
||||
return (
|
||||
@ -34,21 +44,25 @@ export default function EditQna({ topic, title, prevContent, prevTitle, onSubmit
|
||||
<label className={styles.label}>제목</label>
|
||||
<input
|
||||
type="text"
|
||||
maxLength={255}
|
||||
maxLength={200}
|
||||
className={styles.titleInput}
|
||||
placeholder={'제목을 입력하세요'}
|
||||
value={articleTitle}
|
||||
onChange={(e) => setArticleTitle(e.target.value)}
|
||||
/>
|
||||
{articleTitle.length > 190 && <div className={styles.textLength}>{articleTitle.length} / 200</div>}
|
||||
</div>
|
||||
<div className={styles.fieldWrapper}>
|
||||
<label className={styles.label}>내용</label>
|
||||
<textarea
|
||||
ref={textAreaRef}
|
||||
maxLength={1000}
|
||||
className={styles.contentInput}
|
||||
placeholder="내용을 입력하세요"
|
||||
value={articleContent}
|
||||
onChange={handleInput}
|
||||
></textarea>
|
||||
{articleContent.length > 950 && <div className={styles.textLength}>{articleContent.length} / 1000</div>}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -108,3 +108,9 @@
|
||||
cursor: not-allowed;
|
||||
stroke: var(--text-color-tertiary);
|
||||
}
|
||||
|
||||
.textLength {
|
||||
margin-top: 4px;
|
||||
align-self: end;
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@ -41,11 +41,13 @@ export default function FreeboardComment({ content, author, onDeleteSubmit, onEd
|
||||
<input
|
||||
type="text"
|
||||
value={newComment}
|
||||
maxLength={200}
|
||||
onChange={(e) => setNewComment(e.target.value)}
|
||||
placeholder="댓글 수정하기"
|
||||
className={styles.input}
|
||||
required
|
||||
/>
|
||||
{newComment.length > 190 && <div className={styles.textLength}>{newComment.length} / 200</div>}
|
||||
<button
|
||||
type="submit"
|
||||
className={styles.button}
|
||||
|
@ -91,3 +91,9 @@
|
||||
.delete {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.textLength {
|
||||
margin-top: 4px;
|
||||
align-self: end;
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@ -19,11 +19,13 @@ export default function FreeboardCommentInput({ onCommentSubmit }) {
|
||||
<input
|
||||
type="text"
|
||||
value={newComment}
|
||||
maxLength={200}
|
||||
onChange={(e) => setNewComment(e.target.value)}
|
||||
placeholder="댓글 작성하기"
|
||||
className={styles.input}
|
||||
required
|
||||
/>
|
||||
{newComment.length > 190 && <div className={styles.textLength}>{newComment.length} / 200</div>}
|
||||
<button
|
||||
type="submit"
|
||||
className={styles.button}
|
||||
|
@ -26,3 +26,9 @@
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.textLength {
|
||||
margin-top: 4px;
|
||||
align-self: end;
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import styles from './InfoEditForm.module.css';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function InfoEditForm({ name, email, onSubmit }) {
|
||||
export default function InfoEditForm({ name, email, onSubmit, usingEmail }) {
|
||||
const [username, setUsername] = useState(name);
|
||||
const [useremail, setUseremail] = useState(email);
|
||||
|
||||
@ -39,17 +39,18 @@ export default function InfoEditForm({ name, email, onSubmit }) {
|
||||
htmlFor="useremail"
|
||||
className={styles.textBody}
|
||||
>
|
||||
이메일을 입력하세요.
|
||||
이메일을 입력하세요
|
||||
</label>
|
||||
<input
|
||||
placeholder="이메일"
|
||||
type="text"
|
||||
id="useremail"
|
||||
className={`${styles.input} ${styles.textBody}`}
|
||||
className={`${styles.input} ${styles.textBody} ${usingEmail && styles.errorBox}`}
|
||||
value={useremail}
|
||||
onChange={(e) => setUseremail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
{usingEmail && <div className={styles.errorText}>이미 사용중인 이메일입니다</div>}
|
||||
</div>
|
||||
<button
|
||||
disabled={(!username && !useremail) || (username == name && useremail == email)}
|
||||
|
@ -64,3 +64,12 @@
|
||||
cursor: not-allowed;
|
||||
stroke: var(--text-color-tertiary);
|
||||
}
|
||||
|
||||
.errorBox {
|
||||
outline: none;
|
||||
border: 1px solid var(--error-border);
|
||||
}
|
||||
|
||||
.errorText {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import styles from './QuizCard.module.css';
|
||||
import { STATIC_URL } from '../../constants';
|
||||
|
||||
export default function QuizCard({ index, question, answer, image, choices, userAnswer, correct = true }) {
|
||||
// TODO: 정답 / 오답 관련 표현 필요 시 추가
|
||||
console.log(choices);
|
||||
export default function QuizDetailCard({ index, question, answer, image, choices, userAnswer = null, correct = true }) {
|
||||
console.log(correct);
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<div className={`${styles.card} ${!correct && styles.incorrect}`}>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.heading}>{index}번 퀴즈</span>
|
||||
</div>
|
||||
|
@ -6,6 +6,12 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.incorrect {
|
||||
background-color: #db3232;
|
||||
border: 1px solid var(--red300);
|
||||
}
|
||||
|
||||
.header {
|
||||
|
@ -21,19 +21,16 @@ export default function QuizsetForm({ headerTitle, topic, to, onSubmit, initialV
|
||||
}, [initialValue]);
|
||||
|
||||
const handleAddQuiz = () => {
|
||||
console.log(quizzes);
|
||||
setQuizzes([...quizzes, { id: quizId, question: '', answer: '', choices: [], image: null }]);
|
||||
setQuizId(quizId + 1);
|
||||
};
|
||||
|
||||
const updateQuiz = (id, updatedQuiz) => {
|
||||
console.log(quizzes);
|
||||
const updatedQuizzes = quizzes.map((quiz) => (quiz.id === id ? updatedQuiz : quiz));
|
||||
setQuizzes(updatedQuizzes);
|
||||
};
|
||||
|
||||
const deleteQuiz = (id) => {
|
||||
console.log(quizzes);
|
||||
setQuizzes(quizzes.filter((quiz) => quiz.id !== id));
|
||||
};
|
||||
|
||||
|
10
frontend/src/hooks/api/useReportSetDelete.js
Normal file
10
frontend/src/hooks/api/useReportSetDelete.js
Normal file
@ -0,0 +1,10 @@
|
||||
import instance from '../../utils/axios/instance';
|
||||
import { API_URL } from '../../constants';
|
||||
|
||||
export function useReportSetDelete() {
|
||||
const reportsetDelete = (reportsetId) => {
|
||||
return instance.delete(`${API_URL}/report/teacher/report/${reportsetId}`);
|
||||
};
|
||||
|
||||
return { reportsetDelete };
|
||||
}
|
@ -2,18 +2,26 @@ import { InfoEditForm } from '../../components/InfoEditForm';
|
||||
import { useAuth } from '../../hooks/api/useAuth';
|
||||
import { useUserInfo } from '../../hooks/api/useUserInfo';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function MyInfoChangePage() {
|
||||
const navigate = useNavigate();
|
||||
const { data } = useUserInfo();
|
||||
const myInfo = data.data?.userInfo;
|
||||
const { updateInfo } = useAuth();
|
||||
const [usingEmail, setUsingEmail] = useState(false);
|
||||
|
||||
const handleSubmit = async (e, username, useremail) => {
|
||||
e.preventDefault();
|
||||
await updateInfo(username, useremail)
|
||||
.then(() => navigate('/'))
|
||||
.catch((err) => console.log(err));
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
console.log(err.response.data);
|
||||
if (err.response.data === '이미 사용 중인 이메일입니다.') {
|
||||
setUsingEmail(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@ -21,6 +29,7 @@ export default function MyInfoChangePage() {
|
||||
name={myInfo.name}
|
||||
email={myInfo.email}
|
||||
onSubmit={handleSubmit}
|
||||
usingEmail={usingEmail}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ export default function StudentReportDetailPage() {
|
||||
answer={quiz.answer}
|
||||
choices={quiz.choices}
|
||||
userAnswer={quiz.userAnswer}
|
||||
correct={quiz.correct}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -9,8 +9,9 @@ export default function StudentReportPage() {
|
||||
const { lectureId } = useParams();
|
||||
const { data } = useStudentReports(lectureId);
|
||||
const reports = data?.data;
|
||||
console.log(reports);
|
||||
|
||||
const totalCounts = reports.reduce(
|
||||
const totalCounts = reports.reduce?.(
|
||||
(acc, report) => {
|
||||
if (acc.allCount > 0) {
|
||||
acc.correctCount += report.correctCount;
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { ArticleLink } from '../../components/ArticleLink';
|
||||
import ArticleBoard from '../../components/ArticleBoard/ArticleBoard';
|
||||
import { useReportSetDetail } from '../../hooks/api/useReportSetDetail';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useReportSetDelete } from '../../hooks/api/useReportSetDelete';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
|
||||
export default function TeacherReportsetDetailPage() {
|
||||
const { reportsetId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { reportsetDelete } = useReportSetDelete();
|
||||
const { data } = useReportSetDetail(reportsetId);
|
||||
const reports = data?.data;
|
||||
|
||||
@ -16,7 +19,14 @@ export default function TeacherReportsetDetailPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = async (e) => {
|
||||
e.preventDefault();
|
||||
await reportsetDelete(reportsetId);
|
||||
navigate('..');
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ArticleBoard
|
||||
title="퀴즈 조회"
|
||||
canCreate={false}
|
||||
@ -38,5 +48,12 @@ export default function TeacherReportsetDetailPage() {
|
||||
);
|
||||
})}
|
||||
</ArticleBoard>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
리포트 삭제
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user