diff --git a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx index f2538cff..ba88fc7b 100644 --- a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx +++ b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import RoundTabButton from "./components/RoundTabButton"; import refreshIcon from "../../assets/refresh.svg"; import { WaitingCard } from "./components/WaitingCard"; @@ -104,74 +104,83 @@ const AdminHome = () => { }, [reservations, activeTab]); // 호출 버튼 클릭 이벤트 - const handleCall = (id: number, reservationNumber: number) => { - updateStatus( - { storeId, reservationNumber, status: "CALLING" }, - { - onSuccess: () => { - setReservations((prev) => - prev.map((res) => - res.id === id - ? (() => { - // 입장/취소 기록은 흐름상 초기화하는게 보통 자연스러움 - return { - ...res, - status: "CALLING", - calledAt: new Date().toISOString(), - confirmedAt: undefined, - cancelledAt: undefined, - }; - })() - : res - ) - ); - }, - } - ); - }; + const handleCall = useCallback( + (id: number, reservationNumber: number) => { + updateStatus( + { storeId, reservationNumber, status: "CALLING" }, + { + onSuccess: () => { + setReservations((prev) => + prev.map((res) => + res.id === id + ? (() => { + // 입장/취소 기록은 흐름상 초기화하는게 보통 자연스러움 + return { + ...res, + status: "CALLING", + calledAt: new Date().toISOString(), + confirmedAt: undefined, + cancelledAt: undefined, + }; + })() + : res + ) + ); + }, + } + ); + }, + [storeId, updateStatus] + ); - const handleEnter = (id: number, reservationNumber: number) => { - const now = new Date().toISOString(); - updateStatus( - { storeId, reservationNumber, status: "CONFIRMED" }, - { - onSuccess: () => { - setReservations((prev) => - prev.map((res) => { - if (res.id !== id) return res; - return { ...res, status: "CONFIRMED", confirmedAt: now }; - }) - ); - }, - } - ); - }; + const handleEnter = useCallback( + (id: number, reservationNumber: number) => { + const now = new Date().toISOString(); + updateStatus( + { storeId, reservationNumber, status: "CONFIRMED" }, + { + onSuccess: () => { + setReservations((prev) => + prev.map((res) => { + if (res.id !== id) return res; + return { ...res, status: "CONFIRMED", confirmedAt: now }; + }) + ); + }, + } + ); + }, + [storeId, updateStatus] + ); - const handleClose = (id: number, reservationNumber: number) => { - const now = new Date().toISOString(); - updateStatus( - { storeId, reservationNumber, status: "CANCELLED" }, - { - onSuccess: () => { - setReservations((prev) => - prev.map((res) => { - if (res.id !== id) return res; - return { ...res, status: "CANCELLED", cancelledAt: now }; - }) - ); - }, - } - ); - }; + const handleClose = useCallback( + (id: number, reservationNumber: number) => { + const now = new Date().toISOString(); + updateStatus( + { storeId, reservationNumber, status: "CANCELLED" }, + { + onSuccess: () => { + setReservations((prev) => + prev.map((res) => { + if (res.id !== id) return res; + return { ...res, status: "CANCELLED", cancelledAt: now }; + }) + ); + }, + } + ); + }, + [storeId, updateStatus] + ); - const handleNoShow = (id: number) => { + const handleNoShow = useCallback((id: number) => { setNoShowIds((prev) => { if (!prev.includes(id)) return [...prev, id]; return prev; }); - }; + }, []); - const handleRefresh = async () => { + const handleRefresh = useCallback(async () => { if (isRefreshing) return; // 중복 클릭 방지 setIsRefreshing(true); try { @@ -180,7 +189,7 @@ const AdminHome = () => { // 살짝 딜레이를 주면 회전이 끊기지 않고 보여짐 (선택) setTimeout(() => setIsRefreshing(false), 300); } - }; + }, [isRefreshing, refetchWaiting, refetchCompleted]); useEffect(() => { if (!Array.isArray(waitingList) || !Array.isArray(completedList)) return; diff --git a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx index ac1ebab4..2b587a59 100644 --- a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx +++ b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx @@ -2,7 +2,7 @@ import CloseButton from "../../../components/closeButton"; import callIcon from "../../../assets/Call.svg"; import openDoorIcon from "../../../assets/door_open.svg"; import alarmIcon from "../../../assets/alarm.svg"; -import { useEffect, useState } from "react"; +import { memo, useEffect, useState } from "react"; const totalDurationSec = 10; // 10초, 10분은 600 @@ -37,7 +37,24 @@ const diffMinutes = (start?: string, end?: string) => { ); }; -export function WaitingCard({ +const areEqual = (prev: WaitingCardProps, next: WaitingCardProps) => { + return ( + prev.number === next.number && + prev.time === next.time && + prev.waitMinutes === next.waitMinutes && + prev.peopleCount === next.peopleCount && + prev.name === next.name && + prev.phoneNumber === next.phoneNumber && + prev.status === next.status && + prev.requestedAt === next.requestedAt && + prev.calledAt === next.calledAt && + prev.confirmedAt === next.confirmedAt && + prev.cancelledAt === next.cancelledAt && + prev.isNoShow === next.isNoShow + ); +}; + +export const WaitingCard = memo(function WaitingCard({ number, time, waitMinutes, @@ -222,4 +239,4 @@ export function WaitingCard({ ); -} +}, areEqual); diff --git a/apps/nowait-user/src/hooks/order/useStoreMenus.ts b/apps/nowait-user/src/hooks/order/useStoreMenus.ts new file mode 100644 index 00000000..c5edf52d --- /dev/null +++ b/apps/nowait-user/src/hooks/order/useStoreMenus.ts @@ -0,0 +1,23 @@ +import { useQuery } from "@tanstack/react-query"; +import { getStoreMenus } from "../../api/menu"; + +export const storeMenusQuery = (storeId?: string) => ({ + queryKey: ["storeMenus", storeId], + queryFn: () => getStoreMenus(storeId!), +}); + +export const useStoreMenus = (storeId?: string) => { + return useQuery({ + ...storeMenusQuery(storeId!), + enabled: !!storeId, + select: (data) => data?.response, + }); +}; + +export const useStoreMenuList = (storeId?: string) => { + return useQuery({ + ...storeMenusQuery(storeId!), + enabled: !!storeId, + select: (data) => data?.response?.menuReadDto, + }); +}; diff --git a/apps/nowait-user/src/pages/order/addMenu/AddMenuPage.tsx b/apps/nowait-user/src/pages/order/addMenu/AddMenuPage.tsx index 3d278cfe..5c47cc0f 100644 --- a/apps/nowait-user/src/pages/order/addMenu/AddMenuPage.tsx +++ b/apps/nowait-user/src/pages/order/addMenu/AddMenuPage.tsx @@ -1,82 +1,72 @@ -import { useState } from "react"; -import QuantitySelector from "../../../components/common/QuantitySelector"; +import { useState, useMemo } from "react"; import { useNavigate, useParams } from "react-router-dom"; +import { useQuery } from "@tanstack/react-query"; import PageFooterButton from "../../../components/order/PageFooterButton"; +import FullPageLoader from "../../../components/FullPageLoader"; import { Button } from "@repo/ui"; -import type { CartType } from "../../../types/order/cart"; -import { useCartStore } from "../../../stores/cartStore"; -import NumberFlow from "@number-flow/react"; -import defaultMenuImageLg from "../../../assets/default-image-lg.png"; -import { useQuery } from "@tanstack/react-query"; import { getStoreMenu } from "../../../api/menu"; -import FullPageLoader from "../../../components/FullPageLoader"; +import { useCartStore } from "../../../stores/cartStore"; +import type { CartType } from "../../../types/order/cart"; +import MenuInfoSection from "./components/MenuInfoSection"; +import MenuOrderSection from "./components/MenuOrderSection"; + const AddMenuPage = () => { const navigate = useNavigate(); - const { storeId, menuId } = useParams(); + const { storeId, menuId } = useParams<{ storeId: string; menuId: string }>(); + + const [quantity, setQuantity] = useState(1); + const { addToCart } = useCartStore(); const { data: menu, isLoading } = useQuery({ - queryKey: ["menu", menuId], - queryFn: () => getStoreMenu(storeId!, Number(menuId!)), - select: (data) => data?.response, + queryKey: ["menu", storeId, menuId], + queryFn: () => getStoreMenu(storeId!, Number(menuId)), + select: (data) => data.response, + enabled: !!storeId && !!menuId, }); - const [quantity, setQuantity] = useState(1); - const { addToCart } = useCartStore(); + const totalPrice = useMemo( + () => (menu ? menu.price * quantity : 0), + [menu, quantity] + ); + + const handleAddToCart = () => { + if (!menu) return; - const addToCartButton = () => { const item: CartType = { - menuId: menu!.menuId, - image: menu!.images[0]?.imageUrl, - name: menu!.name, + menuId: menu.menuId, + image: menu.images?.[0]?.imageUrl, + name: menu.name, quantity, - originPrice: menu!.price, - price: menu!.price * quantity, + originPrice: menu.price, + price: totalPrice, }; + addToCart(item); navigate(`/${storeId}`, { - state: { added: true, addedPrice: menu!.price * quantity, isBack: true }, - replace: false, + state: { + added: true, + addedPrice: totalPrice, + isBack: true, + }, }); }; - if (isLoading) return ; + if (isLoading || !menu) return ; return (
-
-

