diff --git a/app/adminpage/_components/AdminDateSelector.tsx b/app/adminpage/_components/AdminDateSelector.tsx new file mode 100644 index 0000000..b0d0855 --- /dev/null +++ b/app/adminpage/_components/AdminDateSelector.tsx @@ -0,0 +1,29 @@ +"use client"; + +import React from "react"; + +interface AdminDateSelectorProps { + selectedDate: string; + onSelectDate: (date: string) => void; +} + +export const AdminDateSelector = ({ + selectedDate, + onSelectDate, +}: AdminDateSelectorProps) => { + const dates = ["오늘", "내일", "모레"]; + + return ( +
+ {dates.map((date) => ( + + ))} +
+ ); +}; diff --git a/app/adminpage/_components/AdminDropdown.tsx b/app/adminpage/_components/AdminDropdown.tsx new file mode 100644 index 0000000..de40b15 --- /dev/null +++ b/app/adminpage/_components/AdminDropdown.tsx @@ -0,0 +1,71 @@ +"use client"; + +import React, { useState, useRef, useEffect } from "react"; +import { ChevronDown } from "lucide-react"; + +interface AdminDropdownProps { + options: string[]; + selectedValue: string; + onSelect: (value: string) => void; + height?: string; + className?: string; +} + +export const AdminDropdown = ({ + options, + selectedValue, + onSelect, + height, + className +}: AdminDropdownProps) => { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + return ( +
+
setIsOpen(!isOpen)} + className="w-full h-full bg-[#f4f4f4] rounded-lg border border-[#e5e5e5] px-3 flex items-center justify-between cursor-pointer text-[18px] font-semibold text-black" + > + {selectedValue} + +
+ + {isOpen && ( +
+ {options.map((option) => ( +
{ + onSelect(option); + setIsOpen(false); + }} + className="px-3 py-2 hover:bg-[#f4f4f4] cursor-pointer text-[18px] font-medium text-black" + > + {option} +
+ ))} +
+ )} +
+ ); +}; diff --git a/app/adminpage/_components/AdminHeader.tsx b/app/adminpage/_components/AdminHeader.tsx new file mode 100644 index 0000000..e4aaa1a --- /dev/null +++ b/app/adminpage/_components/AdminHeader.tsx @@ -0,0 +1,107 @@ +"use client"; + +import React from "react"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; +import { ChevronDown } from "lucide-react"; + +interface AdminHeaderProps { + adminSelect?: string; + setAdminSelect?: (val: string) => void; + university?: string; + role?: string; + nickname?: string; +} + +export const AdminHeader = ({ + adminSelect, + setAdminSelect, + university, + role, + nickname +}: AdminHeaderProps) => { + const router = useRouter(); + + const goToMainButton = () => { + if (setAdminSelect) setAdminSelect("Main"); + router.push("/adminpage/myPage"); + }; + + const goToTeamButton = () => { + if (setAdminSelect) setAdminSelect("팀관리"); + router.push("/adminpage/myPage?tab=팀관리"); + }; + + const goToMemberButton = () => { + if (setAdminSelect) setAdminSelect("가입자관리"); + router.push("/adminpage/myPage?tab=가입자관리"); + }; + + const getRoleLabel = (role?: string) => { + if (!role) return ""; + return role.includes("ADMIN") ? "관리자" : "오퍼레이터"; + }; + + return ( +
+ 코매칭 로고 router.push("/adminpage")} + /> + + + +
+
+
{university}
+
{getRoleLabel(role)} {nickname}님
+
+ +
+
+ ); +}; + +export const AdminRegisterHeader = () => { + const router = useRouter(); + return ( +
+ 코매칭 로고 router.push("/adminpage")} + /> +
+ ); +}; diff --git a/app/adminpage/_components/AdminListItem.tsx b/app/adminpage/_components/AdminListItem.tsx new file mode 100644 index 0000000..973353d --- /dev/null +++ b/app/adminpage/_components/AdminListItem.tsx @@ -0,0 +1,59 @@ +"use client"; + +import React from "react"; + +interface AdminListItemProps { + title: string; + subTitle?: string; + statusText: string; + date: string; + startTime: string; + endTime: string; + onCancel?: () => void; + cancelButtonText?: string; +} + +export const AdminListItem = ({ + title, + subTitle, + statusText, + date, + startTime, + endTime, + onCancel, + cancelButtonText +}: AdminListItemProps) => { + return ( +
+
+ {statusText} + {title} {subTitle && {subTitle}} +
+
+
+
+ 시작일: + {date} +
+
+ 시작 시각: + {startTime} +
+
+ 종료 시각: + {endTime} +
+
+ + {onCancel && ( + + )} +
+
+ ); +}; diff --git a/app/adminpage/_components/AdminNotAllowed.tsx b/app/adminpage/_components/AdminNotAllowed.tsx new file mode 100644 index 0000000..76035c6 --- /dev/null +++ b/app/adminpage/_components/AdminNotAllowed.tsx @@ -0,0 +1,17 @@ +"use client"; + +import React from "react"; +import { Ban } from "lucide-react"; + +export default function AdminNotAllowed() { + return ( +
+ +
+ 미승인 오퍼레이터입니다. +
+ 관리자의 승인을 대기해 주세요. +
+
+ ); +} diff --git a/app/adminpage/_components/AdminTimeRow.tsx b/app/adminpage/_components/AdminTimeRow.tsx new file mode 100644 index 0000000..f085341 --- /dev/null +++ b/app/adminpage/_components/AdminTimeRow.tsx @@ -0,0 +1,41 @@ +"use client"; + +import React from "react"; +import { AdminDropdown } from "./AdminDropdown"; + +interface AdminTimeRowProps { + hours: string[]; + minutes: string[]; + selectedHour: string; + selectedMinute: string; + onHourSelect: (hour: string) => void; + onMinuteSelect: (minute: string) => void; + suffix: string; +} + +export const AdminTimeRow = ({ + hours, + minutes, + selectedHour, + selectedMinute, + onHourSelect, + onMinuteSelect, + suffix, +}: AdminTimeRowProps) => { + return ( +
+ + + + {suffix} +
+ ); +}; diff --git a/app/adminpage/_components/AdminWarnItem.tsx b/app/adminpage/_components/AdminWarnItem.tsx new file mode 100644 index 0000000..bb736da --- /dev/null +++ b/app/adminpage/_components/AdminWarnItem.tsx @@ -0,0 +1,21 @@ +"use client"; + +import React from "react"; + +interface AdminWarnItemProps { + reason: string; + time: string; +} + +export const AdminWarnItem = ({ reason, time }: AdminWarnItemProps) => { + return ( +
+
+ {reason} +
+
+ {time} +
+
+ ); +}; diff --git a/app/adminpage/_components/AdminWarningModal.tsx b/app/adminpage/_components/AdminWarningModal.tsx new file mode 100644 index 0000000..df233ef --- /dev/null +++ b/app/adminpage/_components/AdminWarningModal.tsx @@ -0,0 +1,37 @@ +"use client"; + +import React from "react"; +import { TriangleAlert } from "lucide-react"; + +interface AdminWarningModalProps { + isOpen: boolean; + onClose: () => void; + message: React.ReactNode; +} + +export const AdminWarningModal = ({ + isOpen, + onClose, + message, +}: AdminWarningModalProps) => { + if (!isOpen) return null; + + return ( +
+
+
+ +
+ {message} +
+
+
+ 확인 +
+
+
+ ); +}; diff --git a/app/adminpage/_components/ManagementComponents.tsx b/app/adminpage/_components/ManagementComponents.tsx new file mode 100644 index 0000000..f29e72d --- /dev/null +++ b/app/adminpage/_components/ManagementComponents.tsx @@ -0,0 +1,142 @@ +"use client"; + +import React from "react"; +import { useRouter } from "next/navigation"; +import { useToggle1000Button } from "@/hooks/useAdminManagement"; + +// --- Types --- +interface ManagementProps { + adminData: { + nickname: string; + role: string; + university: string; + schoolEmail: string; + }; +} + +// --- AdminMyPageMain --- +export const AdminMyPageMain = ({ adminData }: ManagementProps) => { + return ( +
+
+ 내 정보 + 모든 기능을 이용할 수 있습니다 +
+
+ 이름 : {adminData.nickname} +
+
+ 권한 : {adminData.role} +
+
+ 소속 : {adminData.university} +
+
+ 웹메일 : {adminData.schoolEmail} +
+
+
+
+ ); +}; + +// --- MasterManageComponent --- +export const MasterManageComponent = () => { + const router = useRouter(); + const toggle1000 = useToggle1000Button(); + + const handle1000Button = async () => { + try { + const data = await toggle1000.mutateAsync(); + if (data.data === "활성화") { + alert("1000원 버튼 활성화 되었습니다."); + } else if (data.data === "비활성화") { + alert("1000원 버튼 비활성화 되었습니다."); + } + } catch (error) { + console.error("천원 버튼 요청 실패", error); + alert("천원 버튼 요청에 실패했습니다."); + } + }; + + const menuItems = [ + { title: "가입자 결제 요청 관리", sub: "유저 결제 요청 관리", path: "/adminpage/payrequest" }, + { title: "가입자 검색 및 관리", sub: "결제내역 및 포인트 사용내역 열람, 포인트 조정, 블랙리스트 추가", path: "/adminpage/myPage/search" }, + { title: "가입자 성비 분석", sub: "가입자의 성비 분석", path: "/adminpage/myPage/gender" }, + { title: "공지사항 등록", sub: "전체알림 공지", path: "/adminpage/myPage/notice" }, + { title: "블랙리스트 확인 및 해제", sub: "블랙리스트 조회와 해제", path: "/adminpage/myPage/blacklist" }, + { title: "이벤트 등록", sub: "관리자의 이벤트 등록", path: "/adminpage/myPage/event" }, + { title: "문의 및 신고목록", sub: "가입자로부터 온 문의와 신고 열람", path: "/adminpage/myPage/Q&A" }, + ]; + + return ( +
+ {menuItems.map((item, idx) => ( +
router.push(item.path)} + className="bg-white rounded-[24px] border border-white/30 shadow-[1px_1px_20px_1px_rgba(196,196,196,0.3)] flex flex-col justify-center text-left p-6 gap-2 cursor-pointer min-w-[317px]" + > +
{item.title}
+
{item.sub}
+
+ ))} +
+
1000원 맞추기 버튼
+
코매칭 마지막 날 진행할 유저포인트 천원 버튼 활성화
+
+
+ ); +}; + +// --- OperatorManageComponent --- +export const OperatorManageComponent = () => { + const router = useRouter(); + + const menuItems = [ + { title: "가입자 결제 요청 관리", sub: "유저 결제 요청 관리", path: "/adminpage/payrequest" }, + { title: "가입자 검색 및 관리", sub: "결제내역 및 포인트 사용내역 열람, 포인트 조정, 블랙리스트 추가", path: "/adminpage/myPage/search" }, + { title: "가입자 성비 분석", sub: "가입자의 성비 분석", path: null }, + { title: "문의 및 신고목록", sub: "가입자로부터 온 문의와 신고 열람", path: null }, + { title: "블랙리스트 확인 및 해제", sub: "블랙리스트 조회와 해제", path: null }, + ]; + + return ( +
+ {menuItems.map((item, idx) => ( +
item.path && router.push(item.path)} + className="bg-white rounded-[24px] border border-white/30 shadow-[1px_1px_20px_1px_rgba(196,196,196,0.3)] flex flex-col justify-center text-left p-6 gap-2 cursor-pointer min-w-[317px]" + > +
{item.title}
+
{item.sub}
+
+ ))} +
+ ); +}; + +// --- AdminTeamManage --- +export const AdminTeamManage = () => { + return ( +
+
+
+ 오퍼레이터 승인 요청 +
+ 3 +
+
+ 오퍼레이터 승인 요청 +
+
+ 오퍼레이터 관리 + 오퍼레이터 관리 +
+
+ ); +}; diff --git a/app/adminpage/_components/Pagination.tsx b/app/adminpage/_components/Pagination.tsx new file mode 100644 index 0000000..aeb3c9e --- /dev/null +++ b/app/adminpage/_components/Pagination.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { + ChevronLeft, + ChevronRight, + ChevronsLeft, + ChevronsRight, +} from "lucide-react"; +interface PaginationProps { + totalPage: number; + currentPage: number; + onPageChange: (page: number) => void; +} + +export const Pagination = ({ + totalPage, + currentPage, + onPageChange, +}: PaginationProps) => { + const pagePerGroup = 10; + const currentGroup = Math.floor((currentPage - 1) / pagePerGroup); + const startPage = currentGroup * pagePerGroup + 1; + const endPage = Math.min(startPage + pagePerGroup - 1, totalPage); + + const handlePrevGroup = () => { + onPageChange(Math.max(1, startPage - 1)); + }; + + const handleNextGroup = () => { + onPageChange(Math.min(totalPage, endPage + 1)); + }; + + const handleNextPage = () => { + if (currentPage < totalPage) onPageChange(currentPage + 1); + }; + + const handlePrevPage = () => { + if (currentPage > 1) onPageChange(currentPage - 1); + }; + + const pageNumbers = []; + for (let i = startPage; i <= endPage; i++) { + pageNumbers.push(i); + } + + return ( +
+ + + + {pageNumbers.map((page) => ( + + ))} + + + +
+ ); +}; diff --git a/app/adminpage/_components/RequestUserComponent.tsx b/app/adminpage/_components/RequestUserComponent.tsx new file mode 100644 index 0000000..1da170d --- /dev/null +++ b/app/adminpage/_components/RequestUserComponent.tsx @@ -0,0 +1,120 @@ +"use client"; + +import React from "react"; +import { Coins } from "lucide-react"; +import { useApproveCharge, useRejectCharge } from "@/hooks/useAdminManagement"; + +interface RequestUserProps { + contact: string; + orderId: string; + point: number; + username: string; + price: number; + requestAt: string; + productName: string; + onUpdate: () => void; + realName: string; +} + +export const RequestUserComponent = ({ + orderId, + username, + price, + requestAt, + productName, + onUpdate, + realName, +}: RequestUserProps) => { + const approveMutation = useApproveCharge(); + const rejectMutation = useRejectCharge(); + + const formatDateTime = (isoString: string) => { + if (!isoString) return "알 수 없음"; + try { + const date = new Date(isoString); + if (isNaN(date.getTime())) return "알 수 없음"; + + // KST 시간대 적용 (UTC+9) + const kstDate = new Date(date.getTime() + 9 * 60 * 60 * 1000); + + const year = kstDate.getUTCFullYear(); + const month = String(kstDate.getUTCMonth() + 1).padStart(2, "0"); + const day = String(kstDate.getUTCDate()).padStart(2, "0"); + const hours = String(kstDate.getUTCHours()).padStart(2, "0"); + const minutes = String(kstDate.getUTCMinutes()).padStart(2, "0"); + + return `${year}-${month}-${day} ${hours}시 ${minutes}분`; + } catch (error) { + return "알 수 없음"; + } + }; + + const handleApprove = async () => { + try { + await approveMutation.mutateAsync(orderId); + alert("충전 요청이 수락되었습니다."); + onUpdate(); + } catch (error) { + alert("수락 처리 중 오류가 발생했습니다."); + } + }; + + const handleReject = async () => { + try { + await rejectMutation.mutateAsync(orderId); + alert("충전 요청이 거절되었습니다."); + onUpdate(); + } catch (error) { + alert("거절 처리 중 오류가 발생했습니다."); + } + }; + + return ( +
+
+
+ 닉네임 : {username} +
+
+ 입금자명 : {realName} +
+
+ 요청시각 : {formatDateTime(requestAt)} +
+
주문번호 : {orderId}
+
+ +
+
+
+ + {productName} +
+
+ 가격 : + + {price}원 + +
+
+ +
+ + +
+
+
+ ); +}; diff --git a/app/adminpage/_components/ScreenAdminLoginPage.tsx b/app/adminpage/_components/ScreenAdminLoginPage.tsx new file mode 100644 index 0000000..12c5fe7 --- /dev/null +++ b/app/adminpage/_components/ScreenAdminLoginPage.tsx @@ -0,0 +1,141 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; + +import React, { useState } from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useAdminLogin, useAdminInfo } from "@/hooks/useAdminAuth"; + +export default function ScreenAdminLoginPage() { + const [passwordVisible, setPasswordVisible] = useState(false); + const [formData, setFormData] = useState({ accountId: "", password: "" }); + const router = useRouter(); + + const loginMutation = useAdminLogin(); + const { refetch: fetchAdminInfo } = useAdminInfo(); + + const togglePasswordVisibility = () => { + setPasswordVisible(!passwordVisible); + }; + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + try { + const data = await loginMutation.mutateAsync(formData); + + if (data.status >= 200 && data.status < 300) { + await fetchAdminInfo(); + router.push("/adminpage/myPage"); + } else { + alert("로그인 실패: " + (data.message || "알 수 없는 오류")); + } + } catch (error: any) { + console.error("로그인 중 에러 발생:", error); + alert("로그인 중 오류가 발생했습니다."); + } + }; + + return ( +
+
+
+ Logo +
+

Partners Page

+ +
+ + +
+ +
+ +
+ +
+ + +
+ +
+
+ + 가입하기 + + | + + ID/비밀번호 찾기 + +
+ + 문의하기 + +
+
+
+ ); +} diff --git a/app/adminpage/_components/SearchUserComponent.tsx b/app/adminpage/_components/SearchUserComponent.tsx new file mode 100644 index 0000000..306fe3f --- /dev/null +++ b/app/adminpage/_components/SearchUserComponent.tsx @@ -0,0 +1,35 @@ +"use client"; + +import React from "react"; +import { useRouter } from "next/navigation"; + +interface SearchUserProps { + nickname: string; + email: string; + uuid: string; +} + +export const SearchUserComponent = ({ nickname, email, uuid }: SearchUserProps) => { + const router = useRouter(); + + return ( +
+
+ Nickname : + {nickname} +
+
+
+ E-mail : + {email} +
+ +
+
+ ); +}; diff --git a/app/adminpage/layout.tsx b/app/adminpage/layout.tsx new file mode 100644 index 0000000..06689d9 --- /dev/null +++ b/app/adminpage/layout.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useAdminInfo } from "@/hooks/useAdminAuth"; +import { useRouter, usePathname } from "next/navigation"; +import { useEffect } from "react"; + +export default function AdminLayout({ children }: { children: React.ReactNode }) { + const { data: adminInfo, isLoading, isError } = useAdminInfo(); + const router = useRouter(); + const pathname = usePathname(); + + useEffect(() => { + // If we are not on the login or register page and not loading, check for auth + if (!isLoading && !adminInfo && pathname !== "/adminpage" && pathname !== "/adminpage/register") { + router.push("/adminpage"); + } + }, [adminInfo, isLoading, pathname, router]); + + // Optionally show a loading spinner while checking auth + if (isLoading && pathname !== "/adminpage" && pathname !== "/adminpage/register") { + return ( +
+
+
+ ); + } + + return <>{children}; +} diff --git a/app/adminpage/myPage/_components/ScreenAdminMyPage.tsx b/app/adminpage/myPage/_components/ScreenAdminMyPage.tsx new file mode 100644 index 0000000..3d06fe1 --- /dev/null +++ b/app/adminpage/myPage/_components/ScreenAdminMyPage.tsx @@ -0,0 +1,79 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { AdminHeader } from "../../_components/AdminHeader"; +import { + AdminMyPageMain, + MasterManageComponent, + OperatorManageComponent, + AdminTeamManage, +} from "../../_components/ManagementComponents"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; +import { useRouter, useSearchParams } from "next/navigation"; +import AdminNotAllowed from "../../_components/AdminNotAllowed"; + +export default function ScreenAdminMyPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const tab = searchParams.get("tab"); + const [adminSelect, setAdminSelect] = useState(tab || "Main"); + const { data: adminResponse, isLoading } = useAdminInfo(); + + if (isLoading) { + return ( +
+
+
+ ); + } + + const adminData = adminResponse?.data; + + if (!adminData) return null; + + // Handle specific roles + if (adminData.role === "ROLE_SEMI_OPERATOR") { + return ( +
+ + +
+ ); + } + + if (adminData.role === "ROLE_SEMI_ADMIN") { + router.replace("/adminpage/webmail-check"); + return null; + } + + return ( +
+ + +
+ {adminSelect === "Main" && } + + {adminSelect === "가입자관리" && ( +
+ {adminData.role === "ROLE_OPERATOR" && } + {adminData.role === "ROLE_ADMIN" && } +
+ )} + + {adminSelect === "팀관리" && } +
+
+ ); +} diff --git a/app/adminpage/myPage/event/_components/ScreenAdminEventPage.tsx b/app/adminpage/myPage/event/_components/ScreenAdminEventPage.tsx new file mode 100644 index 0000000..51635a2 --- /dev/null +++ b/app/adminpage/myPage/event/_components/ScreenAdminEventPage.tsx @@ -0,0 +1,82 @@ +"use client"; + +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import { AdminHeader } from "../../../_components/AdminHeader"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; + +export default function ScreenAdminEventPage() { + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const router = useRouter(); + const { data: adminResponse } = useAdminInfo(); + const adminData = adminResponse?.data; + + // Mock heart logic if needed in future + const [remainingEvents] = useState(3); + + return ( +
+ + +
+
+
router.push("/adminpage/myPage/event/free-match")} + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_1px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 매칭 기회 제공 이벤트 +
+
+ 이벤트 1회당 이성뽑기 1회 상한 존재 +
+
+ +
router.push("/adminpage/myPage/event/discount")} + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_1px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 포인트 충전 할인 이벤트 +
+
+ 40%의 할인 상한 존재, 최대 2시간 상한 존재 +
+
+
+ +
+
router.push("/adminpage/myPage/event/list")} + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_1px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 이벤트 예약목록 및 취소 +
+
+ 두 이벤트 예약 리스트 통합 예약 내역 및 취소 +
+
+ +
router.push("/adminpage/myPage/event/history")} + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_1px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 이벤트 히스토리 +
+
+ 지금까지 진행한 과거 이벤트의 히스토리 +
+
+
+
+
+ ); +} diff --git a/app/adminpage/myPage/event/discount/_components/ScreenEventDiscountPage.tsx b/app/adminpage/myPage/event/discount/_components/ScreenEventDiscountPage.tsx new file mode 100644 index 0000000..ddfb353 --- /dev/null +++ b/app/adminpage/myPage/event/discount/_components/ScreenEventDiscountPage.tsx @@ -0,0 +1,167 @@ +"use client"; + +import React, { useState } from "react"; +import { TriangleAlert } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { AdminHeader } from "../../../../_components/AdminHeader"; +import { AdminDropdown } from "../../../../_components/AdminDropdown"; +import { AdminDateSelector } from "../../../../_components/AdminDateSelector"; +import { AdminTimeRow } from "../../../../_components/AdminTimeRow"; +import { AdminWarningModal } from "../../../../_components/AdminWarningModal"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; + +const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0")); +const minutes = Array.from({ length: 6 }, (_, i) => + String(i * 10).padStart(2, "0"), +); +const percentages = Array.from({ length: 4 }, (_, i) => String((i + 1) * 10)); + +export default function ScreenEventDiscountPage() { + const router = useRouter(); + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const [selectedDate, setSelectedDate] = useState("오늘"); + const [startTime, setStartTime] = useState("선택"); + const [startMinutes, setStartMinutes] = useState("선택"); + const [endTime, setEndTime] = useState("선택"); + const [endMinutes, setEndMinutes] = useState("선택"); + const [selectedDiscount, setSelectedDiscount] = useState("선택"); + const [showModal, setShowModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + const { data: adminResponse } = useAdminInfo(); + const adminData = adminResponse?.data; + + const getDurationText = () => { + const sH = parseInt(startTime); + const sM = parseInt(startMinutes); + const eH = parseInt(endTime); + const eM = parseInt(endMinutes); + + if (isNaN(sH) || isNaN(sM) || isNaN(eH) || isNaN(eM)) return "0시간"; + + const diff = eH * 60 + eM - (sH * 60 + sM); + if (diff <= 0) return "0시간"; + + const h = Math.floor(diff / 60); + const m = diff % 60; + return m > 0 ? `${h}시간 ${m}분` : `${h}시간`; + }; + + const handleConfirm = () => { + const sH = parseInt(startTime); + const sM = parseInt(startMinutes); + const eH = parseInt(endTime); + const eM = parseInt(endMinutes); + + if (isNaN(sH) || isNaN(sM) || isNaN(eH) || isNaN(eM)) { + alert("시간을 올바르게 선택해주세요."); + return; + } + if (selectedDiscount === "선택") { + alert("할인율을 선택해주세요."); + return; + } + + const startTotal = sH * 60 + sM; + const endTotal = eH * 60 + eM; + + if (startTotal >= endTotal) { + setErrorMessage( + <> + 이벤트 시작 시간이 종료 시간보다
같거나 늦을 수 없습니다. + , + ); + setShowModal(true); + return; + } + + // API Call logic + router.push("/adminpage/myPage/event/registercomplete"); + }; + + return ( +
+ + +
+
+
+ 포인트 충전 할인 이벤트 예약 +
+
+ +
+ + +
+
+ 이벤트 시간설정(최대 2시간) +
+ + + + + +
+
+ 교내 가입자 전원에게{" "} + + {getDurationText()}동안 최대 3번 구매 가능한 + +
+
+ +
+ %의 포인트 충전 할인을 + 제공합니다. +
+
+
+ + +
+
+
+ + setShowModal(false)} + message={errorMessage} + /> +
+ ); +} diff --git a/app/adminpage/myPage/event/discount/page.tsx b/app/adminpage/myPage/event/discount/page.tsx new file mode 100644 index 0000000..cd6c5f0 --- /dev/null +++ b/app/adminpage/myPage/event/discount/page.tsx @@ -0,0 +1,5 @@ +import ScreenEventDiscountPage from "./_components/ScreenEventDiscountPage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/event/free-match/_components/EventStatusCard.tsx b/app/adminpage/myPage/event/free-match/_components/EventStatusCard.tsx new file mode 100644 index 0000000..1638be8 --- /dev/null +++ b/app/adminpage/myPage/event/free-match/_components/EventStatusCard.tsx @@ -0,0 +1,34 @@ +"use client"; + +import React from "react"; +import { Heart } from "lucide-react"; + +interface EventStatusCardProps { + remainingEvents: number; +} + +export const EventStatusCard = ({ remainingEvents }: EventStatusCardProps) => { + return ( +
+
+ 매칭 기회 제공 이벤트 예약 +
+
+ 현재 잔여 이벤트 횟수는 {remainingEvents}회입니다. +
+
+ {[...Array(4)].map((_, i) => ( + + ))} +
+
+ ); +}; diff --git a/app/adminpage/myPage/event/free-match/_components/ScreenEventFreeMatchPage.tsx b/app/adminpage/myPage/event/free-match/_components/ScreenEventFreeMatchPage.tsx new file mode 100644 index 0000000..3beab61 --- /dev/null +++ b/app/adminpage/myPage/event/free-match/_components/ScreenEventFreeMatchPage.tsx @@ -0,0 +1,144 @@ +"use client"; + +import React, { useState } from "react"; +import { Heart, TriangleAlert } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { AdminHeader } from "../../../../_components/AdminHeader"; +import { AdminDateSelector } from "../../../../_components/AdminDateSelector"; +import { AdminTimeRow } from "../../../../_components/AdminTimeRow"; +import { AdminWarningModal } from "../../../../_components/AdminWarningModal"; +import { EventStatusCard } from "./EventStatusCard"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; + +const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0")); +const minutes = Array.from({ length: 6 }, (_, i) => + String(i * 10).padStart(2, "0"), +); + +export default function ScreenEventFreeMatchPage() { + const router = useRouter(); + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const [selectedDate, setSelectedDate] = useState("오늘"); + const [startTime, setStartTime] = useState("선택"); + const [startMinutes, setStartMinutes] = useState("선택"); + const [endTime, setEndTime] = useState("선택"); + const [endMinutes, setEndMinutes] = useState("선택"); + const [showModal, setShowModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + const { data: adminResponse } = useAdminInfo(); + const adminData = adminResponse?.data; + + const [remainingEvents] = useState(3); + + const handleConfirm = () => { + const sH = parseInt(startTime); + const sM = parseInt(startMinutes); + const eH = parseInt(endTime); + const eM = parseInt(endMinutes); + + if (isNaN(sH) || isNaN(sM) || isNaN(eH) || isNaN(eM)) { + alert("시간을 올바르게 선택해주세요."); + return; + } + + const startTotal = sH * 60 + sM; + const endTotal = eH * 60 + eM; + + if (selectedDate === "오늘") { + const now = new Date(); + if (startTotal < now.getHours() * 60 + now.getMinutes()) { + setErrorMessage( + <> + 이벤트 시작 시각은 현재 시각보다
이전으로 설정할 수 없습니다. + , + ); + setShowModal(true); + return; + } + } + + if (startTotal >= endTotal) { + setErrorMessage( + <> + 이벤트 시작 시간이 종료 시간보다
같거나 늦을 수 없습니다. + , + ); + setShowModal(true); + return; + } + + // API Call logic here (using fetch or axios) + // For now, redirect to completion page + router.push("/adminpage/myPage/event/registercomplete"); + }; + + return ( +
+ + +
+ + +
+ + +
+
+ 이벤트 시간설정 +
+ + + + +
+ +
+
+ 교내 가입자 전원에게{" "} + 매칭 1회의 기회를 + 제공합니다. +
+ +
+
+
+ + setShowModal(false)} + message={errorMessage} + /> +
+ ); +} diff --git a/app/adminpage/myPage/event/free-match/page.tsx b/app/adminpage/myPage/event/free-match/page.tsx new file mode 100644 index 0000000..fa8084f --- /dev/null +++ b/app/adminpage/myPage/event/free-match/page.tsx @@ -0,0 +1,5 @@ +import ScreenEventFreeMatchPage from "./_components/ScreenEventFreeMatchPage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/event/history/_components/ScreenEventHistoryPage.tsx b/app/adminpage/myPage/event/history/_components/ScreenEventHistoryPage.tsx new file mode 100644 index 0000000..c8728f7 --- /dev/null +++ b/app/adminpage/myPage/event/history/_components/ScreenEventHistoryPage.tsx @@ -0,0 +1,69 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; + +import React, { useState } from "react"; +import { AdminHeader } from "../../../../_components/AdminHeader"; +import { AdminListItem } from "../../../../_components/AdminListItem"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; +import { useEventList } from "@/hooks/useAdminManagement"; +import { formatDateTime } from "@/utils/dateFormatter"; + +export default function ScreenEventHistoryPage() { + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const { data: adminResponse } = useAdminInfo(); + const { data: eventResponse } = useEventList("HISTORY"); + + const adminData = adminResponse?.data; + const eventList = eventResponse?.data || []; + + return ( +
+ + +
+
+
+ 이벤트 히스토리 +
+
+ 진행한 이벤트의 히스토리 +
+ +
+ {eventList.length > 0 ? ( + eventList.map((item: any) => { + const start = formatDateTime(item.start); + const end = formatDateTime(item.end); + return ( + + ); + }) + ) : ( +
+ 이벤트 히스토리가 없습니다. +
+ )} +
+
+
+
+ ); +} diff --git a/app/adminpage/myPage/event/history/page.tsx b/app/adminpage/myPage/event/history/page.tsx new file mode 100644 index 0000000..29ed4f8 --- /dev/null +++ b/app/adminpage/myPage/event/history/page.tsx @@ -0,0 +1,5 @@ +import ScreenEventHistoryPage from "./_components/ScreenEventHistoryPage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/event/list/_components/ScreenEventListPage.tsx b/app/adminpage/myPage/event/list/_components/ScreenEventListPage.tsx new file mode 100644 index 0000000..57db735 --- /dev/null +++ b/app/adminpage/myPage/event/list/_components/ScreenEventListPage.tsx @@ -0,0 +1,84 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; + +import React, { useState } from "react"; +import { AdminHeader } from "../../../../_components/AdminHeader"; +import { AdminListItem } from "../../../../_components/AdminListItem"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; +import { useEventList, useDeleteEvent } from "@/hooks/useAdminManagement"; +import { formatDateTime } from "@/utils/dateFormatter"; + +export default function ScreenEventListPage() { + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const { data: adminResponse } = useAdminInfo(); + const { data: eventResponse, refetch } = useEventList("RESERVATION"); + const deleteEventMutation = useDeleteEvent(); + + const adminData = adminResponse?.data; + const eventList = eventResponse?.data || []; + + const handleCancel = async (id: number) => { + if (confirm("이 이벤트를 정말로 취소하시겠어요?")) { + try { + await deleteEventMutation.mutateAsync(id); + alert("이벤트가 취소되었습니다."); + refetch(); + } catch (error) { + alert("이벤트 취소 중 오류가 발생했습니다."); + } + } + }; + + return ( +
+ + +
+
+
+ 이벤트 예약목록 및 취소 +
+
+ 두 이벤트 예약 리스트 통합 예약 내역 및 취소 +
+ +
+ {eventList.length > 0 ? ( + eventList.map((item: any) => { + const start = formatDateTime(item.start); + const end = formatDateTime(item.end); + return ( + handleCancel(item.id)} + cancelButtonText="이벤트 취소" + /> + ); + }) + ) : ( +
+ 예약된 이벤트가 없습니다. +
+ )} +
+
+
+
+ ); +} diff --git a/app/adminpage/myPage/event/list/page.tsx b/app/adminpage/myPage/event/list/page.tsx new file mode 100644 index 0000000..2658f63 --- /dev/null +++ b/app/adminpage/myPage/event/list/page.tsx @@ -0,0 +1,5 @@ +import ScreenEventListPage from "./_components/ScreenEventListPage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/event/page.tsx b/app/adminpage/myPage/event/page.tsx new file mode 100644 index 0000000..684ee20 --- /dev/null +++ b/app/adminpage/myPage/event/page.tsx @@ -0,0 +1,5 @@ +import ScreenAdminEventPage from "./_components/ScreenAdminEventPage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/event/registercomplete/_components/ScreenEventRegisterCompletePage.tsx b/app/adminpage/myPage/event/registercomplete/_components/ScreenEventRegisterCompletePage.tsx new file mode 100644 index 0000000..9bf577e --- /dev/null +++ b/app/adminpage/myPage/event/registercomplete/_components/ScreenEventRegisterCompletePage.tsx @@ -0,0 +1,69 @@ +"use client"; + +import React, { useState } from "react"; +import { Heart } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { AdminHeader } from "../../../../_components/AdminHeader"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; + +export default function ScreenEventRegisterCompletePage() { + const router = useRouter(); + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const { data: adminResponse } = useAdminInfo(); + const adminData = adminResponse?.data; + + return ( +
+ + +
+
+
+ 이벤트 등록 완료 안내 +
+ +
+ 해당 이벤트 예약 내역은 좌측 하단 이벤트 예약목록에서 열람하거나{" "} + 취소할 수 + 있습니다. +
+ 이벤트 사유를 공지하고 싶다면 우측 하단의 공지사항 등록을 + 이용하십시오. +
+
+ +
+
router.push("/adminpage/myPage/event/list")} + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 이벤트 예약목록 및 취소 +
+
+ 두 이벤트 예약 리스트 통합 예약 내역 및 취소 +
+
+ +
router.push("/adminpage/myPage/notice/reservation")} + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 공지사항 등록 +
+
+ 이벤트 사유를 공지하고 싶으신가요? +
+
+
+
+
+ ); +} diff --git a/app/adminpage/myPage/event/registercomplete/page.tsx b/app/adminpage/myPage/event/registercomplete/page.tsx new file mode 100644 index 0000000..7358243 --- /dev/null +++ b/app/adminpage/myPage/event/registercomplete/page.tsx @@ -0,0 +1,5 @@ +import ScreenEventRegisterCompletePage from "./_components/ScreenEventRegisterCompletePage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/notice/_components/ScreenAdminNoticeMainPage.tsx b/app/adminpage/myPage/notice/_components/ScreenAdminNoticeMainPage.tsx new file mode 100644 index 0000000..68238c9 --- /dev/null +++ b/app/adminpage/myPage/notice/_components/ScreenAdminNoticeMainPage.tsx @@ -0,0 +1,65 @@ +"use client"; + +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import { AdminHeader } from "../../../_components/AdminHeader"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; + +export default function ScreenAdminNoticeMainPage() { + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const router = useRouter(); + const { data: adminResponse } = useAdminInfo(); + const adminData = adminResponse?.data; + + return ( +
+ + +
+
router.push("/adminpage/myPage/notice/reservation")} + className="flex w-full cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_1px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 공지사항 예약 +
+
+ 공지사항은 예약제 +
+
+ +
+
router.push("/adminpage/myPage/notice/list")} + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_1px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 공지사항 예약목록 및 취소 +
+
+ 전체 공지사항 예약 내역 및 취소 +
+
+ +
router.push("/adminpage/myPage/notice/history")} + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_1px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 공지사항 히스토리 +
+
+ 지금까지 진행한 과거 공지사항 히스토리 +
+
+
+
+
+ ); +} diff --git a/app/adminpage/myPage/notice/complete/_components/ScreenNoticeRegisterCompletePage.tsx b/app/adminpage/myPage/notice/complete/_components/ScreenNoticeRegisterCompletePage.tsx new file mode 100644 index 0000000..47a3a19 --- /dev/null +++ b/app/adminpage/myPage/notice/complete/_components/ScreenNoticeRegisterCompletePage.tsx @@ -0,0 +1,68 @@ +"use client"; + +import React, { useState } from "react"; +import { Heart } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { AdminHeader } from "../../../../_components/AdminHeader"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; + +export default function ScreenNoticeRegisterCompletePage() { + const router = useRouter(); + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const { data: adminResponse } = useAdminInfo(); + const adminData = adminResponse?.data; + + return ( +
+ + +
+
+
+ 공지사항 예약 완료 안내 +
+ +
+ 해당 공지사항 예약 내역은 좌측 하단 공지사항 예약목록에서 열람하거나{" "} + 취소할 수 + 있습니다. +
+ 이벤트 예약을 잊으셨다면 우측 하단의 이벤트 예약을 이용하십시오. +
+
+ +
+
router.push("/adminpage/myPage/notice/list")} + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 공지사항 예약목록 및 취소 +
+
+ 전체 공지사항 예약 내역 및 취소 +
+
+ +
router.push("/adminpage/myPage/event/page")} // or event main + className="flex flex-1 cursor-pointer flex-col gap-2 rounded-[24px] border border-white/30 bg-white p-6 pt-[26px] shadow-[1px_1px_20px_rgba(196,196,196,0.3)] transition-shadow hover:shadow-lg" + > +
+ 이벤트 예약 +
+
+ 이벤트를 아직 예약하지 않으셨나요? +
+
+
+
+
+ ); +} diff --git a/app/adminpage/myPage/notice/complete/page.tsx b/app/adminpage/myPage/notice/complete/page.tsx new file mode 100644 index 0000000..711eea9 --- /dev/null +++ b/app/adminpage/myPage/notice/complete/page.tsx @@ -0,0 +1,5 @@ +import ScreenNoticeRegisterCompletePage from "./_components/ScreenNoticeRegisterCompletePage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/notice/history/_components/ScreenNoticeHistoryPage.tsx b/app/adminpage/myPage/notice/history/_components/ScreenNoticeHistoryPage.tsx new file mode 100644 index 0000000..e32d5bf --- /dev/null +++ b/app/adminpage/myPage/notice/history/_components/ScreenNoticeHistoryPage.tsx @@ -0,0 +1,64 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; + +import React, { useState } from "react"; +import { AdminHeader } from "../../../../_components/AdminHeader"; +import { AdminListItem } from "../../../../_components/AdminListItem"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; +import { useNoticeList } from "@/hooks/useAdminManagement"; +import { formatDateTime } from "@/utils/dateFormatter"; + +export default function ScreenNoticeHistoryPage() { + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const { data: adminResponse } = useAdminInfo(); + const { data: noticeResponse } = useNoticeList("HISTORY"); + + const adminData = adminResponse?.data; + const noticeList = noticeResponse?.data || []; + + return ( +
+ + +
+
+
+ 공지사항 히스토리 +
+
+ 진행한 공지사항의 히스토리 +
+ +
+ {noticeList.length > 0 ? ( + noticeList.map((item: any) => { + const posted = formatDateTime(item.postedAt); + const closed = formatDateTime(item.closedAt); + return ( + + ); + }) + ) : ( +
+ 공지사항 히스토리가 없습니다. +
+ )} +
+
+
+
+ ); +} diff --git a/app/adminpage/myPage/notice/history/page.tsx b/app/adminpage/myPage/notice/history/page.tsx new file mode 100644 index 0000000..c15128f --- /dev/null +++ b/app/adminpage/myPage/notice/history/page.tsx @@ -0,0 +1,5 @@ +import ScreenNoticeHistoryPage from "./_components/ScreenNoticeHistoryPage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/notice/list/_components/ScreenNoticeListPage.tsx b/app/adminpage/myPage/notice/list/_components/ScreenNoticeListPage.tsx new file mode 100644 index 0000000..91fd095 --- /dev/null +++ b/app/adminpage/myPage/notice/list/_components/ScreenNoticeListPage.tsx @@ -0,0 +1,79 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; + +import React, { useState } from "react"; +import { AdminHeader } from "../../../../_components/AdminHeader"; +import { AdminListItem } from "../../../../_components/AdminListItem"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; +import { useNoticeList, useDeleteNotice } from "@/hooks/useAdminManagement"; +import { formatDateTime } from "@/utils/dateFormatter"; + +export default function ScreenNoticeListPage() { + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const { data: adminResponse } = useAdminInfo(); + const { data: noticeResponse, refetch } = useNoticeList("RESERVATION"); + const deleteNoticeMutation = useDeleteNotice(); + + const adminData = adminResponse?.data; + const noticeList = noticeResponse?.data || []; + + const handleCancel = async (id: number) => { + if (confirm("이 공지를 정말로 취소하시겠어요?")) { + try { + await deleteNoticeMutation.mutateAsync(id); + alert("공지가 삭제되었습니다."); + refetch(); + } catch (error) { + alert("공지 삭제 중 오류가 발생했습니다."); + } + } + }; + + return ( +
+ + +
+
+
+ 공지사항 예약목록 및 취소 +
+
+ 전체 공지사항 예약 내역 및 취소 +
+ +
+ {noticeList.length > 0 ? ( + noticeList.map((item: any) => { + const posted = formatDateTime(item.postedAt); + const closed = formatDateTime(item.closedAt); + return ( + handleCancel(item.id)} + cancelButtonText="공지 취소" + /> + ); + }) + ) : ( +
+ 예약된 공지사항이 없습니다. +
+ )} +
+
+
+
+ ); +} diff --git a/app/adminpage/myPage/notice/list/page.tsx b/app/adminpage/myPage/notice/list/page.tsx new file mode 100644 index 0000000..7c2372a --- /dev/null +++ b/app/adminpage/myPage/notice/list/page.tsx @@ -0,0 +1,5 @@ +import ScreenNoticeListPage from "./_components/ScreenNoticeListPage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/notice/page.tsx b/app/adminpage/myPage/notice/page.tsx new file mode 100644 index 0000000..c191974 --- /dev/null +++ b/app/adminpage/myPage/notice/page.tsx @@ -0,0 +1,5 @@ +import ScreenAdminNoticeMainPage from "./_components/ScreenAdminNoticeMainPage"; + +export default function Page() { + return ; +} diff --git a/app/adminpage/myPage/notice/reservation/_components/ScreenNoticeReservationPage.tsx b/app/adminpage/myPage/notice/reservation/_components/ScreenNoticeReservationPage.tsx new file mode 100644 index 0000000..a222892 --- /dev/null +++ b/app/adminpage/myPage/notice/reservation/_components/ScreenNoticeReservationPage.tsx @@ -0,0 +1,224 @@ +"use client"; + +import React, { useState } from "react"; +import { TriangleAlert } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { AdminHeader } from "../../../../_components/AdminHeader"; +import { AdminDropdown } from "../../../../_components/AdminDropdown"; +import { AdminDateSelector } from "../../../../_components/AdminDateSelector"; +import { AdminTimeRow } from "../../../../_components/AdminTimeRow"; +import { AdminWarningModal } from "../../../../_components/AdminWarningModal"; +import { useAdminInfo } from "@/hooks/useAdminAuth"; +import { useRegisterNotice } from "@/hooks/useAdminManagement"; + +const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0")); +const minutes = Array.from({ length: 12 }, (_, i) => + String(i * 5).padStart(2, "0"), +); // 5 min interval for better precision? Old used 10 but let's stick to 10 if that's standard. +// Actually standard was minutes = Array.from({ length: 6 }, (_, i) => String(i * 10).padStart(2, '0')); +const stdMinutes = Array.from({ length: 6 }, (_, i) => + String(i * 10).padStart(2, "0"), +); + +export default function ScreenNoticeReservationPage() { + const router = useRouter(); + const [adminSelect, setAdminSelect] = useState("가입자관리"); + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + const [selectedDate, setSelectedDate] = useState("오늘"); + const [startTime, setStartTime] = useState("선택"); + const [startMinutes, setStartMinutes] = useState("선택"); + const [endTime, setEndTime] = useState("선택"); + const [endMinutes, setEndMinutes] = useState("선택"); + const [showModal, setShowModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + const { data: adminResponse } = useAdminInfo(); + const adminData = adminResponse?.data; + const registerNoticeMutation = useRegisterNotice(); + + const handleConfirm = async () => { + if (!title.trim() || !content.trim()) { + setErrorMessage( + <> + 공지사항 제목과 내용을
모두 입력해주세요. + , + ); + setShowModal(true); + return; + } + + const sH = parseInt(startTime); + const sM = parseInt(startMinutes); + const eH = parseInt(endTime); + const eM = parseInt(endMinutes); + + if (isNaN(sH) || isNaN(sM) || isNaN(eH) || isNaN(eM)) { + alert("시간을 올바르게 선택해주세요."); + return; + } + + const startTotal = sH * 60 + sM; + const endTotal = eH * 60 + eM; + + if (selectedDate === "오늘") { + const now = new Date(); + const currentTotal = now.getHours() * 60 + now.getMinutes(); + if (startTotal < currentTotal + 10) { + setErrorMessage( + <> + 공지사항 시작 시각은 현재 시각보다
최소 10분 이후로 설정해야 + 합니다. + , + ); + setShowModal(true); + return; + } + } + + if (startTotal >= endTotal) { + setErrorMessage( + <> + 시작 시간이 종료 시간보다
같거나 늦을 수 없습니다. + , + ); + setShowModal(true); + return; + } + + const today = new Date(); + const selectedDateObject = new Date(today); + if (selectedDate === "내일") + selectedDateObject.setDate(today.getDate() + 1); + else if (selectedDate === "모레") + selectedDateObject.setDate(today.getDate() + 2); + + const formatTimePart = (h: number, m: number) => { + const d = new Date(selectedDateObject); + d.setHours(h, m, 0, 0); + // Adjust to KST (UTC+9) + const kst = new Date(d.getTime() + 9 * 60 * 60 * 1000); + return kst.toISOString().slice(0, 19); + }; + + const payload = { + title, + content, + postedAt: formatTimePart(sH, sM), + closedAt: formatTimePart(eH, eM), + }; + + try { + await registerNoticeMutation.mutateAsync(payload); + router.push("/adminpage/myPage/notice/complete"); + } catch (error) { + setErrorMessage( + <> + 공지사항 등록 중 오류가 발생했습니다.
다시 시도해주세요. + , + ); + setShowModal(true); + } + }; + + return ( +
+ + +
+
+
+
+
+ 공지사항 제목 등록 +
+
+ 아래에 공지사항 제목을 입력해주세요. +
+ setTitle(e.target.value)} + placeholder="제목을 입력하세요" + className="w-full rounded-md border border-[#979797] p-3 text-2xl font-medium shadow-inner outline-none placeholder:text-[#b3b3b3] focus:ring-2 focus:ring-[#ff775e]" + /> +
+ +
+
+ 내용 등록 +
+
+ 아래에 공지사항 내용을 입력하세요. +
+