Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 155 additions & 78 deletions front-end/src/pages/professor/course/classroom/ProfessorClassroom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) => {
Expand Down Expand Up @@ -60,17 +71,136 @@ const ProfessorClassroom = () => {

const isUploadingRef = useRef(false);
const reactionsRef = useRef(reactionsCount);
const sseRef = useRef<EventSource | null>(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]);
Expand All @@ -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 (
<>
<div className={S.professorClassroom}>
{courseInfo && (
<PDFMainComponent
courseInfo={courseInfo}
setIsUploading={setIsUploading}
setModal={setModal}
courseId={Number(courseId)}
closeModal={closeModal}
reactionsCount={reactionsCount}
reactions={reactions}
dispatch={dispatch}
openModal={openModal}
/>
)}
<PDFMainComponent
courseInfo={courseInfo}
setIsUploading={setIsUploading}
setModal={setModal}
closeModal={closeModal}
reactionsCount={reactionsCount}
reactions={reactions}
dispatch={dispatch}
openModal={openModal}
popupError={popupError}
/>
<PDFSideBar
questions={questions}
requests={requests}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,31 @@ import ExpandSvg from '@/assets/icons/expansion.svg?react';
import usePDF from '@/hooks/usePDF';
import AccessCodeModal from './AccessCodeModal';
import { Course, Reaction, ReactionType } from '@/core/model';
import { professorRepository } from '@/di';
import { courseRepository } from '@/di';
import { Action } from '../ProfessorClassroom';

type PDFMainComponentProps = {
courseInfo: Course;
courseInfo: Course | null;
setIsUploading: React.Dispatch<React.SetStateAction<boolean>>;
setModal: React.Dispatch<React.SetStateAction<ReactNode>>;
courseId: number;
closeModal: () => void;
openModal: () => void;
reactions: ReactionType[];
reactionsCount: Record<Reaction, number>;
dispatch: React.Dispatch<Action>;
popupError: (error: unknown) => void;
};

const PDFMainComponent = ({
courseInfo,
setIsUploading,
setModal,
courseId,
closeModal,
openModal,
reactions,
reactionsCount,
dispatch,
popupError,
}: PDFMainComponentProps) => {
const {
scale,
Expand All @@ -47,15 +47,24 @@ const PDFMainComponent = ({

const fileInputRef = useRef<HTMLInputElement>(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();
Expand Down Expand Up @@ -83,8 +92,8 @@ const PDFMainComponent = ({
// 해당강의의 pdf가 변경되었다는것을 알려야함
setIsUploading(false);
} catch (error) {
//추후 에러 핸들링
console.log(error);
setIsUploading(false);
popupError(error);
}
}
}
Expand All @@ -93,7 +102,7 @@ const PDFMainComponent = ({
<div className={S.mainContainer}>
<div className={S.mainHeader}>
<div className={S.accessCodeContainer}>
<div className={S.accessCode}>입장코드 {courseInfo.accessCode}</div>
<div className={S.accessCode}>입장코드 {courseInfo?.accessCode}</div>
<button
className={S.expandCode}
onClick={() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ type RequestBoxProps = {
};

const EmojiType = {
hard: WishEmoji,
fast: WindEmoji,
question: BulbEmoji,
size: MagnifierEmoji,
sound: EarEmoji,
[RequestHard.kind]: WishEmoji,
[RequestFast.kind]: WindEmoji,
[RequestQuestion.kind]: BulbEmoji,
[RequestSize.kind]: MagnifierEmoji,
[RequestSound.kind]: EarEmoji,
};

const RequestBox = ({ request }: RequestBoxProps) => {
Expand Down
Loading