fix: 게시판 부분 maxLength 추가

This commit is contained in:
정기영 2024-08-09 15:45:32 +09:00
parent 591cfa1e30
commit 1a0b020ec5
23 changed files with 217 additions and 65 deletions

View File

@ -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}

View File

@ -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"

View File

@ -108,3 +108,9 @@
cursor: not-allowed;
stroke: var(--text-color-tertiary);
}
.textLength {
margin-top: 4px;
align-self: end;
color: var(--error-color);
}

View File

@ -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"

View File

@ -108,3 +108,9 @@
cursor: not-allowed;
stroke: var(--text-color-tertiary);
}
.textLength {
margin-top: 4px;
align-self: end;
color: var(--error-color);
}

View File

@ -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"

View File

@ -108,3 +108,9 @@
cursor: not-allowed;
stroke: var(--text-color-tertiary);
}
.textLength {
margin-top: 4px;
align-self: end;
color: var(--error-color);
}

View File

@ -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"

View File

@ -108,3 +108,9 @@
cursor: not-allowed;
stroke: var(--text-color-tertiary);
}
.textLength {
margin-top: 4px;
align-self: end;
color: var(--error-color);
}

View File

@ -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}

View File

@ -91,3 +91,9 @@
.delete {
color: var(--error-color);
}
.textLength {
margin-top: 4px;
align-self: end;
color: var(--error-color);
}

View File

@ -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}

View File

@ -26,3 +26,9 @@
border-radius: 8px;
cursor: pointer;
}
.textLength {
margin-top: 4px;
align-self: end;
color: var(--error-color);
}

View File

@ -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)}

View File

@ -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);
}

View File

@ -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>

View File

@ -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 {

View File

@ -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));
};

View 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 };
}

View File

@ -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}
/>
);
}

View File

@ -34,6 +34,7 @@ export default function StudentReportDetailPage() {
answer={quiz.answer}
choices={quiz.choices}
userAnswer={quiz.userAnswer}
correct={quiz.correct}
/>
))}
</div>

View File

@ -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;

View File

@ -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>
);
}