diff --git a/Mocks/reservationStatus.mock.ts b/Mocks/reservationStatus.mock.ts
new file mode 100644
index 0000000..36ba65e
--- /dev/null
+++ b/Mocks/reservationStatus.mock.ts
@@ -0,0 +1,44 @@
+import { Reservation } from "@/feature/reservationStatus/types/reservation";
+
+export const mockReservations: Reservation[] = [
+ {
+ id: "선기훈",
+ title: "함께 배우는 플로잉 댄스",
+ date: "2025-12-01T11:00:00",
+ price: 10000,
+ people: 1,
+ status: "pending",
+ },
+ {
+ id: "정만철",
+ title: "함께 배우는 플로잉 댄스",
+ date: "2025-02-01T18:00:00",
+ price: 10000,
+ people: 1,
+ status: "pending",
+ },
+ {
+ id: "오승환",
+ title: "내 강아지 인생 사진 찍어주기",
+ date: "2026-02-11T13:00:00",
+ price: 35000,
+ people: 1,
+ status: "canceled",
+ },
+ {
+ id: "이대호",
+ title: "이색 액티비티 체험",
+ date: "2026-01-10T10:00:00",
+ price: 60000,
+ people: 3,
+ status: "declined",
+ },
+ {
+ id: "양의지",
+ title: "별과 함께하는 북촌 체험",
+ date: "2026-01-14T15:00:00",
+ price: 40000,
+ people: 2,
+ status: "completed",
+ },
+];
diff --git a/feature/Experience/schedule/DateInput.tsx b/feature/Experience/schedule/DateInput.tsx
index e460ac4..f4249d6 100644
--- a/feature/Experience/schedule/DateInput.tsx
+++ b/feature/Experience/schedule/DateInput.tsx
@@ -2,7 +2,7 @@
import { useState, useRef } from "react";
import CalendarIcon from "@/public/icon_calendar.svg";
import DatePicker from "./Datepicker";
-import { Input } from "@/components/common/input";
+import { Input } from "@/components/input/Input";
import { useClickOutside } from "@/hooks/useClickOutside";
interface Props {
diff --git a/feature/MyInfo/MyInfoClient.tsx b/feature/MyInfo/MyInfoClient.tsx
index c2ce4dc..72f584e 100644
--- a/feature/MyInfo/MyInfoClient.tsx
+++ b/feature/MyInfo/MyInfoClient.tsx
@@ -6,7 +6,7 @@ import Sidebar from "@/feature/MyInfo/Sidebar";
import MyInfoView from "@/feature/MyInfo/MyInfoView";
import ReservationView from "@/feature/MyInfo/ReservationView";
import MyExperinenceView from "@/feature/MyInfo/MyExperinenceView";
-import ReservaionStatusView from "@/feature/MyInfo/ReservaionStatusView";
+import ReservationStatusPage from "@/feature/reservationStatus/ReservationStatusPage";
import type { SidebarMenu } from "@/types/SidebarTypes";
const DEFAULT_MENU: SidebarMenu = "MY_INFO";
@@ -15,8 +15,7 @@ export default function MyInfoClient() {
const searchParams = useSearchParams();
const router = useRouter();
- const activeMenu =
- (searchParams.get("menu") as SidebarMenu) ?? DEFAULT_MENU;
+ const activeMenu = (searchParams.get("menu") as SidebarMenu) ?? DEFAULT_MENU;
const handleMenuChange = (menu: SidebarMenu) => {
router.push(`/myinfo?menu=${menu}`);
@@ -32,9 +31,7 @@ export default function MyInfoClient() {
{activeMenu === "MY_INFO" && }
{activeMenu === "RESERVATIONS" && }
{activeMenu === "MY_EXPERIENCE" && }
- {activeMenu === "RESERVATION_STATUS" && (
-
- )}
+ {activeMenu === "RESERVATION_STATUS" && }
diff --git a/feature/MyInfo/ReservaionStatusView.tsx b/feature/MyInfo/ReservaionStatusView.tsx
deleted file mode 100644
index 65987cf..0000000
--- a/feature/MyInfo/ReservaionStatusView.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-"use client";
-
-
-type Props = {
- mode?: "view" | "edit";
- onEdit?: () => void;
- onCancel?: () => void;
-};
-
-export default function ReservaionStatusView({
- mode = "view",
- onEdit,
- onCancel,
-}: Props) {
-// const { data, loading, error } = useMyInfo();
-
-// if (loading) return
로딩중...
;
-// if (error || !data) return 에러
;
-
- return (
- 예약현황 들어갈곳
- //
- );
-}
diff --git a/feature/reservationStatus/Calendar/CalendarCell.tsx b/feature/reservationStatus/Calendar/CalendarCell.tsx
new file mode 100644
index 0000000..bb054f1
--- /dev/null
+++ b/feature/reservationStatus/Calendar/CalendarCell.tsx
@@ -0,0 +1,103 @@
+"use client";
+
+import { CalendarDate } from "../types/calendar";
+import { ReservationBadge } from "@/feature/reservationStatus/types/reservationStatus";
+import { cn } from "@/lib/utils/twmerge";
+import { StatusBadge } from "./StatusBadge";
+import { toDateKey } from "@/lib/utils/date";
+
+interface Props {
+ date: CalendarDate;
+ badges: ReservationBadge[];
+ isSelected: boolean;
+ onSelectDate: (
+ key: string,
+ position: { top: number; left: number; width: number; height: number }
+ ) => void;
+ containerRef: React.RefObject;
+}
+
+export default function CalendarCell({
+ date,
+ badges,
+ isSelected,
+ onSelectDate,
+ containerRef,
+}: Props) {
+ const handleClick = (e: React.MouseEvent) => {
+ if (!date.isCurrentMonth) return;
+ if (!containerRef.current) return;
+
+ const rect = e.currentTarget.getBoundingClientRect();
+ const containerRect = containerRef.current.getBoundingClientRect();
+
+ const dateKey = toDateKey(date.date);
+ const MODAL_VERTICAL_OFFSET = 70;
+
+ const position = {
+ top:
+ rect.top -
+ containerRect.top +
+ rect.height / 2 +
+ MODAL_VERTICAL_OFFSET,
+ left:
+ rect.left -
+ containerRect.left +
+ rect.width +
+ 12,
+ width: rect.width,
+ height: rect.height,
+ };
+
+
+
+ onSelectDate(dateKey, position);
+ };
+
+ return (
+
+ {/* 날짜 숫자 */}
+
+
+ {date.day}
+
+
+ {/* 예약 있음 표시 (점) */}
+ {badges.length > 0 && (
+
+ )}
+
+
+ {/* 상태 배지 */}
+
+ {badges.map((badge) => (
+
+ ))}
+
+
+ );
+}
diff --git a/feature/reservationStatus/Calendar/CalendarGrid.tsx b/feature/reservationStatus/Calendar/CalendarGrid.tsx
new file mode 100644
index 0000000..1a3eed3
--- /dev/null
+++ b/feature/reservationStatus/Calendar/CalendarGrid.tsx
@@ -0,0 +1,66 @@
+import { CalendarDate } from "@/feature/reservationStatus/types/calendar";
+import { ReservationMap } from "../utils/mapReservationsToCalendar";
+import { toDateKey } from "@/lib/utils/date";
+import CalendarCell from "./CalendarCell";
+import { useRef } from "react";
+
+const DAYS = [
+ { id: 0, label: "S" },
+ { id: 1, label: "M" },
+ { id: 2, label: "T" },
+ { id: 3, label: "W" },
+ { id: 4, label: "T" },
+ { id: 5, label: "F" },
+ { id: 6, label: "S" },
+];
+
+interface Props {
+ dates: CalendarDate[];
+ badgesMap: ReservationMap;
+ selectedDateKey: string | null;
+ onSelectDate: (
+ key: string,
+ position: { top: number; left: number; width: number; height: number }
+ ) => void;
+}
+
+
+export default function CalendarGrid({
+ dates,
+ badgesMap,
+ selectedDateKey,
+ onSelectDate,
+}: Props) {
+ const gridRef = useRef(null);
+
+ return (
+
+ {/* 요일 */}
+
+ {DAYS.map((day) => (
+
+ {day.label}
+
+ ))}
+
+
+ {/* 날짜 */}
+
+ {dates.map((date) => {
+ const dateKey = toDateKey(date.date);
+
+ return (
+
+ );
+ })}
+
+
+ );
+}
diff --git a/feature/reservationStatus/Calendar/CalendarHeader.tsx b/feature/reservationStatus/Calendar/CalendarHeader.tsx
new file mode 100644
index 0000000..5850f55
--- /dev/null
+++ b/feature/reservationStatus/Calendar/CalendarHeader.tsx
@@ -0,0 +1,33 @@
+// CalendarHeader.tsx
+import ArrowLeft from "@/public/icon_arrow_left.svg"
+import ArrowRight from "@/public/icon_arrow_right.svg"
+
+interface Props {
+ year: number;
+ month: number;
+ onPrev: () => void;
+ onNext: () => void;
+}
+
+export default function CalendarHeader({
+ year,
+ month,
+ onPrev,
+ onNext,
+}: Props) {
+ return (
+
+
+
+
+ {year}년 {month + 1}월
+
+
+
+
+ );
+}
diff --git a/feature/reservationStatus/Calendar/ReservationCalendar.tsx b/feature/reservationStatus/Calendar/ReservationCalendar.tsx
new file mode 100644
index 0000000..c4e093f
--- /dev/null
+++ b/feature/reservationStatus/Calendar/ReservationCalendar.tsx
@@ -0,0 +1,48 @@
+"use client";
+
+import { getMonthCalendar } from "@/feature/reservationStatus/types/calendar";
+import { mapReservationsToCalendar } from "../utils/mapReservationsToCalendar";
+import { Reservation } from "../types/reservation";
+import CalendarHeader from "./CalendarHeader";
+import CalendarGrid from "./CalendarGrid";
+import { useCalendar } from "@/hooks/useCalendar";
+
+interface Props {
+ reservations: Reservation[];
+ selectedDateKey: string | null;
+ onSelectDate: (key: string, position: { top: number; left: number }) => void;
+}
+
+export default function ReservationCalendar({
+ reservations,
+ selectedDateKey,
+ onSelectDate,
+}: Props) {
+ const today = new Date();
+
+ const { year, month, prevMonth, nextMonth } = useCalendar(
+ today.getFullYear(),
+ today.getMonth(),
+ );
+
+ const calendarDates = getMonthCalendar(year, month);
+ const reservationMap = mapReservationsToCalendar(reservations);
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/feature/reservationStatus/Calendar/StatusBadge.tsx b/feature/reservationStatus/Calendar/StatusBadge.tsx
new file mode 100644
index 0000000..009ee16
--- /dev/null
+++ b/feature/reservationStatus/Calendar/StatusBadge.tsx
@@ -0,0 +1,27 @@
+import {
+ ReservationStatusCode,
+ RESERVATION_STATUS_LABEL,
+} from "@/feature/reservationStatus/types/reservationStatus";
+
+interface StatusBadgeProps {
+ status: ReservationStatusCode;
+ count: number;
+}
+
+const STYLE_MAP: Record = {
+ pending: "bg-blue-50 text-blue-500",
+ confirmed: "bg-orange-50 text-orange-500",
+ declined: "bg-red-50 text-red-500",
+ canceled: "bg-gray-100 text-gray-400",
+ completed: "bg-gray-100 text-gray-500",
+};
+
+export function StatusBadge({ status, count }: StatusBadgeProps) {
+ return (
+
+ {RESERVATION_STATUS_LABEL[status]} {count}
+
+ );
+}
diff --git a/feature/reservationStatus/Category.tsx b/feature/reservationStatus/Category.tsx
new file mode 100644
index 0000000..c64c1f5
--- /dev/null
+++ b/feature/reservationStatus/Category.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+import { Reservation } from "./types/reservation";
+
+interface Props {
+ reservations: Reservation[];
+ selectedTitle: string | null;
+ onChange: (title: string | null) => void;
+}
+
+export default function Category({
+ reservations,
+ selectedTitle,
+ onChange,
+}: Props) {
+ const titles = Array.from(new Set(reservations.map((r) => r.title)));
+
+ return (
+
+ );
+}
diff --git a/feature/reservationStatus/MobileBottomSheet.tsx b/feature/reservationStatus/MobileBottomSheet.tsx
new file mode 100644
index 0000000..835f021
--- /dev/null
+++ b/feature/reservationStatus/MobileBottomSheet.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import ReservationDetailContent from "./ReservationDetailContent";
+import { Reservation } from "./types/reservation";
+
+interface Props {
+ dateKey: string;
+ reservations: Reservation[];
+ onClose: () => void;
+}
+
+export default function ReservationBottomSheet({
+ dateKey,
+ reservations,
+ onClose,
+}: Props) {
+ return (
+ <>
+ {/* 배경 오버레이 */}
+
+
+ {/* Bottom Sheet */}
+
+ >
+ );
+}
diff --git a/feature/reservationStatus/ReservaionStatusView.tsx b/feature/reservationStatus/ReservaionStatusView.tsx
index e051f3a..fa9a7be 100644
--- a/feature/reservationStatus/ReservaionStatusView.tsx
+++ b/feature/reservationStatus/ReservaionStatusView.tsx
@@ -63,18 +63,16 @@ export default function ReservationStatusView({
style={{
top: modalPosition.top,
left: modalPosition.left,
- transform: "translate(-50%, 10px)",
}}
>
{
setSelectedDateKey(null);
setModalPosition(null);
}}
- // 위치 좌표 전달
- position={modalPosition}
/>
)}
diff --git a/feature/reservationStatus/ReservationDetailContent.tsx b/feature/reservationStatus/ReservationDetailContent.tsx
new file mode 100644
index 0000000..c8280a6
--- /dev/null
+++ b/feature/reservationStatus/ReservationDetailContent.tsx
@@ -0,0 +1,41 @@
+import { Reservation } from "./types/reservation";
+import useReservationDetail from "@/hooks/useReservationDetail";
+import ReservationHeader from "./ReservationHeader";
+import ReservationTabs from "./ReservationTabs";
+import ReservationTimeFilter from "./ReservationTimeFilter";
+import ReservationList from "./ReservationList";
+
+interface Props {
+ dateKey: string;
+ reservations: Reservation[];
+ onClose: () => void;
+}
+
+export default function ReservationDetailContent(props: Props) {
+ const {
+ activeTab,
+ selectedTime,
+ setActiveTab,
+ setSelectedTime,
+ filteredReservations,
+ getCount,
+ handleStatusChange,
+ } = useReservationDetail(props.reservations);
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/feature/reservationStatus/ReservationHeader.tsx b/feature/reservationStatus/ReservationHeader.tsx
new file mode 100644
index 0000000..2800872
--- /dev/null
+++ b/feature/reservationStatus/ReservationHeader.tsx
@@ -0,0 +1,18 @@
+import DeleteIcon from "@/public/icon_delete.svg";
+
+export default function ReservationHeader({
+ dateKey,
+ onClose,
+}: {
+ dateKey: string;
+ onClose: () => void;
+}) {
+ return (
+
+
{dateKey}
+
+
+ );
+}
diff --git a/feature/reservationStatus/ReservationItem.tsx b/feature/reservationStatus/ReservationItem.tsx
new file mode 100644
index 0000000..e806222
--- /dev/null
+++ b/feature/reservationStatus/ReservationItem.tsx
@@ -0,0 +1,74 @@
+import { Reservation,ReservationStatusCode } from "./types/reservation";
+import { STATUS_UI_CONFIG } from "./constants/ReservationUI";
+
+interface Props {
+ reservation: Reservation;
+ activeTab: ReservationStatusCode;
+ onStatusChange: (
+ id: string | number,
+ status: ReservationStatusCode
+ ) => void;
+}
+
+export default function ReservationItem({
+ reservation,
+ activeTab,
+ onStatusChange,
+}: Props) {
+ return (
+
+
+
+
+
+ 닉네임
+
+
+ {reservation.id}
+
+
+
+
+
+ 인원
+
+
+ {reservation.people}명
+
+
+
+
+
+ {activeTab === "pending" ? (
+
+
+
+
+ ) : (
+
+ {STATUS_UI_CONFIG[reservation.status]?.label}
+
+ )}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/feature/reservationStatus/ReservationList.tsx b/feature/reservationStatus/ReservationList.tsx
new file mode 100644
index 0000000..f76017a
--- /dev/null
+++ b/feature/reservationStatus/ReservationList.tsx
@@ -0,0 +1,40 @@
+import { Reservation,ReservationStatusCode } from "./types/reservation";
+import ReservationItem from "./ReservationItem";
+
+interface Props {
+ reservations: Reservation[];
+ activeTab: ReservationStatusCode;
+ onStatusChange: (
+ id: string | number,
+ status: ReservationStatusCode
+ ) => void;
+}
+
+export default function ReservationList({
+ reservations,
+ activeTab,
+ onStatusChange,
+}: Props) {
+ return (
+
+
예약 내역
+
+ {reservations.length === 0 ? (
+
+ 내역이 없습니다.
+
+ ) : (
+
+ {reservations.map((reservation) => (
+
+ ))}
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/feature/reservationStatus/ReservationSidemodal.tsx b/feature/reservationStatus/ReservationSidemodal.tsx
new file mode 100644
index 0000000..bb006f5
--- /dev/null
+++ b/feature/reservationStatus/ReservationSidemodal.tsx
@@ -0,0 +1,24 @@
+import { Reservation } from "./types/reservation";
+import ReservationDetailContent from "./ReservationDetailContent";
+
+interface Props {
+ dateKey: string;
+ reservations: Reservation[];
+ onClose: () => void;
+}
+
+export default function ReservationSideModal({
+ dateKey,
+ reservations,
+ onClose,
+}: Props) {
+ return (
+
+ );
+}
diff --git a/feature/reservationStatus/ReservationStatusPage.tsx b/feature/reservationStatus/ReservationStatusPage.tsx
new file mode 100644
index 0000000..4728203
--- /dev/null
+++ b/feature/reservationStatus/ReservationStatusPage.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { useState } from "react";
+import Category from "./Category";
+import ReservationStatusView from "./ReservaionStatusView";
+import { mockReservations } from "@/Mocks/reservationStatus.mock";
+
+export default function ReservationStatusPage() {
+ const [selectedTitle, setSelectedTitle] = useState(null);
+
+ return (
+
+ {/* 헤더 */}
+
+
+ {/* 카테고리 */}
+
+
+
+
+ {/* 달력 */}
+
+
+ );
+}
diff --git a/feature/reservationStatus/ReservationTabs.tsx b/feature/reservationStatus/ReservationTabs.tsx
new file mode 100644
index 0000000..4a4ebc2
--- /dev/null
+++ b/feature/reservationStatus/ReservationTabs.tsx
@@ -0,0 +1,35 @@
+import { ReservationStatusCode } from "./types/reservation";
+import { TABS } from "./constants/ReservationUI";
+
+interface Props {
+ activeTab: ReservationStatusCode;
+ onChange: (status: ReservationStatusCode) => void;
+ getCount: (status: ReservationStatusCode) => number;
+}
+
+export default function ReservationTabs({
+ activeTab,
+ onChange,
+ getCount,
+}: Props) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/feature/reservationStatus/ReservationTimeFilter.tsx b/feature/reservationStatus/ReservationTimeFilter.tsx
new file mode 100644
index 0000000..6f1827c
--- /dev/null
+++ b/feature/reservationStatus/ReservationTimeFilter.tsx
@@ -0,0 +1,37 @@
+import { TIME_SLOTS } from "./constants/ReservationUI";
+
+interface Props {
+ value: string;
+ onChange: (value: string) => void;
+}
+
+export default function ReservationTimeFilter({
+ value,
+ onChange,
+}: Props) {
+ return (
+
+
+
+
+
+
+
+ ▼
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/feature/reservationStatus/constants/ReservationUI.tsx b/feature/reservationStatus/constants/ReservationUI.tsx
new file mode 100644
index 0000000..1395d5a
--- /dev/null
+++ b/feature/reservationStatus/constants/ReservationUI.tsx
@@ -0,0 +1,42 @@
+import { ReservationStatusCode } from "../types/reservation";
+
+export const TIME_SLOTS = [
+ "전체",
+ ...Array.from({ length: 24 }, (_, i) => {
+ const start = String(i).padStart(2, "0") + ":00";
+ const end = String(i + 1).padStart(2, "0") + ":00";
+ return `${start} - ${end}`;
+ }),
+];
+
+export const TABS: { label: string; status: ReservationStatusCode }[] = [
+ { label: "신청", status: "pending" },
+ { label: "승인", status: "confirmed" },
+ { label: "거절", status: "declined" },
+];
+
+export const STATUS_UI_CONFIG: Record<
+ ReservationStatusCode,
+ { label: string; badgeStyle: string }
+> = {
+ pending: {
+ label: "신청",
+ badgeStyle: "bg-blue-50 text-blue-600 border-blue-100",
+ },
+ confirmed: {
+ label: "예약 승인",
+ badgeStyle: "bg-cyan-50 text-cyan-500 border-cyan-100",
+ },
+ declined: {
+ label: "예약 거절",
+ badgeStyle: "bg-red-50 text-red-400 border-red-100",
+ },
+ canceled: {
+ label: "예약 취소",
+ badgeStyle: "bg-gray-100 text-gray-500 border-gray-200",
+ },
+ completed: {
+ label: "방문 완료",
+ badgeStyle: "bg-blue-50 text-blue-600 border-blue-100",
+ },
+} as const;
diff --git a/feature/reservationStatus/constants/statusMap.ts b/feature/reservationStatus/constants/statusMap.ts
new file mode 100644
index 0000000..c0f6abf
--- /dev/null
+++ b/feature/reservationStatus/constants/statusMap.ts
@@ -0,0 +1,13 @@
+import { ReservationStatusCode } from "../types/reservation";
+import { ReservationStatusLabel } from "../types/reservationStatus";
+
+export const STATUS_TO_CALENDAR: Record<
+ ReservationStatusCode,
+ ReservationStatusLabel | null
+> = {
+ pending: "예약",
+ confirmed: "승인",
+ completed: "완료",
+ declined: null,
+ canceled: null,
+};
diff --git a/feature/reservationStatus/types/calendar.ts b/feature/reservationStatus/types/calendar.ts
new file mode 100644
index 0000000..8baff1a
--- /dev/null
+++ b/feature/reservationStatus/types/calendar.ts
@@ -0,0 +1,48 @@
+export interface CalendarDate {
+ date: Date;
+ day: number;
+ isCurrentMonth: boolean;
+ isToday: boolean;
+}
+
+export function getMonthCalendar(year: number, month: number): CalendarDate[] {
+ const result: CalendarDate[] = [];
+ const today = new Date();
+ const todayReset = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
+
+ const firstDayOfMonth = new Date(year, month, 1);
+ const lastDayOfMonth = new Date(year, month + 1, 0);
+
+ const startDayOfWeek = firstDayOfMonth.getDay();
+ const totalDays = lastDayOfMonth.getDate();
+
+ for (let i = startDayOfWeek; i > 0; i--) {
+ const date = new Date(year, month, 1 - i);
+ result.push(formatDateObject(date, false, todayReset));
+ }
+
+ for (let day = 1; day <= totalDays; day++) {
+ const date = new Date(year, month, day);
+ result.push(formatDateObject(date, true, todayReset));
+ }
+
+ const remainingSlots = 7 - (result.length % 7);
+ if (remainingSlots < 7) {
+ for (let i = 1; i <= remainingSlots; i++) {
+ const date = new Date(year, month + 1, i);
+ result.push(formatDateObject(date, false, todayReset));
+ }
+ }
+
+ return result;
+}
+
+// 헬퍼 함수: 반복적인 객체 생성을 깔끔하게 분리
+function formatDateObject(date: Date, isCurrentMonth: boolean, todayTime: number): CalendarDate {
+ return {
+ date,
+ day: date.getDate(),
+ isCurrentMonth,
+ isToday: new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() === todayTime,
+ };
+}
\ No newline at end of file
diff --git a/feature/reservationStatus/types/reservation.ts b/feature/reservationStatus/types/reservation.ts
new file mode 100644
index 0000000..388c118
--- /dev/null
+++ b/feature/reservationStatus/types/reservation.ts
@@ -0,0 +1,16 @@
+// feature/reservation/types/reservation.ts
+export type ReservationStatusCode =
+ | "pending"
+ | "confirmed"
+ | "declined"
+ | "canceled"
+ | "completed";
+
+export interface Reservation {
+ id: number | string;
+ title: string;
+ date: string;
+ price: number;
+ people: number;
+ status: ReservationStatusCode;
+}
diff --git a/feature/reservationStatus/types/reservationStatus.ts b/feature/reservationStatus/types/reservationStatus.ts
new file mode 100644
index 0000000..2f00d3c
--- /dev/null
+++ b/feature/reservationStatus/types/reservationStatus.ts
@@ -0,0 +1,25 @@
+export type ReservationStatusCode =
+ | "pending"
+ | "confirmed"
+ | "declined"
+ | "canceled"
+ | "completed";
+
+export type ReservationStatusLabel = "신청" | "승인" | "거절" | "취소" | "완료";
+
+export const RESERVATION_STATUS_LABEL: Record<
+ ReservationStatusCode,
+ ReservationStatusLabel
+> = {
+ pending: "신청",
+ confirmed: "승인",
+ declined: "거절",
+ canceled: "취소",
+ completed: "완료",
+};
+
+export interface ReservationBadge {
+ id: string;
+ status: ReservationStatusCode;
+ count: number;
+}
diff --git a/feature/reservationStatus/utils/mapReservationsToCalendar.ts b/feature/reservationStatus/utils/mapReservationsToCalendar.ts
new file mode 100644
index 0000000..b780684
--- /dev/null
+++ b/feature/reservationStatus/utils/mapReservationsToCalendar.ts
@@ -0,0 +1,36 @@
+import dayjs from "dayjs";
+import { Reservation } from "../types/reservation";
+import { ReservationBadge } from
+ "@/feature/reservationStatus/types/reservationStatus";
+
+export type ReservationMap = Record;
+
+export function mapReservationsToCalendar(
+ reservations: Reservation[]
+): ReservationMap {
+ const map: ReservationMap = {};
+
+ reservations.forEach((reservation) => {
+ const dateKey = dayjs(reservation.date).format("YYYY-MM-DD");
+
+ if (!map[dateKey]) {
+ map[dateKey] = [];
+ }
+
+ const existing = map[dateKey].find(
+ (badge) => badge.status === reservation.status
+ );
+
+ if (existing) {
+ existing.count += 1;
+ } else {
+ map[dateKey].push({
+ id: `${dateKey}-${reservation.status}`,
+ status: reservation.status,
+ count: 1,
+ });
+ }
+ });
+
+ return map;
+}
diff --git a/hooks/useCalendar.ts b/hooks/useCalendar.ts
new file mode 100644
index 0000000..218bba8
--- /dev/null
+++ b/hooks/useCalendar.ts
@@ -0,0 +1,25 @@
+import { useState } from "react";
+
+export function useCalendar(initialYear: number, initialMonth: number) {
+ const [current, setCurrent] = useState({
+ year: initialYear,
+ month: initialMonth,
+ });
+
+ const changeMonth = (delta: number) => {
+ setCurrent((prev) => {
+ const date = new Date(prev.year, prev.month + delta, 1);
+ return {
+ year: date.getFullYear(),
+ month: date.getMonth(),
+ };
+ });
+ };
+
+ return {
+ year: current.year,
+ month: current.month,
+ prevMonth: () => changeMonth(-1),
+ nextMonth: () => changeMonth(1),
+ };
+}
diff --git a/hooks/useIsCompact.ts b/hooks/useIsCompact.ts
new file mode 100644
index 0000000..59b54f7
--- /dev/null
+++ b/hooks/useIsCompact.ts
@@ -0,0 +1,17 @@
+import { useEffect, useState } from "react";
+
+export function useIsCompact() {
+ const [isCompact, setIsCompact] = useState(false);
+
+ useEffect(() => {
+ const check = () => {
+ setIsCompact(window.innerWidth < 1024);
+ };
+
+ check();
+ window.addEventListener("resize", check);
+ return () => window.removeEventListener("resize", check);
+ }, []);
+
+ return isCompact;
+}
diff --git a/hooks/useReservationDetail.ts b/hooks/useReservationDetail.ts
new file mode 100644
index 0000000..ef77691
--- /dev/null
+++ b/hooks/useReservationDetail.ts
@@ -0,0 +1,47 @@
+import { useState } from "react";
+import {
+ Reservation,
+ ReservationStatusCode,
+} from "@/feature/reservationStatus/types/reservation";
+
+export default function useReservationDetail(
+ reservations: Reservation[]
+) {
+ const [localReservations, setLocalReservations] =
+ useState(reservations);
+
+ const [activeTab, setActiveTab] =
+ useState("pending");
+
+ const [selectedTime, setSelectedTime] = useState("전체");
+
+ const handleStatusChange = (
+ id: string | number,
+ status: ReservationStatusCode
+ ) => {
+ setLocalReservations((prev) =>
+ prev.map((r) => (r.id === id ? { ...r, status } : r))
+ );
+ };
+
+ const filteredReservations = localReservations.filter((r) => {
+ if (r.status !== activeTab) return false;
+ if (selectedTime === "전체") return true;
+
+ const hour = new Date(r.date).getHours();
+ return hour === Number(selectedTime.split(":")[0]);
+ });
+
+ const getCount = (status: ReservationStatusCode) =>
+ localReservations.filter((r) => r.status === status).length;
+
+ return {
+ activeTab,
+ selectedTime,
+ setActiveTab,
+ setSelectedTime,
+ filteredReservations,
+ getCount,
+ handleStatusChange,
+ };
+}
diff --git a/lib/utils/date.ts b/lib/utils/date.ts
new file mode 100644
index 0000000..aa2a7c3
--- /dev/null
+++ b/lib/utils/date.ts
@@ -0,0 +1,6 @@
+import dayjs from "dayjs";
+
+export function toDateKey(date: Date) {
+ return dayjs(date).format("YYYY-MM-DD")
+
+}
diff --git a/lib/utils/dayjs.ts b/lib/utils/dayjs.ts
new file mode 100644
index 0000000..8a6da8d
--- /dev/null
+++ b/lib/utils/dayjs.ts
@@ -0,0 +1,10 @@
+import dayjs from "dayjs";
+import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
+import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
+import weekday from "dayjs/plugin/weekday";
+
+dayjs.extend(isSameOrBefore);
+dayjs.extend(isSameOrAfter);
+dayjs.extend(weekday);
+
+export default dayjs;
diff --git a/package-lock.json b/package-lock.json
index eaac6ff..69792e3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"axios": "^1.13.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "dayjs": "^1.11.19",
"next": "16.1.0",
"react": "19.2.3",
"react-datepicker": "^9.1.0",
@@ -3819,6 +3820,12 @@
"integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
"license": "MIT"
},
+ "node_modules/dayjs": {
+ "version": "1.11.19",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
+ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
diff --git a/package.json b/package.json
index a1ac4ff..39bd1f2 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"axios": "^1.13.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "dayjs": "^1.11.19",
"next": "16.1.0",
"react": "19.2.3",
"react-datepicker": "^9.1.0",