관리자 페이지 교육과정 등록, 비밀번호 재설정 생성

This commit is contained in:
wallace
2025-11-10 21:20:09 +09:00
parent efd5ab915e
commit 9a11a8afd1
12 changed files with 1353 additions and 12 deletions

0
app/admin_home/page.tsx Normal file
View File

608
app/admin_lecture1/page.tsx Normal file
View File

@@ -0,0 +1,608 @@
"use client";
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Close from '../../public/svg/close';
const imgLogo = "http://localhost:3845/assets/89fda8e949171025b1232bae70fc9d442e4e70c8.png";
const imgArrowDisabled = "http://localhost:3845/assets/6edcb2defc36a2bf4a05a3abe53b8da3d42b2cb4.svg";
const imgArrowDefault = "http://localhost:3845/assets/ad0cb4418492f1b020bb38a2ff038a331294ce87.svg";
const imgArrowNext = "http://localhost:3845/assets/6328cf96ee1169c1425c2ce55e7a2dcca0374508.svg";
const imgLogout = "http://localhost:3845/assets/2277a58aa2f582fd42132439745c5bb051f322c2.svg";
const imgClose = "http://localhost:3845/assets/51206896b81ebe68b2d80b5b603e576227587007.svg";
const imgCloseX = "http://localhost:3845/assets/91a45a4f2e47cd854574f32ee6ac1b84e12807cf.svg";
const imgMingcuteDownLine = "http://localhost:3845/assets/a50ea05576e2bcccf052b5be6750edaf476ad4d4.svg";
interface Course {
id: number;
courseName: string;
instructorName: string;
isIncluded: string;
createdAt: string;
registrar: 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) {
// Disabled Next 버튼
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) {
// Disabled Previous 버튼
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) {
// Default Next 버튼
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>
);
}
// Default Previous 버튼
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 AdminLecture1Page() {
const router = useRouter();
const [currentPage, setCurrentPage] = useState(1);
const [isModalOpen, setIsModalOpen] = useState(false);
const [courseName, setCourseName] = useState('');
const [instructorName, setInstructorName] = useState('');
const [showInstructorDropdown, setShowInstructorDropdown] = useState(false);
const [nextId, setNextId] = useState(14);
// 샘플 강좌 데이터
const [courses, setCourses] = useState<Course[]>([
{ id: 39, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 38, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 37, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 36, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 35, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 34, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 33, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 32, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 31, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 30, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 29, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 28, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 27, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 26, courseName: '방사선 원리', instructorName: '김강사', isIncluded: '포함', createdAt: '2025-12-11', registrar: '김등록' },
{ id: 25, courseName: '방사선 원리', instructorName: '김강사', isIncluded: '포함', createdAt: '2025-12-11', registrar: '김등록' },
{ id: 24, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 23, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 22, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 21, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 20, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 19, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 18, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 17, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 16, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 15, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 14, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 13, courseName: '방사선 원리', instructorName: '김강사', isIncluded: '포함', createdAt: '2025-12-11', registrar: '김등록' },
{ id: 12, courseName: '방사선 원리', instructorName: '김강사', isIncluded: '포함', createdAt: '2025-12-11', registrar: '김등록' },
{ id: 11, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 10, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 9, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 8, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 7, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 6, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 5, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 4, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 3, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 2, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
{ id: 1, courseName: '방사선 적용', instructorName: '김강사', isIncluded: '포함', createdAt: '2026-01-15', registrar: '이철수' },
]);
const instructors = ['김강사', '이강사', '박강사', '최강사'];
const handleOpenModal = () => {
setIsModalOpen(true);
setCourseName('');
setInstructorName('');
};
const handleCloseModal = () => {
setIsModalOpen(false);
setCourseName('');
setInstructorName('');
setShowInstructorDropdown(false);
};
const handleSave = () => {
if (!courseName.trim() || !instructorName.trim()) {
alert('교육 과정명과 강사를 입력해 주세요.');
return;
}
const today = new Date();
const formattedDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
const newCourse: Course = {
id: nextId,
courseName: courseName.trim(),
instructorName: instructorName.trim(),
isIncluded: '미포함',
createdAt: formattedDate,
registrar: '관리자',
};
const updatedCourses = [...courses, newCourse];
setCourses(updatedCourses);
setNextId(nextId + 1);
// 13개를 넘어가면 해당 페이지로 이동
const newTotalPages = Math.ceil(updatedCourses.length / 13);
if (newTotalPages > 1 && updatedCourses.length > 13) {
setCurrentPage(newTotalPages);
}
handleCloseModal();
};
const handleSelectInstructor = (instructor: string) => {
setInstructorName(instructor);
setShowInstructorDropdown(false);
};
const itemsPerPage = 13;
const totalPages = Math.ceil(courses.length / itemsPerPage);
const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentCourses = courses.slice(startIndex, endIndex);
return (
<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 overflow-hidden pointer-events-none">
<img alt="" className="absolute h-[291.74%] left-[-100%] max-w-none top-[-95.73%] w-[301.18%]" src={imgLogo} />
</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="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="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 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]">
<img alt="" className="block max-w-none size-full" src={imgLogout} />
</div>
</div>
</div>
<button
onClick={() => {
localStorage.removeItem('isLoggedIn');
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]">
<div className="border-[#2b82e8] border-b-[2px] border-solid box-border content-stretch flex gap-[150px] items-center relative shrink-0">
<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>
</div>
</div>
{/* 교육 과정 등록 버튼 */}
<button
onClick={handleOpenModal}
className="absolute bg-[#160e0e] box-border content-stretch flex gap-[10px] items-center justify-center right-[101px] p-[10px] rounded-[10px] top-[72px] w-[167px] cursor-pointer hover:bg-[#2a1f1f] transition-colors"
>
<p className="font-bold leading-[1.6] not-italic relative shrink-0 text-[18px] text-nowrap text-white whitespace-pre">
</p>
</button>
{/* 테이블 */}
<div className="absolute content-stretch flex flex-col items-start left-[48px] right-[101px] top-[186px] h-[671px] overflow-hidden">
{/* 테이블 헤더 */}
<div className="bg-[#f5fbff] 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_0.5px_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_0.5px_0.5px_0px] 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_0.5px_0.5px_0px] 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_0.5px_0.5px_0px] 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_0.5px_0.5px_0px] 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="content-stretch flex flex-col items-start relative shrink-0 w-full max-h-[630px] overflow-y-auto">
{currentCourses.map((course) => (
<div key={course.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_0.5px_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]">{course.courseName}</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0.5px_0.5px_0px] 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]">{course.instructorName}</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0.5px_0.5px_0px] 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]">{course.isIncluded}</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0.5px_0.5px_0px] 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]">{course.createdAt}</p>
</div>
</div>
<div className="basis-0 border-[0.5px_0.5px_0.5px_0px] 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]">{course.registrar}</p>
</div>
</div>
</div>
</div>
))}
</div>
</div>
{/* 페이지네이션 */}
<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>
{/* 모달 팝업 */}
{isModalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* 배경 오버레이 */}
<div
className="absolute inset-0 bg-[rgba(0,0,0,0.6)]"
onClick={handleCloseModal}
/>
{/* 모달 컨텐츠 */}
<div
className="relative bg-[#f7f7f7] h-[504px] overflow-clip shadow-[0px_4px_4px_0px_rgba(0,0,0,0.25)] w-[683px]"
onClick={(e) => e.stopPropagation()}
>
{/* 닫기 버튼 */}
<button
onClick={handleCloseModal}
className="absolute left-[622px] overflow-clip size-[30px] top-[41px] cursor-pointer hover:opacity-80 transition-opacity z-10"
>
<div className="absolute inset-0 pointer-events-none flex items-center justify-center">
<Close />
</div>
</button>
{/* 저장 버튼 */}
<button
onClick={handleSave}
className="absolute bg-[#599ded] box-border content-stretch flex gap-[10px] h-[50px] items-center justify-center left-[358px] p-[10px] rounded-[10px] top-[414px] w-[264px] cursor-pointer hover:bg-[#4a8ddc] transition-colors"
>
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[18px] text-nowrap text-white whitespace-pre">
</p>
</button>
{/* 취소 버튼 */}
<button
onClick={handleCloseModal}
className="absolute bg-[#eeeeee] box-border content-stretch flex gap-[10px] h-[50px] items-center justify-center left-[62px] p-[10px] rounded-[10px] top-[414px] w-[264px] cursor-pointer hover:bg-[#e0e0e0] transition-colors"
>
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
</p>
</button>
{/* 폼 컨텐츠 */}
<div className="absolute content-stretch flex flex-col gap-[20px] items-start left-[31px] top-[41px] w-[621px]">
<div className="content-stretch flex flex-col gap-[20px] items-start relative shrink-0 w-[621px]">
{/* 제목 */}
<div className="content-stretch flex gap-[10px] items-center relative shrink-0 w-full">
<div className="flex flex-col font-bold justify-center leading-[0] not-italic relative shrink-0 text-[20px] text-[#404040] text-nowrap">
<p className="leading-[1.6] whitespace-pre"> </p>
</div>
</div>
{/* 교육 과정명 */}
<div className="content-stretch flex flex-col gap-[20px] items-start relative shrink-0 w-full">
<div className="content-stretch flex gap-[16px] items-center relative shrink-0 w-full">
<div className="content-stretch flex gap-[10px] items-center relative shrink-0 w-[177px]">
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
<span className="text-[#e61a1a]"> *</span>
</p>
</div>
<input
type="text"
value={courseName}
onChange={(e) => setCourseName(e.target.value)}
placeholder="교육 과정명을 입력해 주세요."
className="basis-0 border border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center justify-center min-h-px min-w-px p-[10px] relative rounded-[8px] shrink-0 bg-white text-[16px] text-[#515151] focus:outline-none focus:border-[#2b82e8]"
/>
</div>
</div>
{/* 강사 */}
<div className="content-stretch flex flex-col gap-[20px] items-start relative shrink-0 w-full">
<div className="content-stretch flex gap-[16px] items-center relative shrink-0 w-full">
<div className="content-stretch flex gap-[10px] items-center relative shrink-0 w-[177px]">
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
<span className="text-[#e61a1a]"> *</span>
</p>
</div>
<div className="basis-0 relative grow min-h-px min-w-px shrink-0">
<button
type="button"
onClick={() => setShowInstructorDropdown(!showInstructorDropdown)}
className="basis-0 border border-[#b9b9b9] border-solid box-border content-stretch flex gap-[10px] grow items-center justify-between min-h-px min-w-px p-[10px] relative rounded-[8px] shrink-0 w-full bg-white cursor-pointer hover:border-[#2b82e8] transition-colors"
>
<p className={`basis-0 font-medium grow leading-[1.6] min-h-px min-w-px not-italic relative shrink-0 text-left text-[16px] ${instructorName ? 'text-[#515151]' : 'text-[#b9b9b9]'}`}>
{instructorName || '강사를 선택해 주세요.'}
</p>
<div className="overflow-clip relative shrink-0 size-[24px]">
<img alt="" className="block max-w-none size-full" src={imgMingcuteDownLine} />
</div>
</button>
{showInstructorDropdown && (
<div className="absolute top-full left-0 right-0 mt-1 bg-white box-border content-stretch flex flex-col gap-[4px] items-start p-[8px] rounded-[8px] shadow-[0px_8px_16px_-2px_rgba(27,33,44,0.12)] z-10">
{instructors.map((instructor) => (
<button
key={instructor}
type="button"
onClick={() => handleSelectInstructor(instructor)}
className={`box-border content-stretch flex gap-[10px] h-[38px] items-center overflow-clip p-[8px] relative rounded-[8px] shrink-0 w-full transition-colors ${instructorName === instructor ? 'bg-[#e3effc]' : 'bg-white hover:bg-[#f7f7f7]'}`}
>
<div className="basis-0 flex flex-col font-normal grow justify-center leading-[0] min-h-px min-w-px not-italic relative shrink-0 text-[14px] text-[#515151]">
<p className="leading-[1.6]">{instructor}</p>
</div>
</button>
))}
</div>
)}
</div>
</div>
</div>
{/* 교육 과정 이미지 */}
<div className="content-stretch flex flex-col gap-[20px] items-start relative shrink-0 w-full">
<div className="content-stretch flex gap-[20px] items-center relative shrink-0 w-full">
<div className="basis-0 content-stretch flex gap-[16px] grow items-center min-h-px min-w-px relative shrink-0">
<div className="content-stretch flex gap-[10px] items-center relative shrink-0 w-[177px]">
<p className="font-medium leading-[normal] not-italic relative shrink-0 text-[#515151] text-[18px] text-nowrap whitespace-pre">
</p>
</div>
</div>
<div className="content-stretch flex gap-[16px] items-center relative shrink-0">
<button className="bg-[#2b82e8] box-border content-stretch flex gap-2 h-[36px] items-center justify-center overflow-clip px-5 py-[13px] relative rounded-[8px] shrink-0 w-[114px] cursor-pointer hover:bg-[#1f6fc4] transition-colors">
<p className="font-medium leading-[1.6] not-italic relative shrink-0 text-[14px] text-white text-center text-nowrap whitespace-pre">
</p>
</button>
</div>
</div>
<div className="bg-[#f7f7f7] border border-[#eeeeee] border-solid box-border content-stretch flex flex-col gap-4 items-center px-[10px] py-[20px] relative rounded-[10px] shrink-0 w-full">
<div className="content-stretch flex flex-col gap-[2px] items-center relative shrink-0">
<div className="font-medium leading-[1.6] not-italic relative shrink-0 text-[#b9b9b9] text-[16px] text-center text-nowrap whitespace-pre">
<p className="mb-0">30MB .</p>
<p> .</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</div>
);
}

