Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4e965f8
Merge branch 'Feat/#280/class-detail' of https://github.com/KW-ClassL…
iinuyha Sep 5, 2025
247f534
✨ (#305) 강의 상세 페이지 및 정보 섹션 컴포넌트 추가, 스타일 적용
iinuyha Sep 6, 2025
051cf0c
✨ (#305) 강의 제목 상태 관리 스토어 추가 및 강의 상세 페이지와 레이아웃에 통합
iinuyha Sep 6, 2025
172575e
✨ (#305) 강의 상세 페이지에 탭 기능 추가 및 탭에 따라 섹션 렌더링 분기처리
iinuyha Sep 6, 2025
0952656
🔨 (#305) 헤더에서 lectureTitle이 아닌 classTitle 저장 후 불러오기
iinuyha Sep 6, 2025
7e869d2
✨ (#305) 강의 상세 페이지에 콘텐츠 영역 추가 및 스타일 수정
iinuyha Sep 6, 2025
b48221a
✨ (#305) 강의 상세 페이지에 강의 녹음 섹션 추가 및 상태 관리 스토어 통합
iinuyha Sep 7, 2025
c8cdfaf
✨ (#305) next.config.ts에 이미지 도메인 추가
iinuyha Sep 7, 2025
a6b8eb4
✨ (#305) 강의 상세 페이지에 질문 목록 섹션 추가 및 소켓 연결 준비
iinuyha Sep 7, 2025
481290c
✨ (#305) recordSection에 height 100% 추가
iinuyha Sep 7, 2025
9326b71
✨ (#305) FileDisplay 컴포넌트에 파일 크기 표시 기능 추가 및 스타일 수정
iinuyha Sep 7, 2025
c4b2570
✨ (#305) 강의 상세 페이지의 LectureNoteListSection 및 QuizSection에 lectureId p…
iinuyha Sep 7, 2025
22feaf3
✨ (#305) 강의 자료 다운로드 기능 추가 및 스타일 수정
iinuyha Sep 7, 2025
e0eeefb
✨ (#305) 강의 노트 리스트 섹션의 높이를 100%로 설정하여 레이아웃 개선
iinuyha Sep 7, 2025
705b18d
✨ (#305) 강의 자료 및 녹음 파일 다운로드 기능을 downloadUtils 유틸리티로 통합하여 코드 중복 제거 및 에…
iinuyha Sep 7, 2025
30e15e6
Merge branch 'dev' of https://github.com/KW-ClassLog/ClassLog into Fe…
iinuyha Sep 20, 2025
6565ac7
✨ (#305) 학생용 강의 상세 정보 api 연동
iinuyha Sep 20, 2025
0b4ab3d
🔨 (#305) LecrureRecordSection에서 강의 상태에 따른 상태 추가
iinuyha Sep 20, 2025
8380b1b
Merge branch 'dev' of https://github.com/KW-ClassLog/ClassLog into Fe…
iinuyha Sep 21, 2025
9f6f44f
💄 (#305) overflow-y scroll에서 auto로 변경
iinuyha Sep 28, 2025
2d84ae5
🔧 (#305) 강의 상태 관련 타입을 통합하여 코드 정리 및 개선
iinuyha Sep 28, 2025
63e19c4
✨ (#305)강의 상태에 따른 퀴즈 상태 관리 로직 추가
iinuyha Sep 28, 2025
dd2d35c
🔧 (#305) 강의 정보 섹션에서 lectureDate 상태 추가 및 관련 로직 수정
iinuyha Sep 28, 2025
6b315ab
✨ (#305) 퀴즈 목록 받아오기 api 함수 + dto 설정
iinuyha Sep 28, 2025
18671fe
✨ (#305) 퀴즈 제출 API 함수 및 관련 타입 추가
iinuyha Sep 28, 2025
425033e
✨ (#305) 퀴즈 결과 조회 API 함수 및 관련 타입 추가
iinuyha Sep 28, 2025
c119f79
:recycle: (#328) 퀴즈 문제 정렬
mumminn Sep 28, 2025
952a9a3
✨ (#305) 퀴즈 섹션 컴포넌트 추가 및 스타일링, 퀴즈 데이터 로드 로직 구현
iinuyha Sep 28, 2025
88761fa
Merge branch 'Refactor/#328/quiz-sorting' of https://github.com/KW-Cl…
iinuyha Sep 28, 2025
a3c3944
✨ (#305) 퀴즈 섹션 스타일 개선 및 상태에 따른 UI 업데이트, 퀴즈 제출 버튼 추가
iinuyha Sep 28, 2025
58ee79b
✨ (#305) 퀴즈 제출 기능 추가 및 결과 모달 구현, 퀴즈 섹션 리프레시 기능 추가
iinuyha Sep 28, 2025
dc2c788
✨ (#305) 강의 목록 섹션에서 교수 이름 및 강의 날짜 정보를 동적으로 표시하도록 수정
iinuyha Sep 28, 2025
639214c
✨ (#305) 강의 상세 페이지에서 lectureId를 기반으로 클래스 이름을 불러오는 API 함수 추가 및 관련 로직 구현
iinuyha Sep 28, 2025
f171e1e
Merge branch 'dev' of https://github.com/KW-ClassLog/ClassLog into Fe…
iinuyha Sep 28, 2025
a4b5014
🔨 (#305) 퀴즈 섹션의 상태 타입에 'before' 추가 및 상태에 따른 UI 로직 수정
iinuyha Sep 28, 2025
2fa0d64
✨ (#305) 퀴즈 섹션에 퀴즈 결과 데이터 로드 기능 추가 및 UI 개선, 불필요한 카운트 정보 제거
iinuyha Oct 5, 2025
a49e307
🔧 (#305) 강의 ID에 따라 클래스 이름을 가져오는 API 엔드포인트 수정 및 관련 상수 업데이트
iinuyha Oct 5, 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
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;
}
}
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/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