2025-11-18 07:53:09 +09:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { useMemo, useState } from "react";
|
|
|
|
|
import CourseCard from "./CourseCard";
|
|
|
|
|
|
|
|
|
|
type CourseStatus = "전체" | "수강 예정" | "수강중" | "수강 완료";
|
|
|
|
|
|
|
|
|
|
type Lesson = {
|
|
|
|
|
id: string;
|
|
|
|
|
title: string;
|
|
|
|
|
durationMin: number;
|
|
|
|
|
progressPct: number; // 0~100
|
|
|
|
|
isCompleted: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type Course = {
|
|
|
|
|
id: string;
|
|
|
|
|
title: string;
|
|
|
|
|
description: string;
|
|
|
|
|
thumbnail: string;
|
|
|
|
|
status: CourseStatus;
|
|
|
|
|
progressPct: number;
|
|
|
|
|
lessons: Lesson[];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const MOCK_COURSES: Course[] = [
|
|
|
|
|
{
|
|
|
|
|
id: "c1",
|
|
|
|
|
title: "원자재 출입 전 계동",
|
|
|
|
|
description:
|
|
|
|
|
"원자재 이송/보관 기준을 기반으로 가이드하며, 일련의 단계 별 강의에서 세부적인 작업 지침을 다룹니다.",
|
|
|
|
|
thumbnail: "/imgs/talk.png",
|
|
|
|
|
status: "수강중",
|
|
|
|
|
progressPct: 80,
|
|
|
|
|
lessons: [
|
|
|
|
|
{ id: "c1l1", title: "1. 운반과 기준 및 방출 절차", durationMin: 12, progressPct: 100, isCompleted: true },
|
|
|
|
|
{ id: "c1l2", title: "2. 라벨링 원칙과 현장 케어", durationMin: 9, progressPct: 100, isCompleted: true },
|
|
|
|
|
{ id: "c1l3", title: "3. 배치/현황 기록 및 문서 관리", durationMin: 15, progressPct: 60, isCompleted: false },
|
|
|
|
|
{ id: "c1l4", title: "4. 보관 적재 기준 점검", durationMin: 8, progressPct: 0, isCompleted: false },
|
|
|
|
|
{ id: "c1l5", title: "5. 입고 검사 방법 (AQL) 및 유의점", durationMin: 11, progressPct: 0, isCompleted: false },
|
|
|
|
|
{ id: "c1l6", title: "6. 장비 사용, 손질 및 일지 필기", durationMin: 13, progressPct: 0, isCompleted: false },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "c2",
|
|
|
|
|
title: "학점과 평가",
|
|
|
|
|
description:
|
|
|
|
|
"학점과 평가 항목 기준을 가이드하며, 일과 관련된 기본 평가 체계와 피드백 처리 방법에 대해 배웁니다.",
|
|
|
|
|
thumbnail: "/imgs/talk.png",
|
|
|
|
|
status: "수강 완료",
|
|
|
|
|
progressPct: 100,
|
|
|
|
|
lessons: [
|
|
|
|
|
{ id: "c2l1", title: "평가 기준 이해", durationMin: 10, progressPct: 100, isCompleted: true },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "c3",
|
|
|
|
|
title: "부서간 연협",
|
|
|
|
|
description:
|
|
|
|
|
"부서간 협업 절차와 기록 기준을 가이드하며, 일련 단계 별 협업에서 생길 수 있는 리스크 관리법을 다룹니다.",
|
|
|
|
|
thumbnail: "/imgs/talk.png",
|
|
|
|
|
status: "수강중",
|
|
|
|
|
progressPct: 60,
|
|
|
|
|
lessons: [
|
|
|
|
|
{ id: "c3l1", title: "의사소통 원칙", durationMin: 9, progressPct: 100, isCompleted: true },
|
|
|
|
|
{ id: "c3l2", title: "문서 공유와 승인", durationMin: 14, progressPct: 30, isCompleted: false },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "c4",
|
|
|
|
|
title: "작업별 방사선의 이해",
|
|
|
|
|
description:
|
|
|
|
|
"작업별 방사선 안전 기준을 가이드하며, 일과 관련된 위험과 보호 장비 선택법을 배웁니다.",
|
|
|
|
|
thumbnail: "/imgs/talk.png",
|
|
|
|
|
status: "수강 예정",
|
|
|
|
|
progressPct: 0,
|
|
|
|
|
lessons: [
|
|
|
|
|
{ id: "c4l1", title: "기초 이론", durationMin: 12, progressPct: 0, isCompleted: false },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const TABS: CourseStatus[] = ["전체", "수강 예정", "수강중", "수강 완료"];
|
|
|
|
|
|
2025-11-18 06:19:26 +09:00
|
|
|
export default function CoursesPage() {
|
2025-11-18 07:53:09 +09:00
|
|
|
const [activeTab, setActiveTab] = useState<CourseStatus>("전체");
|
|
|
|
|
|
|
|
|
|
const countsByStatus = useMemo(() => {
|
|
|
|
|
return {
|
|
|
|
|
전체: MOCK_COURSES.length,
|
|
|
|
|
"수강 예정": MOCK_COURSES.filter((c) => c.status === "수강 예정").length,
|
|
|
|
|
수강중: MOCK_COURSES.filter((c) => c.status === "수강중").length,
|
|
|
|
|
"수강 완료": MOCK_COURSES.filter((c) => c.status === "수강 완료").length,
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const filtered = useMemo(() => {
|
|
|
|
|
if (activeTab === "전체") return MOCK_COURSES;
|
|
|
|
|
return MOCK_COURSES.filter((c) => c.status === activeTab);
|
|
|
|
|
}, [activeTab]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<main className="flex w-full flex-col">
|
|
|
|
|
<div className="flex h-[100px] items-center px-8">
|
|
|
|
|
<h1 className="text-[24px] font-bold leading-[1.5] text-[#1b2027]">내 강좌실</h1>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="px-8">
|
|
|
|
|
<div className="border-b border-[#ecf0ff]">
|
|
|
|
|
<ul className="flex gap-6">
|
|
|
|
|
{TABS.map((tab) => {
|
|
|
|
|
const isActive = activeTab === tab;
|
|
|
|
|
return (
|
|
|
|
|
<li key={tab}>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setActiveTab(tab)}
|
|
|
|
|
className={[
|
|
|
|
|
"relative -mb-px h-12 px-1 text-[14px] leading-[1.5]",
|
|
|
|
|
isActive ? "font-semibold text-[#1f2b91]" : "font-medium text-[#6c7682]",
|
|
|
|
|
].join(" ")}
|
|
|
|
|
>
|
|
|
|
|
{tab}{" "}
|
|
|
|
|
<span className="ml-1 text-[#8c95a1]">
|
|
|
|
|
{countsByStatus[tab as keyof typeof countsByStatus] ?? 0}
|
|
|
|
|
</span>
|
|
|
|
|
{isActive ? (
|
|
|
|
|
<span className="absolute inset-x-0 -bottom-[1px] h-[2px] bg-[#1f2b91]" />
|
|
|
|
|
) : null}
|
|
|
|
|
</button>
|
|
|
|
|
</li>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="px-8 pb-16 pt-6">
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
{filtered.map((course) => (
|
|
|
|
|
<CourseCard key={course.id} course={course} />
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* pagination */}
|
|
|
|
|
<div className="mt-10 flex items-center justify-center gap-2">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="flex h-8 w-8 items-center justify-center rounded-full text-[#8c95a1] hover:bg-[#f1f3f5]"
|
|
|
|
|
aria-label="이전 페이지"
|
|
|
|
|
>
|
|
|
|
|
‹
|
|
|
|
|
</button>
|
|
|
|
|
{[1, 2, 3].map((p) => (
|
|
|
|
|
<button
|
|
|
|
|
key={p}
|
|
|
|
|
type="button"
|
|
|
|
|
className={[
|
|
|
|
|
"flex h-8 w-8 items-center justify-center rounded-full text-[13px] leading-[1.4]",
|
|
|
|
|
p === 2 ? "bg-[#1f2b91] text-white" : "text-[#4c5561] hover:bg-[#f1f3f5]",
|
|
|
|
|
].join(" ")}
|
|
|
|
|
>
|
|
|
|
|
{p}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="flex h-8 w-8 items-center justify-center rounded-full text-[#8c95a1] hover:bg-[#f1f3f5]"
|
|
|
|
|
aria-label="다음 페이지"
|
|
|
|
|
>
|
|
|
|
|
›
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</main>
|
|
|
|
|
);
|
2025-11-18 06:19:26 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|