View File

View File

@@ -71,7 +71,7 @@ export default function LoginPage() {
}
// 아이디와 비밀번호 검증
if (username === 'admin' && password === '1234') {
if (username === 'qwre@naver.com' && password === '1234') {
// 로그인 성공
localStorage.setItem('isLoggedIn', 'true');
// 아이디 기억하기 체크 시 아이디 저장

View File

@@ -34,11 +34,14 @@ export default function MyLecturePage() {
<header className="absolute content-stretch flex items-center justify-between left-[calc(12.5%+91px)] top-[43px] w-[1332px]">
<div className="content-stretch flex gap-[99px] items-center relative shrink-0">
{/* 로고 */}
<div className="h-[74px] relative shrink-0 w-[72px]">
<button
onClick={() => router.push('/')}
className="h-[74px] relative shrink-0 w-[72px] cursor-pointer"
>
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<img alt="" className="absolute h-[291.74%] left-[-100%] max-w-none top-[-95.73%] w-[301.18%]" src={imgImage2} />
</div>
</div>
</button>
{/* 메뉴 */}
<div className="content-stretch flex gap-[24px] items-center relative shrink-0">
<div className="content-stretch flex gap-[150px] items-center relative shrink-0">

View File

@@ -94,11 +94,14 @@ export default function MyLecturePage() {
<header className="absolute content-stretch flex items-center justify-between left-[calc(12.5%+91px)] top-[43px] w-[1332px]">
<div className="content-stretch flex gap-[99px] items-center relative shrink-0">
{/* 로고 */}
<div className="h-[74px] relative shrink-0 w-[72px]">
<button
onClick={() => router.push('/')}
className="h-[74px] relative shrink-0 w-[72px] cursor-pointer"
>
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<img alt="" className="absolute h-[291.74%] left-[-100%] max-w-none top-[-95.73%] w-[301.18%]" src={imgImage2} />
</div>
</div>
</button>
{/* 메뉴 */}
<div className="content-stretch flex gap-[24px] items-center relative shrink-0">
<div className="content-stretch flex gap-[150px] items-center relative shrink-0">
@@ -320,7 +323,7 @@ export default function MyLecturePage() {
</div>
{/* 푸터 */}
<footer className="absolute bg-[#f7f7f7] box-border content-stretch flex flex-col gap-[10px] h-[225px] items-start left-0 px-[243px] py-[39px] top-[1226px] w-full">
<footer className="absolute bg-[#f7f7f7] box-border content-stretch flex flex-col mt-[111px] gap-[10px] h-[225px] items-start left-0 px-[243px] py-[39px] top-[1226px] w-full">
<div className="content-stretch flex gap-[49px] items-center relative shrink-0">
{/* 로고 */}
<div className="h-[74px] relative shrink-0 w-[72px]">

View File

@@ -72,11 +72,14 @@ export default function HomePage() {
<header className="absolute content-stretch flex items-center justify-between left-[calc(12.5%+91.375px)] top-[43px] w-[1332px]">
<div className="content-stretch flex gap-[99px] items-center relative shrink-0">
{/* 로고 */}
<div className="h-[74px] relative shrink-0 w-[72px]">
<button
onClick={() => router.push('/')}
className="h-[74px] relative shrink-0 w-[72px] cursor-pointer"
>
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<img alt="" className="absolute h-[291.74%] left-[-100%] max-w-none top-[-95.73%] w-[301.18%]" src={imgImage2} />
</div>
</div>
</button>
{/* 메뉴 */}
<div className="content-stretch flex gap-[24px] items-center relative shrink-0">
<div className="content-stretch flex gap-[150px] items-center relative shrink-0">

253
app/pwfind/page.tsx Normal file
View File

@@ -0,0 +1,253 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
export default function PasswordFindPage() {
const router = useRouter();
const [email, setEmail] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [emailError, setEmailError] = useState('');
const [newPasswordError, setNewPasswordError] = useState('');
const [confirmPasswordError, setConfirmPasswordError] = useState('');
const [isCodeSent, setIsCodeSent] = useState(false);
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setEmail(value);
if (emailError) {
setEmailError('');
}
// 이메일이 변경되면 인증 상태 초기화
if (isCodeSent) {
setIsCodeSent(false);
}
};
const handleNewPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setNewPassword(value);
if (newPasswordError) {
setNewPasswordError('');
}
// 새 비밀번호가 변경되면 확인 비밀번호도 다시 검증
if (confirmPassword && value !== confirmPassword) {
setConfirmPasswordError('비밀번호와 일치하지 않아요.');
} else if (confirmPassword && value === confirmPassword) {
setConfirmPasswordError('');
}
};
const handleConfirmPasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setConfirmPassword(value);
if (confirmPasswordError) {
setConfirmPasswordError('');
}
// 비밀번호 확인 실시간 검증
if (value && newPassword && value !== newPassword) {
setConfirmPasswordError('비밀번호와 일치하지 않아요.');
} else if (value && newPassword && value === newPassword) {
setConfirmPasswordError('');
}
};
const validateEmail = (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const validatePassword = (password: string) => {
return password.length >= 8 && password.length <= 16 && /[a-zA-Z]/.test(password) && /[0-9]/.test(password);
};
const handleSendCode = () => {
if (!email.trim()) {
setEmailError('이메일을 입력해 주세요.');
return;
}
if (!validateEmail(email)) {
setEmailError('올바른 이메일을 입력해주세요.');
return;
}
// TODO: 인증번호 발송 API 호출
setIsCodeSent(true);
setEmailError('');
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setEmailError('');
setNewPasswordError('');
setConfirmPasswordError('');
let isValid = true;
if (!email.trim()) {
setEmailError('이메일을 입력해 주세요.');
isValid = false;
} else if (!validateEmail(email)) {
setEmailError('올바른 이메일을 입력해주세요.');
isValid = false;
}
if (!isCodeSent) {
setEmailError('인증번호를 발송해주세요.');
isValid = false;
}
if (!newPassword.trim()) {
setNewPasswordError('새 비밀번호를 입력해 주세요.');
isValid = false;
} else if (!validatePassword(newPassword)) {
setNewPasswordError('비밀번호는 8~16자의 영문/숫자를 포함해야 합니다.');
isValid = false;
}
if (!confirmPassword.trim()) {
setConfirmPasswordError('새 비밀번호 확인을 입력해 주세요.');
isValid = false;
} else if (newPassword !== confirmPassword) {
setConfirmPasswordError('비밀번호와 일치하지 않아요.');
isValid = false;
}
if (isValid) {
// TODO: 비밀번호 재설정 API 호출
router.push('/login');
}
};
const canSubmit = email.trim() !== '' &&
newPassword.trim() !== '' &&
confirmPassword.trim() !== '' &&
isCodeSent &&
newPassword === confirmPassword &&
validatePassword(newPassword);
return (
<div className="relative flex flex-col min-h-screen bg-white">
{/* 메인 콘텐츠 영역 - 카피라이트와 브라우저 최상단 사이의 중앙 */}
<div className="flex-1 flex flex-col items-center justify-center pb-[100px]">
{/* 제목 */}
<h1 className="font-bold text-[#1D1D1D] text-[32px] leading-tight">
</h1>
{/* 안내 문구 */}
<p className="text-[#515151] text-[18px] font-medium leading-normal mt-[40px]">
.
</p>
{/* 입력 폼 */}
<form
className="w-[829px] border border-[#b9b9b9] rounded-[8px] mt-[40px] px-[31px] py-[29px]"
onSubmit={handleSubmit}
>
<div className="flex flex-col gap-[20px]">
{/* 이메일(아이디) + 인증번호 발송 */}
<div className="flex flex-col">
<div className="flex gap-[10px] items-center justify-between h-[42px]">
<span className="text-[#515151] text-[18px] font-medium w-[177px]">
()
</span>
<input
type="email"
value={email}
onChange={handleEmailChange}
className={`h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white w-[401px] focus:border-[#1669CA] focus:outline-none ${emailError ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
placeholder="이메일을 입력해 주세요."
/>
<button
type="button"
onClick={handleSendCode}
className="bg-[#2B82E8] rounded-[10px] px-[10px] text-[18px] font-medium text-white h-[43px] w-[158px] transition hover:bg-[#1669ca] flex items-center justify-center"
>
</button>
</div>
{emailError && (
<p className="text-[13px] text-[#E85D5D] ml-[187px] mt-[16px]">{emailError}</p>
)}
{isCodeSent && !emailError && (
<p className="text-[#1669ca] text-[12px] font-medium ml-[187px] mt-[16px]">
. .
</p>
)}
</div>
{/* 새 비밀번호 */}
<div className="flex flex-col">
<div className="flex gap-[16px] items-center h-[42px]">
<span className="text-[#515151] text-[18px] font-medium w-[177px]">
</span>
<input
type="password"
value={newPassword}
onChange={handleNewPasswordChange}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white focus:border-[#1669CA] focus:outline-none ${newPasswordError ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
placeholder="새 비밀번호를 입력해 주세요."
/>
</div>
{newPasswordError && (
<p className="text-[13px] text-[#E85D5D] ml-[193px] mt-[16px]">{newPasswordError}</p>
)}
</div>
{/* 새 비밀번호 확인 */}
<div className="flex flex-col">
<div className="flex gap-[16px] items-center h-[42px]">
<span className="text-[#515151] text-[18px] font-medium w-[177px]">
</span>
<input
type="password"
value={confirmPassword}
onChange={handleConfirmPasswordChange}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white focus:border-[#1669CA] focus:outline-none ${confirmPasswordError ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
placeholder="새 비밀번호를 다시 입력해 주세요."
/>
</div>
{confirmPasswordError && (
<p className="text-[13px] text-[#E85D5D] ml-[193px] mt-[16px]">{confirmPasswordError}</p>
)}
</div>
</div>
</form>
{/* 버튼 영역 */}
<div className="mt-[54px] flex gap-[21px]">
<Link
href="/login"
className="border border-[#1669ca] bg-white rounded-[10px] h-[55px] w-[334px] flex items-center justify-center font-medium text-[#515151] text-[18px] hover:bg-[#EDF4FC] transition"
>
</Link>
<button
type="button"
onClick={handleSubmit}
disabled={!canSubmit}
className={`rounded-[10px] h-[55px] w-[334px] font-medium text-[18px] transition flex items-center justify-center
${canSubmit
? 'bg-[#2B82E8] text-white hover:bg-[#1669ca]'
: 'bg-[#b9b9b9] text-white cursor-not-allowed'
}`}
>
</button>
</div>
</div>
{/* 카피라이트 */}
<footer className="absolute bottom-[51.5px] left-1/2 -translate-x-1/2 flex flex-col items-center">
<p className="text-[16px] text-[rgba(0,0,0,0.55)] leading-[1.45] font-medium tracking-[-0.08px]">
Copyright 2025 XL LMS. All rights reserved
</p>
</footer>
</div>
);
}