admin 권한설정 완료, 교육과정 작업중1
This commit is contained in:
225
src/app/admin/courses/CourseRegistrationModal.tsx
Normal file
225
src/app/admin/courses/CourseRegistrationModal.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useRef, useEffect, useMemo } from "react";
|
||||
import ModalCloseSvg from "@/app/svgs/closexsvg";
|
||||
import DropdownIcon from "@/app/svgs/dropdownicon";
|
||||
import { getInstructors, type UserRow } from "@/app/admin/id/page";
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export default function CourseRegistrationModal({ open, onClose }: Props) {
|
||||
const [courseName, setCourseName] = useState("");
|
||||
const [instructorId, setInstructorId] = useState<string>("");
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 강사 목록 가져오기
|
||||
// TODO: 나중에 DB에서 가져오도록 변경 시 async/await 사용
|
||||
// 예: const [instructors, setInstructors] = useState<UserRow[]>([]);
|
||||
// useEffect(() => { getInstructors().then(setInstructors); }, []);
|
||||
const instructors = useMemo(() => getInstructors(), []);
|
||||
|
||||
// 선택된 강사 정보
|
||||
const selectedInstructor = useMemo(() => {
|
||||
return instructors.find(inst => inst.id === instructorId);
|
||||
}, [instructors, instructorId]);
|
||||
|
||||
// 외부 클릭 시 드롭다운 닫기
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isDropdownOpen) {
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [isDropdownOpen]);
|
||||
|
||||
// 모달 클릭 시 이벤트 전파 방지
|
||||
const handleModalClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center"
|
||||
aria-hidden={!open}
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0 bg-black/40"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div
|
||||
ref={modalRef}
|
||||
className="relative z-10 shadow-xl"
|
||||
onClick={handleModalClick}
|
||||
>
|
||||
<div className="bg-white border border-[#dee1e6] rounded-[12px] w-full min-w-[480px] max-h-[90vh] overflow-y-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-[10px] p-6">
|
||||
<h2 className="text-[20px] font-bold leading-[1.5] text-[#333c47]">
|
||||
과목 등록
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="w-6 h-6 flex items-center justify-center cursor-pointer hover:opacity-80 shrink-0"
|
||||
aria-label="닫기"
|
||||
>
|
||||
<ModalCloseSvg />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Form Container */}
|
||||
<div className="px-6 py-0">
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* 교육 과정명 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-[15px] font-semibold leading-[1.5] text-[#6c7682] w-[100px]">
|
||||
교육 과정명<span className="text-[#f64c4c]">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={courseName}
|
||||
onChange={(e) => setCourseName(e.target.value)}
|
||||
placeholder="교육 과정명을 입력해 주세요."
|
||||
className="h-[40px] px-3 py-2 border border-[#dee1e6] rounded-[8px] bg-white text-[16px] font-normal leading-[1.5] text-[#1b2027] placeholder:text-[#b1b8c0] focus:outline-none focus:ring-2 focus:ring-[#1f2b91] focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 강사 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-[15px] font-semibold leading-[1.5] text-[#6c7682] w-[100px]">
|
||||
강사<span className="text-[#f64c4c]">*</span>
|
||||
</label>
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||||
className="w-full h-[40px] px-3 py-2 border border-[#dee1e6] rounded-[8px] bg-white flex items-center justify-between text-left focus:outline-none focus:ring-2 focus:ring-[#1f2b91] focus:border-transparent"
|
||||
>
|
||||
<span
|
||||
className={`text-[16px] font-normal leading-[1.5] flex-1 ${
|
||||
selectedInstructor ? "text-[#1b2027]" : "text-[#6c7682]"
|
||||
}`}
|
||||
>
|
||||
{selectedInstructor?.name || "강사를 선택해 주세요."}
|
||||
</span>
|
||||
<DropdownIcon stroke="#8C95A1" className="shrink-0" />
|
||||
</button>
|
||||
{isDropdownOpen && (
|
||||
<div className="absolute top-full left-0 right-0 mt-1 bg-white border border-[#dee1e6] rounded-[8px] shadow-lg z-20 max-h-[200px] overflow-y-auto">
|
||||
{instructors.length === 0 ? (
|
||||
<div className="px-3 py-2 text-[16px] font-normal leading-[1.5] text-[#6c7682] text-center">
|
||||
등록된 강사가 없습니다.
|
||||
</div>
|
||||
) : (
|
||||
instructors.map((instructor, index) => (
|
||||
<button
|
||||
key={instructor.id}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setInstructorId(instructor.id);
|
||||
setIsDropdownOpen(false);
|
||||
}}
|
||||
className={`w-full px-3 py-2 text-left text-[16px] font-normal leading-[1.5] hover:bg-[#f1f3f5] transition-colors ${
|
||||
instructorId === instructor.id
|
||||
? "bg-[#ecf0ff] text-[#1f2b91] font-semibold"
|
||||
: "text-[#1b2027]"
|
||||
} ${
|
||||
index === 0 ? "rounded-t-[8px]" : ""
|
||||
} ${
|
||||
index === instructors.length - 1 ? "rounded-b-[8px]" : ""
|
||||
}`}
|
||||
>
|
||||
{instructor.name}
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 과목 이미지 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-[15px] font-semibold leading-[1.5] text-[#6c7682] whitespace-pre">
|
||||
과목 이미지
|
||||
</label>
|
||||
<span className="text-[13px] font-normal leading-[1.4] text-[#8c95a1]">
|
||||
30MB 미만의 PNG, JPG
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 border border-[#dee1e6] border-dashed h-[192px] rounded-[8px] flex flex-col items-center justify-center gap-3 cursor-pointer hover:bg-gray-100 transition-colors px-0 py-4">
|
||||
<div className="w-10 h-10 flex items-center justify-center shrink-0">
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20 13.3333V26.6667M13.3333 20H26.6667"
|
||||
stroke="#8C95A1"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-[14px] font-normal leading-[1.5] text-[#8c95a1] whitespace-pre">
|
||||
(클릭하여 이미지 업로드)
|
||||
<br aria-hidden="true" />
|
||||
미첨부 시 기본 이미지가 노출됩니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions Container */}
|
||||
<div className="flex flex-col gap-8 h-[96px] items-center p-6">
|
||||
<div className="flex items-center justify-center gap-3 w-full">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="h-[48px] px-4 rounded-[10px] bg-[#f1f3f5] text-[16px] font-semibold leading-[1.5] text-[#4c5561] w-[136px] hover:bg-[#e5e7eb] transition-colors"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="h-[48px] px-4 rounded-[10px] bg-[#1f2b91] text-[16px] font-semibold leading-[1.5] text-white w-[136px] hover:bg-[#1a2478] transition-colors"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,67 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from "react";
|
||||
import AdminSidebar from "@/app/components/AdminSidebar";
|
||||
import CourseRegistrationModal from "./CourseRegistrationModal";
|
||||
|
||||
export default function AdminCoursesPage() {
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-white">
|
||||
<div className="flex flex-1 min-h-0">
|
||||
<AdminSidebar />
|
||||
|
||||
<main className="flex-1 min-w-0 bg-white">
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="h-[100px] flex items-center px-8 border-b border-[#dee1e6]">
|
||||
<h1 className="text-[24px] font-bold leading-[1.5] text-[#1b2027]">
|
||||
교육과정 관리
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 px-8 pt-8 pb-20">
|
||||
</div>
|
||||
{/* 메인 레이아웃 */}
|
||||
<div className="flex flex-1 min-h-0 justify-center">
|
||||
<div className="w-full max-w-[1120px] flex min-h-0">
|
||||
{/* 사이드바 */}
|
||||
<div className="px-8 flex">
|
||||
<AdminSidebar />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* 메인 콘텐츠 */}
|
||||
<main className="flex-1 min-w-0 bg-white">
|
||||
<div className="h-full flex flex-col">
|
||||
{/* 제목 영역 */}
|
||||
<div className="h-[100px] flex items-center">
|
||||
<h1 className="text-[24px] font-bold leading-[1.5] text-[#1b2027]">
|
||||
교육과정 관리
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* 헤더 영역 (제목과 콘텐츠 사이) */}
|
||||
<div className="pt-8 pb-4 flex items-center justify-between">
|
||||
<p className="text-[15px] font-medium leading-[1.5] text-[#333c47] whitespace-nowrap">
|
||||
총 {totalCount}건
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
className="h-[40px] px-4 rounded-[8px] bg-[#1f2b91] text-[16px] font-semibold leading-[1.5] text-white whitespace-nowrap hover:bg-[#1a2478] transition-colors"
|
||||
>
|
||||
등록하기
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 콘텐츠 영역 */}
|
||||
<div className="flex-1 pt-2 pb-20 flex flex-col">
|
||||
<div className="rounded-[8px] border border-[#dee1e6] bg-[#fafbfc] min-h-[398px] flex items-center justify-center">
|
||||
<p className="text-[15px] font-normal text-[#858fa3]">
|
||||
<span className="block w-full text-center">
|
||||
등록된 교육과정이 없습니다.
|
||||
<br />
|
||||
과목을 등록해주세요.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<CourseRegistrationModal
|
||||
open={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user