교육과정정 상세보기 수정1
This commit is contained in:
@@ -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,50 +46,29 @@ export default function CourseDetailPage() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
let data: any = null;
|
let data: any = null;
|
||||||
|
const subjectId = String(params.id);
|
||||||
|
|
||||||
// 먼저 getLecture 시도
|
// getSubjects로 과목 정보 가져오기
|
||||||
try {
|
try {
|
||||||
const response = await apiService.getLecture(params.id as string);
|
const subjectsResponse = await apiService.getSubjects();
|
||||||
data = response.data;
|
let subjectsData: any[] = [];
|
||||||
} catch (lectureErr) {
|
|
||||||
console.warn('getLecture 실패, getSubjects로 재시도:', lectureErr);
|
|
||||||
|
|
||||||
// getLecture 실패 시 getSubjects로 폴백
|
if (Array.isArray(subjectsResponse.data)) {
|
||||||
try {
|
subjectsData = subjectsResponse.data;
|
||||||
const subjectsResponse = await apiService.getSubjects();
|
} else if (subjectsResponse.data && typeof subjectsResponse.data === 'object') {
|
||||||
let subjectsData: any[] = [];
|
subjectsData = subjectsResponse.data.items ||
|
||||||
|
subjectsResponse.data.courses ||
|
||||||
if (Array.isArray(subjectsResponse.data)) {
|
subjectsResponse.data.data ||
|
||||||
subjectsData = subjectsResponse.data;
|
subjectsResponse.data.list ||
|
||||||
} else if (subjectsResponse.data && typeof subjectsResponse.data === 'object') {
|
subjectsResponse.data.subjects ||
|
||||||
subjectsData = subjectsResponse.data.items ||
|
subjectsResponse.data.subjectList ||
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ID로 과목 찾기
|
||||||
|
data = subjectsData.find((s: any) => String(s.id || s.subjectId) === subjectId);
|
||||||
|
} catch (subjectsErr) {
|
||||||
|
console.error('getSubjects 실패:', subjectsErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -108,42 +88,59 @@ export default function CourseDetailPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 강좌의 차시(lessons) 정보 가져오기
|
// getLectures로 모든 lecture 가져오기
|
||||||
let lessons: any[] = [];
|
let allLectures: any[] = [];
|
||||||
if (data.lessons && Array.isArray(data.lessons)) {
|
try {
|
||||||
lessons = data.lessons;
|
const lecturesResponse = await apiService.getLectures();
|
||||||
} else if (data.lectureId) {
|
if (Array.isArray(lecturesResponse.data)) {
|
||||||
// lectureId가 있으면 해당 강좌의 차시 정보 가져오기 시도
|
allLectures = lecturesResponse.data;
|
||||||
try {
|
} else if (lecturesResponse.data && typeof lecturesResponse.data === 'object') {
|
||||||
const lectureResponse = await apiService.getLecture(data.lectureId);
|
allLectures = lecturesResponse.data.items ||
|
||||||
if (lectureResponse.data?.lessons) {
|
lecturesResponse.data.lectures ||
|
||||||
lessons = lectureResponse.data.lessons;
|
lecturesResponse.data.data ||
|
||||||
}
|
lecturesResponse.data.list ||
|
||||||
} catch (err) {
|
[];
|
||||||
console.warn('차시 정보 가져오기 실패:', err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// subjectId로 필터링
|
||||||
|
const filteredLectures = allLectures.filter((lecture: any) =>
|
||||||
|
String(lecture.subjectId || lecture.subject_id) === subjectId
|
||||||
|
);
|
||||||
|
|
||||||
|
setLectures(filteredLectures);
|
||||||
|
|
||||||
|
// API 응답 데이터를 CourseDetail 타입으로 변환
|
||||||
|
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 · 총 ${filteredLectures.length || 0}강`,
|
||||||
|
submitSummary: data.submitSummary || '',
|
||||||
|
thumbnail: thumbnail,
|
||||||
|
lessons: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// API 응답 데이터를 CourseDetail 타입으로 변환
|
|
||||||
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 · 총 ${lessons.length || 0}강`,
|
|
||||||
submitSummary: data.submitSummary || '',
|
|
||||||
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',
|
|
||||||
state: lesson.isCompleted ? "제출완료" : "제출대기",
|
|
||||||
action: lesson.isCompleted ? "복습하기" : (index === 0 ? "수강하기" : "이어서 수강하기"),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
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,83 +283,103 @@ 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 submitBtnBorder = isSubmitted
|
const isSubmitted = false; // TODO: 진행률 API에서 가져와야 함
|
||||||
? "border-transparent"
|
const action = isSubmitted ? "복습하기" : (index === 0 ? "수강하기" : "이어서 수강하기");
|
||||||
: (l.action === "이어서 수강하기" || l.action === "수강하기" ? "border-[#b1b8c0]" : "border-[#8c95a1]");
|
const submitBtnBorder = isSubmitted
|
||||||
const submitBtnText = isSubmitted ? "text-[#384fbf]" : (l.action === "이어서 수강하기" || l.action === "수강하기" ? "text-[#b1b8c0]" : "text-[#4c5561]");
|
? "border-transparent"
|
||||||
const rightBtnStyle =
|
: (action === "이어서 수강하기" || action === "수강하기" ? "border-[#b1b8c0]" : "border-[#8c95a1]");
|
||||||
l.action === "이어서 수강하기" || l.action === "수강하기"
|
const submitBtnText = isSubmitted ? "text-[#384fbf]" : (action === "이어서 수강하기" || action === "수강하기" ? "text-[#b1b8c0]" : "text-[#4c5561]");
|
||||||
? "bg-[#ecf0ff] text-[#384fbf]"
|
const rightBtnStyle =
|
||||||
: "bg-[#f1f3f5] text-[#4c5561]";
|
action === "이어서 수강하기" || action === "수강하기"
|
||||||
|
? "bg-[#ecf0ff] text-[#384fbf]"
|
||||||
|
: "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]">
|
||||||
<div className="flex gap-[12px] items-center w-full">
|
{index + 1}. {lecture.title || lecture.lectureName || ''}
|
||||||
<p className="font-['Pretendard:Medium',sans-serif] text-[#8c95a1] text-[13px] leading-[1.4] w-[40px]">{l.duration}</p>
|
</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]">
|
||||||
|
{lecture.duration || '00:00'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 버튼 영역 */}
|
{/* 버튼 영역 */}
|
||||||
<div className="relative shrink-0">
|
<div className="relative shrink-0">
|
||||||
<div className="flex gap-[8px] items-center">
|
<div className="flex gap-[8px] items-center">
|
||||||
{/* 학습 제출 버튼 */}
|
{/* 학습 제출 버튼 */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={[
|
className={[
|
||||||
"bg-white box-border flex flex-col h-[32px] items-center justify-center px-[16px] py-[3px] rounded-[6px] shrink-0",
|
"bg-white box-border flex flex-col h-[32px] items-center justify-center px-[16px] py-[3px] rounded-[6px] shrink-0",
|
||||||
"border border-solid",
|
"border border-solid",
|
||||||
submitBtnBorder,
|
submitBtnBorder,
|
||||||
submitBtnText,
|
submitBtnText,
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
>
|
>
|
||||||
{isSubmitted ? (
|
{isSubmitted ? (
|
||||||
<div className="flex gap-[4px] h-[18px] items-center">
|
<div className="flex gap-[4px] h-[18px] items-center">
|
||||||
<div className="relative shrink-0 size-[12px]">
|
<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">
|
<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"/>
|
<path d="M10 3L4.5 8.5L2 6" stroke="#384fbf" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p className="font-['Pretendard:Medium',sans-serif] text-[#384fbf] text-[13px] leading-[1.4]">학습 제출 완료</p>
|
||||||
</div>
|
</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"
|
||||||
|
onClick={() => {
|
||||||
|
const lectureId = lecture.id || lecture.lectureId;
|
||||||
|
if (lectureId) {
|
||||||
|
router.push(`/menu/courses/lessons/${lectureId}/start`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={[
|
||||||
|
"box-border flex flex-col h-[32px] items-center justify-center px-[16px] py-[3px] rounded-[6px] shrink-0 transition-colors",
|
||||||
|
rightBtnStyle,
|
||||||
|
action === "이어서 수강하기" || action === "수강하기"
|
||||||
|
? "hover:bg-[#d0d9ff]"
|
||||||
|
: "hover:bg-[#e5e8eb]",
|
||||||
|
].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",
|
||||||
submitBtnText,
|
action === "이어서 수강하기" || action === "수강하기" ? "text-[#384fbf]" : "text-[#4c5561]",
|
||||||
].join(" ")}>학습 제출 하기</p>
|
].join(" ")}>{action}</p>
|
||||||
)}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
{/* 수강/복습 버튼 */}
|
|
||||||
<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>
|
</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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user