diff --git a/front-end/src/pages/professor/course/classroom/ProfessorClassroom.tsx b/front-end/src/pages/professor/course/classroom/ProfessorClassroom.tsx index 794f644..e524c0d 100644 --- a/front-end/src/pages/professor/course/classroom/ProfessorClassroom.tsx +++ b/front-end/src/pages/professor/course/classroom/ProfessorClassroom.tsx @@ -9,7 +9,7 @@ import { useTransition, } from 'react'; import { classroomRepository, courseRepository } from '@/di'; -import { useParams } from 'react-router'; +import { useNavigate, useParams } from 'react-router'; import useModal from '@/hooks/useModal'; import { @@ -18,20 +18,31 @@ import { Reaction, ReactionType, Requests, + RequestType, } from '@/core/model'; import QuestionModal from './components/question/QuestionModal'; import PDFMainComponent from './components/PDFMainComponent'; import PDFSideBar from './components/PDFSideBar'; +import ProfessorError from '@/utils/professorError'; export type Action = | { type: 'ADD'; payload: Reaction } | { type: 'REMOVE'; payload: string }; +const SSE_URL = import.meta.env.VITE_API_URL; + const initialReactionsCount = () => { const storageCounts = localStorage.getItem('reactions'); return storageCounts ? JSON.parse(storageCounts) - : { clap: 0, cry: 0, like: 0, okay: 0, scream: 0, thumb: 0 }; + : { + CLAP: 0, + CRYING: 0, + HEART_EYES: 0, + OKAY: 0, + SURPRISED: 0, + THUMBS_UP: 0, + }; }; const reactionReducer = (state: ReactionType[], action: Action) => { @@ -60,17 +71,136 @@ const ProfessorClassroom = () => { const isUploadingRef = useRef(false); const reactionsRef = useRef(reactionsCount); + const sseRef = useRef(null); const [isPending, startTransition] = useTransition(); const { openModal, closeModal, Modal } = useModal(); + const navigate = useNavigate(); + + const popupError = ProfessorError({ + setModal, + openModal, + closeModal, + navigate, + }); + + const handleReaction = (type: Reaction) => { + startTransition(() => { + dispatch({ type: 'ADD', payload: type }); + reactionsRef.current = { + ...reactionsRef.current, + [type]: (reactionsRef.current[type] || 0) + 1, + }; + setReactionsCount(reactionsRef.current); + localStorage.setItem('reactions', JSON.stringify(reactionsRef.current)); + }); + }; + + const handleRequest = (request: RequestType) => { + setRequests( + (prev) => + prev.map((req) => + req.type.kind === request ? { ...req, count: req.count + 1 } : req + ) as Requests + ); + }; + + const handleQuestion = (question: Question) => { + setQuestions((prev) => [...prev, question]); + }; + + const handleQuestionCheck = (id: Question['id']) => { + const updatedQuestions = questions.filter((question) => question.id !== id); + + setQuestions(updatedQuestions); + }; + + const connectSSE = () => { + if (sseRef.current) { + return; + } + + const eventSource = new EventSource( + `${SSE_URL}/sse/connection/course/${courseId}`, + { + withCredentials: true, + } + ); + + eventSource.onopen = () => { + sseRef.current = eventSource; + }; + + eventSource.onmessage = (e) => { + const data = JSON.parse(e.data); + + if (data.messageType === 'REACTION') { + handleReaction(data.data.type); + } else if (data.messageType === 'REQUEST') { + handleRequest(data.data.type); + } else if (data.messageType === 'QUESTION') { + handleQuestion(data.data); + } else if (data.messageType === 'QUESTION_CHECK') { + handleQuestionCheck(data.data.id); + } else { + return; + } + }; + + eventSource.onerror = () => { + sseRef.current?.close(); + sseRef.current = null; + connectSSE(); + }; + }; + + // 교수 질문 체크 + const handleResolveClick = async (id: Question['id']) => { + try { + await classroomRepository.checkQuestionByProfessor(id); + const updatedQuestions = questions.filter( + (question) => question.id !== id + ); + + setQuestions(updatedQuestions); + } catch (error) { + popupError(error); + } + }; + + //강의 종료 + const handleCloseClass = () => { + if (!courseId) { + return; + } + + try { + sseRef.current?.close(); + sseRef.current = null; + classroomRepository.closeCourse(courseId); + navigate('/professor'); + } catch (error) { + popupError(error); + } + }; // 가장 처음 course 정보 받아오기 useEffect(() => { async function fetchCourse() { - const course = await courseRepository.getCourseById(Number(courseId)); - setCourseInfo(course); - setQuestions(course.questions); + try { + if (!courseId) { + navigate('/professor'); + return; + } + const course = await courseRepository.getCourseById(courseId); + console.log(course); + setCourseInfo(course); + setQuestions(course.questions); + setRequests(course.requests); + } catch (error) { + popupError(error); + } } fetchCourse(); }, [courseId]); @@ -93,84 +223,31 @@ const ProfessorClassroom = () => { isUploadingRef.current = isUploading; }, [isUploading]); - //예상SSE 연결 - // useEffect(() => { - // const eventSource = new EventSource(`sse 주소 `); - - // eventSource.onmessage = (event) => { - // const data = JSON.parse(event.data); - - // if (data.type === 'reaction') { - // if (isUploadingRef.current) { - // // isUploading이 true일 때는 우선순위를 낮춰서 처리 - // dispatch({ type: 'ADD', payload: data.payload }); - // startTransition(() => { - // reactionsRef.current = { - // ...reactionsRef.current, - // [data.payload]: (reactionsRef.current[data.payload] || 0) + 1, - // }; - // setReactionsCount(reactionsRef.current); - // localStorage.setItem( - // 'reactions', - // JSON.stringify(reactionsRef.current) - // ); - // }); - // } else { - // // isUploading이 false일 때는 일반 처리 - // dispatch({ type: 'ADD', payload: data.payload }); - // reactionsRef.current = { - // ...reactionsRef.current, - // [data.payload]: (reactionsRef.current[data.payload] || 0) + 1, - // }; - // setReactionsCount(reactionsRef.current); - // localStorage.setItem( - // 'reactions', - // JSON.stringify(reactionsRef.current) - // ); - // } - // } else if (data.type === 'question') { - // setQuestions((prev) => [...prev, data.payload]); - // } else if (data.type === 'request') { - // setRequests((prev) => [...prev, data.payload]); - // } - // }; - - // eventSource.onerror = () => { - // eventSource.close(); - // }; - - // return () => { - // eventSource.close(); - // }; - // }, [courseId]); - - // 교수 질문 체크 - const handleResolveClick = async (id: Question['id']) => { - const updatedQuestions = questions.filter((question) => question.id !== id); - await classroomRepository.checkQuestionByProfessor(id); - - setQuestions(updatedQuestions); - }; + useEffect(() => { + if (!sseRef.current) { + connectSSE(); + } - //강의 종료 - const handleCloseClass = () => {}; + return () => { + sseRef.current?.close(); + sseRef.current = null; + }; + }, []); return ( <>
- {courseInfo && ( - - )} + >; setModal: React.Dispatch>; - courseId: number; closeModal: () => void; openModal: () => void; reactions: ReactionType[]; reactionsCount: Record; dispatch: React.Dispatch; + popupError: (error: unknown) => void; }; const PDFMainComponent = ({ courseInfo, setIsUploading, setModal, - courseId, closeModal, openModal, reactions, reactionsCount, dispatch, + popupError, }: PDFMainComponentProps) => { const { scale, @@ -47,15 +47,24 @@ const PDFMainComponent = ({ const fileInputRef = useRef(null); - // 처음 저장된 pdf 받아오기 useEffect(() => { async function fetchPDF() { - const pdfUrl = await professorRepository.getProfessorPDF(courseId); - setPDF(pdfUrl); - loadPdf(pdfUrl); + try { + if (!courseInfo) { + return; + } + const pdfUrl = await courseRepository.getCourseFileUrl(courseInfo.id); + if (!pdfUrl) { + return; + } + setPDF(pdfUrl); + loadPdf(pdfUrl); + } catch (error) { + popupError(error); + } } fetchPDF(); - }, [courseId, courseInfo]); + }, [courseInfo]); const handleCloseModal = () => { closeModal(); @@ -83,8 +92,8 @@ const PDFMainComponent = ({ // 해당강의의 pdf가 변경되었다는것을 알려야함 setIsUploading(false); } catch (error) { - //추후 에러 핸들링 - console.log(error); + setIsUploading(false); + popupError(error); } } } @@ -93,7 +102,7 @@ const PDFMainComponent = ({
-
입장코드 {courseInfo.accessCode}
+
입장코드 {courseInfo?.accessCode}