admin 권한설정 완료, 교육과정 작업중1

This commit is contained in:
2025-11-19 01:41:27 +09:00
parent aca6fa93ea
commit e768f267d3
12 changed files with 962 additions and 118 deletions

View 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>
);
}

View File

@@ -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>
);
}
}