메일페이지 수정중1
This commit is contained in:
@@ -4,6 +4,10 @@ import { useEffect, useState } from 'react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import Image from 'next/image';
|
||||
import apiService from '../../lib/apiService';
|
||||
import BackCircleSvg from '../../svgs/backcirclesvg';
|
||||
|
||||
const imgPlay = '/imgs/play.svg';
|
||||
const imgMusicAudioPlay = '/imgs/music-audio-play.svg';
|
||||
|
||||
type Lesson = {
|
||||
id: string;
|
||||
@@ -39,23 +43,98 @@ export default function CourseDetailPage() {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const response = await apiService.getLecture(params.id as string);
|
||||
|
||||
let data: any = null;
|
||||
|
||||
// 먼저 getLecture 시도
|
||||
try {
|
||||
const response = await apiService.getLecture(params.id as string);
|
||||
data = response.data;
|
||||
} catch (lectureErr) {
|
||||
console.warn('getLecture 실패, getSubjects로 재시도:', lectureErr);
|
||||
|
||||
// getLecture 실패 시 getSubjects로 폴백
|
||||
try {
|
||||
const subjectsResponse = await apiService.getSubjects();
|
||||
let subjectsData: any[] = [];
|
||||
|
||||
if (Array.isArray(subjectsResponse.data)) {
|
||||
subjectsData = subjectsResponse.data;
|
||||
} else if (subjectsResponse.data && typeof subjectsResponse.data === 'object') {
|
||||
subjectsData = subjectsResponse.data.items ||
|
||||
subjectsResponse.data.courses ||
|
||||
subjectsResponse.data.data ||
|
||||
subjectsResponse.data.list ||
|
||||
subjectsResponse.data.subjects ||
|
||||
subjectsResponse.data.subjectList ||
|
||||
[];
|
||||
}
|
||||
|
||||
// ID로 과목 찾기
|
||||
data = subjectsData.find((s: any) => String(s.id || s.subjectId) === String(params.id));
|
||||
|
||||
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) {
|
||||
console.error('getSubjects 실패:', subjectsErr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
throw new Error('강좌를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 썸네일 이미지 가져오기
|
||||
let thumbnail = '/imgs/talk.png';
|
||||
if (data.imageKey) {
|
||||
try {
|
||||
const imageUrl = await apiService.getFile(data.imageKey);
|
||||
if (imageUrl) {
|
||||
thumbnail = imageUrl;
|
||||
}
|
||||
} catch (imgErr) {
|
||||
console.error('이미지 다운로드 실패:', imgErr);
|
||||
}
|
||||
}
|
||||
|
||||
// 강좌의 차시(lessons) 정보 가져오기
|
||||
let lessons: any[] = [];
|
||||
if (data.lessons && Array.isArray(data.lessons)) {
|
||||
lessons = data.lessons;
|
||||
} else if (data.lectureId) {
|
||||
// lectureId가 있으면 해당 강좌의 차시 정보 가져오기 시도
|
||||
try {
|
||||
const lectureResponse = await apiService.getLecture(data.lectureId);
|
||||
if (lectureResponse.data?.lessons) {
|
||||
lessons = lectureResponse.data.lessons;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('차시 정보 가져오기 실패:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// API 응답 데이터를 CourseDetail 타입으로 변환
|
||||
const data = response.data;
|
||||
|
||||
// API 응답 구조에 맞게 데이터 매핑
|
||||
// 실제 API 응답 구조에 따라 조정 필요
|
||||
const courseDetail: CourseDetail = {
|
||||
id: String(data.id || params.id),
|
||||
status: data.status || "수강 예정",
|
||||
title: data.title || data.lectureName || '',
|
||||
title: data.title || data.lectureName || data.subjectName || '',
|
||||
goal: data.objective || data.goal || '',
|
||||
method: data.method || '',
|
||||
summary: data.summary || `VOD · 총 ${data.lessons?.length || 0}강`,
|
||||
summary: data.summary || `VOD · 총 ${lessons.length || 0}강`,
|
||||
submitSummary: data.submitSummary || '',
|
||||
thumbnail: data.thumbnail || data.imageKey || data.imageUrl || '/imgs/talk.png',
|
||||
lessons: (data.lessons || []).map((lesson: any, index: number) => ({
|
||||
thumbnail: thumbnail,
|
||||
lessons: lessons.map((lesson: any, index: number) => ({
|
||||
id: String(lesson.id || lesson.lessonId || index + 1),
|
||||
title: `${index + 1}. ${lesson.title || lesson.lessonName || ''}`,
|
||||
duration: lesson.duration || '00:00',
|
||||
@@ -78,11 +157,19 @@ export default function CourseDetailPage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<main className="flex w-full flex-col">
|
||||
<div className="flex h-[100px] items-center px-8">
|
||||
<main className="flex w-full flex-col items-center">
|
||||
<div className="flex h-[100px] w-full max-w-[1440px] items-center gap-[12px] px-8">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
aria-label="뒤로 가기"
|
||||
className="size-8 rounded-full inline-flex items-center justify-center text-[#8C95A1] hover:bg-black/5 no-underline cursor-pointer"
|
||||
>
|
||||
<BackCircleSvg width={32} height={32} />
|
||||
</button>
|
||||
<h1 className="text-[24px] font-bold leading-[1.5] text-[#1b2027]">교육 과정 상세보기</h1>
|
||||
</div>
|
||||
<section className="px-8 pb-20">
|
||||
<section className="w-full max-w-[1440px] px-8 pb-20">
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<p className="text-[16px] text-[#8c95a1]">로딩 중...</p>
|
||||
</div>
|
||||
@@ -93,11 +180,19 @@ export default function CourseDetailPage() {
|
||||
|
||||
if (error || !course) {
|
||||
return (
|
||||
<main className="flex w-full flex-col">
|
||||
<div className="flex h-[100px] items-center px-8">
|
||||
<main className="flex w-full flex-col items-center">
|
||||
<div className="flex h-[100px] w-full max-w-[1440px] items-center gap-[12px] px-8">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
aria-label="뒤로 가기"
|
||||
className="size-8 rounded-full inline-flex items-center justify-center text-[#8C95A1] hover:bg-black/5 no-underline cursor-pointer"
|
||||
>
|
||||
<BackCircleSvg width={32} height={32} />
|
||||
</button>
|
||||
<h1 className="text-[24px] font-bold leading-[1.5] text-[#1b2027]">교육 과정 상세보기</h1>
|
||||
</div>
|
||||
<section className="px-8 pb-20">
|
||||
<section className="w-full max-w-[1440px] px-8 pb-20">
|
||||
<div className="flex flex-col items-center justify-center py-20">
|
||||
<p className="text-[16px] text-red-500 mb-4">{error || '강좌를 찾을 수 없습니다.'}</p>
|
||||
<button
|
||||
@@ -114,96 +209,160 @@ export default function CourseDetailPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="flex w-full flex-col">
|
||||
<div className="flex h-[100px] items-center px-8">
|
||||
<main className="flex w-full flex-col items-center">
|
||||
{/* 헤더 */}
|
||||
<div className="flex h-[100px] w-full max-w-[1440px] items-center gap-[12px] px-8">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
aria-label="뒤로 가기"
|
||||
className="size-8 rounded-full inline-flex items-center justify-center text-[#8C95A1] hover:bg-black/5 no-underline cursor-pointer"
|
||||
>
|
||||
<BackCircleSvg width={32} height={32} />
|
||||
</button>
|
||||
<h1 className="text-[24px] font-bold leading-[1.5] text-[#1b2027]">교육 과정 상세보기</h1>
|
||||
</div>
|
||||
|
||||
<section className="px-8 pb-20">
|
||||
<div className="rounded-[8px] bg-white px-8 pb-20 pt-6">
|
||||
{/* 상단 소개 카드 */}
|
||||
<div className="flex gap-6 rounded-[8px] bg-[#f8f9fa] p-6">
|
||||
<div className="relative h-[159px] w-[292px] overflow-hidden rounded">
|
||||
<Image
|
||||
src={course.thumbnail}
|
||||
alt={course.title}
|
||||
fill
|
||||
sizes="292px"
|
||||
className="object-cover"
|
||||
unoptimized
|
||||
/>
|
||||
{/* 메인 콘텐츠 */}
|
||||
<section className="w-full max-w-[1440px] px-8 pb-[80px] pt-[24px]">
|
||||
{/* 상단 정보 카드 */}
|
||||
<div className="bg-[#f8f9fa] box-border flex gap-[24px] items-start p-[24px] rounded-[8px] w-full">
|
||||
{/* 이미지 컨테이너 */}
|
||||
<div className="overflow-clip relative rounded-[4px] shrink-0 w-[220.5px] h-[159px]">
|
||||
<Image
|
||||
src={course.thumbnail}
|
||||
alt={course.title}
|
||||
fill
|
||||
sizes="220.5px"
|
||||
className="object-cover"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 교육과정 정보 */}
|
||||
<div className="basis-0 flex flex-col gap-[12px] grow items-start min-h-px min-w-px relative shrink-0">
|
||||
{/* 제목 영역 */}
|
||||
<div className="flex gap-[8px] h-[27px] items-center w-full">
|
||||
<div className="bg-[#e5f5ec] box-border flex h-[20px] items-center justify-center px-[4px] py-0 rounded-[4px] shrink-0">
|
||||
<p className="font-['Pretendard:SemiBold',sans-serif] text-[#0c9d61] text-[13px] leading-[1.4]">{course.status}</p>
|
||||
</div>
|
||||
<h2 className="font-['Pretendard:SemiBold',sans-serif] text-[#333c47] text-[18px] leading-[1.5]">{course.title}</h2>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex h-[27px] items-center gap-2">
|
||||
<span className="h-[20px] rounded-[4px] bg-[#e5f5ec] px-1.5 text-[13px] font-semibold leading-[1.4] text-[#0c9d61]">
|
||||
{course.status}
|
||||
</span>
|
||||
<h2 className="text-[18px] font-semibold leading-[1.5] text-[#333c47]">{course.title}</h2>
|
||||
</div>
|
||||
<div className="mt-3 space-y-1">
|
||||
<p className="text-[15px] leading-[1.5] text-[#333c47]">
|
||||
<span className="font-medium">학습 목표:</span> {course.goal}
|
||||
</p>
|
||||
<p className="text-[15px] leading-[1.5] text-[#333c47]">
|
||||
<span className="font-medium">학습 방법:</span> {course.method}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center gap-5 text-[13px] leading-[1.4] text-[#8c95a1]">
|
||||
<span>{course.summary}</span>
|
||||
{course.submitSummary && <span>{course.submitSummary}</span>}
|
||||
|
||||
{/* 학습 목표 및 방법 */}
|
||||
<div className="flex flex-col gap-[4px] items-start w-full">
|
||||
<p className="font-['Pretendard:Regular',sans-serif] text-[#333c47] text-[15px] leading-[1.5] mb-0">
|
||||
<span className="font-['Pretendard:Medium',sans-serif]">학습 목표:</span>
|
||||
<span>{` ${course.goal}`}</span>
|
||||
</p>
|
||||
<p className="font-['Pretendard:Regular',sans-serif] text-[#333c47] text-[15px] leading-[1.5]">
|
||||
<span className="font-['Pretendard:Medium',sans-serif]">학습 방법:</span>
|
||||
<span>{` ${course.method}`}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 통계 정보 */}
|
||||
<div className="flex gap-[4px] items-center w-full">
|
||||
<div className="flex gap-[20px] items-center">
|
||||
{/* VOD 정보 */}
|
||||
<div className="flex gap-[4px] items-center">
|
||||
<div className="relative shrink-0 size-[16px]">
|
||||
<img src={imgPlay} alt="" className="block max-w-none size-full" />
|
||||
</div>
|
||||
<p className="font-['Pretendard:Medium',sans-serif] text-[#8c95a1] text-[13px] leading-[1.4]">{course.summary}</p>
|
||||
</div>
|
||||
|
||||
{/* 학습 제출 정보 */}
|
||||
{course.submitSummary && (
|
||||
<div className="flex gap-[4px] items-center">
|
||||
<div className="relative shrink-0 size-[16px]">
|
||||
<img src={imgMusicAudioPlay} alt="" className="block max-w-none size-full" />
|
||||
</div>
|
||||
<p className="font-['Pretendard:Medium',sans-serif] text-[#8c95a1] text-[13px] leading-[1.4]">{course.submitSummary}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 차시 리스트 */}
|
||||
<div className="mt-6 space-y-2">
|
||||
{course.lessons.map((l) => {
|
||||
const isSubmitted = l.state === "제출완료";
|
||||
const submitBtnStyle =
|
||||
l.state === "제출완료"
|
||||
? "border border-transparent text-[#384fbf]"
|
||||
: "border " + (l.action === "이어서 수강하기" || l.action === "수강하기" ? "border-[#b1b8c0]" : "border-[#8c95a1]");
|
||||
const rightBtnStyle =
|
||||
l.action === "이어서 수강하기"
|
||||
? "bg-[#ecf0ff] text-[#384fbf]"
|
||||
: l.action === "수강하기"
|
||||
? "bg-[#ecf0ff] text-[#384fbf]"
|
||||
: "bg-[#f1f3f5] text-[#4c5561]";
|
||||
return (
|
||||
<div key={l.id} className="rounded-[8px] border border-[#dee1e6] bg-white">
|
||||
<div className="flex items-center justify-between gap-4 rounded-[8px] px-6 py-4">
|
||||
<div className="min-w-0">
|
||||
<p className="text-[16px] font-semibold leading-[1.5] text-[#333c47]">{l.title}</p>
|
||||
<div className="mt-1 flex items-center gap-3">
|
||||
<p className="w-[40px] text-[13px] leading-[1.4] text-[#8c95a1]">{l.duration}</p>
|
||||
{/* 차시 리스트 */}
|
||||
<div className="flex flex-col gap-[8px] items-start mt-[24px] w-full">
|
||||
{course.lessons.map((l) => {
|
||||
const isSubmitted = l.state === "제출완료";
|
||||
const submitBtnBorder = isSubmitted
|
||||
? "border-transparent"
|
||||
: (l.action === "이어서 수강하기" || l.action === "수강하기" ? "border-[#b1b8c0]" : "border-[#8c95a1]");
|
||||
const submitBtnText = isSubmitted ? "text-[#384fbf]" : (l.action === "이어서 수강하기" || l.action === "수강하기" ? "text-[#b1b8c0]" : "text-[#4c5561]");
|
||||
const rightBtnStyle =
|
||||
l.action === "이어서 수강하기" || l.action === "수강하기"
|
||||
? "bg-[#ecf0ff] text-[#384fbf]"
|
||||
: "bg-[#f1f3f5] text-[#4c5561]";
|
||||
|
||||
return (
|
||||
<div key={l.id} 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="basis-0 flex grow h-[46px] items-center justify-between 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">
|
||||
<p className="font-['Pretendard:SemiBold',sans-serif] text-[#333c47] text-[16px] leading-[1.5]">{l.title}</p>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
"h-8 rounded-[6px] px-4 text-[14px] font-medium leading-[1.5]",
|
||||
"bg-white",
|
||||
submitBtnStyle,
|
||||
].join(" ")}
|
||||
>
|
||||
{isSubmitted ? "학습 제출 완료" : "학습 제출 하기"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
"h-8 rounded-[6px] px-4 text-[14px] font-medium leading-[1.5]",
|
||||
rightBtnStyle,
|
||||
].join(" ")}
|
||||
>
|
||||
{l.action}
|
||||
</button>
|
||||
|
||||
{/* 버튼 영역 */}
|
||||
<div className="relative shrink-0">
|
||||
<div className="flex gap-[8px] items-center">
|
||||
{/* 학습 제출 버튼 */}
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
"bg-white box-border flex flex-col h-[32px] items-center justify-center px-[16px] py-[3px] rounded-[6px] shrink-0",
|
||||
"border border-solid",
|
||||
submitBtnBorder,
|
||||
submitBtnText,
|
||||
].join(" ")}
|
||||
>
|
||||
{isSubmitted ? (
|
||||
<div className="flex gap-[4px] h-[18px] items-center">
|
||||
<div className="relative shrink-0 size-[12px]">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 3L4.5 8.5L2 6" stroke="#384fbf" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p className="font-['Pretendard:Medium',sans-serif] text-[#384fbf] text-[13px] leading-[1.4]">학습 제출 완료</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className={[
|
||||
"font-['Pretendard:Medium',sans-serif] text-[14px] leading-[1.5] text-center",
|
||||
submitBtnText,
|
||||
].join(" ")}>학습 제출 하기</p>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* 수강/복습 버튼 */}
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
"box-border flex flex-col h-[32px] items-center justify-center px-[16px] py-[3px] rounded-[6px] shrink-0",
|
||||
rightBtnStyle,
|
||||
].join(" ")}
|
||||
>
|
||||
<p className={[
|
||||
"font-['Pretendard:Medium',sans-serif] text-[14px] leading-[1.5] text-center",
|
||||
l.action === "이어서 수강하기" || l.action === "수강하기" ? "text-[#384fbf]" : "text-[#4c5561]",
|
||||
].join(" ")}>{l.action}</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user