diff --git a/frontend/src/pages/qna/QnADetailPage.module.css b/frontend/src/pages/qna/QnADetailPage.module.css
index 161d608..e75dca6 100644
--- a/frontend/src/pages/qna/QnADetailPage.module.css
+++ b/frontend/src/pages/qna/QnADetailPage.module.css
@@ -1,4 +1,4 @@
-/* ── 전체 페이지 ── */
+/* ── 페이지 레이아웃 ── */
.page {
display: flex;
flex-direction: column;
@@ -12,40 +12,10 @@
box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.25);
}
-/* ── 상단 헤더 (해결 여부 뱃지) ── */
-.topBar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16px 0 10px;
-}
-
-.solvedBadge {
- font-family: var(--font-main);
- font-size: 0.78rem;
- font-weight: 600;
- color: var(--black);
- background: var(--gray20);
- border-radius: 20px;
- padding: 3px 10px;
- letter-spacing: 0.01em;
-}
-
-.unsolvedBadge {
- font-family: var(--font-main);
- font-size: 0.78rem;
- font-weight: 600;
- color: var(--black);
- background: var(--light);
- border-radius: 20px;
- padding: 3px 10px;
-}
-
/* ── 작성자 행 ── */
.authorRow {
display: flex;
align-items: flex-start;
-
gap: 10px;
margin-bottom: 5px;
}
@@ -86,10 +56,15 @@
.authorDate {
font-family: var(--font-main);
- font-size: 0.75rem;
+ font-size: 12px;
color: var(--gray200);
}
+/* ── 질문 메뉴 (점 세 개) ── */
+.menuWrapper {
+ position: relative;
+}
+
.menuBtn {
background: none;
border: none;
@@ -98,7 +73,6 @@
padding: 4px;
display: flex;
align-items: center;
-
}
.dropdownMenu {
@@ -120,7 +94,7 @@
border: none;
background: none;
font-family: var(--font-main);
- font-size: 0.9rem;
+ font-size: 14px;
color: var(--black);
cursor: pointer;
text-align: left;
@@ -130,6 +104,80 @@
background: var(--gray20);
}
+/* ── 해결 여부 뱃지 ── */
+.topBar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 0 10px;
+}
+
+.solvedBadge {
+ font-family: var(--font-main);
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--black);
+ background: var(--gray20);
+ border-radius: 20px;
+ padding: 3px 10px;
+ letter-spacing: 0.01em;
+}
+
+.unsolvedBadge {
+ font-family: var(--font-main);
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--black);
+ background: var(--light);
+ border-radius: 20px;
+ padding: 3px 10px;
+}
+
+/* ── 질문 본문 ── */
+.questionTitle {
+ display: flex;
+ align-items: flex-start;
+ gap: 6px;
+ padding-bottom: 5px;
+}
+
+.qIcon {
+ font-family: var(--font-main);
+ font-size: 36px;
+ font-weight: 900;
+ color: var(--main);
+ line-height: 1.1;
+ flex-shrink: 0;
+}
+
+.qIconResolved {
+ color: var(--gray600);
+}
+
+.questionText {
+ font-family: var(--font-main);
+ font-size: 24px;
+ font-weight: 600;
+ color: var(--black);
+ line-height: 1.3;
+ word-break: keep-all;
+ padding-top: 4px;
+}
+
+/* ── 질문 수정 입력창 ── */
+.editWrapper {
+ flex: 1;
+ display: flex;
+ gap: 8px;
+ align-items: flex-start;
+}
+
+.editButtons {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
.editInput {
flex: 1;
font-family: var(--font-main);
@@ -152,7 +200,7 @@
border-radius: 6px;
padding: 4px 10px;
font-family: var(--font-main);
- font-size: 0.85rem;
+ font-size: 14px;
cursor: pointer;
white-space: nowrap;
}
@@ -164,39 +212,12 @@
border-radius: 6px;
padding: 4px 10px;
font-family: var(--font-main);
- font-size: 0.85rem;
+ font-size: 14px;
cursor: pointer;
white-space: nowrap;
}
-/* ── 질문 본문 ── */
-.questionTitle {
- display: flex;
- align-items: flex-start;
- gap: 6px;
- padding-bottom: 5px;
-}
-
-.qIcon {
- font-family: var(--font-main);
- font-size: 36px;
- font-weight: 900;
- color: var(--main);
- line-height: 1.1;
- flex-shrink: 0;
-}
-
-.questionText {
- font-family: var(--font-main);
- font-size: 24px;
- font-weight: 600;
- color: var(--black);
- line-height: 1.3;
- word-break: keep-all;
- padding-top: 4px;
-}
-
-/* ── 첨부 이미지 ── */
+/* ── 질문 첨부 이미지 ── */
.questionImage {
width: 100%;
border-radius: 12px;
@@ -205,7 +226,7 @@
margin-bottom: 18px;
}
-/* ── 액션 버튼 행 ── */
+/* ── 액션 버튼 행 (좋아요 / 댓글달기) ── */
.actionRow {
display: flex;
align-items: center;
@@ -221,7 +242,7 @@
border: none;
cursor: pointer;
font-family: var(--font-main);
- font-size: 0.85rem;
+ font-size: 14px;
color: var(--gray600);
padding: 4px 10px 4px 8px;
background: var(--gray50);
@@ -242,7 +263,7 @@
border: none;
cursor: pointer;
font-family: var(--font-main);
- font-size: 0.85rem;
+ font-size: 14px;
color: var(--gray600);
padding: 4px 10px;
background: var(--gray50);
@@ -257,8 +278,7 @@
width: 100%;
height: 1px;
border: none;
- margin: 4px 0 0;
- margin-bottom: 20px;
+ margin: 4px 0 20px;
}
/* ── 댓글 목록 ── */
@@ -268,8 +288,11 @@
gap: 0;
}
+.commentBlock {
+ padding: 8px 0;
+}
-/* 댓글 작성자 행 */
+/* ── 댓글 작성자 행 ── */
.commentAuthorRow {
display: flex;
align-items: center;
@@ -295,7 +318,7 @@
.commentAuthorName {
font-family: var(--font-main);
- font-size: 0.9rem;
+ font-size: 14px;
font-weight: 600;
color: var(--black);
display: flex;
@@ -303,6 +326,17 @@
gap: 4px;
}
+.staffBadge {
+ display: inline-flex;
+ align-items: center;
+}
+
+/* ── 댓글 메뉴 (점 세 개) ── */
+.commentMenuWrapper {
+ position: relative;
+ margin-left: auto;
+}
+
/* ── 댓글 말풍선 ── */
.commentBubble {
margin-left: 42px;
@@ -318,7 +352,7 @@
.commentContent {
font-family: var(--font-main);
- font-size: 0.9rem;
+ font-size: 14px;
color: var(--black);
font-weight: 400;
line-height: 1.5;
@@ -341,17 +375,43 @@
display: block;
}
-
-/* 댓글 타임스탬프 */
.commentDate {
margin-left: 42px;
font-family: var(--font-main);
- font-size: 0.72rem;
+ font-size: 11px;
color: var(--gray200);
margin-top: 6px;
}
-/* ── 댓글 입력 바 (하단 고정) ── */
+/* ── 댓글 수정 입력창 ── */
+.commentEditWrapper {
+ display: flex;
+ gap: 8px;
+ align-items: flex-start;
+}
+
+.commentEditButtons {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.editCommentInput {
+ flex: 1;
+ font-family: var(--font-main);
+ font-size: 14px;
+ font-weight: 400;
+ color: var(--black);
+ border: 1px solid var(--gray200);
+ border-radius: 8px;
+ padding: 4px 8px;
+ resize: none;
+ outline: none;
+ line-height: 1.5;
+ width: 100%;
+}
+
+/* ── 하단 댓글 입력바 ── */
.bottomCover {
position: fixed;
bottom: 0;
@@ -380,10 +440,6 @@
gap: 4px;
}
-.commentBlock {
- padding: 8px 0;
-}
-
.commentInputRow {
display: flex;
align-items: center;
@@ -398,7 +454,7 @@
background: var(--gray20);
border: none;
cursor: pointer;
- font-size: 1.2rem;
+ font-size: 19px;
display: flex;
align-items: center;
justify-content: center;
@@ -406,45 +462,12 @@
color: var(--gray600);
}
-.imagePreviewWrapper {
- position: relative;
- display: inline-block;
- margin: 4px 0 0 12px;
- overflow: visible;
- align-self: flex-start;
-}
-
-.imagePreview {
- width: 80px;
- height: 80px;
- object-fit: cover;
- border-radius: 8px;
-}
-
-.imageRemoveBtn {
- position: absolute;
- top: -6px;
- right: -6px;
- width: 20px;
- height: 20px;
- border-radius: 50%;
- background: var(--gray600);
- color: var(--white);
- border: none;
- cursor: pointer;
- font-size: 0.7rem;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 101;
-}
-
.commentInput {
flex: 1;
border: none;
background: none;
font-family: var(--font-main);
- font-size: 1rem;
+ font-size: 16px;
color: var(--black);
outline: none;
height: 100%;
@@ -474,8 +497,36 @@
cursor: default;
}
-/* ── 스태프 뱃지 ── */
-.staffBadge {
- display: inline-flex;
+/* ── 이미지 미리보기 ── */
+.imagePreviewWrapper {
+ position: relative;
+ display: inline-block;
+ margin: 4px 0 0 12px;
+ overflow: visible;
+ align-self: flex-start;
+}
+
+.imagePreview {
+ width: 80px;
+ height: 80px;
+ object-fit: cover;
+ border-radius: 8px;
+}
+
+.imageRemoveBtn {
+ position: absolute;
+ top: -6px;
+ right: -6px;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ background: var(--gray600);
+ color: var(--white);
+ border: none;
+ cursor: pointer;
+ font-size: 11px;
+ display: flex;
align-items: center;
+ justify-content: center;
+ z-index: 101;
}
\ No newline at end of file
diff --git a/frontend/src/pages/qna/QnAListPage.js b/frontend/src/pages/qna/QnAListPage.js
index 4fa8b21..5afae11 100644
--- a/frontend/src/pages/qna/QnAListPage.js
+++ b/frontend/src/pages/qna/QnAListPage.js
@@ -6,16 +6,11 @@ import { authFetch } from '../../utils/Api';
import {
CommentImoji, MeCuriousToo, SortBtn,
OBtn, XBtn, CommentCommentArraw, SumitBtn, StaffCheck, ImgPreview,
-} from '../../components/qna_svg';
+ DAY_PART_KO, DAY_OF_WEEK_KO, uploadImage,
+} from '../../utils/qnaUtils';
const MAX_VISIBLE_COMMENTS = 3;
-const DAY_PART_KO = { AM: '오전', PM: '오후' };
-const DAY_OF_WEEK_KO = {
- MONDAY: '월', TUESDAY: '화', WEDNESDAY: '수',
- THURSDAY: '목', FRIDAY: '금', SATURDAY: '토', SUNDAY: '일',
-};
-
function QnAListPage() {
const { sessionId } = useParams();
const navigate = useNavigate();
@@ -23,34 +18,39 @@ function QnAListPage() {
const isPast = location.state?.status === 'AFTER_SESSION';
const isStaff = localStorage.getItem('role') === 'ADMIN';
+ // ── 세션 / 이해도 상태 ──────────────────────────
const [sessionTitle, setSessionTitle] = useState('');
const [understanding, setUnderstanding] = useState(null);
const [understandingIndex, setUnderstandingIndex] = useState(0);
const [myChoices, setMyChoices] = useState({});
+ // ── 질문 목록 상태 ───────────────────────────────
const [popularQuestions, setPopularQuestions] = useState([]);
const [unresolvedQuestions, setUnresolvedQuestions] = useState([]);
const [resolvedQuestions, setResolvedQuestions] = useState([]);
+ // ── 필터 / 정렬 상태 ─────────────────────────────
const [filterCurious, setFilterCurious] = useState(false);
const [filterUnsolved, setFilterUnsolved] = useState(false);
const [sortOrder, setSortOrder] = useState('정렬');
const [showSortMenu, setShowSortMenu] = useState(false);
+ // ── 댓글 입력 상태 ───────────────────────────────
const [commentOpenId, setCommentOpenId] = useState(null);
const [commentInputs, setCommentInputs] = useState({});
+ const [commentImages, setCommentImages] = useState({});
+ const [commentImagePreviews, setCommentImagePreviews] = useState({});
+ const commentFileRefs = useRef({});
+
+ // ── 새 질문 / 이해도 입력 상태 ──────────────────
const [newQuestion, setNewQuestion] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitError, setSubmitError] = useState(null);
const [selectedImage, setSelectedImage] = useState(null);
const [imagePreview, setImagePreview] = useState(null);
- const [commentImages, setCommentImages] = useState({});
- const [commentImagePreviews, setCommentImagePreviews] = useState({});
- const commentFileRefs = useRef({});
-
const fileInputRef = useRef(null);
-
+ // ── 질문 목록 불러오기 ───────────────────────────
const fetchQuestions = useCallback(async (index) => {
try {
const res = await authFetch(`/api/sessions/${sessionId}/questions?understandingIndex=${index}`);
@@ -69,7 +69,7 @@ function QnAListPage() {
...(questions.resolvedQuestions ?? []),
];
- // 이미지 blob URL 변환만 처리 (개별 API 호출 제거)
+ // 질문 이미지 blob URL 변환
const withBlob = await Promise.all(
allQ.map(async (q) => {
let blobImageUrl = null;
@@ -104,6 +104,7 @@ function QnAListPage() {
if (sessionId) fetchQuestions(understandingIndex);
}, [sessionId, understandingIndex, fetchQuestions]);
+ // ── 이해도 네비게이션 ────────────────────────────
const goPrevUnderstand = () => {
if (understanding?.hasOlder) setUnderstandingIndex(prev => prev + 1);
};
@@ -111,6 +112,7 @@ function QnAListPage() {
if (understanding?.hasNewer) setUnderstandingIndex(prev => prev - 1);
};
+ // ── 이해도 O/X 선택 ──────────────────────────────
const handleUnderstandChoice = async (choice) => {
if (!understanding?.current?.checkId) return;
const checkId = understanding.current.checkId;
@@ -132,6 +134,7 @@ function QnAListPage() {
understoodCount: json.result.understoodCount,
notUnderstoodCount: json.result.notUnderstoodCount,
attendanceCount: json.result.attendanceCount,
+ respondedCount: json.result.respondedCount,
}
}));
}
@@ -140,6 +143,7 @@ function QnAListPage() {
}
};
+ // ── 좋아요 토글 ──────────────────────────────────
const toggleLike = async (e, questionId) => {
e.stopPropagation();
if (isPast) return;
@@ -162,6 +166,7 @@ function QnAListPage() {
}
};
+ // ── 댓글 입력창 토글 ─────────────────────────────
const toggleCommentInput = (e, questionId) => {
e.stopPropagation();
if (isPast) return;
@@ -172,6 +177,7 @@ function QnAListPage() {
setCommentInputs(prev => ({ ...prev, [questionId]: value }));
};
+ // ── 댓글 등록 ────────────────────────────────────
const handleCommentSubmit = async (e, questionId) => {
e.stopPropagation();
const text = (commentInputs[questionId] || '').trim();
@@ -219,25 +225,7 @@ function QnAListPage() {
}
};
- const handleImageSelect = (e) => {
- const file = e.target.files[0];
- if (!file) return;
- setSelectedImage(file);
- setImagePreview(URL.createObjectURL(file));
- };
-
- const uploadImage = async (file) => {
- const formData = new FormData();
- formData.append('file', file);
- const token = localStorage.getItem('token');
- const res = await fetch('/api/images', {
- method: 'POST',
- headers: { 'Authorization': `Bearer ${token}` },
- body: formData,
- });
- const json = await res.json();
- return json.imageUrl;
- };
+ // ── 댓글 이미지 선택 / 붙여넣기 ─────────────────
const handleCommentImageSelect = (e, questionId) => {
const file = e.target.files[0];
if (!file) return;
@@ -259,6 +247,16 @@ function QnAListPage() {
}
}
};
+
+ // ── 질문 이미지 선택 ─────────────────────────────
+ const handleImageSelect = (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+ setSelectedImage(file);
+ setImagePreview(URL.createObjectURL(file));
+ };
+
+ // ── 새 질문 등록 ─────────────────────────────────
const handleNewQuestion = async () => {
const text = newQuestion.trim();
if (!text) return;
@@ -289,6 +287,7 @@ function QnAListPage() {
}
};
+ // ── 이해도 체크 등록 (운영진 전용) ──────────────
const handleNewUnderstandCheck = async () => {
const text = newQuestion.trim();
if (!text) return;
@@ -314,6 +313,7 @@ function QnAListPage() {
}
};
+ // ── 질문 목록 필터 / 정렬 ────────────────────────
const allQuestions = [
...popularQuestions,
...unresolvedQuestions.filter(q => !popularQuestions.some(p => p.questionId === q.questionId)),
@@ -340,6 +340,7 @@ function QnAListPage() {
{sessionTitle}
+ {/* ── 필터 / 정렬 행 ── */}
{isStaff ? (
- {/* 이해도 */}
+ {/* ── 이해도 바 ── */}
- {/* 질문 목록 */}
+ {/* ── 질문 목록 ── */}
{displayedQuestions.map(question => (
navigate(`/sessions/${sessionId}/questions/${question.questionId}`)}>
+
+ {/* 질문 헤더 */}
+ {/* 질문 첨부 이미지 */}
{question.imageUrl && (

e.stopPropagation()} />
)}
+ {/* 댓글 미리보기 */}
{question.previewComments?.length > 0 && (
{question.previewComments.slice(0, MAX_VISIBLE_COMMENTS).map(comment => (
@@ -463,9 +468,8 @@ function QnAListPage() {
e.stopPropagation();
navigate(`/sessions/${sessionId}/questions/${question.questionId}`);
}}
- style={{ cursor: 'pointer' }}
>
-
사진 보기
+
사진보기
)}
@@ -479,6 +483,7 @@ function QnAListPage() {
)}
+ {/* 댓글 입력창 */}
{commentOpenId === question.questionId && (
e.stopPropagation()}>
{commentImagePreviews[question.questionId] && (
@@ -530,6 +535,7 @@ function QnAListPage() {
+ {/* ── 하단 입력바 (지난 세션이면 숨김) ── */}
{!isPast && (
{submitError &&
{submitError}
}
@@ -543,7 +549,8 @@ function QnAListPage() {
)}
- {!isStaff && ( // ← 스태프일 때 + 버튼 숨김
+ {/* 운영진일 때 + 버튼 숨김 */}
+ {!isStaff && (
<>
)}
setNewQuestion(e.target.value)}
diff --git a/frontend/src/pages/qna/QnAListPage.module.css b/frontend/src/pages/qna/QnAListPage.module.css
index 5a93029..9b32d7a 100644
--- a/frontend/src/pages/qna/QnAListPage.module.css
+++ b/frontend/src/pages/qna/QnAListPage.module.css
@@ -1,3 +1,4 @@
+/* ── 페이지 레이아웃 ── */
.page {
display: flex;
flex-direction: column;
@@ -5,15 +6,15 @@
max-width: 780px;
margin: 0 auto;
padding-bottom: 140px;
- padding-left: 1rem;
- padding-right: 1rem;
+ padding-left: 16px;
+ padding-right: 16px;
box-sizing: border-box;
}
-/* 상단 */
+/* ── 상단 타이틀 ── */
.title {
font-family: var(--font-main);
- font-size: 2rem;
+ font-size: 32px;
text-align: center;
margin: 10px;
color: var(--black);
@@ -22,21 +23,21 @@
line-height: normal;
}
-
+/* ── 필터 / 정렬 행 ── */
.filterRow {
display: flex;
align-items: center;
justify-content: flex-end;
- gap: 0.75rem;
- padding: 0.5rem 1rem;
+ gap: 12px;
+ padding: 8px 16px;
}
.curiousLabel {
display: flex;
align-items: center;
- gap: 0.3rem;
+ gap: 5px;
font-family: var(--font-main);
- font-size: 0.8rem;
+ font-size: 13px;
color: var(--gray600);
cursor: pointer;
}
@@ -55,14 +56,13 @@
.sortBtn {
background: none;
border: 1px solid var(--gray600);
- border-radius: 6px;
- padding: 0.25rem 0.6rem;
+ border-radius: 20px;
+ padding: 4px 10px;
font-family: var(--font-main);
- font-size: 0.8rem;
+ font-size: 13px;
color: var(--gray600);
cursor: pointer;
white-space: nowrap;
- border-radius: 20px;
}
.sortMenu {
@@ -75,15 +75,15 @@
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.12);
list-style: none;
margin: 0;
- padding: 0.25rem 0;
+ padding: 4px 0;
z-index: 10;
min-width: 110px;
}
.sortOption {
- padding: 0.5rem 1rem;
+ padding: 8px 16px;
font-family: var(--font-main);
- font-size: 0.85rem;
+ font-size: 14px;
color: var(--black);
cursor: pointer;
white-space: nowrap;
@@ -98,6 +98,7 @@
background: var(--gray200);
}
+/* ── 구분선 ── */
.divider {
background: var(--gray200);
width: 100%;
@@ -105,13 +106,13 @@
border: none;
}
-/*이해도*/
+/* ── 이해도 바 ── */
.understandBar {
margin-top: 20px;
+ margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: space-between;
- margin-bottom: 20px;
border-radius: 10px;
border: 1px solid var(--dark);
background: var(--white);
@@ -152,16 +153,17 @@
font-size: 18px;
}
+/* ── O/X 버튼 ── */
.oxBtn {
border: none;
- font-size: 0.85rem;
+ font-size: 14px;
font-weight: 700;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s, border-color 0.15s;
- margin: 0 0.2rem;
+ margin: 0 3px;
width: 36px;
height: 36px;
aspect-ratio: 1/1;
@@ -180,15 +182,12 @@
color: var(--dark);
}
-
-
-/*질문*/
-.questionList {
- display: flex;
- flex-direction: column;
- gap: 20px;
- align-items: center;
-
+.oxBtn:disabled {
+ cursor: default;
+ background: var(--pale);
+ width: 70px;
+ height: 36px;
+ color: var(--dark);
}
.oxCount {
@@ -199,16 +198,16 @@
font-weight: 500;
}
-.oxBtn:disabled {
- cursor: default;
- background: var(--pale);
- width: 70px;
- height: 36px;
- color: var(--dark);
+/* ── 질문 목록 ── */
+.questionList {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ align-items: center;
}
.questionCard {
- padding: 0.85rem 1rem;
+ padding: 14px 16px;
cursor: pointer;
transition: box-shadow 0.2s;
border-radius: 30px;
@@ -223,11 +222,10 @@
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.15);
}
-
.questionHeader {
display: flex;
align-items: center;
- gap: 0.4rem;
+ gap: 6px;
}
.qIcon {
@@ -253,14 +251,14 @@
.questionActions {
display: flex;
align-items: center;
- gap: 0.6rem;
+ gap: 10px;
flex-shrink: 0;
- margin-left: 0.5rem;
+ margin-left: 8px;
}
.likeBtn {
- justify-content: center;
display: flex;
+ justify-content: center;
align-items: center;
gap: 7px;
border: none;
@@ -270,11 +268,10 @@
color: var(--gray600);
padding: 0 3px 0 0;
transition: color 0.15s;
- width: 37.5px;
+ width: 38px;
height: 25px;
background-color: var(--gray50);
border-radius: 10px;
-
}
.likeBtn.liked {
@@ -283,8 +280,8 @@
}
.commentBtn {
- justify-content: center;
display: flex;
+ justify-content: center;
align-items: center;
gap: 2px;
border: none;
@@ -298,10 +295,8 @@
background: var(--gray50);
width: 87px;
height: 25px;
-
}
-
.questionImage {
width: 100%;
border-radius: 8px;
@@ -310,7 +305,6 @@
display: block;
}
-
/* ── 댓글 미리보기 ── */
.commentPreview {
margin-top: 26px;
@@ -320,33 +314,27 @@
padding-left: 20px;
border-top: 1px solid var(--gray200);
padding-top: 17px;
-
}
.commentWrapper {
display: flex;
flex-direction: column;
- gap: 0.15rem;
-
+ gap: 2px;
}
-
-
.commentItem {
display: flex;
flex-direction: column;
- gap: 0.15rem;
+ gap: 2px;
border-radius: 5px 20px 20px 20px;
background: var(--gray50);
- padding: 0.5rem 0.8rem;
+ padding: 8px 13px;
max-width: 93%;
-
-
}
.commentAuthorName {
font-family: var(--font-main);
- font-size: 0.78rem;
+ font-size: 12px;
font-weight: 500;
color: var(--black);
display: flex;
@@ -362,7 +350,7 @@
.commentContent {
font-family: var(--font-main);
- font-size: 0.82rem;
+ font-size: 13px;
color: var(--black);
font-weight: 400;
line-height: normal;
@@ -371,32 +359,54 @@
gap: 4px;
}
+.commentContent svg {
+ flex-shrink: 0;
+}
+
.commentMore {
font-family: var(--font-main);
- font-size: 0.78rem;
+ font-size: 12px;
color: var(--gray600);
cursor: pointer;
text-decoration: underline;
- margin-top: 0.1rem;
+ margin-top: 2px;
}
.commentMore:hover {
color: var(--black);
}
+/* ── 댓글 이미지 미리보기 태그 ── */
+.commentImagePreview {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 4px;
+ font-family: var(--font-main);
+ font-size: 12px;
+ color: var(--gray600);
+ background: var(--gray200);
+ border-radius: 6px;
+ padding: 3px 8px;
+ width: fit-content;
+ margin-top: 4px;
+ cursor: pointer;
+ line-height: 1;
+ height: 22px;
+}
+.commentImagePreview svg {
+ display: block;
+ flex-shrink: 0;
+}
-/* ── 댓글 입력 행 ── */
+/* ── 댓글 입력창 ── */
.commentInputRow {
display: flex;
flex-direction: column;
margin-top: 19px;
}
-.commentContent svg {
- flex-shrink: 0;
-}
-
.commentInputInner {
display: flex;
align-items: center;
@@ -442,20 +452,6 @@
outline: none;
}
-.commentImagePreview {
- display: flex;
- align-items: center;
- gap: 4px;
- font-family: var(--font-main);
- font-size: 0.78rem;
- color: var(--gray600);
- background: var(--gray200);
- border-radius: 6px;
- padding: 3px 8px;
- width: fit-content;
- margin-top: 4px;
-}
-
.commentInput::placeholder {
color: var(--gray200);
}
@@ -480,8 +476,7 @@
display: block;
}
-/* ── 하단 새 질문 입력바 ── */
-
+/* ── 하단 새 질문 / 이해도 입력바 ── */
.newQuestionBar {
position: fixed;
bottom: 20px;
@@ -506,23 +501,21 @@
left: 0;
right: 0;
height: 80px;
- background: linear-gradient(to bottom,
- transparent,
- var(--white) 60%);
+ background: linear-gradient(to bottom, transparent, var(--white) 60%);
z-index: 99;
}
.errorMsg {
font-family: var(--font-main);
- font-size: 0.78rem;
+ font-size: 12px;
color: #e53935;
- margin: 0 0 0.3rem 0.5rem;
+ margin: 0 0 5px 8px;
}
.newQuestionInputRow {
display: flex;
align-items: center;
- gap: 0.5rem;
+ gap: 8px;
flex: 1;
width: 100%;
height: 40px;
@@ -557,6 +550,11 @@
height: 100%;
}
+/* 운영진일 때 왼쪽 여백 추가 (+ 버튼 없어서 간격 보정) */
+.newQuestionInputStaff {
+ padding-left: 10px;
+}
+
.newQuestionInput::placeholder {
color: var(--gray200);
}
@@ -574,12 +572,12 @@
transition: background 0.15s;
}
-
.newQuestionSubmit:disabled {
background: var(--gray200);
cursor: default;
}
+/* ── 이미지 미리보기 (질문/댓글 공통) ── */
.imagePreviewWrapper {
position: relative;
display: inline-block;
@@ -606,7 +604,7 @@
color: var(--white);
border: none;
cursor: pointer;
- font-size: 0.7rem;
+ font-size: 11px;
display: flex;
align-items: center;
justify-content: center;
diff --git a/frontend/src/pages/qna/QnAMainPage.js b/frontend/src/pages/qna/QnAMainPage.js
index 15f1720..db1dcb2 100644
--- a/frontend/src/pages/qna/QnAMainPage.js
+++ b/frontend/src/pages/qna/QnAMainPage.js
@@ -3,25 +3,19 @@ import { useNavigate } from 'react-router-dom';
import styles from './QnAMainPage.module.css';
import { FiLogIn } from 'react-icons/fi';
import { authFetch } from '../../utils/Api';
+import { getIcon, getTime, formatDate, DAY_OF_WEEK_KO, DAY_PART_KO } from '../../utils/qnaUtils';
-const DAY_PART_KO = { AM: '오전', PM: '오후' };
-
-const DAY_OF_WEEK_KO = {
- MONDAY: '월요일', TUESDAY: '화요일', WEDNESDAY: '수요일',
- THURSDAY: '목요일', FRIDAY: '금요일', SATURDAY: '토요일', SUNDAY: '일요일',
-};
-
-const formatDate = (dateStr) => dateStr?.replace(/-/g, '.') ?? '';
-const getIcon = (dayPart) => dayPart === 'AM' ? '☀' : '☾';
-const getTime = (dayPart) => dayPart === 'AM' ? '10:00 ~ 13:00' : '14:00 ~ 17:00';
function QNAMainPage() {
const navigate = useNavigate();
+
+ // ── 세션 목록 상태 ──────────────────────────────
const [activeSessions, setActiveSessions] = useState([]);
const [pastSessions, setPastSessions] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
+ // ── 세션 목록 불러오기 ──────────────────────────
useEffect(() => {
const fetchSessions = async () => {
try {
@@ -50,7 +44,7 @@ function QNAMainPage() {
return (
- {/* 진행 중인 세션 */}
+ {/* ── 진행 중인 세션 ── */}
{activeSessions.length > 0 && (
<>
@@ -60,7 +54,6 @@ function QNAMainPage() {
key={session.sessionId}
className={styles.card}
onClick={() => navigate(`/sessions/${session.sessionId}/questions`, { state: { status: 'IN_SESSION' } })}
- style={{ cursor: 'pointer' }}
>
{getIcon(session.dayPart)}
@@ -78,28 +71,34 @@ function QNAMainPage() {
>
)}
- {/* 지난 세션 */}
- {pastSessions.map(session => (
-
navigate(`/sessions/${session.sessionId}/questions`, { state: { status: 'AFTER_SESSION' } })}
- >
-
- {getIcon(session.dayPart)}
- {session.title}
-
- • {session.week}주차 {DAY_OF_WEEK_KO[session.dayOfWeek]} {DAY_PART_KO[session.dayPart]}
-
-
- navigate(`/sessions/${session.sessionId}/questions`, { state: { status: 'AFTER_SESSION' } })}
- >
-
-
-
- ))}
+ {/* ── 지난 세션 ── */}
+ {pastSessions.length > 0 && (
+
+ 지난 세션
+
+ {pastSessions.map(session => (
+
navigate(`/sessions/${session.sessionId}/questions`, { state: { status: 'AFTER_SESSION' } })}
+ >
+
+ {getIcon(session.dayPart)}
+ {session.title}
+
+ • {session.week}주차 {DAY_OF_WEEK_KO[session.dayOfWeek]} {DAY_PART_KO[session.dayPart]}
+
+
+
+
+
+
+ ))}
+
+
+ )}
- {/* 둘 다 없을 때 */}
+ {/* ── 세션 없을 때 ── */}
{activeSessions.length === 0 && pastSessions.length === 0 && (
아직 생성된 Q&A가 없어요
diff --git a/frontend/src/pages/qna/QnAMainPage.module.css b/frontend/src/pages/qna/QnAMainPage.module.css
index 655d5cb..af7ee0f 100644
--- a/frontend/src/pages/qna/QnAMainPage.module.css
+++ b/frontend/src/pages/qna/QnAMainPage.module.css
@@ -1,29 +1,30 @@
+/* ── 페이지 레이아웃 ── */
.page {
min-height: 100vh;
max-width: 880px;
margin: 0 auto;
}
-/*다가오는 세션*/
+/* ── 섹션 공통 ── */
.section {
- margin-bottom: 2rem;
+ margin-bottom: 32px;
}
.sectionTitle {
text-align: center;
font-family: var(--font-main);
- font-size: 2rem;
+ font-size: 32px;
font-weight: 700;
color: var(--black);
- padding: 1rem 10rem;
- margin-bottom: 2rem;
+ padding: 16px 160px;
+ margin-bottom: 32px;
}
-
+/* ── 진행 중인 세션 카드 ── */
.card {
background: var(--white);
border-radius: 10px;
- padding: 1rem;
+ padding: 16px;
text-align: center;
width: 356px;
height: 148px;
@@ -41,54 +42,52 @@
color: var(--dark);
}
-
.cardTitle {
font-family: var(--font-main);
- font-size: 1.5rem;
+ font-size: 24px;
font-weight: 500;
color: var(--black);
- margin: 1rem 0;
+ margin: 16px 0;
}
.cardWeek {
font-family: var(--font-main);
- font-size: 1.1rem;
+ font-size: 18px;
color: var(--gray600);
- margin: 0.5rem 0;
+ margin: 8px 0;
}
.cardDate,
.cardTime {
font-family: var(--font-main);
- font-size: 1.1rem;
+ font-size: 18px;
font-weight: 500;
color: var(--gray600);
margin: 0;
}
-
-
-/* 지난 세션 */
-.icon {
- margin-right: 1.25rem;
-}
-
+/* ── 구분선 ── */
.divider {
border: none;
border-top: 1px solid var(--gray200);
- margin: 2rem 0;
+ margin: 32px 0;
+}
+
+/* ── 지난 세션 목록 ── */
+.icon {
+ margin-right: 20px;
}
.list {
display: flex;
flex-direction: column;
- gap: 1.25rem;
+ gap: 20px;
}
.listItem {
background: var(--white);
- border-radius: 0.625rem;
- padding: 1rem 1rem;
+ border-radius: 10px;
+ padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
@@ -103,14 +102,14 @@
.listTitle {
font-family: var(--font-main);
- font-size: 1.5rem;
+ font-size: 24px;
font-weight: 500;
color: var(--black);
}
.listWeek {
font-family: var(--font-main);
- font-size: 1.125rem;
+ font-size: 18px;
font-weight: 500;
color: var(--gray600);
}
@@ -122,14 +121,13 @@
color: var(--black);
}
-
-
.listItem:hover .enterBtn {
color: var(--dark);
}
+/* ── 빈 상태 ── */
.empty {
text-align: center;
- color: #888;
+ color: var(--gray400);
margin-top: 40px;
}
\ No newline at end of file
diff --git a/frontend/src/components/qna_svg.js b/frontend/src/utils/qnaUtils.js
similarity index 93%
rename from frontend/src/components/qna_svg.js
rename to frontend/src/utils/qnaUtils.js
index b80a483..8190c0e 100644
--- a/frontend/src/components/qna_svg.js
+++ b/frontend/src/utils/qnaUtils.js
@@ -1,3 +1,6 @@
+// ── SVG 아이콘 컴포넌트 ────────────────────────────────
+
+// 댓글 아이콘
export const CommentImoji = () => ();
+// 나도 궁금해요 아이콘
export const MeCuriousToo = () => (
@@ -16,6 +20,7 @@ export const MeCuriousToo = () => (
);
+// 운영진 체크 뱃지
export const StaffCheck = () => (
@@ -24,10 +29,12 @@ export const StaffCheck = () => (
);
+// 정렬 드롭다운 화살표
export const SortBtn = () => (
);
+// 이해도 O/X 버튼
export const OBtn = () => (
@@ -46,16 +53,18 @@ export const XBtn = () => (
);
+// 댓글 화살표
export const CommentCommentArraw = () => (
);
+// 제출 버튼
export const SumitBtn = () => (
);
-
+// 이미지 미리보기 아이콘
export const ImgPreview = () => (
@@ -67,4 +76,36 @@ export const ImgPreview = () => (
-);
\ No newline at end of file
+);
+
+
+// ── 날짜/시간 유틸 ────────────────────────────────────
+
+export const DAY_PART_KO = { AM: '오전', PM: '오후' };
+export const DAY_OF_WEEK_KO = {
+ MONDAY: '월', TUESDAY: '화', WEDNESDAY: '수',
+ THURSDAY: '목', FRIDAY: '금', SATURDAY: '토', SUNDAY: '일',
+};
+export const formatDate = (dateStr) => dateStr?.replace(/-/g, '.') ?? '';
+export const getIcon = (dayPart) => dayPart === 'AM' ? '☀' : '☾';
+export const getTime = (dayPart) => dayPart === 'AM' ? '10:00 ~ 13:00' : '14:00 ~ 17:00';
+
+
+// ── 공통 유틸 함수 ────────────────────────────────────
+
+// 운영진 여부 판단 (displayName이 '운영진'으로 시작하면 true)
+export const isStaffDisplay = (displayName) => displayName?.startsWith('운영진') ?? false;
+
+// 이미지 업로드 후 서버 URL 반환
+export const uploadImage = async (file) => {
+ const formData = new FormData();
+ formData.append('file', file);
+ const token = localStorage.getItem('token');
+ const res = await fetch('/api/images', {
+ method: 'POST',
+ headers: { 'Authorization': `Bearer ${token}` },
+ body: formData,
+ });
+ const json = await res.json();
+ return json.imageUrl;
+};
\ No newline at end of file