'use client';
import { useEffect, useState, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import ChevronDownSvg from '../../svgs/chevrondownsvg';
// 드롭다운 아이콘 컴포넌트
function ArrowDownIcon({ className }: { className?: string }) {
return (
);
}
// 검색 아이콘 컴포넌트
function SearchIcon({ className }: { className?: string }) {
return (
);
}
// 태그 컴포넌트
function StatusTag({ text, type = 'default', color = 'primary' }: { text: string; type?: 'default' | 'emphasis'; color?: 'primary' | 'gray' }) {
if (type === 'default' && color === 'primary') {
return (
{text}
);
}
if (type === 'default' && color === 'gray') {
return (
{text}
);
}
return (
{text}
);
}
type LearnerProgress = {
id: string;
courseName: string;
lessonName: string;
learnerName: string;
enrollmentDate: string;
lastStudyDate: string;
progressRate: number;
hasSubmitted: boolean;
score: number | null;
isCompleted: boolean;
};
export default function InstructorCoursesPage() {
const router = useRouter();
const [userRole, setUserRole] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
// 필터 상태
const [selectedCourse, setSelectedCourse] = useState('all');
const [selectedSubmissionStatus, setSelectedSubmissionStatus] = useState('all');
const [selectedCompletionStatus, setSelectedCompletionStatus] = useState('all');
const [searchQuery, setSearchQuery] = useState('');
// 드롭다운 열림 상태
const [isCourseDropdownOpen, setIsCourseDropdownOpen] = useState(false);
const [isSubmissionDropdownOpen, setIsSubmissionDropdownOpen] = useState(false);
const [isCompletionDropdownOpen, setIsCompletionDropdownOpen] = useState(false);
// 데이터
const [courses, setCourses] = useState<{ id: string; name: string }[]>([]);
const [learnerProgress, setLearnerProgress] = useState([]);
const ITEMS_PER_PAGE = 10;
// 사용자 정보 및 권한 확인
useEffect(() => {
let isMounted = true;
async function fetchUserInfo() {
try {
const localStorageToken = localStorage.getItem('token');
const cookieToken = document.cookie
.split('; ')
.find(row => row.startsWith('token='))
?.split('=')[1];
const token = localStorageToken || cookieToken;
if (!token) {
router.push('/login');
return;
}
const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/me`
: 'https://hrdi.coconutmeet.net/auth/me';
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
if (response.status === 401) {
localStorage.removeItem('token');
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
if (isMounted) {
router.push('/login');
}
}
return;
}
const data = await response.json();
if (isMounted) {
const role = data.role || data.userRole || '';
setUserRole(role);
// admin이 아니면 접근 불가
if (role !== 'ADMIN' && role !== 'admin') {
router.push('/');
return;
}
}
} catch (error) {
console.error('사용자 정보 조회 오류:', error);
if (isMounted) {
router.push('/login');
}
}
}
fetchUserInfo();
return () => {
isMounted = false;
};
}, [router]);
// 교육 과정 목록 가져오기
useEffect(() => {
async function fetchCourses() {
try {
const token = localStorage.getItem('token') || document.cookie
.split('; ')
.find(row => row.startsWith('token='))
?.split('=')[1];
const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/subjects`
: 'https://hrdi.coconutmeet.net/subjects';
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
},
});
if (response.ok) {
const data = await response.json();
const coursesArray = Array.isArray(data) ? data : (data.items || data.courses || data.data || []);
setCourses(coursesArray.map((item: any) => ({
id: String(item.id || item.subjectId || ''),
name: item.courseName || item.name || item.subjectName || '',
})));
}
} catch (error) {
console.error('교육 과정 목록 조회 오류:', error);
}
}
fetchCourses();
}, []);
// 학습자 진행 상황 데이터 (더미 데이터 - 실제 API로 교체 필요)
useEffect(() => {
async function fetchLearnerProgress() {
try {
setIsLoading(true);
// TODO: 실제 API 호출로 교체
// 현재는 더미 데이터 사용
const dummyData: LearnerProgress[] = [
{
id: '1',
courseName: '방사선 관리',
lessonName: '{강좌명}',
learnerName: '김하늘',
enrollmentDate: '2025-09-10',
lastStudyDate: '2025-09-10',
progressRate: 100,
hasSubmitted: true,
score: 100,
isCompleted: true,
},
{
id: '2',
courseName: '방사선 관리',
lessonName: '{강좌명}',
learnerName: '김하늘',
enrollmentDate: '2025-09-10',
lastStudyDate: '2025-09-10',
progressRate: 100,
hasSubmitted: true,
score: 100,
isCompleted: true,
},
{
id: '3',
courseName: '방사선 관리',
lessonName: '{강좌명}',
learnerName: '김하늘',
enrollmentDate: '2025-09-10',
lastStudyDate: '2025-09-10',
progressRate: 100,
hasSubmitted: true,
score: 100,
isCompleted: true,
},
{
id: '4',
courseName: '원자로 운전 및 계통',
lessonName: '6. 원자로 시동, 운전 및 정지 절차',
learnerName: '김하늘',
enrollmentDate: '2025-09-10',
lastStudyDate: '2025-09-10',
progressRate: 100,
hasSubmitted: true,
score: 60,
isCompleted: true,
},
{
id: '5',
courseName: '방사선 관리',
lessonName: '{강좌명}',
learnerName: '김하늘',
enrollmentDate: '2025-09-10',
lastStudyDate: '2025-09-10',
progressRate: 100,
hasSubmitted: true,
score: 30,
isCompleted: false,
},
{
id: '6',
courseName: '방사선 관리',
lessonName: '{강좌명}',
learnerName: '김하늘',
enrollmentDate: '2025-09-10',
lastStudyDate: '2025-09-10',
progressRate: 100,
hasSubmitted: true,
score: 30,
isCompleted: false,
},
{
id: '7',
courseName: '방사선 관리',
lessonName: '{강좌명}',
learnerName: '김하늘',
enrollmentDate: '2025-09-10',
lastStudyDate: '2025-09-10',
progressRate: 100,
hasSubmitted: false,
score: null,
isCompleted: false,
},
];
setLearnerProgress(dummyData);
} catch (error) {
console.error('학습자 진행 상황 조회 오류:', error);
} finally {
setIsLoading(false);
}
}
fetchLearnerProgress();
}, []);
// 필터링된 데이터
const filteredData = useMemo(() => {
return learnerProgress.filter((item) => {
// 교육 과정 필터
if (selectedCourse !== 'all' && item.courseName !== selectedCourse) {
return false;
}
// 문제 제출 여부 필터
if (selectedSubmissionStatus === 'submitted' && !item.hasSubmitted) {
return false;
}
if (selectedSubmissionStatus === 'not-submitted' && item.hasSubmitted) {
return false;
}
// 수료 여부 필터
if (selectedCompletionStatus === 'completed' && !item.isCompleted) {
return false;
}
if (selectedCompletionStatus === 'not-completed' && item.isCompleted) {
return false;
}
// 검색어 필터
if (searchQuery && !item.learnerName.toLowerCase().includes(searchQuery.toLowerCase())) {
return false;
}
return true;
});
}, [learnerProgress, selectedCourse, selectedSubmissionStatus, selectedCompletionStatus, searchQuery]);
// 페이지네이션
const totalPages = Math.ceil(filteredData.length / ITEMS_PER_PAGE);
const paginatedData = useMemo(() => {
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
const endIndex = startIndex + ITEMS_PER_PAGE;
return filteredData.slice(startIndex, endIndex);
}, [filteredData, currentPage]);
// 드롭다운 외부 클릭 감지
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as HTMLElement;
if (!target.closest('.dropdown-container')) {
setIsCourseDropdownOpen(false);
setIsSubmissionDropdownOpen(false);
setIsCompletionDropdownOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
return (
강좌 현황
{/* 필터 및 검색 영역 */}
{/* 교육 과정 드롭다운 */}
{isCourseDropdownOpen && (
{courses.map((course) => (
))}
)}
{/* 문제 제출 여부 드롭다운 */}
{isSubmissionDropdownOpen && (
)}
{/* 수료 여부 드롭다운 */}
{isCompletionDropdownOpen && (
)}
{/* 검색바 */}
setSearchQuery(e.target.value)}
className="flex-1 text-[16px] font-normal leading-[1.5] text-[#b1b8c0] outline-none placeholder:text-[#b1b8c0]"
/>
{/* 테이블 영역 */}
{isLoading ? (
) : filteredData.length === 0 ? (
) : (
<>
{/* 테이블 헤더 */}
교육 과정명
강좌명
학습자명
가입일
마지막 수강일
진도율
문제 제출 여부
평가 점수
수료 여부
{/* 테이블 바디 */}
{paginatedData.map((item) => (
{item.courseName}
{item.lessonName}
{item.learnerName}
{item.enrollmentDate}
{item.lastStudyDate}
{item.progressRate}%
{item.hasSubmitted ? (
) : (
)}
{item.score !== null ? `${item.score}점` : '-'}
{item.isCompleted ? (
) : (
)}
))}
>
)}
{/* 페이지네이션 */}
{filteredData.length > ITEMS_PER_PAGE && (
{/* First */}
{/* Prev */}
{/* Page Numbers */}
{Array.from({ length: Math.min(10, totalPages) }, (_, i) => {
const pageNum = i + 1;
const isActive = pageNum === currentPage;
return (
);
})}
{/* Next */}
{/* Last */}
)}
);
}