diff --git a/apps/owner/src/app/(tabs)/order/_components/OrderHistoryCard.tsx b/apps/owner/src/app/(tabs)/order/_components/OrderHistoryCard.tsx new file mode 100644 index 0000000..50161f8 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/_components/OrderHistoryCard.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { Card } from "@compasser/design-system"; +import type { ReservationItem } from "../_types/order"; +import { getStatusClassName, getStatusLabel } from "../_utils/orderStatus"; + +interface OrderHistoryCardProps { + order: ReservationItem; +} + +export default function OrderHistoryCard({ order }: OrderHistoryCardProps) { + return ( + +
+
+ 주문자 + {order.customerName} + + {getStatusLabel(order.status)} + + + 주문내역 + {order.orderDetail} + + 가격 + {order.price} + + 수량 + {order.quantity} +
+ +
+ {order.processedAt} +
+
+
+ ); +} \ No newline at end of file diff --git a/apps/owner/src/app/(tabs)/order/_components/OrderList.tsx b/apps/owner/src/app/(tabs)/order/_components/OrderList.tsx new file mode 100644 index 0000000..3426408 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/_components/OrderList.tsx @@ -0,0 +1,44 @@ +"use client"; + +import ReservationCard from "./ReservationCard"; +import OrderHistoryCard from "./OrderHistoryCard"; +import type { OrderTabKey, ReservationItem } from "../_types/order"; + +interface OrderListProps { + activeTab: OrderTabKey; + orders: ReservationItem[]; + onAccept: (orderId: number) => void; + onReject: (orderId: number) => void; +} + +export default function OrderList({ + activeTab, + orders, + onAccept, + onReject, +}: OrderListProps) { + if (orders.length === 0) { + return ( +
+

주문 내역이 없어요.

+
+ ); + } + + return ( +
+ {activeTab === "reservation" + ? orders.map((order) => ( + + )) + : orders.map((order) => ( + + ))} +
+ ); +} \ No newline at end of file diff --git a/apps/owner/src/app/(tabs)/order/_components/ReservationCard.tsx b/apps/owner/src/app/(tabs)/order/_components/ReservationCard.tsx new file mode 100644 index 0000000..3798d41 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/_components/ReservationCard.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { Card, Button } from "@compasser/design-system"; +import type { ReservationItem } from "../_types/order"; +import { getStatusClassName, getStatusLabel } from "../_utils/orderStatus"; + +interface ReservationCardProps { + order: ReservationItem; + onAccept: (orderId: number) => void; + onReject: (orderId: number) => void; +} + +export default function ReservationCard({ + order, + onAccept, + onReject, +}: ReservationCardProps) { + const isPending = order.status === "pending"; + + return ( + +
+
+ 주문자 + {order.customerName} + + {getStatusLabel(order.status)} + + + 주문내역 + {order.orderDetail} + + 가격 + {order.price} + + 수량 + {order.quantity} +
+ + {isPending ? ( +
+ + + +
+ ) : ( +
+ {order.processedAt} +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/apps/owner/src/app/(tabs)/order/_components/modal/AcceptOrderModal.tsx b/apps/owner/src/app/(tabs)/order/_components/modal/AcceptOrderModal.tsx new file mode 100644 index 0000000..9f02132 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/_components/modal/AcceptOrderModal.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { Button, Modal } from "@compasser/design-system"; + +interface AcceptOrderModalProps { + open: boolean; + onClose: () => void; + onConfirm: () => void; +} + +export default function AcceptOrderModal({ + open, + onClose, + onConfirm, +}: AcceptOrderModalProps) { + return ( + + + + + + } + /> + ); +} \ No newline at end of file diff --git a/apps/owner/src/app/(tabs)/order/_components/modal/RejectOrderModal.tsx b/apps/owner/src/app/(tabs)/order/_components/modal/RejectOrderModal.tsx new file mode 100644 index 0000000..75a3f49 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/_components/modal/RejectOrderModal.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Button, Modal } from "@compasser/design-system"; + +interface RejectOrderModalProps { + open: boolean; + onClose: () => void; + onConfirm: (reason: string) => void; +} + +export default function RejectOrderModal({ + open, + onClose, + onConfirm, +}: RejectOrderModalProps) { + const [reason, setReason] = useState(""); + + useEffect(() => { + if (!open) { + setReason(""); + } + }, [open]); + + return ( + + + + + + } + > +
+ setReason(e.target.value)} + placeholder="거절 사유를 입력해주세요." + className=" + w-full rounded-[8px] border border-primary + px-[1rem] py-[0.6rem] + body2-r text-default + placeholder:body2-r placeholder:text-gray-300 + outline-none + " + /> +
+
+ ); +} \ No newline at end of file diff --git a/apps/owner/src/app/(tabs)/order/_constants/mockOrders.ts b/apps/owner/src/app/(tabs)/order/_constants/mockOrders.ts new file mode 100644 index 0000000..3a72761 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/_constants/mockOrders.ts @@ -0,0 +1,36 @@ +import type { ReservationItem } from "../_types/order"; + +export const INITIAL_RESERVATIONS: ReservationItem[] = [ + { + id: 1, + customerName: "김00", + orderDetail: "랜덤박스 1레벨", + price: "6,000원", + quantity: "1개", + status: "pending", + }, + { + id: 2, + customerName: "고00", + orderDetail: "랜덤박스 1레벨", + price: "6,000원", + quantity: "1개", + status: "pending", + }, + { + id: 3, + customerName: "이00", + orderDetail: "랜덤박스 3레벨", + price: "12,000원", + quantity: "2개", + status: "pending", + }, + { + id: 4, + customerName: "박00", + orderDetail: "랜덤박스 2레벨", + price: "9,000원", + quantity: "3개", + status: "pending", + } +]; \ No newline at end of file diff --git a/apps/owner/src/app/(tabs)/order/_types/order.ts b/apps/owner/src/app/(tabs)/order/_types/order.ts new file mode 100644 index 0000000..2d48126 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/_types/order.ts @@ -0,0 +1,23 @@ +export type OrderTabKey = "reservation" | "order"; + +export type ReservationStatus = "pending" | "completed" | "cancelled"; + +export interface ReservationItem { + id: number; + customerName: string; + orderDetail: string; + price: string; + quantity: string; + status: ReservationStatus; + processedAt?: string; +} + +export interface AcceptModalState { + isOpen: boolean; + orderId: number | null; +} + +export interface RejectModalState { + isOpen: boolean; + orderId: number | null; +} \ No newline at end of file diff --git a/apps/owner/src/app/(tabs)/order/_utils/formatProcessAt.ts b/apps/owner/src/app/(tabs)/order/_utils/formatProcessAt.ts new file mode 100644 index 0000000..b74de73 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/_utils/formatProcessAt.ts @@ -0,0 +1,7 @@ +export const formatProcessedAt = (date: Date) => { + const year = String(date.getFullYear()).slice(2); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + + return `${year}/${month}/${day}`; +}; \ No newline at end of file diff --git a/apps/owner/src/app/(tabs)/order/_utils/orderStatus.ts b/apps/owner/src/app/(tabs)/order/_utils/orderStatus.ts new file mode 100644 index 0000000..d5bf8a4 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/_utils/orderStatus.ts @@ -0,0 +1,27 @@ +import type { ReservationStatus } from "../_types/order"; + +export const getStatusLabel = (status: ReservationStatus) => { + switch (status) { + case "pending": + return "확인 대기중"; + case "completed": + return "거래완료"; + case "cancelled": + return "거래취소"; + default: + return ""; + } +}; + +export const getStatusClassName = (status: ReservationStatus) => { + switch (status) { + case "pending": + return "body1-m text-secondary"; + case "completed": + return "body1-m text-primary"; + case "cancelled": + return "body1-m text-gray-500"; + default: + return ""; + } +}; \ No newline at end of file diff --git a/apps/owner/src/app/(tabs)/order/page.tsx b/apps/owner/src/app/(tabs)/order/page.tsx new file mode 100644 index 0000000..0bd8071 --- /dev/null +++ b/apps/owner/src/app/(tabs)/order/page.tsx @@ -0,0 +1,153 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { Header, TopTabBar } from "@compasser/design-system"; +import OrderList from "./_components/OrderList"; +import AcceptOrderModal from "./_components/modal/AcceptOrderModal"; +import RejectOrderModal from "./_components/modal/RejectOrderModal"; +import { INITIAL_RESERVATIONS } from "./_constants/mockOrders"; +import { formatProcessedAt } from "./_utils/formatProcessAt"; +import type { + AcceptModalState, + OrderTabKey, + RejectModalState, + ReservationItem, +} from "./_types/order"; + +export default function OrderStatusPage() { + const [activeTab, setActiveTab] = useState("reservation"); + const [orders, setOrders] = useState(INITIAL_RESERVATIONS); + const isOrderTabKey = (key: string): key is OrderTabKey => + key === "reservation" || key === "order"; + + const [acceptModal, setAcceptModal] = useState({ + isOpen: false, + orderId: null, + }); + + const [rejectModal, setRejectModal] = useState({ + isOpen: false, + orderId: null, + }); + + const reservationOrders = useMemo( + () => orders.filter((order) => order.status === "pending"), + [orders] + ); + + const completedOrders = useMemo( + () => orders.filter((order) => order.status !== "pending"), + [orders] + ); + + const currentOrders = + activeTab === "reservation" ? reservationOrders : completedOrders; + + const openAcceptModal = (orderId: number) => { + setAcceptModal({ + isOpen: true, + orderId, + }); + }; + + const closeAcceptModal = () => { + setAcceptModal({ + isOpen: false, + orderId: null, + }); + }; + + const openRejectModal = (orderId: number) => { + setRejectModal({ + isOpen: true, + orderId, + }); + }; + + const closeRejectModal = () => { + setRejectModal({ + isOpen: false, + orderId: null, + }); + }; + + const handleAcceptOrder = () => { + if (acceptModal.orderId === null) return; + + const now = formatProcessedAt(new Date()); + + setOrders((prev) => + prev.map((order) => + order.id === acceptModal.orderId + ? { + ...order, + status: "completed", + processedAt: now, + } + : order + ) + ); + + closeAcceptModal(); + }; + + const handleRejectOrder = (_reason: string) => { + if (rejectModal.orderId === null) return; + + const now = formatProcessedAt(new Date()); + + setOrders((prev) => + prev.map((order) => + order.id === rejectModal.orderId + ? { + ...order, + status: "cancelled", + processedAt: now, + } + : order + ) + ); + + closeRejectModal(); + }; + + return ( + <> +
+
+ + { + if (isOrderTabKey(key)) setActiveTab(key); + }} + /> + +
+ +
+
+ + + + + + ); +} \ No newline at end of file