345 lines
12 KiB
TypeScript
345 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import MainLogoSvg from '../svgs/mainlogosvg';
|
|
import ChevronDownSvg from '../svgs/chevrondownsvg';
|
|
import apiService from '../lib/apiService';
|
|
|
|
// 아이콘 컴포넌트들
|
|
function BookIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className={className}
|
|
>
|
|
<path
|
|
d="M4 19.5C4 18.837 4.263 18.201 4.732 17.732C5.201 17.263 5.837 17 6.5 17H20"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
<path
|
|
d="M6.5 2H20V22H6.5C5.837 22 5.201 21.737 4.732 21.268C4.263 20.799 4 20.163 4 19.5V4.5C4 3.837 4.263 3.201 4.732 2.732C5.201 2.263 5.837 2 6.5 2Z"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function DocumentIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className={className}
|
|
>
|
|
<path
|
|
d="M14 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V8L14 2Z"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
<path
|
|
d="M14 2V8H20"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
<path
|
|
d="M16 13H8"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
<path
|
|
d="M16 17H8"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
<path
|
|
d="M10 9H9H8"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function CheckCircleIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className={className}
|
|
>
|
|
<path
|
|
d="M22 11.08V12C21.9988 14.1564 21.3005 16.2547 20.0093 17.9818C18.7182 19.7088 16.9033 20.9725 14.8354 21.5839C12.7674 22.1953 10.5573 22.1219 8.53447 21.3746C6.51168 20.6273 4.78465 19.2461 3.61096 17.4371C2.43727 15.628 1.87979 13.4881 2.02168 11.3363C2.16356 9.18455 2.99721 7.13631 4.39828 5.49706C5.79935 3.85781 7.69279 2.71537 9.79619 2.24013C11.8996 1.7649 14.1003 1.98232 16.07 2.85999"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
<path
|
|
d="M22 4L12 14.01L9 11.01"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function UserIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 16 16"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className={className}
|
|
>
|
|
<path
|
|
d="M8 8C9.65685 8 11 6.65685 11 5C11 3.34315 9.65685 2 8 2C6.34315 2 5 3.34315 5 5C5 6.65685 6.34315 8 8 8Z"
|
|
fill="currentColor"
|
|
/>
|
|
<path
|
|
d="M2 13.3333C2 11.0862 3.75333 9.33333 6 9.33333H10C12.2467 9.33333 14 11.0862 14 13.3333V14H2V13.3333Z"
|
|
fill="currentColor"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function ChevronRightIcon({ className }: { className?: string }) {
|
|
return (
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 16 16"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className={className}
|
|
>
|
|
<path
|
|
d="M6 12L10 8L6 4"
|
|
stroke="currentColor"
|
|
strokeWidth="1.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
type Activity = {
|
|
id: string;
|
|
userName: string;
|
|
message: string;
|
|
timestamp: string;
|
|
};
|
|
|
|
export default function InstructorPage() {
|
|
const router = useRouter();
|
|
const [userName, setUserName] = useState<string>('');
|
|
const [userRole, setUserRole] = useState<string>('');
|
|
const [totalCourses, setTotalCourses] = useState<number>(5);
|
|
const [submissionStatus, setSubmissionStatus] = useState<{ current: number; total: number }>({ current: 10, total: 50 });
|
|
const [completionStatus, setCompletionStatus] = useState<{ current: number; total: number }>({ current: 14, total: 50 });
|
|
const [activities, setActivities] = useState<Activity[]>([
|
|
{ id: '1', userName: '김하늘', message: '{강좌명} 문제를 제출했습니다.', timestamp: '2025-12-12 14:44' },
|
|
{ id: '2', userName: '김하늘', message: '{강좌명} 문제를 제출했습니다.', timestamp: '2025-12-12 14:44' },
|
|
{ id: '3', userName: '김하늘', message: '모든 강좌를 수강했습니다.', timestamp: '2025-12-12 14:44' },
|
|
]);
|
|
|
|
// 사용자 정보 가져오기
|
|
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 response = await apiService.getCurrentUser();
|
|
|
|
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 = response.data;
|
|
|
|
if (isMounted) {
|
|
const role = data.role || data.userRole || '';
|
|
setUserRole(role);
|
|
|
|
// admin이 아니면 접근 불가
|
|
if (role !== 'ADMIN' && role !== 'admin') {
|
|
router.push('/');
|
|
return;
|
|
}
|
|
|
|
if (data.name) {
|
|
setUserName(data.name);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('사용자 정보 조회 오류:', error);
|
|
if (isMounted) {
|
|
router.push('/login');
|
|
}
|
|
}
|
|
}
|
|
|
|
fetchUserInfo();
|
|
|
|
return () => {
|
|
isMounted = false;
|
|
};
|
|
}, [router]);
|
|
|
|
return (
|
|
<div className="bg-white min-h-screen flex flex-col">
|
|
<div className="flex-1 max-w-[1440px] w-full mx-auto px-0">
|
|
<div className="flex flex-col gap-[40px] w-full">
|
|
{/* 강좌별 상세 내역 섹션 */}
|
|
<div className="flex flex-col w-full">
|
|
<div className="flex h-[100px] items-center justify-between px-[32px]">
|
|
<h2 className="text-[24px] font-bold leading-[1.5] text-[#1b2027]">
|
|
강좌별 상세 내역
|
|
</h2>
|
|
<Link
|
|
href="/instructor/courses"
|
|
className="flex items-center gap-[2px] text-[14px] font-medium text-[#6c7682]"
|
|
>
|
|
<span>전체보기</span>
|
|
<ChevronRightIcon />
|
|
</Link>
|
|
</div>
|
|
<div className="flex flex-col gap-[16px] pb-[32px] pt-0 px-[32px]">
|
|
<div className="flex gap-[16px] h-[120px]">
|
|
{/* 총 강좌 수 카드 */}
|
|
<div className="flex-1 bg-white border border-[#dee1e6] rounded-[16px] flex gap-[16px] items-center justify-center p-[24px]">
|
|
<div className="bg-[#ecf0ff] rounded-full size-[48px] flex items-center justify-center shrink-0">
|
|
<BookIcon className="size-[24px] text-[#060958]" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col items-start">
|
|
<p className="text-[14px] font-normal text-[#333c47] leading-[1.5]">
|
|
총 강좌 수
|
|
</p>
|
|
<p className="text-[20px] font-bold text-[#333c47] leading-[1.5]">
|
|
{totalCourses}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 학습자 문제 제출 현황 카드 */}
|
|
<div className="flex-1 bg-white border border-[#dee1e6] rounded-[16px] flex gap-[16px] items-center justify-center p-[24px]">
|
|
<div className="bg-[#ecf0ff] rounded-full size-[48px] flex items-center justify-center shrink-0">
|
|
<DocumentIcon className="size-[24px] text-[#060958]" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col items-start">
|
|
<p className="text-[14px] font-normal text-[#333c47] leading-[1.5]">
|
|
학습자 문제 제출 현황
|
|
</p>
|
|
<p className="text-[20px] font-bold text-[#333c47] leading-[1.5]">
|
|
{submissionStatus.current} / {submissionStatus.total}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 학습자 수료 현황 카드 */}
|
|
<div className="flex-1 bg-white border border-[#dee1e6] rounded-[16px] flex gap-[16px] items-center justify-center p-[24px]">
|
|
<div className="bg-[#ecf0ff] rounded-full size-[48px] flex items-center justify-center shrink-0">
|
|
<CheckCircleIcon className="size-[24px] text-[#060958]" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col items-start">
|
|
<p className="text-[14px] font-normal text-[#333c47] leading-[1.5]">
|
|
학습자 수료 현황
|
|
</p>
|
|
<p className="text-[20px] font-bold text-[#333c47] leading-[1.5]">
|
|
{completionStatus.current} / {completionStatus.total}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 최근 학습자 활동 섹션 */}
|
|
<div className="flex flex-col w-full">
|
|
<div className="flex gap-[10px] h-[100px] items-center px-[32px]">
|
|
<h2 className="text-[24px] font-bold leading-[1.5] text-[#1b2027]">
|
|
최근 학습자 활동
|
|
</h2>
|
|
</div>
|
|
<div className="flex flex-col gap-[16px] pb-[80px] pt-0 px-[32px]">
|
|
<div className="flex flex-col gap-[8px] min-h-[256px]">
|
|
{activities.map((activity) => (
|
|
<div
|
|
key={activity.id}
|
|
className="bg-white border border-[#dee1e6] rounded-[8px] flex gap-[12px] items-center p-[17px]"
|
|
>
|
|
<div className="bg-[#f1f3f5] rounded-full size-[32px] flex items-center justify-center shrink-0">
|
|
<UserIcon className="size-[16px] text-[#333c47]" />
|
|
</div>
|
|
<div className="flex-1 flex flex-col items-start">
|
|
<div className="flex flex-col text-[15px] leading-[1.5] text-[#1b2027]">
|
|
<span className="font-semibold">{activity.userName}</span>
|
|
<span className="font-normal">{activity.message}</span>
|
|
</div>
|
|
</div>
|
|
<p className="text-[13px] font-normal text-[#6c7682] leading-[1.4] shrink-0">
|
|
{activity.timestamp}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|