From eb714c3a13b7545480800ee40b4b1bd0340cfec5 Mon Sep 17 00:00:00 2001 From: Son Ahyun Date: Sun, 5 Oct 2025 17:45:58 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=E2=9C=A8=20(#332)=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=20=EB=AA=A9=EB=A1=9D=EC=9D=98=20=EC=86=8C?= =?UTF-8?q?=EC=BC=93=20=EB=A1=9C=EC=A7=81=EC=9D=84=20useLectureChat=20?= =?UTF-8?q?=ED=9B=85=EC=9C=BC=EB=A1=9C=20=ED=86=B5=ED=95=A9=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=A0=84=EC=86=A1?= =?UTF-8?q?=C2=B7=ED=91=9C=EC=8B=9C=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20UI?= =?UTF-8?q?=EB=A5=BC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QuestionListSection.module.scss | 10 +- .../QuestionListSection.tsx | 112 ++++-------------- 2 files changed, 32 insertions(+), 90 deletions(-) diff --git a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss index c16fec61..38e61133 100644 --- a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss +++ b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss @@ -22,6 +22,9 @@ } .questionItem { + display: flex; + flex-direction: column; + gap: $spacing-xs; width: fit-content; padding: $spacing-sm; border: 1px solid $color-neutral-7; @@ -34,7 +37,6 @@ } .timestamp { - margin-top: $spacing-xs; font-size: $font-size-sm; color: $color-neutral-5; } @@ -46,6 +48,12 @@ font-weight: $font-weight-light; } +.teacherName { + font-size: $font-size-sm; + font-weight: $font-weight-medium; + color: $color-blue; +} + .questionInputContainer { display: flex; gap: $spacing-sm; diff --git a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx index c8c5c684..d898e1b7 100644 --- a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx +++ b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx @@ -1,5 +1,6 @@ -import React, { useEffect, useState, useRef, useCallback } from "react"; +import React, { useEffect, useState } from "react"; import { useLectureStatusStore } from "@/store/useLectureStatusStore"; +import { useLectureChat } from "@/hooks/useLectureChat"; import NoDataView from "@/components/NoDataView/NoDataView"; import { MessageCircle, Send } from "lucide-react"; import styles from "./QuestionListSection.module.scss"; @@ -13,104 +14,27 @@ export default function QuestionListSection({ lectureId: string; }) { const { lectureStatus } = useLectureStatusStore(); - const [questions, setQuestions] = useState([]); + const { messages, connected, sendMessage } = useLectureChat(lectureId); const [questionInput, setQuestionInput] = useState(""); const [loading, setLoading] = useState(true); - const socketRef = useRef(null); - - // 소켓 연결 함수 - const connectSocket = useCallback(() => { - if (socketRef.current?.readyState === WebSocket.OPEN) return; - - try { - // TODO: 실제 소켓 서버 URL로 변경 - const socketUrl = `ws://localhost:8080/ws/lecture/${lectureId}`; - socketRef.current = new WebSocket(socketUrl); - - socketRef.current.onopen = () => { - console.log("소켓 연결 성공"); - }; - - socketRef.current.onmessage = (event) => { - try { - const data = JSON.parse(event.data); - handleSocketMessage(data); - } catch (error) { - console.error("소켓 메시지 파싱 오류:", error); - } - }; - - socketRef.current.onclose = () => { - console.log("소켓 연결 종료"); - }; - - socketRef.current.onerror = (error) => { - console.error("소켓 오류:", error); - }; - } catch (error) { - console.error("소켓 연결 실패:", error); - } - }, [lectureId]); - - // 소켓 메시지 처리 함수 - const handleSocketMessage = (data: { - type: string; - question?: string; - questions?: string[]; - }) => { - switch (data.type) { - case "newQuestion": - setQuestions((prev) => [...prev, data.question || ""]); - break; - case "questionList": - setQuestions(data.questions || []); - break; - default: - console.log("알 수 없는 메시지 타입:", data.type); - } - }; // 질문 전송 함수 const sendQuestion = () => { - if (!questionInput.trim() || !socketRef.current) return; - - const message = { - type: "sendQuestion", - lectureId: lectureId, - question: questionInput.trim(), - timestamp: new Date().toISOString(), - }; + if (!questionInput.trim() || !connected) return; - socketRef.current.send(JSON.stringify(message)); + sendMessage(questionInput.trim()); setQuestionInput(""); // 입력창 초기화 }; useEffect(() => { - // TODO: API 호출로 변경 - setQuestions([ - "dd", - "AsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfasAsdfas", - "Asdfadfg", - "Asdfadfg", - ]); + // TODO: API 호출로 변경 - 기존 질문 목록 불러오기 setLoading(false); + }, [lectureId]); - // 강의 중일 때만 소켓 연결 - if (lectureStatus === "onLecture") { - connectSocket(); - } - - // 컴포넌트 언마운트 시 소켓 연결 해제 - return () => { - if (socketRef.current) { - socketRef.current.close(); - } - }; - }, [lectureId, lectureStatus, connectSocket]); - - const now = () => { + // 시간 포맷팅 함수 + const formatTime = (timestamp: string) => { try { - const date = new Date(); + const date = new Date(timestamp); const hours = date.getHours().toString().padStart(2, "0"); const minutes = date.getMinutes().toString().padStart(2, "0"); return `${hours}:${minutes}`; @@ -126,10 +50,19 @@ export default function QuestionListSection({ {lectureStatus === "onLecture" ? (
    - {questions.map((q, index) => ( + {messages.map((message, index) => (
  • -
    {q}
    -
    {now()}
    +
    +
    {message.content}
    +
    +
    + {formatTime(message.timestamp)} +
    + {message.role === "TEACHER" && ( +
    + * 강사가 보낸 메시지입니다. +
    + )}
  • ))}
@@ -143,6 +76,7 @@ export default function QuestionListSection({ icon={} onClick={sendQuestion} ariaLabel={"전송"} + disabled={!connected} />
From 582b6e37eaccf934baf9b6a14efb8327982db007 Mon Sep 17 00:00:00 2001 From: Son Ahyun Date: Sun, 5 Oct 2025 17:58:22 +0900 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=A8=20(#332)=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EC=B1=84=ED=8C=85=20=EB=AA=A9=EB=A1=9D=EC=9D=84=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8A=94=20API=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=A0=95=EC=9D=98,=20=EC=97=94=EB=93=9C=ED=8F=AC?= =?UTF-8?q?=EC=9D=B8=ED=8A=B8=20=EC=83=81=EC=88=98=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/api/lectures/fetchChattingList.ts | 19 +++++++++++++++++++ frontend/constants/endpoints.ts | 2 ++ .../types/lectures/fetchChattingListTypes.ts | 5 +++++ 3 files changed, 26 insertions(+) create mode 100644 frontend/api/lectures/fetchChattingList.ts create mode 100644 frontend/types/lectures/fetchChattingListTypes.ts diff --git a/frontend/api/lectures/fetchChattingList.ts b/frontend/api/lectures/fetchChattingList.ts new file mode 100644 index 00000000..8e1e1159 --- /dev/null +++ b/frontend/api/lectures/fetchChattingList.ts @@ -0,0 +1,19 @@ +import { axiosInstance } from "@/api/axiosInstance"; +import axios from "axios"; // 추가 +import { ENDPOINTS } from "@/constants/endpoints"; +import { ApiResponse } from "@/types/apiResponseTypes"; +import { FetchChattingListResult } from "@/types/lectures/fetchChattingListTypes"; + +export async function fetchChattingList(lectureId: string) { + try { + const response = await axiosInstance.get< + ApiResponse + >(ENDPOINTS.LECTURES.GET_CHATTING_LIST(lectureId)); + return response.data; + } catch (error: unknown) { + if (axios.isAxiosError(error) && error.response) { + return error.response.data as ApiResponse; + } + throw error; + } +} diff --git a/frontend/constants/endpoints.ts b/frontend/constants/endpoints.ts index 18ae98e7..a21129e7 100644 --- a/frontend/constants/endpoints.ts +++ b/frontend/constants/endpoints.ts @@ -72,6 +72,8 @@ export const ENDPOINTS = { `${BASE_API}/lectures/student/${lectureId}`, GET_CLASS_NAME: (lectureId: string) => `${BASE_API}/lectures/classes/${lectureId}`, + GET_CHATTING_LIST: (lectureId: string) => + `${BASE_API}/lectures/chatting/before/${lectureId}`, // 노트 관련 UPLOAD_NOTE: (classId: string) => diff --git a/frontend/types/lectures/fetchChattingListTypes.ts b/frontend/types/lectures/fetchChattingListTypes.ts new file mode 100644 index 00000000..ca6703e3 --- /dev/null +++ b/frontend/types/lectures/fetchChattingListTypes.ts @@ -0,0 +1,5 @@ +export type FetchChattingListResult = { + content: string; + timestamp: string; + role: string; +}; From fc978db254f4a5159361b100b4bd014a6cb0929f Mon Sep 17 00:00:00 2001 From: Son Ahyun Date: Sun, 5 Oct 2025 18:03:13 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=E2=9C=A8=20(#332)=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=20=EB=AA=A9=EB=A1=9D=20=EC=84=B9=EC=85=98?= =?UTF-8?q?=EC=97=90=20=EA=B3=BC=EA=B1=B0=20=EB=A9=94=EC=8B=9C=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=ED=91=9C=EC=8B=9C=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QuestionListSection.tsx | 43 ++++++++++++++++--- .../types/lectures/fetchChattingListTypes.ts | 2 +- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx index d898e1b7..1697c664 100644 --- a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx +++ b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx @@ -1,12 +1,13 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { useLectureStatusStore } from "@/store/useLectureStatusStore"; -import { useLectureChat } from "@/hooks/useLectureChat"; +import { ChatMessage, useLectureChat } from "@/hooks/useLectureChat"; import NoDataView from "@/components/NoDataView/NoDataView"; import { MessageCircle, Send } from "lucide-react"; import styles from "./QuestionListSection.module.scss"; import LoadingSpinner from "@/components/LoadingSpinner/LoadingSpinner"; import BasicInput from "@/components/Input/BasicInput/BasicInput"; import IconButton from "@/components/Button/IconButton/IconButton"; +import { fetchChattingList } from "@/api/lectures/fetchChattingList"; export default function QuestionListSection({ lectureId, @@ -17,6 +18,7 @@ export default function QuestionListSection({ const { messages, connected, sendMessage } = useLectureChat(lectureId); const [questionInput, setQuestionInput] = useState(""); const [loading, setLoading] = useState(true); + const [previousMessages, setPreviousMessages] = useState([]); // 질문 전송 함수 const sendQuestion = () => { @@ -27,10 +29,41 @@ export default function QuestionListSection({ }; useEffect(() => { - // TODO: API 호출로 변경 - 기존 질문 목록 불러오기 - setLoading(false); + let isMounted = true; + const loadPreviousMessages = async () => { + try { + const res = await fetchChattingList(lectureId); + if (!isMounted) return; + if (res.isSuccess && Array.isArray(res.result)) { + const mapped: ChatMessage[] = res.result.map((m) => ({ + senderId: null, + senderName: null, + content: m.content, + role: m.role, + timestamp: m.timestamp, + })); + setPreviousMessages(mapped); + } else { + setPreviousMessages([]); + } + } catch { + if (!isMounted) return; + setPreviousMessages([]); + } finally { + if (isMounted) setLoading(false); + } + }; + loadPreviousMessages(); + return () => { + isMounted = false; + }; }, [lectureId]); + const combinedMessages = useMemo(() => { + // 과거 메시지 이후에 실시간 메시지 순서로 노출 + return [...previousMessages, ...messages]; + }, [previousMessages, messages]); + // 시간 포맷팅 함수 const formatTime = (timestamp: string) => { try { @@ -50,7 +83,7 @@ export default function QuestionListSection({ {lectureStatus === "onLecture" ? (
    - {messages.map((message, index) => ( + {combinedMessages.map((message, index) => (
  • {message.content}
    diff --git a/frontend/types/lectures/fetchChattingListTypes.ts b/frontend/types/lectures/fetchChattingListTypes.ts index ca6703e3..f11a3ed4 100644 --- a/frontend/types/lectures/fetchChattingListTypes.ts +++ b/frontend/types/lectures/fetchChattingListTypes.ts @@ -2,4 +2,4 @@ export type FetchChattingListResult = { content: string; timestamp: string; role: string; -}; +}[]; From 7ed4d66faeb0b4bc00307fedf391aa463e1f6aed Mon Sep 17 00:00:00 2001 From: Son Ahyun Date: Mon, 6 Oct 2025 16:24:17 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=94=A7=20(#332)=20useAuthStore?= =?UTF-8?q?=EC=97=90=EC=84=9C=20refresh=5Ftoken=20=EC=BF=A0=ED=82=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/api/axiosInstance.ts | 2 +- frontend/store/resetAllStores.ts | 33 -------------------------------- frontend/store/useAuthStore.ts | 23 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 34 deletions(-) delete mode 100644 frontend/store/resetAllStores.ts diff --git a/frontend/api/axiosInstance.ts b/frontend/api/axiosInstance.ts index 2514ad49..89b3c2db 100644 --- a/frontend/api/axiosInstance.ts +++ b/frontend/api/axiosInstance.ts @@ -7,7 +7,7 @@ export const axiosInstance = axios.create({ withCredentials: true, }); -// 토큰이 필요하지 않은 API들 (로그인, 로그아웃, 회원가입, 이메일 인증) +// 토큰이 필요하지 않은 API들 (로그인, 회원가입, 이메일 인증 페이지) const noTokenRequired = [ "/users/login", "/users/signup", diff --git a/frontend/store/resetAllStores.ts b/frontend/store/resetAllStores.ts deleted file mode 100644 index 8f052884..00000000 --- a/frontend/store/resetAllStores.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useAuthStore } from "./useAuthStore"; -import { useQuizStore } from "./useQuizStore"; -import useLectureListStore from "./useLectureListStore"; -import useSelectedClassStore from "./useSelectedClassStore"; -import useClassListStore from "./useClassListStore"; -import { useSignupStore } from "./useSignupStore"; -import { useLectureStatusStore } from "./useLectureStatusStore"; - -/** - * 모든 스토어를 초기 상태로 리셋하는 함수 - * 로그아웃이나 토큰 만료 시 사용 - */ -export const resetAllStores = () => { - // 각 스토어의 reset 함수 호출 - // auth store는 직접 초기화 (무한 루프 방지) - useAuthStore.setState({ - accessToken: null, - userId: null, - role: null, - iat: null, - exp: null, - }); - localStorage.removeItem("accessToken"); - - useQuizStore.getState().reset(); - useLectureListStore.getState().reset(); - useSelectedClassStore.getState().reset(); - useClassListStore.getState().reset(); - useSignupStore.getState().reset(); - useLectureStatusStore.getState().clearLectureStatus(); - - console.log("모든 스토어가 초기화되었습니다."); -}; diff --git a/frontend/store/useAuthStore.ts b/frontend/store/useAuthStore.ts index cab5c9ad..7d6a09a8 100644 --- a/frontend/store/useAuthStore.ts +++ b/frontend/store/useAuthStore.ts @@ -6,6 +6,8 @@ import useLectureListStore from "./useLectureListStore"; import useSelectedClassStore from "./useSelectedClassStore"; import useClassListStore from "./useClassListStore"; import { useSignupStore } from "./useSignupStore"; +import { useLectureStatusStore } from "./useLectureStatusStore"; +import { useClassTitleStore } from "./useClassTitleStore"; interface AuthState { accessToken: string | null; @@ -37,6 +39,12 @@ function getInitialAuthState() { if (decodedToken.exp < currentTime) { // 토큰이 만료되었으면 localStorage에서 제거하고 초기 상태 반환 localStorage.removeItem("accessToken"); + + // refresh_token 쿠키 삭제 + if (typeof document !== "undefined") { + document.cookie = + "refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + } return { accessToken: null, userId: null, @@ -56,6 +64,12 @@ function getInitialAuthState() { } catch { // 토큰 파싱 실패 시 localStorage에서 제거하고 초기 상태 반환 localStorage.removeItem("accessToken"); + + // refresh_token 쿠키 삭제 + if (typeof document !== "undefined") { + document.cookie = + "refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + } return { accessToken: null, userId: null, @@ -131,6 +145,13 @@ export const useAuthStore = create((set, get) => ({ logout: () => { set({ accessToken: null, userId: null, role: null, iat: null, exp: null }); localStorage.removeItem("accessToken"); + + // refresh_token 쿠키 삭제 + if (typeof document !== "undefined") { + document.cookie = + "refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + } + if (refreshTimeout) clearTimeout(refreshTimeout); if (expirationCheckInterval) clearInterval(expirationCheckInterval); @@ -140,6 +161,8 @@ export const useAuthStore = create((set, get) => ({ useSelectedClassStore.getState().reset(); useClassListStore.getState().reset(); useSignupStore.getState().reset(); + useLectureStatusStore.getState().clearLectureStatus(); + useClassTitleStore.getState().clearClassTitle(); console.log("로그아웃: 모든 스토어가 초기화되었습니다."); }, From c3820f1fc0bd4a8835de7044c897dbdd58da9b49 Mon Sep 17 00:00:00 2001 From: Son Ahyun Date: Mon, 6 Oct 2025 17:02:27 +0900 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=94=A7=20(#332)=20useAuthStore?= =?UTF-8?q?=EC=97=90=EC=84=9C=20refresh=5Ftoken=20=EC=BF=A0=ED=82=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/store/useAuthStore.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/frontend/store/useAuthStore.ts b/frontend/store/useAuthStore.ts index 7d6a09a8..41e80639 100644 --- a/frontend/store/useAuthStore.ts +++ b/frontend/store/useAuthStore.ts @@ -40,11 +40,6 @@ function getInitialAuthState() { // 토큰이 만료되었으면 localStorage에서 제거하고 초기 상태 반환 localStorage.removeItem("accessToken"); - // refresh_token 쿠키 삭제 - if (typeof document !== "undefined") { - document.cookie = - "refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; - } return { accessToken: null, userId: null, @@ -65,11 +60,6 @@ function getInitialAuthState() { // 토큰 파싱 실패 시 localStorage에서 제거하고 초기 상태 반환 localStorage.removeItem("accessToken"); - // refresh_token 쿠키 삭제 - if (typeof document !== "undefined") { - document.cookie = - "refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; - } return { accessToken: null, userId: null, @@ -146,12 +136,6 @@ export const useAuthStore = create((set, get) => ({ set({ accessToken: null, userId: null, role: null, iat: null, exp: null }); localStorage.removeItem("accessToken"); - // refresh_token 쿠키 삭제 - if (typeof document !== "undefined") { - document.cookie = - "refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; - } - if (refreshTimeout) clearTimeout(refreshTimeout); if (expirationCheckInterval) clearInterval(expirationCheckInterval); From db8186c4c54593da6a2622f217ab83eb9903b76a Mon Sep 17 00:00:00 2001 From: Son Ahyun Date: Mon, 6 Oct 2025 17:09:28 +0900 Subject: [PATCH 6/9] =?UTF-8?q?=E2=9C=A8=20(#332)=20useAuthStore=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20=EC=8B=9C=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EB=90=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EC=96=B4=20=EC=B4=88=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/store/useAuthStore.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/store/useAuthStore.ts b/frontend/store/useAuthStore.ts index 41e80639..816ffe9c 100644 --- a/frontend/store/useAuthStore.ts +++ b/frontend/store/useAuthStore.ts @@ -147,6 +147,7 @@ export const useAuthStore = create((set, get) => ({ useSignupStore.getState().reset(); useLectureStatusStore.getState().clearLectureStatus(); useClassTitleStore.getState().clearClassTitle(); + useSelectedClassStore.getState().reset(); console.log("로그아웃: 모든 스토어가 초기화되었습니다."); }, From e847a36c91f06bdb5cbd64a5161996affca2ed8d Mon Sep 17 00:00:00 2001 From: Son Ahyun Date: Mon, 6 Oct 2025 17:11:45 +0900 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=94=A7=20=20(#332)=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20ID=20=EC=83=81=ED=83=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=A1=9C=EC=A7=81=EC=9D=84=20useSelectedClassStore?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LectureNoteButton/LectureNoteButton.tsx | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/frontend/app/teacher/lecture-live/[lectureId]/_components/LectureNote/LectureNoteButton/LectureNoteButton.tsx b/frontend/app/teacher/lecture-live/[lectureId]/_components/LectureNote/LectureNoteButton/LectureNoteButton.tsx index a920027f..ccdc2fc6 100644 --- a/frontend/app/teacher/lecture-live/[lectureId]/_components/LectureNote/LectureNoteButton/LectureNoteButton.tsx +++ b/frontend/app/teacher/lecture-live/[lectureId]/_components/LectureNote/LectureNoteButton/LectureNoteButton.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useParams } from "next/navigation"; import IconButton from "@/components/Button/IconButton/IconButton"; import ToolPopover from "../../ToolPopover/ToolPopover"; @@ -10,6 +10,7 @@ import { FileText } from "lucide-react"; import { fetchLectureNoteByLectureId } from "@/api/lectures/fetchLectureNoteByLectureId"; import { FetchLectureNoteByLectureIdResult } from "@/types/lectures/fetchLectureNoteByLectureIdTypes"; import styles from "./LectureNoteButton.module.scss"; +import useSelectedClassStore from "@/store/useSelectedClassStore"; export default function LectureNoteButton() { const { lectureId } = useParams<{ lectureId: string }>(); @@ -17,22 +18,12 @@ export default function LectureNoteButton() { const [openDoc, setOpenDoc] = useState(false); const [uploadOpen, setUploadOpen] = useState(false); - const [classId, setClassId] = useState(null); - const [lectureNotes, setLectureNotes] = useState([]); + const [lectureNotes, setLectureNotes] = useState< + FetchLectureNoteByLectureIdResult[] + >([]); + const { selectedClassId } = useSelectedClassStore(); - useEffect(() => { - const raw = localStorage.getItem("class-storage"); - if (raw) { - try { - const parsed = JSON.parse(raw); - setClassId(parsed.state?.selectedClassId ?? null); - } catch (err) { - console.error("class-storage 파싱 실패:", err); - } - } - }, []); - - const fetchLectureNotes = async () => { + const fetchLectureNotes = useCallback(async () => { try { const response = await fetchLectureNoteByLectureId(lectureId); if (response.isSuccess && response.result) { @@ -45,11 +36,11 @@ export default function LectureNoteButton() { console.error("강의자료 조회 오류:", err); setLectureNotes([]); } - }; + }, [lectureId]); useEffect(() => { if (lectureId) fetchLectureNotes(); - }, [lectureId]); + }, [lectureId, fetchLectureNotes]); return ( <> @@ -69,15 +60,15 @@ export default function LectureNoteButton() { side="bottom" > setOpenDoc(false)} - onUploadRequest={() => setUploadOpen(true)} + notes={lectureNotes} + onPicked={() => setOpenDoc(false)} + onUploadRequest={() => setUploadOpen(true)} /> - {uploadOpen && classId && ( + {uploadOpen && selectedClassId && ( setUploadOpen(false)} registeredFiles={lectureNotes.map((n) => n.lectureNoteName)} @@ -89,4 +80,4 @@ export default function LectureNoteButton() { )} ); -} \ No newline at end of file +} From d181c2d8d1fe1e4a8be8e84bcad5372880b5f8d8 Mon Sep 17 00:00:00 2001 From: Son Ahyun Date: Mon, 6 Oct 2025 17:36:05 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9C=A8=20(#332)=20=EA=B0=95=EC=9D=98=20?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=20=EB=AA=A9=EB=A1=9D=20=EC=84=B9=EC=85=98?= =?UTF-8?q?=EC=9D=98=20=EB=A7=88=EC=A7=80=EB=A7=89=20=EC=A7=88=EB=AC=B8?= =?UTF-8?q?=EC=97=90=20=EC=97=AC=EB=B0=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QuestionListSection/QuestionListSection.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss index 38e61133..57bfe001 100644 --- a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss +++ b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss @@ -33,6 +33,7 @@ &:last-child { border-bottom: none; + margin-bottom: $spacing-sm; } } From beb0b6ac8f19847747b30157c327dea464f7bc0c Mon Sep 17 00:00:00 2001 From: Son Ahyun Date: Mon, 6 Oct 2025 17:42:32 +0900 Subject: [PATCH 9/9] =?UTF-8?q?=E2=9C=A8=20(#332)=20=ED=95=AD=EC=83=81=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=20=EC=B1=84=ED=8C=85=EC=9D=B4=20=EB=B3=B4?= =?UTF-8?q?=EC=9D=B4=EB=8F=84=EB=A1=9D=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QuestionListSection/QuestionListSection.module.scss | 4 ++++ .../QuestionListSection/QuestionListSection.tsx | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss index 57bfe001..f70ac5ca 100644 --- a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss +++ b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.module.scss @@ -37,6 +37,10 @@ } } +.bottomSpacer { + height: 1px; +} + .timestamp { font-size: $font-size-sm; color: $color-neutral-5; diff --git a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx index 1697c664..193d1329 100644 --- a/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx +++ b/frontend/app/student/lecture-detail/[lectureId]/_components/QuestionListSection/QuestionListSection.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { useLectureStatusStore } from "@/store/useLectureStatusStore"; import { ChatMessage, useLectureChat } from "@/hooks/useLectureChat"; import NoDataView from "@/components/NoDataView/NoDataView"; @@ -19,6 +19,7 @@ export default function QuestionListSection({ const [questionInput, setQuestionInput] = useState(""); const [loading, setLoading] = useState(true); const [previousMessages, setPreviousMessages] = useState([]); + const listEndRef = useRef(null); // 질문 전송 함수 const sendQuestion = () => { @@ -64,6 +65,11 @@ export default function QuestionListSection({ return [...previousMessages, ...messages]; }, [previousMessages, messages]); + // 새로운 메시지가 추가될 때 항상 맨 아래로 스크롤 + useEffect(() => { + listEndRef.current?.scrollIntoView({ behavior: "smooth", block: "end" }); + }, [combinedMessages]); + // 시간 포맷팅 함수 const formatTime = (timestamp: string) => { try { @@ -98,6 +104,7 @@ export default function QuestionListSection({ )}
  • ))} +