- 음식 메뉴 이미지 -

-
-

- {menu!.name} -

-

- {menu!.description} -

-
-
- {/* 메뉴 가격 및 수량 컨트롤 */} -
-
-

- -

- -
-
+ + + + - @@ -84,4 +74,4 @@ const AddMenuPage = () => { ); }; -export default AddMenuPage; +export default AddMenuPage; \ No newline at end of file diff --git a/apps/nowait-user/src/pages/order/addMenu/components/MenuInfoSection.tsx b/apps/nowait-user/src/pages/order/addMenu/components/MenuInfoSection.tsx new file mode 100644 index 00000000..ce183997 --- /dev/null +++ b/apps/nowait-user/src/pages/order/addMenu/components/MenuInfoSection.tsx @@ -0,0 +1,36 @@ +import defaultMenuImageLg from "../../../../assets/default-image-lg.png"; + +interface Props { + menu: { + name: string; + description?: string; + images?: { imageUrl: string }[]; + }; +} + +const MenuInfoSection = ({ menu }: Props) => { + const image = menu.images?.[0]?.imageUrl ?? defaultMenuImageLg; + + return ( +
+
+ 음식 메뉴 이미지 +
+ +
+

{menu.name}

+ {menu.description && ( +

+ {menu.description} +

+ )} +
+
+ ); +}; + +export default MenuInfoSection; diff --git a/apps/nowait-user/src/pages/order/addMenu/components/MenuOrderSection.tsx b/apps/nowait-user/src/pages/order/addMenu/components/MenuOrderSection.tsx new file mode 100644 index 00000000..a250b802 --- /dev/null +++ b/apps/nowait-user/src/pages/order/addMenu/components/MenuOrderSection.tsx @@ -0,0 +1,32 @@ +import NumberFlow from "@number-flow/react"; +import QuantitySelector from "../../../../components/common/QuantitySelector"; + +interface Props { + price: number; + quantity: number; + onChangeQuantity: React.Dispatch>; +} + +const MenuOrderSection = ({ + price, + quantity, + onChangeQuantity, +}: Props) => { + return ( +
+
+

+ +

+ + +
+
+ ); +}; + +export default MenuOrderSection; diff --git a/apps/nowait-user/src/pages/order/home/StorePage.tsx b/apps/nowait-user/src/pages/order/home/StorePage.tsx index 843ea730..f9dabe09 100644 --- a/apps/nowait-user/src/pages/order/home/StorePage.tsx +++ b/apps/nowait-user/src/pages/order/home/StorePage.tsx @@ -8,8 +8,7 @@ import { useToastStore } from "../../../stores/toastStore"; import StoreHeader from "./components/StoreHeader"; import MenuList from "../../../components/common/MenuList"; import SectionDivider from "../../../components/SectionDivider"; -import { useQuery } from "@tanstack/react-query"; -import { getStoreMenus } from "../../../api/menu"; +import { useStoreMenus } from "../../../hooks/order/useStoreMenus"; const StorePage = () => { const navigate = useNavigate(); @@ -20,6 +19,7 @@ const StorePage = () => { ?.addedPrice; const { cart } = useCartStore(); const { showToast } = useToastStore(); + const { data: menus, isLoading } = useStoreMenus(storeId); //메뉴 추가 시 toast 띄우기 useEffect(() => { @@ -29,16 +29,13 @@ const StorePage = () => { } }, [added]); - const { data: menus, isLoading } = useQuery({ - queryKey: ["storeMenus", storeId], - queryFn: () => getStoreMenus(storeId!), - select: (data) => data?.response, - }); - - console.log(menus, "asd"); return (
-
0 ? "pb-[116px]" : ""} px-5`}> +
0 ? "pb-[116px]" : "" + } px-5`} + >
diff --git a/apps/nowait-user/src/pages/order/home/RedirectToStorePage.tsx b/apps/nowait-user/src/pages/order/home/components/RedirectToStorePage.tsx similarity index 95% rename from apps/nowait-user/src/pages/order/home/RedirectToStorePage.tsx rename to apps/nowait-user/src/pages/order/home/components/RedirectToStorePage.tsx index 329a6e0b..46c9fb69 100644 --- a/apps/nowait-user/src/pages/order/home/RedirectToStorePage.tsx +++ b/apps/nowait-user/src/pages/order/home/components/RedirectToStorePage.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { useCartStore } from "../../../stores/cartStore"; +import { useCartStore } from "../../../../stores/cartStore"; const RedirectToStorePage = () => { const { storeId, tableId } = useParams(); diff --git a/apps/nowait-user/src/pages/order/orderDetails/OrderDetailsPage.tsx b/apps/nowait-user/src/pages/order/orderDetails/OrderDetailsPage.tsx index 35c06f87..1c8856fe 100644 --- a/apps/nowait-user/src/pages/order/orderDetails/OrderDetailsPage.tsx +++ b/apps/nowait-user/src/pages/order/orderDetails/OrderDetailsPage.tsx @@ -1,9 +1,9 @@ -import EmptyOrderDetails from "./components/EmptyOrderDetails"; import { useQuery } from "@tanstack/react-query"; -import { getOrderDetails } from "../../../api/order"; import { useParams } from "react-router-dom"; import BackOnlyHeader from "../../../components/BackOnlyHeader"; import FullPageLoader from "../../../components/FullPageLoader"; +import EmptyOrderDetails from "./components/EmptyOrderDetails"; +import { getOrderDetails } from "../../../api/order"; interface OrderDetailsType { menuId: number; @@ -17,7 +17,8 @@ const statusMap = { WAITING_FOR_PAYMENT: { label: "입금 대기 중", color: "text-black-90" }, COOKING: { label: "조리 중", color: "text-black-90" }, COOKED: { label: "조리 완료", color: "text-black-60" }, -}; +} as const; + type OrderStatus = keyof typeof statusMap; const OrderDetailsPage = () => { @@ -29,8 +30,9 @@ const OrderDetailsPage = () => { queryFn: () => getOrderDetails(storeId!, Number(tableId!)), select: (data) => data?.response, }); - console.log(orderDetails); + if (isLoading) return ; + //주문내역 없을 시 if (!orderDetails || orderDetails?.length < 1) return ; diff --git a/apps/nowait-user/src/pages/order/orderList/OrderListPage.tsx b/apps/nowait-user/src/pages/order/orderList/OrderListPage.tsx index 420067c8..dfdcc3f7 100644 --- a/apps/nowait-user/src/pages/order/orderList/OrderListPage.tsx +++ b/apps/nowait-user/src/pages/order/orderList/OrderListPage.tsx @@ -1,59 +1,21 @@ -import CartItem from "./components/CartItem"; -import PageFooterButton from "../../../components/order/PageFooterButton"; -import { Button } from "@repo/ui"; -import { useNavigate, useParams } from "react-router-dom"; -import TotalButton from "../../../components/order/TotalButton"; -import { useCartStore } from "../../../stores/cartStore"; -import { AnimatePresence } from "framer-motion"; +import SoldOutModal from "./components/SoldOutModal"; +import { useOrderListLogic } from "./hooks/useOrderListLogic"; +import CartList from "./components/CartList"; +import OrderFooter from "./components/OrderFooter"; import EmptyCart from "./components/EmptyCart"; -import { SmallActionButton } from "../../../components/SmallActionButton"; -import Add from "../../../assets/icon/Add.svg?react"; import BackHeader from "../../../components/BackHeader"; -import { useEffect, useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { getStoreMenus } from "../../../api/menu"; -import { getSoldOutMenusInCart } from "../../../utils/checkSoldOutMenus"; -import useModal from "../../../hooks/useModal"; -import Portal from "../../../components/common/modal/Portal"; -import type { CartType } from "../../../types/order/cart"; -import { motion } from "framer-motion"; const OrderListPage = () => { - const navigate = useNavigate(); - const { storeId } = useParams(); - const { cart, removeFromCart } = useCartStore(); - const modal = useModal(); - const [soldOutMenus, setSoldOutMenus] = useState(); - const [isAnimatingOut, setIsAnimatingOut] = useState(false); - - const { data: menus } = useQuery({ - queryKey: ["storeMenuList", storeId], - queryFn: () => getStoreMenus(storeId!), - select: (data) => data?.response?.menuReadDto, - }); - - // 맨위로 위치 초기화 - useEffect(() => { - window.scrollTo(0, 0); - }, []); - - useEffect(() => { - let timerId: number | undefined; - - if (cart.length === 0) { - if (!isAnimatingOut) { - timerId = window.setTimeout(() => { - setIsAnimatingOut(true); - }, 350); - } - } else { - if (isAnimatingOut) setIsAnimatingOut(false); - } - - return () => { - if (timerId) clearTimeout(timerId); - }; - }, [cart, isAnimatingOut]); + + const { + cart, + storeId, + isAnimatingOut, + modal, + soldOutMenus, + handleOrder, + handleRemoveSoldOutMenus, + } = useOrderListLogic(); if (cart.length === 0 && isAnimatingOut) { return ; @@ -66,97 +28,14 @@ const OrderListPage = () => {

주문 총 {cart.length}건

- - - {cart.map((item) => { - return ( - - ); - })} - - - navigate(`/${storeId}`, { state: { isBack: true } }) - } - className="py-5 border-none" - > - 메뉴 추가하기 - - - - - - - + - - - - {modal.isOpen && soldOutMenus!.length > 0 && ( - -
{ - modal.close(); - soldOutMenus?.forEach((menu: CartType) => - removeFromCart(menu.menuId) - ); - }} - > -
e.stopPropagation()} - className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 min-w-[calc(100%-35px)] max-w-[430px] bg-white rounded-[20px] px-[22px] pt-[30px] pb-[22px]" - > -

