Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
88c00b2
✨ (#305) 강의 상세 페이지 및 정보 섹션 컴포넌트 추가, 스타일 적용
iinuyha Sep 6, 2025
6af5c66
✨ (#305) 강의 제목 상태 관리 스토어 추가 및 강의 상세 페이지와 레이아웃에 통합
iinuyha Sep 6, 2025
96fc162
✨ (#305) 강의 상세 페이지에 탭 기능 추가 및 탭에 따라 섹션 렌더링 분기처리
iinuyha Sep 6, 2025
f638fab
🔨 (#305) 헤더에서 lectureTitle이 아닌 classTitle 저장 후 불러오기
iinuyha Sep 6, 2025
db51702
✨ (#305) 강의 상세 페이지에 콘텐츠 영역 추가 및 스타일 수정
iinuyha Sep 6, 2025
2dd9e50
✨ (#305) 강의 상세 페이지에 강의 녹음 섹션 추가 및 상태 관리 스토어 통합
iinuyha Sep 7, 2025
f4d8982
✨ (#305) 강의 상세 페이지에 질문 목록 섹션 추가 및 소켓 연결 준비
iinuyha Sep 7, 2025
ade2123
✨ (#305) recordSection에 height 100% 추가
iinuyha Sep 7, 2025
023aaae
✨ (#305) FileDisplay 컴포넌트에 파일 크기 표시 기능 추가 및 스타일 수정
iinuyha Sep 7, 2025
9a04e96
✨ (#305) 강의 상세 페이지의 LectureNoteListSection 및 QuizSection에 lectureId p…
iinuyha Sep 7, 2025
52e36e3
✨ (#305) 강의 자료 다운로드 기능 추가 및 스타일 수정
iinuyha Sep 7, 2025
7a309c3
✨ (#305) 강의 노트 리스트 섹션의 높이를 100%로 설정하여 레이아웃 개선
iinuyha Sep 7, 2025
b0d574f
✨ (#305) 강의 자료 및 녹음 파일 다운로드 기능을 downloadUtils 유틸리티로 통합하여 코드 중복 제거 및 에…
iinuyha Sep 7, 2025
41c3e0b
✨ (#305) 학생용 강의 상세 정보 api 연동
iinuyha Sep 20, 2025
223e37f
🔨 (#305) LecrureRecordSection에서 강의 상태에 따른 상태 추가
iinuyha Sep 20, 2025
0602714
💄 (#305) overflow-y scroll에서 auto로 변경
iinuyha Sep 28, 2025
2da9672
🔧 (#305) 강의 상태 관련 타입을 통합하여 코드 정리 및 개선
iinuyha Sep 28, 2025
1ed5dd3
✨ (#305)강의 상태에 따른 퀴즈 상태 관리 로직 추가
iinuyha Sep 28, 2025
37a908d
🔧 (#305) 강의 정보 섹션에서 lectureDate 상태 추가 및 관련 로직 수정
iinuyha Sep 28, 2025
d0e6489
✨ (#305) 퀴즈 목록 받아오기 api 함수 + dto 설정
iinuyha Sep 28, 2025
e1b10c2
✨ (#305) 퀴즈 제출 API 함수 및 관련 타입 추가
iinuyha Sep 28, 2025
f24fefd
✨ (#305) 퀴즈 결과 조회 API 함수 및 관련 타입 추가
iinuyha Sep 28, 2025
4a64009
✨ (#305) 퀴즈 섹션 컴포넌트 추가 및 스타일링, 퀴즈 데이터 로드 로직 구현
iinuyha Sep 28, 2025
b08afb5
:recycle: (#328) 퀴즈 문제 정렬
mumminn Sep 28, 2025
7ab9a29
✨ (#305) 퀴즈 섹션 스타일 개선 및 상태에 따른 UI 업데이트, 퀴즈 제출 버튼 추가
iinuyha Sep 28, 2025
983ec00
✨ (#305) 퀴즈 제출 기능 추가 및 결과 모달 구현, 퀴즈 섹션 리프레시 기능 추가
iinuyha Sep 28, 2025
5d09aaf
✨ (#305) 강의 목록 섹션에서 교수 이름 및 강의 날짜 정보를 동적으로 표시하도록 수정
iinuyha Sep 28, 2025
b02995a
✨ (#305) 강의 상세 페이지에서 lectureId를 기반으로 클래스 이름을 불러오는 API 함수 추가 및 관련 로직 구현
iinuyha Sep 28, 2025
63bd5ad
🔨 (#305) 퀴즈 섹션의 상태 타입에 'before' 추가 및 상태에 따른 UI 로직 수정
iinuyha Sep 28, 2025
4311733
✨ (#305) 퀴즈 섹션에 퀴즈 결과 데이터 로드 기능 추가 및 UI 개선, 불필요한 카운트 정보 제거
iinuyha Oct 5, 2025
d346412
🔧 (#305) 강의 ID에 따라 클래스 이름을 가져오는 API 엔드포인트 수정 및 관련 상수 업데이트
iinuyha Oct 5, 2025
9ecf664
✨ (#332) 강의 질문 목록의 소켓 로직을 useLectureChat 훅으로 통합하고 메시지 전송·표시 로직 및 UI를 개선
iinuyha Oct 5, 2025
841cfd8
✨ (#332) 강의 채팅 목록을 가져오는 API 함수 추가 및 관련 타입 정의, 엔드포인트 상수 업데이트
iinuyha Oct 5, 2025
89d761b
✨ (#332) 강의 질문 목록 섹션에 과거 메시지를 불러오는 기능 추가 및 메시지 표시 로직 개선
iinuyha Oct 5, 2025
f8904ea
🔧 (#332) useAuthStore에서 refresh_token 쿠키 삭제 로직 추가
iinuyha Oct 6, 2025
8ab4b28
🔧 (#332) useAuthStore에서 refresh_token 쿠키 삭제 로직 제거
iinuyha Oct 6, 2025
627783d
✨ (#332) useAuthStore에서 로그아웃 시 선택된 클래스 스토어 초기화 로직 추가
iinuyha Oct 6, 2025
916e153
🔧 (#332) 클래스 ID 상태 관리 로직을 useSelectedClassStore 사용으로 변경
iinuyha Oct 6, 2025
53d1ef2
✨ (#332) 강의 질문 목록 섹션의 마지막 질문에 여백 추가
iinuyha Oct 6, 2025
f0c2ca4
✨ (#332) 항상 최신 채팅이 보이도록 스크롤 추가
iinuyha Oct 6, 2025
8fe88b6
✨ (#336) 강의 종료시 채팅 저장 API 연동
mumminn Oct 6, 2025
f11ff1e
📦 (#336) 채팅 파일명 변경
mumminn Oct 6, 2025
9b0e279
✨ (#336) 이전 채팅 불러오도록 추가
mumminn Oct 6, 2025
ecb1519
✨ (#336) 학생 채팅일 경우 이벤트 발생
mumminn Oct 6, 2025
e82e0c7
🐛 (#336) 빌드 에러 수정
mumminn Oct 9, 2025
9eec310
:sparkles: (#315) 채팅 내용 저장 로직 설계
sunninz Sep 18, 2025
45ab082
:fire: (#336) 잘못된 충돌 해결 코드 삭제 및 dev 기준으로 수정
mumminn Oct 9, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,31 @@ public class QuizConverter {
private final OptionRepository optionRepository;

public QuizListResponseDTO toQuizListResponseDTO(UUID lectureId, List<Quiz> quizList) {
List<QuizListResponseDTO.QuizDTO> quizDTOs = quizList.stream().map(quiz -> {
List<OptionResponseDTO> options = new ArrayList<>();
if (quiz.getType() == QuizType.MULTIPLE_CHOICE) {
options = optionRepository.findByQuizId(quiz.getId())
.stream()
.map(option -> new OptionResponseDTO(
option.getId(),
option.getOptionOrder(),
option.getText()
))
.toList();
}
return new QuizListResponseDTO.QuizDTO(
quiz.getId(),
quiz.getQuizOrder(),
quiz.getQuiz(),
quiz.getSolution(),
QuizResultStudentConverter.toCamelCase(quiz.getType()),
options
);
}).toList();
List<QuizListResponseDTO.QuizDTO> quizDTOs = quizList.stream()
.sorted((q1, q2) -> Integer.compare(q1.getQuizOrder(), q2.getQuizOrder()))
.map(quiz -> {
List<OptionResponseDTO> options = new ArrayList<>();
if (quiz.getType() == QuizType.MULTIPLE_CHOICE) {
options = optionRepository.findByQuizId(quiz.getId())
.stream()
.map(option -> new OptionResponseDTO(
option.getId(),
option.getOptionOrder(),
option.getText()
))
.sorted((o1, o2) -> Integer.compare(o1.getOptionOrder(), o2.getOptionOrder()))
.toList();
}
return new QuizListResponseDTO.QuizDTO(
quiz.getId(),
quiz.getQuizOrder(),
quiz.getQuiz(),
quiz.getSolution(),
QuizResultStudentConverter.toCamelCase(quiz.getType()),
options
);
})
.toList();

return new QuizListResponseDTO(lectureId, quizDTOs);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/api/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const axiosInstance = axios.create({
withCredentials: true,
});

// 토큰이 필요하지 않은 API들 (로그인, 로그아웃, 회원가입, 이메일 인증)
// 토큰이 필요하지 않은 API들 (로그인, 회원가입, 이메일 인증 페이지)
const noTokenRequired = [
"/users/login",
"/users/signup",
Expand Down
20 changes: 20 additions & 0 deletions frontend/api/classes/fetchClassNameByLectureId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { axiosInstance } from "@/api/axiosInstance";
import axios from "axios";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";
import { FetchClassNameByLectureIdResult } from "@/types/classes/fetchClassNameByLectureIdTypes";

export async function fetchClassNameByLectureId(lectureId: string) {
try {
const response = await axiosInstance.get<
ApiResponse<FetchClassNameByLectureIdResult>
>(ENDPOINTS.LECTURES.GET_CLASS_NAME(lectureId));
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response
.data as ApiResponse<FetchClassNameByLectureIdResult>;
}
throw error;
}
}
19 changes: 19 additions & 0 deletions frontend/api/lectures/fetchChattingList.ts
Original file line number Diff line number Diff line change
@@ -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<FetchChattingListResult>
>(ENDPOINTS.LECTURES.GET_CHATTING_LIST(lectureId));
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response.data as ApiResponse<FetchChattingListResult>;
}
throw error;
}
}
20 changes: 20 additions & 0 deletions frontend/api/lectures/fetchStudentLectureDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { axiosInstance } from "@/api/axiosInstance";
import axios from "axios";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";
import { FetchStudentLectureDetailResult } from "@/types/lectures/fetchStudentLectureDetailTypes";

export async function fetchStudentLectureDetail(lectureId: string) {
try {
const response = await axiosInstance.get<
ApiResponse<FetchStudentLectureDetailResult>
>(ENDPOINTS.LECTURES.GET_STUDENT_LECTURE_DETAIL(lectureId));
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response
.data as ApiResponse<FetchStudentLectureDetailResult>;
}
throw error;
}
}
19 changes: 19 additions & 0 deletions frontend/api/lectures/saveChatting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from "axios";
import { axiosInstance } from "@/api/axiosInstance";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";

export async function saveChatting( lectureId: string ) {
try {
const response = await axiosInstance.post<ApiResponse<null>>(
ENDPOINTS.LECTURES.SAVE_CHAT(lectureId)
);

return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response.data as ApiResponse<null>;
}
throw error;
}
}
19 changes: 19 additions & 0 deletions frontend/api/quizzes/fetchQuizList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from "axios";
import { axiosInstance } from "@/api/axiosInstance";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";
import { fetchQuizListResult } from "@/types/quizzes/fetchQuizListTypes";

export async function fetchQuizList(lectureId: string) {
try {
const response = await axiosInstance.get<ApiResponse<fetchQuizListResult>>(
ENDPOINTS.QUIZZES.GET(lectureId)
);
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response.data as ApiResponse<fetchQuizListResult>;
}
throw error;
}
}
19 changes: 19 additions & 0 deletions frontend/api/quizzes/getMyQuizResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from "axios";
import { axiosInstance } from "@/api/axiosInstance";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";
import { getMyQuizResultResult } from "@/types/quizzes/getMyQuizResultTypes";

export async function getMyQuizResult(lectureId: string) {
try {
const response = await axiosInstance.get<
ApiResponse<getMyQuizResultResult>
>(ENDPOINTS.QUIZZES.GET_RESULT(lectureId));
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response.data as ApiResponse<getMyQuizResultResult>;
}
throw error;
}
}
23 changes: 23 additions & 0 deletions frontend/api/quizzes/submitQuiz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import axios from "axios";
import { axiosInstance } from "@/api/axiosInstance";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";
import {
SubmitQuizRequest,
SubmitQuizResult,
} from "@/types/quizzes/submitQuizTypes";

export async function submitQuiz(data: SubmitQuizRequest) {
try {
const response = await axiosInstance.post<
ApiResponse<SubmitQuizResult | null>
>(ENDPOINTS.QUIZZES.SUBMIT, data);

return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response.data as ApiResponse<SubmitQuizResult | null>;
}
throw error;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@
border: 1px solid $color-neutral-7;
border-radius: $radius-md;
background-color: $color-white;
cursor: pointer;
transition: background-color 0.3s ease;
&:hover {
background-color: $color-skyblue;
}
}

.fileItem > :first-child {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { useParams } from "next/navigation";
import { fetchLectureNotesByClass } from "@/api/lectures/fetchLectureNotesByClass";
import FileDisplay from "@/components/FileDisplay/FileDisplay";
import LoadingSpinner from "@/components/LoadingSpinner/LoadingSpinner";
import IconButton from "@/components/Button/IconButton/IconButton";
import { Download } from "lucide-react";
import { FetchLectureNoteByLectureIdResult } from "@/types/lectures/fetchLectureNoteByLectureIdTypes";
import { downloadFileWithErrorHandling } from "@/utils/downloadUtils";

interface LectureNote {
lectureNoteId: string;
Expand Down Expand Up @@ -44,24 +48,28 @@ export default function LectureNote() {
);
}

const handleNoteClick = (lectureNoteUrl: string) => {
window.open(lectureNoteUrl, "_blank");
const handleDownload = async (note: FetchLectureNoteByLectureIdResult) => {
await downloadFileWithErrorHandling(
note.lectureNoteUrl,
note.lectureNoteName || "강의자료"
);
};

return (
<div className={styles.container}>
{lectureNotes.length > 0 ? (
<div className={styles.fileList}>
{lectureNotes.map((note) => (
<div
key={note.lectureNoteId}
className={styles.fileItem}
onClick={() => handleNoteClick(note.lectureNoteUrl)}
>
<FileDisplay fileName={note.lectureNoteName} />
<div className={styles.fileInfo}>
<span className={styles.fileSize}>{note.fileSize}</span>
</div>
<div key={note.lectureNoteId} className={styles.fileItem}>
<FileDisplay
fileName={note.lectureNoteName}
size={note.fileSize}
/>
<IconButton
icon={<Download />}
onClick={() => handleDownload(note)}
ariaLabel={"강의자료 다운로드"}
/>
</div>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,24 @@ export default function ClassListSection() {
<div className={styles.classHeader}>
<div className={styles.classTitle}>
<span className={styles.className}>{classItem.className}</span>
<span className={styles.teacherName}>박재성</span>
<span className={styles.teacherName}>
{classItem.professorName}
</span>
</div>
<ChevronRight size={20} className={styles.arrowIcon} />
</div>

<div className={styles.classDetails}>
<div className={styles.detailItem}>
<Clock size={16} />
<span>월 (10:15~11:45)/수 (12:00~13:15)</span>
<span>{classItem.classDate}</span>
</div>

<div className={styles.detailItem}>
<Calendar size={16} />
<span>2024.03.04 ~ 2025.06.13</span>
<span>
{classItem.startDate} ~ {classItem.endDate}
</span>
</div>
</div>
</div>
Expand Down
6 changes: 5 additions & 1 deletion frontend/app/student/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import BackWithTitleHeader from "@/components/Header/Student/BackWithTitleHeader
import TitleHeader from "@/components/Header/Student/TitleHeader/TitleHeader";

import Navigation from "@/components/Navigation/Navigation";
import { useClassTitleStore } from "@/store/useClassTitleStore";

export default function StudentLayout({
children,
}: {
children: React.ReactNode;
}) {
const pathname = usePathname();
const { classTitle } = useClassTitleStore();

// 현재 경로에 해당하는 라우트 설정 찾기
const currentRoute = Object.values(STUDENT_ROUTE_CONFIG).find((config) => {
Expand All @@ -38,7 +40,9 @@ export default function StudentLayout({
case StudentHeaderType.TITLE:
return <TitleHeader title={currentRoute.title || ""} />;
case StudentHeaderType.BACK_WITH_TITLE:
return <BackWithTitleHeader title={currentRoute.title || ""} />;
return (
<BackWithTitleHeader title={currentRoute.title || classTitle || ""} />
);
case StudentHeaderType.BACK_WITH_PROFILE:
return <BackWithProfileHeader />;
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.recordSection {
height: 100%;
}

.card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 16px;
}

.title {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
color: #333;
}

.audioItem {
display: flex;
flex-direction: column;
gap: 12px;
}

.audioName {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}

.audioPlayer {
width: 100%;
height: 40px;
}
Loading