Compare commits

...

5 Commits

Author SHA1 Message Date
koreacomp5
dae09ff2e7 mota1 2025-11-15 22:28:31 +09:00
wallace
0af1028b7b 시드 만들었음음 2025-11-13 18:07:45 +09:00
wallace
efdcde8b12 페이지네이션 아이콘 추가가 2025-11-13 15:53:07 +09:00
wallace
a7cad39b3c admin 페이지 메인 수정정 2025-11-13 15:32:58 +09:00
wallace
16d6cb5b30 admin 임시 리스트 생성성 2025-11-13 15:28:45 +09:00
16 changed files with 1398 additions and 277 deletions

View File

@@ -4,11 +4,146 @@ import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { isAdminLoggedIn } from '../../lib/auth'; import { isAdminLoggedIn } from '../../lib/auth';
import LoginPage from '../login/page'; import LoginPage from '../login/page';
import Logout from '../../public/svg/logout';
const imgArrowDisabled = "http://localhost:3845/assets/6edcb2defc36a2bf4a05a3abe53b8da3d42b2cb4.svg";
const imgArrowDefault = "http://localhost:3845/assets/ad0cb4418492f1b020bb38a2ff038a331294ce87.svg";
const imgArrowNext = "http://localhost:3845/assets/6328cf96ee1169c1425c2ce55e7a2dcca0374508.svg";
interface User {
id: number;
joinDate: string;
name: string;
email: string;
role: string;
accountStatus: string;
accountManagement: string;
}
type PaginationMove = "Previous" | "Next";
type PaginationStatus = "Default" | "Disabled";
function PaginationBtnMove({ status = "Default", move = "Previous" }: { status?: PaginationStatus; move?: PaginationMove }) {
const isDisabled = status === "Disabled";
const isNext = move === "Next";
if (isDisabled && isNext) {
return (
<div className="border border-[#eeeeee] border-solid relative rounded-[8px] shrink-0 size-[30px]">
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className="absolute flex items-center justify-center left-[5px] size-[20px] top-[5px]">
<div className="flex-none rotate-[180deg]">
<div className="overflow-clip relative size-[20px]">
<div className="absolute flex inset-[15.63%_30.21%] items-center justify-center">
<div className="flex-none h-[16.5px] rotate-[180deg] w-[9.5px]">
<div className="relative size-full">
<div className="absolute inset-0">
<img alt="" className="block max-w-none size-full" style={{ filter: 'opacity(0.88) brightness(0) saturate(100%) invert(88%)', opacity: 1 }} src={imgArrowNext} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
if (isDisabled && !isNext) {
return (
<div className="border border-[#eeeeee] border-solid relative rounded-[8px] shrink-0 size-[30px]">
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className="absolute flex items-center justify-center left-[5px] size-[20px] top-[5px]">
<div className="flex-none rotate-[180deg]">
<div className="overflow-clip relative size-[20px]">
<div className="absolute inset-[15.63%_30.21%]">
<div className="absolute inset-0">
<img alt="" className="block max-w-none size-full" style={{ filter: 'opacity(0.88) brightness(0) saturate(100%) invert(88%)', opacity: 1 }} src={imgArrowDefault} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
if (isNext) {
return (
<div className="border border-[#eeeeee] border-solid relative rounded-[8px] shrink-0 size-[30px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className="absolute flex items-center justify-center left-[5px] size-[20px] top-[5px]">
<div className="flex-none rotate-[180deg]">
<div className="overflow-clip relative size-[20px]">
<div className="absolute flex inset-[15.63%_30.21%] items-center justify-center">
<div className="flex-none h-[16.5px] rotate-[180deg] w-[9.5px]">
<div className="relative size-full">
<div className="absolute inset-0">
<img alt="" className="block max-w-none size-full" src={imgArrowNext} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
return (
<div className="border border-[#eeeeee] border-solid relative rounded-[8px] shrink-0 size-[30px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className="absolute flex items-center justify-center left-[5px] size-[20px] top-[5px]">
<div className="flex-none rotate-[180deg]">
<div className="overflow-clip relative size-[20px]">
<div className="absolute inset-[15.63%_30.21%]">
<div className="absolute inset-0">
<img alt="" className="block max-w-none size-full" src={imgArrowDefault} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default function AdminHomePage() { export default function AdminHomePage() {
const router = useRouter(); const router = useRouter();
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false); const [isAuthenticated, setIsAuthenticated] = useState(false);
const [selectedTab, setSelectedTab] = useState<'전체' | '학습자' | '강사' | '운영자'>('전체');
const [currentPage, setCurrentPage] = useState(1);
// 샘플 사용자 데이터
const [users, setUsers] = useState<User[]>([
{ id: 39, joinDate: '2026-01-15', name: '홍길동', email: 'hong@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 38, joinDate: '2026-01-14', name: '김철수', email: 'kim@example.com', role: '강사', accountStatus: '활성', accountManagement: '관리' },
{ id: 37, joinDate: '2026-01-13', name: '이영희', email: 'lee@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 36, joinDate: '2026-01-12', name: '박민수', email: 'park@example.com', role: '운영자', accountStatus: '활성', accountManagement: '관리' },
{ id: 35, joinDate: '2026-01-11', name: '최지영', email: 'choi@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 34, joinDate: '2026-01-10', name: '정대현', email: 'jung@example.com', role: '강사', accountStatus: '활성', accountManagement: '관리' },
{ id: 33, joinDate: '2026-01-09', name: '강미영', email: 'kang@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 32, joinDate: '2026-01-08', name: '윤성호', email: 'yoon@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 31, joinDate: '2026-01-07', name: '임수진', email: 'lim@example.com', role: '강사', accountStatus: '활성', accountManagement: '관리' },
{ id: 30, joinDate: '2026-01-06', name: '한지훈', email: 'han@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 29, joinDate: '2026-01-05', name: '송민경', email: 'song@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 28, joinDate: '2026-01-04', name: '오준혁', email: 'oh@example.com', role: '운영자', accountStatus: '활성', accountManagement: '관리' },
{ id: 27, joinDate: '2026-01-03', name: '류현우', email: 'ryu@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 26, joinDate: '2026-01-02', name: '신동욱', email: 'shin@example.com', role: '강사', accountStatus: '활성', accountManagement: '관리' },
{ id: 25, joinDate: '2026-01-01', name: '조은서', email: 'cho@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 24, joinDate: '2025-12-31', name: '배성민', email: 'bae@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 23, joinDate: '2025-12-30', name: '전혜진', email: 'jeon@example.com', role: '강사', accountStatus: '활성', accountManagement: '관리' },
{ id: 22, joinDate: '2025-12-29', name: '남궁준', email: 'namgung@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 21, joinDate: '2025-12-28', name: '서아름', email: 'seo@example.com', role: '학습자', accountStatus: '활성', accountManagement: '관리' },
{ id: 20, joinDate: '2025-12-27', name: '권태영', email: 'kwon@example.com', role: '운영자', accountStatus: '활성', accountManagement: '관리' },
]);
useEffect(() => { useEffect(() => {
// 관리자 인증 확인 // 관리자 인증 확인
@@ -36,13 +171,291 @@ export default function AdminHomePage() {
return <LoginPage />; return <LoginPage />;
} }
const itemsPerPage = 13;
const totalPages = Math.ceil(users.length / itemsPerPage);
const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentUsers = users.slice(startIndex, endIndex);
return ( return (
<div className="bg-white relative size-full min-h-screen"> <div className="bg-white relative size-full min-h-screen">
<div className="flex items-center justify-center min-h-screen"> {/* 사이드바 */}
<div className="text-center"> <div className="absolute bg-white border-r border-[#eeeeee] border-solid box-border content-stretch flex flex-col gap-[45px] items-center left-0 min-h-[1080px] pb-8 pt-[30px] px-0 top-0 w-[250px]">
<h1 className="text-2xl font-bold text-[#404040] mb-4"> </h1> {/* 로고 */}
<p className="text-[#515151]"> .</p> <button
onClick={() => router.push('/')}
className="h-[102px] relative shrink-0 w-[99px] cursor-pointer hover:opacity-80 transition-opacity"
>
<div className="absolute inset-0 flex items-center justify-center overflow-hidden pointer-events-none">
<img alt="로고" className="h-full w-full object-contain" src="/logo.svg" />
</div> </div>
</button>
{/* 메뉴 */}
<div className="box-border content-stretch flex flex-col items-center pb-0 pt-4 px-0 relative shrink-0 w-[250px]">
<div className="box-border content-stretch flex flex-col gap-2 items-start p-3 relative shrink-0 w-full">
<button className="bg-[#f7f7f7] box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button
onClick={() => router.push('/admin_lecture1')}
className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors"
>
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button
onClick={() => router.push('/admin_lecture2')}
className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors"
>
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
/
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
</div>
</div>
{/* 로그아웃 */}
<button
onClick={() => {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('isAdminLoggedIn');
router.push('/login');
}}
className="content-stretch flex gap-[9px] h-[36px] items-center relative shrink-0 cursor-pointer hover:opacity-80 transition-opacity"
>
<div className="flex items-center justify-center relative shrink-0">
<div className="flex-none rotate-[180deg] scale-y-[-100%]">
<div className="h-[23.12px] relative w-[22px]">
<Logout />
</div>
</div>
</div>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center px-[10px] py-[5px] relative shrink-0">
<p className="font-medium leading-[1.6] not-italic relative shrink-0 text-[16px] text-[#404040] text-nowrap whitespace-pre">
</p>
</div>
</button>
</div>
{/* 메인 콘텐츠 */}
<div className="absolute left-[250px] top-0 right-0 min-h-screen">
{/* 페이지 타이틀 탭 */}
<div className="absolute content-stretch flex gap-[24px] items-center left-[48px] top-[45px]">
<button
onClick={() => setSelectedTab('전체')}
className={`border-b-[2px] border-solid box-border content-stretch flex gap-[150px] items-center relative shrink-0 ${selectedTab === '전체' ? 'border-[#2b82e8]' : 'border-transparent'}`}
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
</p>
</div>
</button>
<button
onClick={() => setSelectedTab('학습자')}
className={`border-b-[2px] border-solid box-border content-stretch flex gap-[150px] items-center relative shrink-0 ${selectedTab === '학습자' ? 'border-[#2b82e8]' : 'border-transparent'}`}
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
</p>
</div>
</button>
<button
onClick={() => setSelectedTab('강사')}
className={`border-b-[2px] border-solid box-border content-stretch flex gap-[150px] items-center relative shrink-0 ${selectedTab === '강사' ? 'border-[#2b82e8]' : 'border-transparent'}`}
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
</p>
</div>
</button>
<button
onClick={() => setSelectedTab('운영자')}
className={`border-b-[2px] border-solid box-border content-stretch flex gap-[150px] items-center relative shrink-0 ${selectedTab === '운영자' ? 'border-[#2b82e8]' : 'border-transparent'}`}
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
</p>
</div>
</button>
</div>
{/* 사용자 목록 테이블 */}
<div className="absolute content-stretch flex flex-col items-start left-[48px] right-[101px] top-[135px] h-[671px] overflow-hidden">
{/* 테이블 헤더 */}
<div className="bg-[rgba(235,247,255,0.5)] content-stretch flex h-[41px] items-center relative shrink-0 w-full">
<div className="content-stretch flex items-center relative shrink-0 w-full">
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"></p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"></p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]">()</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"></p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"> </p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"> </p>
</div>
</div>
</div>
</div>
{/* 테이블 바디 */}
{users.length === 0 ? (
<div className="relative w-full flex items-center justify-center" style={{ minHeight: '400px' }}>
<div className="content-stretch flex flex-col gap-[16px] items-center relative shrink-0">
<div className="content-stretch flex flex-col gap-[2px] items-center relative shrink-0">
<p className="font-medium leading-[1.6] not-italic relative shrink-0 text-[16px] text-[#969696] text-nowrap whitespace-pre">
.
</p>
</div>
</div>
</div>
) : (
<div className="content-stretch flex flex-col items-start relative shrink-0 w-full max-h-[630px] overflow-y-auto">
{currentUsers.map((user) => (
<div key={user.id} className="bg-white content-stretch flex items-center relative shrink-0 w-full">
<div className="content-stretch flex items-center relative shrink-0 w-full">
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-medium grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]">{user.joinDate}</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-medium grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]">{user.name}</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-medium grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]">{user.email}</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-medium grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]">{user.role}</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-medium grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]">{user.accountStatus}</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-medium grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]">{user.accountManagement}</p>
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
{/* 페이지네이션 */}
{users.length > 0 && (
<div className="absolute content-stretch flex gap-2 items-center justify-center left-[48px] right-[101px] top-[909px]">
<button onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}>
<PaginationBtnMove status={currentPage === 1 ? "Disabled" : "Default"} move="Previous" />
</button>
<div className="content-stretch flex gap-2 items-center relative shrink-0">
{pageNumbers.map((pageNum) => (
<button
key={pageNum}
onClick={() => setCurrentPage(pageNum)}
className={`relative rounded-[8px] shrink-0 size-[30px] cursor-pointer transition-colors ${currentPage === pageNum
? 'bg-[#1d1d1d]'
: 'border border-[#eeeeee] border-solid hover:bg-gray-50'
}`}
>
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className={`absolute flex flex-col font-medium justify-center leading-[0] left-[15.5px] not-italic text-[14px] text-center text-nowrap top-[15px] translate-x-[-50%] translate-y-[-50%] ${currentPage === pageNum ? 'text-white' : 'text-[#515151]'
}`}>
<p className="leading-[1.6] whitespace-pre">{pageNum}</p>
</div>
</div>
</button>
))}
</div>
<button onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}>
<PaginationBtnMove status={currentPage === totalPages ? "Disabled" : "Default"} move="Next" />
</button>
</div>
)}
</div> </div>
</div> </div>
); );

View File

@@ -277,7 +277,10 @@ export default function AdminLecture1Page() {
{/* 메뉴 */} {/* 메뉴 */}
<div className="box-border content-stretch flex flex-col items-center pb-0 pt-4 px-0 relative shrink-0 w-[250px]"> <div className="box-border content-stretch flex flex-col items-center pb-0 pt-4 px-0 relative shrink-0 w-[250px]">
<div className="box-border content-stretch flex flex-col gap-2 items-start p-3 relative shrink-0 w-full"> <div className="box-border content-stretch flex flex-col gap-2 items-start p-3 relative shrink-0 w-full">
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors"> <button
onClick={() => router.push('/admin_home')}
className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors"
>
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2"> <div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap"> <p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
@@ -346,7 +349,14 @@ export default function AdminLecture1Page() {
</div> </div>
</div> </div>
{/* 로그아웃 */} {/* 로그아웃 */}
<div className="content-stretch flex gap-[9px] items-center relative shrink-0"> <button
onClick={() => {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('isAdminLoggedIn');
router.push('/login');
}}
className="content-stretch flex gap-[9px] h-[36px] items-center relative shrink-0 cursor-pointer hover:opacity-80 transition-opacity"
>
<div className="flex items-center justify-center relative shrink-0"> <div className="flex items-center justify-center relative shrink-0">
<div className="flex-none rotate-[180deg] scale-y-[-100%]"> <div className="flex-none rotate-[180deg] scale-y-[-100%]">
<div className="h-[23.12px] relative w-[22px]"> <div className="h-[23.12px] relative w-[22px]">
@@ -354,14 +364,6 @@ export default function AdminLecture1Page() {
</div> </div>
</div> </div>
</div> </div>
<button
onClick={() => {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('isAdminLoggedIn');
router.push('/');
}}
className="content-stretch flex h-[36px] items-center justify-between relative shrink-0 w-[76px] cursor-pointer hover:opacity-80 transition-opacity"
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center px-[10px] py-[5px] relative shrink-0"> <div className="box-border content-stretch flex gap-[10px] items-center justify-center px-[10px] py-[5px] relative shrink-0">
<p className="font-medium leading-[1.6] not-italic relative shrink-0 text-[16px] text-[#404040] text-nowrap whitespace-pre"> <p className="font-medium leading-[1.6] not-italic relative shrink-0 text-[16px] text-[#404040] text-nowrap whitespace-pre">
@@ -369,7 +371,6 @@ export default function AdminLecture1Page() {
</div> </div>
</button> </button>
</div> </div>
</div>
{/* 메인 콘텐츠 */} {/* 메인 콘텐츠 */}
<div className="absolute left-[250px] top-0 right-0 min-h-screen"> <div className="absolute left-[250px] top-0 right-0 min-h-screen">

View File

@@ -6,6 +6,104 @@ import { isAdminLoggedIn } from '../../lib/auth';
import LoginPage from '../login/page'; import LoginPage from '../login/page';
import Logout from '../../public/svg/logout'; import Logout from '../../public/svg/logout';
const imgArrowDisabled = "http://localhost:3845/assets/6edcb2defc36a2bf4a05a3abe53b8da3d42b2cb4.svg";
const imgArrowDefault = "http://localhost:3845/assets/ad0cb4418492f1b020bb38a2ff038a331294ce87.svg";
const imgArrowNext = "http://localhost:3845/assets/6328cf96ee1169c1425c2ce55e7a2dcca0374508.svg";
type PaginationMove = "Previous" | "Next";
type PaginationStatus = "Default" | "Disabled";
function PaginationBtnMove({ status = "Default", move = "Previous" }: { status?: PaginationStatus; move?: PaginationMove }) {
const isDisabled = status === "Disabled";
const isNext = move === "Next";
if (isDisabled && isNext) {
return (
<div className="border border-[#eeeeee] border-solid relative rounded-[8px] shrink-0 size-[30px]">
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className="absolute flex items-center justify-center left-[5px] size-[20px] top-[5px]">
<div className="flex-none rotate-[180deg]">
<div className="overflow-clip relative size-[20px]">
<div className="absolute flex inset-[15.63%_30.21%] items-center justify-center">
<div className="flex-none h-[16.5px] rotate-[180deg] w-[9.5px]">
<div className="relative size-full">
<div className="absolute inset-0">
<img alt="" className="block max-w-none size-full" style={{ filter: 'opacity(0.88) brightness(0) saturate(100%) invert(88%)', opacity: 1 }} src={imgArrowNext} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
if (isDisabled && !isNext) {
return (
<div className="border border-[#eeeeee] border-solid relative rounded-[8px] shrink-0 size-[30px]">
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className="absolute flex items-center justify-center left-[5px] size-[20px] top-[5px]">
<div className="flex-none rotate-[180deg]">
<div className="overflow-clip relative size-[20px]">
<div className="absolute inset-[15.63%_30.21%]">
<div className="absolute inset-0">
<img alt="" className="block max-w-none size-full" style={{ filter: 'opacity(0.88) brightness(0) saturate(100%) invert(88%)', opacity: 1 }} src={imgArrowDefault} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
if (isNext) {
return (
<div className="border border-[#eeeeee] border-solid relative rounded-[8px] shrink-0 size-[30px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className="absolute flex items-center justify-center left-[5px] size-[20px] top-[5px]">
<div className="flex-none rotate-[180deg]">
<div className="overflow-clip relative size-[20px]">
<div className="absolute flex inset-[15.63%_30.21%] items-center justify-center">
<div className="flex-none h-[16.5px] rotate-[180deg] w-[9.5px]">
<div className="relative size-full">
<div className="absolute inset-0">
<img alt="" className="block max-w-none size-full" src={imgArrowNext} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
return (
<div className="border border-[#eeeeee] border-solid relative rounded-[8px] shrink-0 size-[30px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className="absolute flex items-center justify-center left-[5px] size-[20px] top-[5px]">
<div className="flex-none rotate-[180deg]">
<div className="overflow-clip relative size-[20px]">
<div className="absolute inset-[15.63%_30.21%]">
<div className="absolute inset-0">
<img alt="" className="block max-w-none size-full" src={imgArrowDefault} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
interface Lecture { interface Lecture {
id: number; id: number;
courseName: string; courseName: string;
@@ -20,7 +118,29 @@ export default function AdminLecture2Page() {
const router = useRouter(); const router = useRouter();
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false); const [isAuthenticated, setIsAuthenticated] = useState(false);
const [lectures, setLectures] = useState<Lecture[]>([]); const [currentPage, setCurrentPage] = useState(1);
const [lectures, setLectures] = useState<Lecture[]>([
{ id: 39, courseName: '방사선 적용', lectureName: '방사선 기본 원리', attachedFile: '파일1.pdf', questionCount: 10, registrar: '이철수', createdAt: '2026-01-15' },
{ id: 38, courseName: '방사선 적용', lectureName: '방사선 안전 관리', attachedFile: '파일2.pdf', questionCount: 15, registrar: '이철수', createdAt: '2026-01-14' },
{ id: 37, courseName: '방사선 원리', lectureName: '방사선 측정 기법', attachedFile: '파일3.pdf', questionCount: 12, registrar: '김등록', createdAt: '2026-01-13' },
{ id: 36, courseName: '방사선 적용', lectureName: '방사선 장비 사용법', attachedFile: '파일4.pdf', questionCount: 8, registrar: '이철수', createdAt: '2026-01-12' },
{ id: 35, courseName: '방사선 원리', lectureName: '방사선 물리학 기초', attachedFile: '파일5.pdf', questionCount: 20, registrar: '김등록', createdAt: '2026-01-11' },
{ id: 34, courseName: '방사선 적용', lectureName: '방사선 보호 장비', attachedFile: '파일6.pdf', questionCount: 14, registrar: '이철수', createdAt: '2026-01-10' },
{ id: 33, courseName: '방사선 적용', lectureName: '방사선 응용 실습', attachedFile: '파일7.pdf', questionCount: 16, registrar: '이철수', createdAt: '2026-01-09' },
{ id: 32, courseName: '방사선 원리', lectureName: '방사선 화학 반응', attachedFile: '파일8.pdf', questionCount: 11, registrar: '김등록', createdAt: '2026-01-08' },
{ id: 31, courseName: '방사선 적용', lectureName: '방사선 의료 응용', attachedFile: '파일9.pdf', questionCount: 18, registrar: '이철수', createdAt: '2026-01-07' },
{ id: 30, courseName: '방사선 적용', lectureName: '방사선 산업 응용', attachedFile: '파일10.pdf', questionCount: 13, registrar: '이철수', createdAt: '2026-01-06' },
{ id: 29, courseName: '방사선 원리', lectureName: '방사선 생물학', attachedFile: '파일11.pdf', questionCount: 17, registrar: '김등록', createdAt: '2026-01-05' },
{ id: 28, courseName: '방사선 적용', lectureName: '방사선 환경 모니터링', attachedFile: '파일12.pdf', questionCount: 9, registrar: '이철수', createdAt: '2026-01-04' },
{ id: 27, courseName: '방사선 적용', lectureName: '방사선 검사 기법', attachedFile: '파일13.pdf', questionCount: 19, registrar: '이철수', createdAt: '2026-01-03' },
{ id: 26, courseName: '방사선 원리', lectureName: '방사선 에너지 전달', attachedFile: '파일14.pdf', questionCount: 21, registrar: '김등록', createdAt: '2026-01-02' },
{ id: 25, courseName: '방사선 적용', lectureName: '방사선 처리 기술', attachedFile: '파일15.pdf', questionCount: 7, registrar: '이철수', createdAt: '2026-01-01' },
{ id: 24, courseName: '방사선 적용', lectureName: '방사선 품질 관리', attachedFile: '파일16.pdf', questionCount: 22, registrar: '이철수', createdAt: '2025-12-31' },
{ id: 23, courseName: '방사선 원리', lectureName: '방사선 방어 이론', attachedFile: '파일17.pdf', questionCount: 6, registrar: '김등록', createdAt: '2025-12-30' },
{ id: 22, courseName: '방사선 적용', lectureName: '방사선 계측법', attachedFile: '파일18.pdf', questionCount: 24, registrar: '이철수', createdAt: '2025-12-29' },
{ id: 21, courseName: '방사선 적용', lectureName: '방사선 안전 규정', attachedFile: '파일19.pdf', questionCount: 5, registrar: '이철수', createdAt: '2025-12-28' },
{ id: 20, courseName: '방사선 원리', lectureName: '방사선 핵물리학', attachedFile: '파일20.pdf', questionCount: 25, registrar: '김등록', createdAt: '2025-12-27' },
]);
// 관리자 인증 확인 // 관리자 인증 확인
useEffect(() => { useEffect(() => {
@@ -48,6 +168,13 @@ export default function AdminLecture2Page() {
return <LoginPage />; return <LoginPage />;
} }
const itemsPerPage = 13;
const totalPages = Math.ceil(lectures.length / itemsPerPage);
const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentLectures = lectures.slice(startIndex, endIndex);
return ( return (
<div className="bg-white relative size-full min-h-screen"> <div className="bg-white relative size-full min-h-screen">
{/* 사이드바 */} {/* 사이드바 */}
@@ -64,7 +191,10 @@ export default function AdminLecture2Page() {
{/* 메뉴 */} {/* 메뉴 */}
<div className="box-border content-stretch flex flex-col items-center pb-0 pt-4 px-0 relative shrink-0 w-[250px]"> <div className="box-border content-stretch flex flex-col items-center pb-0 pt-4 px-0 relative shrink-0 w-[250px]">
<div className="box-border content-stretch flex flex-col gap-2 items-start p-3 relative shrink-0 w-full"> <div className="box-border content-stretch flex flex-col gap-2 items-start p-3 relative shrink-0 w-full">
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors"> <button
onClick={() => router.push('/admin_home')}
className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors"
>
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2"> <div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap"> <p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
@@ -133,7 +263,14 @@ export default function AdminLecture2Page() {
</div> </div>
</div> </div>
{/* 로그아웃 */} {/* 로그아웃 */}
<div className="content-stretch flex gap-[9px] items-center relative shrink-0"> <button
onClick={() => {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('isAdminLoggedIn');
router.push('/login');
}}
className="content-stretch flex gap-[9px] h-[36px] items-center relative shrink-0 cursor-pointer hover:opacity-80 transition-opacity"
>
<div className="flex items-center justify-center relative shrink-0"> <div className="flex items-center justify-center relative shrink-0">
<div className="flex-none rotate-[180deg] scale-y-[-100%]"> <div className="flex-none rotate-[180deg] scale-y-[-100%]">
<div className="h-[23.12px] relative w-[22px]"> <div className="h-[23.12px] relative w-[22px]">
@@ -141,14 +278,6 @@ export default function AdminLecture2Page() {
</div> </div>
</div> </div>
</div> </div>
<button
onClick={() => {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('isAdminLoggedIn');
router.push('/');
}}
className="content-stretch flex h-[36px] items-center justify-between relative shrink-0 w-[76px] cursor-pointer hover:opacity-80 transition-opacity"
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center px-[10px] py-[5px] relative shrink-0"> <div className="box-border content-stretch flex gap-[10px] items-center justify-center px-[10px] py-[5px] relative shrink-0">
<p className="font-medium leading-[1.6] not-italic relative shrink-0 text-[16px] text-[#404040] text-nowrap whitespace-pre"> <p className="font-medium leading-[1.6] not-italic relative shrink-0 text-[16px] text-[#404040] text-nowrap whitespace-pre">
@@ -156,7 +285,6 @@ export default function AdminLecture2Page() {
</div> </div>
</button> </button>
</div> </div>
</div>
{/* 메인 콘텐츠 */} {/* 메인 콘텐츠 */}
<div className="absolute left-[250px] top-0 right-0 min-h-screen"> <div className="absolute left-[250px] top-0 right-0 min-h-screen">
@@ -181,7 +309,7 @@ export default function AdminLecture2Page() {
</button> </button>
{/* 테이블 */} {/* 테이블 */}
<div className="absolute content-stretch flex flex-col items-start left-[48px] right-[101px] top-[186px]"> <div className="absolute content-stretch flex flex-col items-start left-[48px] right-[101px] top-[186px] h-[671px] overflow-hidden">
{/* 테이블 헤더 */} {/* 테이블 헤더 */}
<div className="bg-[rgba(235,247,255,0.5)] content-stretch flex h-[41px] items-center relative shrink-0 w-full"> <div className="bg-[rgba(235,247,255,0.5)] content-stretch flex h-[41px] items-center relative shrink-0 w-full">
<div className="content-stretch flex items-center relative shrink-0 w-full"> <div className="content-stretch flex items-center relative shrink-0 w-full">
@@ -218,7 +346,7 @@ export default function AdminLecture2Page() {
</div> </div>
</div> </div>
{/* 빈 상태 또는 테이블 바디 */} {/* 테이블 바디 */}
{lectures.length === 0 ? ( {lectures.length === 0 ? (
<div className="relative w-full flex items-center justify-center" style={{ minHeight: '400px' }}> <div className="relative w-full flex items-center justify-center" style={{ minHeight: '400px' }}>
<div className="content-stretch flex flex-col gap-[16px] items-center relative shrink-0"> <div className="content-stretch flex flex-col gap-[16px] items-center relative shrink-0">
@@ -230,8 +358,8 @@ export default function AdminLecture2Page() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="content-stretch flex flex-col items-start relative shrink-0 w-full"> <div className="content-stretch flex flex-col items-start relative shrink-0 w-full max-h-[630px] overflow-y-auto">
{lectures.map((lecture) => ( {currentLectures.map((lecture) => (
<div key={lecture.id} className="bg-white content-stretch flex items-center relative shrink-0 w-full"> <div key={lecture.id} className="bg-white content-stretch flex items-center relative shrink-0 w-full">
<div className="content-stretch flex items-center relative shrink-0 w-full"> <div className="content-stretch flex items-center relative shrink-0 w-full">
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0"> <div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
@@ -270,6 +398,37 @@ export default function AdminLecture2Page() {
</div> </div>
)} )}
</div> </div>
{/* 페이지네이션 */}
{lectures.length > 0 && (
<div className="absolute content-stretch flex gap-2 items-center justify-center left-[48px] right-[101px] top-[909px]">
<button onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}>
<PaginationBtnMove status={currentPage === 1 ? "Disabled" : "Default"} move="Previous" />
</button>
<div className="content-stretch flex gap-2 items-center relative shrink-0">
{pageNumbers.map((pageNum) => (
<button
key={pageNum}
onClick={() => setCurrentPage(pageNum)}
className={`relative rounded-[8px] shrink-0 size-[30px] cursor-pointer transition-colors ${currentPage === pageNum
? 'bg-[#1d1d1d]'
: 'border border-[#eeeeee] border-solid hover:bg-gray-50'
}`}
>
<div className="overflow-clip relative rounded-[inherit] size-[30px]">
<div className={`absolute flex flex-col font-medium justify-center leading-[0] left-[15.5px] not-italic text-[14px] text-center text-nowrap top-[15px] translate-x-[-50%] translate-y-[-50%] ${currentPage === pageNum ? 'text-white' : 'text-[#515151]'
}`}>
<p className="leading-[1.6] whitespace-pre">{pageNum}</p>
</div>
</div>
</button>
))}
</div>
<button onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}>
<PaginationBtnMove status={currentPage === totalPages ? "Disabled" : "Default"} move="Next" />
</button>
</div>
)}
</div> </div>
</div> </div>
); );

View File

@@ -5,8 +5,8 @@ import Image from 'next/image';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import logo from '../logo.svg'; import logo from '../logo.svg';
import CheckboxOff from '../../public/svg/checkbox_off';
const checkIcon = "http://localhost:3845/assets/68720b08a673d8b68ae6482d642eeab286c9462b.svg"; import CheckboxOn from '../../public/svg/checkbox_on';
type CheckboxProps = { type CheckboxProps = {
checked: boolean; checked: boolean;
@@ -20,18 +20,9 @@ function Checkbox({ checked, onChange, label }: CheckboxProps) {
<button <button
type="button" type="button"
onClick={onChange} onClick={onChange}
className="relative w-[18px] h-[18px] rounded-[4px]" className="relative w-[18px] h-[18px] flex items-center justify-center"
> >
{checked ? ( {checked ? <CheckboxOn /> : <CheckboxOff />}
<>
<div className="absolute bg-[#515151] left-0 rounded-[4px] w-[18px] h-[18px] top-0" />
<div className="absolute left-[3px] w-3 h-3 top-[3px]">
<img alt="" className="block max-w-none w-full h-full" src={checkIcon} />
</div>
</>
) : (
<div className="absolute border border-[#b9b9b9] border-solid left-0 rounded-[4px] w-[18px] h-[18px] top-0" />
)}
</button> </button>
<span className="text-sm text-[#515151] leading-[1.6]">{label}</span> <span className="text-sm text-[#515151] leading-[1.6]">{label}</span>
</div> </div>

View File

@@ -6,7 +6,6 @@ import Image from 'next/image';
import { isAdminLoggedIn } from '../lib/auth'; import { isAdminLoggedIn } from '../lib/auth';
import LoginPage from './login/page'; import LoginPage from './login/page';
import Header from './components/Header'; import Header from './components/Header';
import Logout from '../public/svg/logout';
const imgImage2 = "http://localhost:3845/assets/89fda8e949171025b1232bae70fc9d442e4e70c8.png"; const imgImage2 = "http://localhost:3845/assets/89fda8e949171025b1232bae70fc9d442e4e70c8.png";
const imgImage7 = "http://localhost:3845/assets/a4e4d09643b890b56084560cc24d6e532a03487b.png"; const imgImage7 = "http://localhost:3845/assets/a4e4d09643b890b56084560cc24d6e532a03487b.png";
@@ -21,7 +20,6 @@ export default function HomePage() {
const [isAdmin, setIsAdmin] = useState(false); const [isAdmin, setIsAdmin] = useState(false);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [currentHeroSlide, setCurrentHeroSlide] = useState(0); const [currentHeroSlide, setCurrentHeroSlide] = useState(0);
const [selectedTab, setSelectedTab] = useState<'전체' | '학습자' | '강사' | '운영자'>('전체');
// 임시 데이터 - 실제로는 API에서 가져올 데이터 // 임시 데이터 - 실제로는 API에서 가져올 데이터
const [courses, setCourses] = useState([ const [courses, setCourses] = useState([
@@ -61,7 +59,12 @@ export default function HomePage() {
setIsLoggedIn(loginStatus); setIsLoggedIn(loginStatus);
setIsAdmin(adminStatus); setIsAdmin(adminStatus);
setIsLoading(false); setIsLoading(false);
}, []);
// 관리자일 경우 admin_home 페이지로 리다이렉트
if (adminStatus) {
router.push('/admin_home');
}
}, [router]);
if (isLoading) { if (isLoading) {
return null; // 로딩 중 return null; // 로딩 중
@@ -72,219 +75,9 @@ export default function HomePage() {
return <LoginPage />; return <LoginPage />;
} }
// 관리자일 경우 권한 설정 페이지 표시 // 관리자일 경우 리다이렉트 중
if (isAdmin) { if (isAdmin) {
return ( return null;
<div className="bg-white relative size-full min-h-screen">
{/* 사이드바 */}
<div className="absolute bg-white border-r border-[#eeeeee] border-solid box-border content-stretch flex flex-col gap-[45px] items-center left-0 min-h-[1080px] pb-8 pt-[30px] px-0 top-0 w-[250px]">
{/* 로고 */}
<button
onClick={() => router.push('/')}
className="h-[102px] relative shrink-0 w-[99px] cursor-pointer hover:opacity-80 transition-opacity"
>
<div className="absolute inset-0 flex items-center justify-center overflow-hidden pointer-events-none">
<img alt="로고" className="h-full w-full object-contain" src="/logo.svg" />
</div>
</button>
{/* 메뉴 */}
<div className="box-border content-stretch flex flex-col items-center pb-0 pt-4 px-0 relative shrink-0 w-[250px]">
<div className="box-border content-stretch flex flex-col gap-2 items-start p-3 relative shrink-0 w-full">
<button className="bg-[#f7f7f7] box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button
onClick={() => router.push('/admin_lecture1')}
className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors"
>
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button
onClick={() => router.push('/admin_lecture2')}
className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors"
>
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
/
</p>
</div>
</button>
<button className="box-border content-stretch flex gap-2 h-[34px] items-center pl-2 pr-2 py-1 relative rounded-lg shrink-0 w-[226px] cursor-pointer hover:bg-gray-50 transition-colors">
<div className="basis-0 content-stretch flex gap-2 grow items-center min-h-px min-w-px relative shrink-0 pl-2">
<p className="[white-space-collapse:collapse] basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic overflow-ellipsis overflow-hidden relative shrink-0 text-[14px] text-[#404040] text-left text-nowrap">
</p>
</div>
</button>
</div>
</div>
{/* 로그아웃 */}
<div className="content-stretch flex gap-[9px] items-center relative shrink-0">
<div className="flex items-center justify-center relative shrink-0">
<div className="flex-none rotate-[180deg] scale-y-[-100%]">
<div className="h-[23.12px] relative w-[22px]">
<Logout />
</div>
</div>
</div>
<button
onClick={() => {
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('isAdminLoggedIn');
router.push('/');
}}
className="content-stretch flex h-[36px] items-center justify-between relative shrink-0 w-[76px] cursor-pointer hover:opacity-80 transition-opacity"
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center px-[10px] py-[5px] relative shrink-0">
<p className="font-medium leading-[1.6] not-italic relative shrink-0 text-[16px] text-[#404040] text-nowrap whitespace-pre">
</p>
</div>
</button>
</div>
</div>
{/* 메인 콘텐츠 */}
<div className="absolute left-[250px] top-0 right-0 min-h-screen">
{/* 페이지 타이틀 탭 */}
<div className="absolute content-stretch flex gap-[24px] items-center left-[48px] top-[45px]">
<button
onClick={() => setSelectedTab('전체')}
className={`border-b-[2px] border-solid box-border content-stretch flex gap-[150px] items-center relative shrink-0 ${selectedTab === '전체' ? 'border-[#2b82e8]' : 'border-transparent'}`}
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
<p className={`font-bold leading-[normal] not-italic relative shrink-0 text-[18px] text-nowrap whitespace-pre ${selectedTab === '전체' ? 'text-[#515151]' : 'text-[#515151]'}`}>
</p>
</div>
</button>
<button
onClick={() => setSelectedTab('학습자')}
className={`border-b-[2px] border-solid box-border content-stretch flex gap-[150px] items-center relative shrink-0 ${selectedTab === '학습자' ? 'border-[#2b82e8]' : 'border-transparent'}`}
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
</p>
</div>
</button>
<button
onClick={() => setSelectedTab('강사')}
className={`border-b-[2px] border-solid box-border content-stretch flex gap-[150px] items-center relative shrink-0 ${selectedTab === '강사' ? 'border-[#2b82e8]' : 'border-transparent'}`}
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
</p>
</div>
</button>
<button
onClick={() => setSelectedTab('운영자')}
className={`border-b-[2px] border-solid box-border content-stretch flex gap-[150px] items-center relative shrink-0 ${selectedTab === '운영자' ? 'border-[#2b82e8]' : 'border-transparent'}`}
>
<div className="box-border content-stretch flex gap-[10px] items-center justify-center p-[10px] relative shrink-0">
<p className="font-bold leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
</p>
</div>
</button>
</div>
{/* 사용자 목록 테이블 */}
<div className="absolute content-stretch flex flex-col items-start left-[48px] right-[101px] top-[135px]">
{/* 테이블 헤더 */}
<div className="bg-[rgba(235,247,255,0.5)] content-stretch flex h-[41px] items-center relative shrink-0 w-full">
<div className="content-stretch flex items-center relative shrink-0 w-full">
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"></p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"></p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]">()</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"></p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"> </p>
</div>
</div>
<div className="basis-0 border-[0.5px_0px_0.5px_0.5px] border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center min-h-px min-w-px p-[10px] relative shrink-0">
<div className="basis-0 flex flex-col font-bold grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[18px] text-[#515151]">
<p className="leading-[normal]"> </p>
</div>
</div>
</div>
</div>
{/* 빈 상태 메시지 */}
<div className="relative w-full flex items-center justify-center" style={{ minHeight: '400px' }}>
<div className="content-stretch flex flex-col gap-[16px] items-center relative shrink-0">
<div className="content-stretch flex flex-col gap-[2px] items-center relative shrink-0">
<p className="font-medium leading-[1.6] not-italic relative shrink-0 text-[16px] text-[#969696] text-nowrap whitespace-pre">
.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
} }
// 일반 사용자일 경우 기존 메인 페이지 표시 // 일반 사용자일 경우 기존 메인 페이지 표시

64
app/pages/page.tsx Normal file
View File

@@ -0,0 +1,64 @@
import Link from "next/link";
import path from "path";
import { promises as fs } from "fs";
const APP_DIR = path.join(process.cwd(), "app");
async function collectRoutes(rootDir: string): Promise<string[]> {
const routes: string[] = [];
async function walk(relativeDir: string) {
const absoluteDir = path.join(rootDir, relativeDir);
const entries = await fs.readdir(absoluteDir, { withFileTypes: true });
const names = entries.map((e) => e.name);
if (names.includes("page.tsx")) {
const routePath =
relativeDir === "" ? "/" : `/${relativeDir.replace(/\\\\/g, "/")}`;
routes.push(routePath);
}
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (["api", "components"].includes(entry.name)) continue;
if (entry.name.startsWith("(")) continue;
if (entry.name.startsWith("_")) continue;
if (entry.name === "node_modules") continue;
await walk(path.join(relativeDir, entry.name));
}
}
await walk("");
routes.sort((a, b) => {
if (a === "/" && b !== "/") return -1;
if (b === "/" && a !== "/") return 1;
return a.localeCompare(b);
});
return routes;
}
export default async function Pages() {
const routes = await collectRoutes(APP_DIR);
return (
<main style={{ maxWidth: 800, margin: "0 auto", padding: "24px" }}>
<h1 style={{ fontSize: 24, fontWeight: 700, marginBottom: 16 }}>
</h1>
<ul style={{ display: "grid", gap: 8, listStyle: "none", padding: 0 }}>
{routes.map((route) => (
<li key={route}>
<Link href={route} style={{ color: "#2563eb" }}>
{route}
</Link>
</li>
))}
</ul>
</main>
);
}

520
package-lock.json generated
View File

@@ -27,6 +27,7 @@
"eslint-config-next": "16.0.1", "eslint-config-next": "16.0.1",
"prisma": "^6.19.0", "prisma": "^6.19.0",
"tailwindcss": "^4", "tailwindcss": "^4",
"tsx": "^4.19.2",
"typescript": "5.9.3" "typescript": "5.9.3"
} }
}, },
@@ -316,6 +317,448 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0", "version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
@@ -3372,6 +3815,48 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -4011,6 +4496,21 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -7001,6 +7501,26 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tsx": {
"version": "4.20.6",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz",
"integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tunnel-agent": { "node_modules/tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",

View File

@@ -7,14 +7,14 @@
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint", "lint": "eslint",
"db:generate": "prisma generate", "generate": "prisma generate",
"db:push": "prisma db push", "push": "prisma db push",
"db:migrate": "prisma migrate dev", "migrate": "prisma migrate dev",
"db:studio": "prisma studio", "studio": "prisma studio",
"db:seed": "tsx prisma/seed.ts" "seed": "npx tsx prisma/seed.ts"
}, },
"prisma": { "prisma": {
"seed": "tsx prisma/seed.ts" "seed": "npx tsx prisma/seed.ts"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.19.0", "@prisma/client": "^6.19.0",

BIN
prisma/prisma/dev.db Normal file

Binary file not shown.

126
prisma/seed.ts Normal file
View File

@@ -0,0 +1,126 @@
import { PrismaClient } from '../lib/generated/prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
async function main() {
console.log('🌱 시드 데이터 생성 시작...');
// 기존 데이터 삭제 (순서 중요: 외래키 관계 고려)
console.log('🗑️ 기존 데이터 삭제 중...');
await prisma.userLecture.deleteMany();
await prisma.lecture.deleteMany();
await prisma.curriculum.deleteMany();
await prisma.user.deleteMany();
// 비밀번호 해시 (기본 비밀번호: password123)
const hashedPassword = await bcrypt.hash('password123', 10);
// 1. 사용자 데이터 생성
console.log('👥 사용자 데이터 생성 중...');
const users = await prisma.user.createMany({
data: [
// 운영자
{ email: 'park@example.com', name: '박민수', password: hashedPassword, role: 'ADMIN', isActive: true },
{ email: 'oh@example.com', name: '오준혁', password: hashedPassword, role: 'ADMIN', isActive: true },
{ email: 'kwon@example.com', name: '권태영', password: hashedPassword, role: 'ADMIN', isActive: true },
// 강사
{ email: 'kim@example.com', name: '김철수', password: hashedPassword, role: 'INSTRUCTOR', isActive: true },
{ email: 'jung@example.com', name: '정대현', password: hashedPassword, role: 'INSTRUCTOR', isActive: true },
{ email: 'lim@example.com', name: '임수진', password: hashedPassword, role: 'INSTRUCTOR', isActive: true },
{ email: 'shin@example.com', name: '신동욱', password: hashedPassword, role: 'INSTRUCTOR', isActive: true },
{ email: 'jeon@example.com', name: '전혜진', password: hashedPassword, role: 'INSTRUCTOR', isActive: true },
// 학습자
{ email: 'hong@example.com', name: '홍길동', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'lee@example.com', name: '이영희', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'choi@example.com', name: '최지영', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'kang@example.com', name: '강미영', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'yoon@example.com', name: '윤성호', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'han@example.com', name: '한지훈', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'song@example.com', name: '송민경', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'ryu@example.com', name: '류현우', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'cho@example.com', name: '조은서', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'bae@example.com', name: '배성민', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'namgung@example.com', name: '남궁준', password: hashedPassword, role: 'STUDENT', isActive: true },
{ email: 'seo@example.com', name: '서아름', password: hashedPassword, role: 'STUDENT', isActive: true },
],
});
console.log(`${users.count}명의 사용자 생성 완료`);
// 사용자 ID 조회 (강사와 등록자로 사용)
const instructorKim = await prisma.user.findUnique({ where: { email: 'kim@example.com' } });
const registrarLee = await prisma.user.findUnique({ where: { email: 'lee@example.com' } });
const registrarPark = await prisma.user.findUnique({ where: { email: 'park@example.com' } });
const registrarKim = await prisma.user.findUnique({ where: { email: 'kim@example.com' } });
if (!instructorKim || !registrarLee || !registrarPark || !registrarKim) {
throw new Error('필수 사용자를 찾을 수 없습니다.');
}
// 2. 교육 과정 데이터 생성
console.log('📚 교육 과정 데이터 생성 중...');
const curriculum1 = await prisma.curriculum.create({
data: {
title: '방사선 적용',
thumbnailImage: null,
instructorId: instructorKim.id,
},
});
const curriculum2 = await prisma.curriculum.create({
data: {
title: '방사선 원리',
thumbnailImage: null,
instructorId: instructorKim.id,
},
});
console.log('✅ 교육 과정 생성 완료');
// 3. 강좌 데이터 생성
console.log('📖 강좌 데이터 생성 중...');
const lectures = await prisma.lecture.createMany({
data: [
// 방사선 적용 강좌들
{ title: '방사선 기본 원리', attachmentFile: '파일1.pdf', evaluationQuestionCount: 10, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 안전 관리', attachmentFile: '파일2.pdf', evaluationQuestionCount: 15, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 장비 사용법', attachmentFile: '파일4.pdf', evaluationQuestionCount: 8, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 보호 장비', attachmentFile: '파일6.pdf', evaluationQuestionCount: 14, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 응용 실습', attachmentFile: '파일7.pdf', evaluationQuestionCount: 16, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 의료 응용', attachmentFile: '파일9.pdf', evaluationQuestionCount: 18, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 산업 응용', attachmentFile: '파일10.pdf', evaluationQuestionCount: 13, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 환경 모니터링', attachmentFile: '파일12.pdf', evaluationQuestionCount: 9, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 검사 기법', attachmentFile: '파일13.pdf', evaluationQuestionCount: 19, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 처리 기술', attachmentFile: '파일15.pdf', evaluationQuestionCount: 7, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 품질 관리', attachmentFile: '파일16.pdf', evaluationQuestionCount: 22, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 계측법', attachmentFile: '파일18.pdf', evaluationQuestionCount: 24, curriculumId: curriculum1.id, registrantId: registrarLee.id },
{ title: '방사선 안전 규정', attachmentFile: '파일19.pdf', evaluationQuestionCount: 5, curriculumId: curriculum1.id, registrantId: registrarLee.id },
// 방사선 원리 강좌들
{ title: '방사선 측정 기법', attachmentFile: '파일3.pdf', evaluationQuestionCount: 12, curriculumId: curriculum2.id, registrantId: registrarKim.id },
{ title: '방사선 물리학 기초', attachmentFile: '파일5.pdf', evaluationQuestionCount: 20, curriculumId: curriculum2.id, registrantId: registrarKim.id },
{ title: '방사선 화학 반응', attachmentFile: '파일8.pdf', evaluationQuestionCount: 11, curriculumId: curriculum2.id, registrantId: registrarKim.id },
{ title: '방사선 생물학', attachmentFile: '파일11.pdf', evaluationQuestionCount: 17, curriculumId: curriculum2.id, registrantId: registrarKim.id },
{ title: '방사선 에너지 전달', attachmentFile: '파일14.pdf', evaluationQuestionCount: 21, curriculumId: curriculum2.id, registrantId: registrarKim.id },
{ title: '방사선 방어 이론', attachmentFile: '파일17.pdf', evaluationQuestionCount: 6, curriculumId: curriculum2.id, registrantId: registrarKim.id },
{ title: '방사선 핵물리학', attachmentFile: '파일20.pdf', evaluationQuestionCount: 25, curriculumId: curriculum2.id, registrantId: registrarKim.id },
],
});
console.log(`${lectures.count}개의 강좌 생성 완료`);
console.log('🎉 시드 데이터 생성 완료!');
}
main()
.catch((e) => {
console.error('❌ 시드 데이터 생성 실패:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@@ -0,0 +1,7 @@
export default function CheckboxOff() {
return (
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="17" height="17" rx="3.5" stroke="#B9B9B9" />
</svg>
);
}

View File

@@ -0,0 +1,15 @@
export default function CheckboxOn() {
return (
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="18" height="18" rx="4" fill="#515151" />
<g clipPath="url(#clip0_40000058_4306)">
<path d="M7.49999 11.0849L5.41499 8.99992L4.70499 9.70492L7.49999 12.4999L13.5 6.49992L12.795 5.79492L7.49999 11.0849Z" fill="white" />
</g>
<defs>
<clipPath id="clip0_40000058_4306">
<rect width="12" height="12" fill="white" transform="translate(3 3)" />
</clipPath>
</defs>
</svg>
);
}

View File

@@ -0,0 +1,8 @@
export default function PageNavLeftOff() {
return (
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.2002 0.5H18.7998C21.8166 0.5 23.2067 0.506341 24.3174 0.867188C26.6007 1.60907 28.3909 3.39933 29.1328 5.68262C29.4937 6.79334 29.5 8.1834 29.5 11.2002V18.7998C29.5 21.8166 29.4937 23.2067 29.1328 24.3174C28.3909 26.6007 26.6007 28.3909 24.3174 29.1328C23.2067 29.4937 21.8166 29.5 18.7998 29.5H11.2002C8.1834 29.5 6.79334 29.4937 5.68262 29.1328C3.39933 28.3909 1.60907 26.6007 0.867188 24.3174C0.506341 23.2067 0.5 21.8166 0.5 18.7998V11.2002C0.5 8.1834 0.506341 6.79334 0.867188 5.68262C1.60907 3.39933 3.39933 1.60907 5.68262 0.867188C6.79334 0.506341 8.1834 0.5 11.2002 0.5Z" stroke="#EEEEEE" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.7002 21.5193C18.3199 21.9522 17.6592 21.9961 17.2246 21.6173L11.9708 17.0382C10.7328 15.9592 10.7328 14.0408 11.9708 12.9618L17.2246 8.38272C17.6592 8.00388 18.3199 8.04776 18.7002 8.48071C19.0805 8.91367 19.0365 9.57175 18.6018 9.95059L13.3481 14.5296C13.0624 14.7786 13.0624 15.2213 13.3481 15.4703L18.6018 20.0494C19.0365 20.4282 19.0805 21.0863 18.7002 21.5193Z" fill="#E1E1E1" />
</svg>
);
}

View File

@@ -0,0 +1,8 @@
export default function PageNavLeftOn() {
return (
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.2002 0.5H18.7998C21.8166 0.5 23.2067 0.506341 24.3174 0.867188C26.6007 1.60907 28.3909 3.39933 29.1328 5.68262C29.4937 6.79334 29.5 8.1834 29.5 11.2002V18.7998C29.5 21.8166 29.4937 23.2067 29.1328 24.3174C28.3909 26.6007 26.6007 28.3909 24.3174 29.1328C23.2067 29.4937 21.8166 29.5 18.7998 29.5H11.2002C8.1834 29.5 6.79334 29.4937 5.68262 29.1328C3.39933 28.3909 1.60907 26.6007 0.867188 24.3174C0.506341 23.2067 0.5 21.8166 0.5 18.7998V11.2002C0.5 8.1834 0.506341 6.79334 0.867188 5.68262C1.60907 3.39933 3.39933 1.60907 5.68262 0.867188C6.79334 0.506341 8.1834 0.5 11.2002 0.5Z" stroke="#EEEEEE" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.7002 21.5193C18.3199 21.9522 17.6592 21.9961 17.2246 21.6173L11.9708 17.0382C10.7328 15.9592 10.7328 14.0408 11.9708 12.9618L17.2246 8.38272C17.6592 8.00388 18.3199 8.04776 18.7002 8.48071C19.0805 8.91367 19.0365 9.57175 18.6018 9.95059L13.3481 14.5296C13.0624 14.7786 13.0624 15.2213 13.3481 15.4703L18.6018 20.0494C19.0365 20.4282 19.0805 21.0863 18.7002 21.5193Z" fill="#515151" />
</svg>
);
}

View File

@@ -0,0 +1,8 @@
export default function PageNavRightOff() {
return (
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.2002 0.5H18.7998C21.8166 0.5 23.2067 0.506341 24.3174 0.867188C26.6007 1.60907 28.3909 3.39933 29.1328 5.68262C29.4937 6.79334 29.5 8.1834 29.5 11.2002V18.7998C29.5 21.8166 29.4937 23.2067 29.1328 24.3174C28.3909 26.6007 26.6007 28.3909 24.3174 29.1328C23.2067 29.4937 21.8166 29.5 18.7998 29.5H11.2002C8.1834 29.5 6.79334 29.4937 5.68262 29.1328C3.39933 28.3909 1.60907 26.6007 0.867188 24.3174C0.506341 23.2067 0.5 21.8166 0.5 18.7998V11.2002C0.5 8.1834 0.506341 6.79334 0.867188 5.68262C1.60907 3.39933 3.39933 1.60907 5.68262 0.867188C6.79334 0.506341 8.1834 0.5 11.2002 0.5Z" stroke="#EEEEEE" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2998 8.48074C11.6801 8.04778 12.3408 8.00391 12.7754 8.38275L18.0292 12.9618C19.2672 14.0408 19.2672 15.9592 18.0292 17.0382L12.7754 21.6173C12.3408 21.9961 11.6801 21.9522 11.2998 21.5193C10.9195 21.0863 10.9635 20.4282 11.3982 20.0494L16.6519 15.4704C16.9376 15.2214 16.9376 14.7787 16.6519 14.5297L11.3982 9.95061C10.9635 9.57178 10.9195 8.91369 11.2998 8.48074Z" fill="#E1E1E1" />
</svg>
);
}

View File

@@ -0,0 +1,8 @@
export default function PageNavRightOn() {
return (
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.2002 0.5H18.7998C21.8166 0.5 23.2067 0.506341 24.3174 0.867188C26.6007 1.60907 28.3909 3.39933 29.1328 5.68262C29.4937 6.79334 29.5 8.1834 29.5 11.2002V18.7998C29.5 21.8166 29.4937 23.2067 29.1328 24.3174C28.3909 26.6007 26.6007 28.3909 24.3174 29.1328C23.2067 29.4937 21.8166 29.5 18.7998 29.5H11.2002C8.1834 29.5 6.79334 29.4937 5.68262 29.1328C3.39933 28.3909 1.60907 26.6007 0.867188 24.3174C0.506341 23.2067 0.5 21.8166 0.5 18.7998V11.2002C0.5 8.1834 0.506341 6.79334 0.867188 5.68262C1.60907 3.39933 3.39933 1.60907 5.68262 0.867188C6.79334 0.506341 8.1834 0.5 11.2002 0.5Z" stroke="#EEEEEE" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2998 8.48074C11.6801 8.04778 12.3408 8.00391 12.7754 8.38275L18.0292 12.9618C19.2672 14.0408 19.2672 15.9592 18.0292 17.0382L12.7754 21.6173C12.3408 21.9961 11.6801 21.9522 11.2998 21.5193C10.9194 21.0863 10.9635 20.4282 11.3981 20.0494L16.6519 15.4704C16.9376 15.2214 16.9376 14.7787 16.6519 14.5297L11.3982 9.95062C10.9635 9.57178 10.9195 8.91369 11.2998 8.48074Z" fill="#515151" />
</svg>
);
}