교육과정정 상세보기 수정1

This commit is contained in:
wallace
2025-12-01 11:31:09 +09:00
parent 8ec9e4e402
commit 3ef3990f1d
3 changed files with 169 additions and 152 deletions

View File

@@ -33,6 +33,7 @@ export default function CourseDetailPage() {
const params = useParams(); const params = useParams();
const router = useRouter(); const router = useRouter();
const [course, setCourse] = useState<CourseDetail | null>(null); const [course, setCourse] = useState<CourseDetail | null>(null);
const [lectures, setLectures] = useState<any[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -45,15 +46,9 @@ export default function CourseDetailPage() {
setError(null); setError(null);
let data: any = null; let data: any = null;
const subjectId = String(params.id);
// 먼저 getLecture 시도 // getSubjects로 과목 정보 가져오기
try {
const response = await apiService.getLecture(params.id as string);
data = response.data;
} catch (lectureErr) {
console.warn('getLecture 실패, getSubjects로 재시도:', lectureErr);
// getLecture 실패 시 getSubjects로 폴백
try { try {
const subjectsResponse = await apiService.getSubjects(); const subjectsResponse = await apiService.getSubjects();
let subjectsData: any[] = []; let subjectsData: any[] = [];
@@ -71,25 +66,10 @@ export default function CourseDetailPage() {
} }
// ID로 과목 찾기 // ID로 과목 찾기
data = subjectsData.find((s: any) => String(s.id || s.subjectId) === String(params.id)); data = subjectsData.find((s: any) => String(s.id || s.subjectId) === subjectId);
if (!data) {
// getLectures로도 시도
try {
const lecturesResponse = await apiService.getLectures();
const lectures = Array.isArray(lecturesResponse.data)
? lecturesResponse.data
: lecturesResponse.data?.items || lecturesResponse.data?.lectures || lecturesResponse.data?.data || [];
data = lectures.find((l: any) => String(l.id || l.lectureId) === String(params.id));
} catch (lecturesErr) {
console.error('getLectures 실패:', lecturesErr);
}
}
} catch (subjectsErr) { } catch (subjectsErr) {
console.error('getSubjects 실패:', subjectsErr); console.error('getSubjects 실패:', subjectsErr);
} }
}
if (!data) { if (!data) {
throw new Error('강좌를 찾을 수 없습니다.'); throw new Error('강좌를 찾을 수 없습니다.');
@@ -108,22 +88,27 @@ export default function CourseDetailPage() {
} }
} }
// 강좌의 차시(lessons) 정보 가져오기 // getLectures로 모든 lecture 가져오기
let lessons: any[] = []; let allLectures: any[] = [];
if (data.lessons && Array.isArray(data.lessons)) {
lessons = data.lessons;
} else if (data.lectureId) {
// lectureId가 있으면 해당 강좌의 차시 정보 가져오기 시도
try { try {
const lectureResponse = await apiService.getLecture(data.lectureId); const lecturesResponse = await apiService.getLectures();
if (lectureResponse.data?.lessons) { if (Array.isArray(lecturesResponse.data)) {
lessons = lectureResponse.data.lessons; allLectures = lecturesResponse.data;
} } else if (lecturesResponse.data && typeof lecturesResponse.data === 'object') {
} catch (err) { allLectures = lecturesResponse.data.items ||
console.warn('차시 정보 가져오기 실패:', err); lecturesResponse.data.lectures ||
} lecturesResponse.data.data ||
lecturesResponse.data.list ||
[];
} }
// subjectId로 필터링
const filteredLectures = allLectures.filter((lecture: any) =>
String(lecture.subjectId || lecture.subject_id) === subjectId
);
setLectures(filteredLectures);
// API 응답 데이터를 CourseDetail 타입으로 변환 // API 응답 데이터를 CourseDetail 타입으로 변환
const courseDetail: CourseDetail = { const courseDetail: CourseDetail = {
id: String(data.id || params.id), id: String(data.id || params.id),
@@ -131,19 +116,31 @@ export default function CourseDetailPage() {
title: data.title || data.lectureName || data.subjectName || '', title: data.title || data.lectureName || data.subjectName || '',
goal: data.objective || data.goal || '', goal: data.objective || data.goal || '',
method: data.method || '', method: data.method || '',
summary: data.summary || `VOD · 총 ${lessons.length || 0}`, summary: data.summary || `VOD · 총 ${filteredLectures.length || 0}`,
submitSummary: data.submitSummary || '', submitSummary: data.submitSummary || '',
thumbnail: thumbnail, thumbnail: thumbnail,
lessons: lessons.map((lesson: any, index: number) => ({ lessons: [],
id: String(lesson.id || lesson.lessonId || index + 1),
title: `${index + 1}. ${lesson.title || lesson.lessonName || ''}`,
duration: lesson.duration || '00:00',
state: lesson.isCompleted ? "제출완료" : "제출대기",
action: lesson.isCompleted ? "복습하기" : (index === 0 ? "수강하기" : "이어서 수강하기"),
})),
}; };
setCourse(courseDetail); setCourse(courseDetail);
} catch (lecturesErr) {
console.error('getLectures 실패:', lecturesErr);
// API 응답 데이터를 CourseDetail 타입으로 변환 (lecture 없이)
const courseDetail: CourseDetail = {
id: String(data.id || params.id),
status: data.status || "수강 예정",
title: data.title || data.lectureName || data.subjectName || '',
goal: data.objective || data.goal || '',
method: data.method || '',
summary: data.summary || `VOD · 총 0강`,
submitSummary: data.submitSummary || '',
thumbnail: thumbnail,
lessons: [],
};
setCourse(courseDetail);
}
} catch (err) { } catch (err) {
console.error('강좌 조회 실패:', err); console.error('강좌 조회 실패:', err);
setError(err instanceof Error ? err.message : '강좌를 불러오는데 실패했습니다.'); setError(err instanceof Error ? err.message : '강좌를 불러오는데 실패했습니다.');
@@ -286,29 +283,35 @@ export default function CourseDetailPage() {
</div> </div>
</div> </div>
{/* 차시 리스트 */} {/* Lecture 리스트 */}
<div className="flex flex-col gap-[8px] items-start mt-[24px] w-full"> <div className="flex flex-col gap-[8px] items-start mt-[24px] w-full">
{course.lessons.map((l) => { {lectures.length > 0 ? (
const isSubmitted = l.state === "제출완료"; lectures.map((lecture: any, index: number) => {
const isSubmitted = false; // TODO: 진행률 API에서 가져와야 함
const action = isSubmitted ? "복습하기" : (index === 0 ? "수강하기" : "이어서 수강하기");
const submitBtnBorder = isSubmitted const submitBtnBorder = isSubmitted
? "border-transparent" ? "border-transparent"
: (l.action === "이어서 수강하기" || l.action === "수강하기" ? "border-[#b1b8c0]" : "border-[#8c95a1]"); : (action === "이어서 수강하기" || action === "수강하기" ? "border-[#b1b8c0]" : "border-[#8c95a1]");
const submitBtnText = isSubmitted ? "text-[#384fbf]" : (l.action === "이어서 수강하기" || l.action === "수강하기" ? "text-[#b1b8c0]" : "text-[#4c5561]"); const submitBtnText = isSubmitted ? "text-[#384fbf]" : (action === "이어서 수강하기" || action === "수강하기" ? "text-[#b1b8c0]" : "text-[#4c5561]");
const rightBtnStyle = const rightBtnStyle =
l.action === "이어서 수강하기" || l.action === "수강하기" action === "이어서 수강하기" || action === "수강하기"
? "bg-[#ecf0ff] text-[#384fbf]" ? "bg-[#ecf0ff] text-[#384fbf]"
: "bg-[#f1f3f5] text-[#4c5561]"; : "bg-[#f1f3f5] text-[#4c5561]";
return ( return (
<div key={l.id} className="bg-white border border-[#dee1e6] border-solid relative rounded-[8px] w-full"> <div key={lecture.id || lecture.lectureId || index} className="bg-white border border-[#dee1e6] border-solid relative rounded-[8px] w-full">
<div className="box-border flex gap-[16px] items-center overflow-clip px-[24px] py-[16px] rounded-[inherit] w-full"> <div className="box-border flex gap-[16px] items-center overflow-clip px-[24px] py-[16px] rounded-[inherit] w-full">
<div className="basis-0 flex grow h-[46px] items-center justify-between min-h-px min-w-px relative shrink-0"> <div className="basis-0 flex grow h-[46px] items-center justify-between min-h-px min-w-px relative shrink-0">
{/* 차시 정보 */} {/* Lecture 정보 */}
<div className="basis-0 grow min-h-px min-w-px relative shrink-0"> <div className="basis-0 grow min-h-px min-w-px relative shrink-0">
<div className="flex flex-col gap-[4px] items-start w-full"> <div className="flex flex-col gap-[4px] items-start w-full">
<p className="font-['Pretendard:SemiBold',sans-serif] text-[#333c47] text-[16px] leading-[1.5]">{l.title}</p> <p className="font-['Pretendard:SemiBold',sans-serif] text-[#333c47] text-[16px] leading-[1.5]">
{index + 1}. {lecture.title || lecture.lectureName || ''}
</p>
<div className="flex gap-[12px] items-center w-full"> <div className="flex gap-[12px] items-center w-full">
<p className="font-['Pretendard:Medium',sans-serif] text-[#8c95a1] text-[13px] leading-[1.4] w-[40px]">{l.duration}</p> <p className="font-['Pretendard:Medium',sans-serif] text-[#8c95a1] text-[13px] leading-[1.4] w-[40px]">
{lecture.duration || '00:00'}
</p>
</div> </div>
</div> </div>
</div> </div>
@@ -346,15 +349,24 @@ export default function CourseDetailPage() {
{/* 수강/복습 버튼 */} {/* 수강/복습 버튼 */}
<button <button
type="button" type="button"
onClick={() => {
const lectureId = lecture.id || lecture.lectureId;
if (lectureId) {
router.push(`/menu/courses/lessons/${lectureId}/start`);
}
}}
className={[ className={[
"box-border flex flex-col h-[32px] items-center justify-center px-[16px] py-[3px] rounded-[6px] shrink-0", "box-border flex flex-col h-[32px] items-center justify-center px-[16px] py-[3px] rounded-[6px] shrink-0 transition-colors",
rightBtnStyle, rightBtnStyle,
action === "이어서 수강하기" || action === "수강하기"
? "hover:bg-[#d0d9ff]"
: "hover:bg-[#e5e8eb]",
].join(" ")} ].join(" ")}
> >
<p className={[ <p className={[
"font-['Pretendard:Medium',sans-serif] text-[14px] leading-[1.5] text-center", "font-['Pretendard:Medium',sans-serif] text-[14px] leading-[1.5] text-center",
l.action === "이어서 수강하기" || l.action === "수강하기" ? "text-[#384fbf]" : "text-[#4c5561]", action === "이어서 수강하기" || action === "수강하기" ? "text-[#384fbf]" : "text-[#4c5561]",
].join(" ")}>{l.action}</p> ].join(" ")}>{action}</p>
</button> </button>
</div> </div>
</div> </div>
@@ -362,7 +374,12 @@ export default function CourseDetailPage() {
</div> </div>
</div> </div>
); );
})} })
) : (
<div className="flex items-center justify-center py-8 w-full">
<p className="text-[14px] text-[#8c95a1]"> .</p>
</div>
)}
</div> </div>
</section> </section>
</main> </main>

View File

@@ -4,7 +4,7 @@ import MenuSidebar from "./MenuSidebar";
export default function MenuLayout({ children }: { children: ReactNode }) { export default function MenuLayout({ children }: { children: ReactNode }) {
return ( return (
<div className="mx-auto flex w-full max-w-[1440px] min-h-full"> <div className="mx-auto flex w-full max-w-[1440px] min-h-full">
<aside className="w-[320px] border-r border-[#dee1e6] px-4 py-6"> <aside className="hidden w-[320px] border-r border-[#dee1e6] px-4 py-6">
<MenuSidebar /> <MenuSidebar />
</aside> </aside>
<section className="flex-1">{children}</section> <section className="flex-1">{children}</section>

View File

@@ -496,7 +496,7 @@ export default function Home() {
</section> </section>
{/* 공지사항 */} {/* 공지사항 */}
<section className="mt-9"> <section className="mt-9 pb-20">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<h2 className="m-0 text-[24px] font-bold leading-normal text-[#1B2027]"></h2> <h2 className="m-0 text-[24px] font-bold leading-normal text-[#1B2027]"></h2>