- 현재{" "} - {soldOutMenus?.map((menu: CartType, idx: number) => ( - - {menu.name} - {idx < soldOutMenus.length - 1 && ", "} - - ))} - 는(은) 품절 상태입니다. -

- -
-
-
+ + {modal.isOpen && soldOutMenus && soldOutMenus!.length > 0 && ( + )}
); diff --git a/apps/nowait-user/src/pages/order/orderList/components/CartItem.tsx b/apps/nowait-user/src/pages/order/orderList/components/CartItem.tsx index 702aeeff..ea29aca1 100644 --- a/apps/nowait-user/src/pages/order/orderList/components/CartItem.tsx +++ b/apps/nowait-user/src/pages/order/orderList/components/CartItem.tsx @@ -4,14 +4,20 @@ import { useCartStore } from "../../../../stores/cartStore"; import { motion } from "framer-motion"; interface PropsType { - id: number; + menuId: number; name: string; originPrice: number; price: number; quantity: number; } -const CartItem = ({ id, name, originPrice, price, quantity }: PropsType) => { +const CartItem = ({ + menuId, + name, + originPrice, + price, + quantity, +}: PropsType) => { const { removeFromCart, increaseQuantity, decreaseQuantity } = useCartStore(); return ( { {originPrice.toLocaleString()}원
-
{ + const navigate = useNavigate(); + + return ( + + + {cart.map((item) => ( + + ))} + + navigate(`/${storeId}`, { state: { isBack: true } })} + className="py-5 border-none" + > + 메뉴 추가하기 + + + + + + + + ); +}; + +export default CartList; diff --git a/apps/nowait-user/src/pages/order/orderList/components/OrderFooter.tsx b/apps/nowait-user/src/pages/order/orderList/components/OrderFooter.tsx new file mode 100644 index 00000000..5b1fbdf0 --- /dev/null +++ b/apps/nowait-user/src/pages/order/orderList/components/OrderFooter.tsx @@ -0,0 +1,19 @@ +import { Button } from "@repo/ui"; +import PageFooterButton from "../../../../components/order/PageFooterButton"; +import TotalButton from "../../../../components/order/TotalButton"; + +interface Props { + onOrder: () => void; +} + +const OrderFooter = ({ onOrder }: Props) => { + return ( + + + + ); +}; + +export default OrderFooter; diff --git a/apps/nowait-user/src/pages/order/orderList/components/SoldOutModal.tsx b/apps/nowait-user/src/pages/order/orderList/components/SoldOutModal.tsx new file mode 100644 index 00000000..e96f1421 --- /dev/null +++ b/apps/nowait-user/src/pages/order/orderList/components/SoldOutModal.tsx @@ -0,0 +1,48 @@ +import { Button } from "@repo/ui"; +import Portal from "../../../../components/common/modal/Portal"; +import type { CartType } from "../../../../types/order/cart"; + +interface Props { + menus: CartType[]; + onConfirm: () => void; +} + +const SoldOutModal = ({ menus, onConfirm }: Props) => { + return ( + +
{ + onConfirm(); + }} + > +
e.stopPropagation()} + className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 min-w-[calc(100%-35px)] max-w-[430px] bg-white rounded-[20px] px-[22px] pt-[30px] pb-[22px]" + > +

+ 현재{" "} + {menus?.map((menu: CartType, idx: number) => ( + + {menu.name} + {idx < menus.length - 1 && ", "} + + ))} + 는(은) 품절 상태입니다. +

+ +
+
+
+ ); +}; + +export default SoldOutModal; diff --git a/apps/nowait-user/src/pages/order/orderList/hooks/useOrderListLogic.ts b/apps/nowait-user/src/pages/order/orderList/hooks/useOrderListLogic.ts new file mode 100644 index 00000000..dc3898c9 --- /dev/null +++ b/apps/nowait-user/src/pages/order/orderList/hooks/useOrderListLogic.ts @@ -0,0 +1,70 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { useCartStore } from "../../../../stores/cartStore"; +import useModal from "../../../../hooks/useModal"; +import { useStoreMenuList } from "../../../../hooks/order/useStoreMenus"; +import type { CartType } from "../../../../types/order/cart"; +import { getSoldOutMenusInCart } from "../../../../utils/checkSoldOutMenus"; + +export const useOrderListLogic = () => { + const navigate = useNavigate(); + const { storeId } = useParams(); + const { cart, removeFromCart } = useCartStore(); + const modal = useModal(); + + const { data: menus } = useStoreMenuList(storeId); + + const [soldOutMenus, setSoldOutMenus] = useState(); + const [isAnimatingOut, setIsAnimatingOut] = useState(false); + + // 스크롤 초기화 + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + // EmptyCart 애니메이션 타이밍 제어 + useEffect(() => { + let timer: number | undefined; + + if (cart.length === 0 && !isAnimatingOut) { + timer = window.setTimeout(() => setIsAnimatingOut(true), 350); + } + + if (cart.length > 0 && isAnimatingOut) { + setIsAnimatingOut(false); + } + + return () => { + if (timer) clearTimeout(timer); + }; + }, [cart, isAnimatingOut]); + + const handleOrder = () => { + if (!menus) return; + + const soldOut = getSoldOutMenusInCart(cart, menus); + + if (soldOut.length > 0) { + setSoldOutMenus(soldOut); + modal.open(); + return; + } + + navigate(`/${storeId}/remittance`); + }; + + const handleRemoveSoldOutMenus = () => { + soldOutMenus?.forEach((menu) => removeFromCart(menu.menuId)); + modal.close(); + }; + + return { + cart, + storeId, + isAnimatingOut, + modal, + soldOutMenus, + handleOrder, + handleRemoveSoldOutMenus, + }; +}; diff --git a/apps/nowait-user/src/pages/order/remittenceWait/RemittanceWaitPage.tsx b/apps/nowait-user/src/pages/order/remittenceWait/RemittanceWaitPage.tsx index 4892e397..0c46e357 100644 --- a/apps/nowait-user/src/pages/order/remittenceWait/RemittanceWaitPage.tsx +++ b/apps/nowait-user/src/pages/order/remittenceWait/RemittanceWaitPage.tsx @@ -14,44 +14,48 @@ import { useFallbackImage } from "../../../hooks/useFallbackImage"; const RemittanceWaitPage = () => { const navigate = useNavigate(); const location = useLocation(); - const payer = location.state as string; const { storeId } = useParams(); + const tableId = localStorage.getItem("tableId"); + const payer = location.state as string; + const { cart, clearCart } = useCartStore(); const { showToast } = useToastStore(); - const totalPrice = sumTotalPrice(cart); + const [isLoading, setIsLoading] = useState(false); // 중복 요청 방지 const { isLoaded, loadedSrc } = useFallbackImage(remittanceWait); + const totalPrice = sumTotalPrice(cart); + + const payload = { + depositorName: payer, + items: cart.map((item) => ({ + menuId: item.menuId, + quantity: item.quantity, + })), + totalPrice, + }; + const orderButton = async () => { + if (isLoading) return; try { setIsLoading(true); - const payload = { - depositorName: payer, - items: cart.map((item) => ({ - menuId: item.menuId, - quantity: item.quantity, - })), - totalPrice, - }; const res = await createOrder(storeId!, Number(tableId!), payload); - console.log(res, "주문 생성"); - if (res?.success) { - //입금자명 로컬스토리지 저장 - localStorage.setItem("depositorName", res.response.depositorName); - //장바구니 비우기 - clearCart(); - navigate(`/${storeId}/order/success`, { replace: true }); - } else { - // 서버가 success:false 반환한 경우 - console.error("주문 실패:", res); + if (!res?.success) { showToast("주문에 실패했습니다. 다시 시도해 주세요"); return; } + + //입금자명 로컬스토리지 저장 + localStorage.setItem("depositorName", res.response.depositorName); + //장바구니 비우기 + clearCart(); + navigate(`/${storeId}/order/success`, { replace: true }); } catch (error) { console.log(error); + showToast("주문 처리 중 오류가 발생했습니다"); } finally { - setIsLoading(true); + setIsLoading(false); } }; @@ -66,7 +70,6 @@ const RemittanceWaitPage = () => { > 입금 대기중인 이미지 { const navigate = useNavigate(); const { id: storeId } = useParams(); + const { data: menus, isLoading: menusIsLoading } = useStoreMenus(storeId); const { data: store, - isLoading, + isLoading: storeIsLoading, isError, } = useQuery({ queryKey: ["store", storeId], @@ -33,12 +34,6 @@ const StoreDetailPage = () => { select: (data) => data?.response, }); - const { data: menus, isLoading: menusIsLoading } = useQuery({ - queryKey: ["storeMenus", storeId], - queryFn: () => getStoreMenus(storeId!), - select: (data) => data?.response, - }); - //맨위로 위치 초기화 useEffect(() => { window.scrollTo(0, 0); @@ -61,7 +56,7 @@ const StoreDetailPage = () => { console.log(error); } }; - if (isLoading) return ; + if (storeIsLoading) return ; if (isError) return ; return (
diff --git a/apps/nowait-user/src/routes/Router.tsx b/apps/nowait-user/src/routes/Router.tsx index e384e4ab..adb14e28 100644 --- a/apps/nowait-user/src/routes/Router.tsx +++ b/apps/nowait-user/src/routes/Router.tsx @@ -3,7 +3,7 @@ import HomePage from "../pages/home/HomePage"; import WaitingSuccessPage from "../pages/waiting/waitingSuccess/WaitingSuccessPage"; import MapPage from "../pages/waiting/boothMap/MapPage"; import StoreDetailPage from "../pages/waiting/storeDetail/StoreDetailPage"; -import RedirectToStorePage from "../pages/order/home/RedirectToStorePage"; +import RedirectToStorePage from "../pages/order/home/components/RedirectToStorePage"; import StorePage from "../pages/order/home/StorePage"; import OrderListPage from "../pages/order/orderList/OrderListPage"; import OrderSuccessPage from "../pages/order/orderSuccess/OrderSuccessPage";