diff --git a/apps/nowait-admin/src/hooks/useGetReservationList.tsx b/apps/nowait-admin/src/hooks/useGetReservationList.tsx index 252efcc3..40b7481f 100644 --- a/apps/nowait-admin/src/hooks/useGetReservationList.tsx +++ b/apps/nowait-admin/src/hooks/useGetReservationList.tsx @@ -1,5 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import userApi from "../utils/UserApi"; +import adminApi from "../utils/AdminApi"; export interface Reservation { id: number; @@ -27,11 +27,14 @@ const fetchReservations = async ( storeId: number ): Promise => { const token = localStorage.getItem("adminToken"); - const res = await userApi.get(`/reservations/admin/${storeId}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + const res = await adminApi.get( + `/reservations/admin/${storeId}`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); return res.data.response; }; diff --git a/apps/nowait-admin/src/hooks/usePostAdminLogin.tsx b/apps/nowait-admin/src/hooks/usePostAdminLogin.tsx index 82444f80..2893ba0f 100644 --- a/apps/nowait-admin/src/hooks/usePostAdminLogin.tsx +++ b/apps/nowait-admin/src/hooks/usePostAdminLogin.tsx @@ -1,9 +1,9 @@ // hooks/useLoginMutation.ts import { useMutation } from "@tanstack/react-query"; -import userApi from "../utils/UserApi"; +import adminApi from "../utils/AdminApi"; const postLogin = (data: { email: string; password: string }) => { - return userApi.post(`/admin/users/login`, data); + return adminApi.post(`/admin/users/login`, data); }; export const usePostLoginMutation = () => { diff --git a/apps/nowait-admin/src/hooks/useUpdateReservationStatus.tsx b/apps/nowait-admin/src/hooks/useUpdateReservationStatus.tsx index 25a6c0e6..f9a9eb45 100644 --- a/apps/nowait-admin/src/hooks/useUpdateReservationStatus.tsx +++ b/apps/nowait-admin/src/hooks/useUpdateReservationStatus.tsx @@ -1,5 +1,5 @@ import { useMutation } from "@tanstack/react-query"; -import UserApi from "../utils/UserApi"; +import AdminApi from "../utils/AdminApi"; export const useUpdateReservationStatus = () => { const token = localStorage.getItem("adminToken"); @@ -11,7 +11,7 @@ export const useUpdateReservationStatus = () => { reservationId: number; status: "WAITING" | "CALLING" | "CONFIRMED" | "CANCELLED" | "NO_SHOW"; }) => { - const res = await UserApi.patch( + const res = await AdminApi.patch( `/reservations/admin/updates/${reservationId}`, { status }, { diff --git a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx index 9b002a3f..eac0e0b3 100644 --- a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx +++ b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx @@ -9,12 +9,7 @@ import onIcon from "../../assets/toggleOn.svg"; // 켜짐 상태 이미지 import offIcon from "../../assets/toggleOFF.svg"; import { useWindowWidth } from "../../hooks/useWindowWidth"; import { useUpdateReservationStatus } from "../../hooks/useUpdateReservationStatus"; -type WaitingStatus = - | "WAITING" - | "CALLING" - | "CONFIRMED" - | "CANCELLED" - | "NO_SHOW"; +type WaitingStatus = "WAITING" | "CALLING" | "CONFIRMED" | "CANCELLED"; interface Reservation { id: number; @@ -30,6 +25,7 @@ interface Reservation { const AdminHome = () => { const width = useWindowWidth(); + const [noShowIds, setNoShowIds] = useState([]); const { mutate: updateStatus } = useUpdateReservationStatus(); console.log(width); @@ -43,6 +39,20 @@ const AdminHome = () => { const { data, isLoading, isError } = useGetReservationList(storeId); const toggle = () => setIsOn((prev) => !prev); + + const waitingCount = reservations.filter( + (res) => res.status === "WAITING" + ).length; + const callingCount = reservations.filter( + (res) => res.status === "CALLING" + ).length; + const confirmedCount = reservations.filter( + (res) => res.status === "CONFIRMED" + ).length; + const cancelledCount = reservations.filter( + (res) => res.status === "CANCELLED" + ).length; + const statusMap = { WAITING: "대기 중", CALLING: "호출 중", @@ -51,6 +61,7 @@ const AdminHome = () => { }; const filteredReservations = useMemo(() => { + const sorted = [...reservations].sort((a, b) => a.id - b.id); if (activeTab === "전체 보기") return reservations; const targetStatus = Object.entries(statusMap).find( @@ -59,7 +70,7 @@ const AdminHome = () => { if (!targetStatus) return []; - return reservations.filter((res) => res.status === targetStatus); + return sorted.filter((res) => res.status === targetStatus); }, [reservations, activeTab]); // 호출 버튼 클릭 이벤트 @@ -120,16 +131,18 @@ const AdminHome = () => { const handleNoShow = (id: number) => { const target = reservations.find((res) => res.id === id); - if (target?.status === "NO_SHOW") return; + if (!target || target.status === "CANCELLED") return; + updateStatus( - { reservationId: id, status: "NO_SHOW" }, + { reservationId: id, status: "CANCELLED" }, { onSuccess: () => { setReservations((prev) => prev.map((res) => - res.id === id ? { ...res, status: "NO_SHOW" } : res + res.id === id ? { ...res, status: "CANCELLED" } : res ) ); + setNoShowIds((prev) => [...prev, id]); }, } ); @@ -188,8 +201,16 @@ const AdminHome = () => {
- - + +
@@ -239,6 +260,7 @@ const AdminHome = () => { phone="010-****-****" status={res.status} calledAt={res.calledAt} + isNoShow={noShowIds.includes(res.id)} onCall={() => handleCall(res.id)} onEnter={() => handleEnter(res.id)} onClose={() => handleClose(res.id)} diff --git a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx index 518948ab..a9b716db 100644 --- a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx +++ b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx @@ -2,12 +2,9 @@ import CloseButton from "../../../components/closeButton"; import callIcon from "../../../assets/Call.svg"; import openDoorIcon from "../../../assets/door_open.svg"; import { useEffect, useState } from "react"; -type WaitingCardStatus = - | "WAITING" - | "CALLING" - | "CONFIRMED" - | "CANCELLED" - | "NO_SHOW"; + +const totalDurationSec = 600; +type WaitingCardStatus = "WAITING" | "CALLING" | "CONFIRMED" | "CANCELLED"; interface WaitingCardProps { number: number; time: string; @@ -16,7 +13,8 @@ interface WaitingCardProps { name: string; phone: string; status: WaitingCardStatus; - calledAt?: string; + calledAt: string | undefined; + isNoShow: boolean; onCall: () => void; onEnter: () => void; onClose: () => void; @@ -35,34 +33,35 @@ export function WaitingCard({ phone, status, calledAt, + isNoShow, onCall, onEnter, onClose, onNoShow, }: WaitingCardProps) { - const [currentStatus, setCurrentStatus] = useState(status); - const [elapsed, setElapsed] = useState("00:00"); + const [elapsed, setElapsed] = useState("10:00"); useEffect(() => { - if (status === "CALLING" && currentStatus !== "NO_SHOW") { + if (status === "CALLING") { const timer = setTimeout(() => { - setCurrentStatus("NO_SHOW"); onNoShow(); - }, 10 * 60 * 1000); + }, 10 * 1 * 1000); return () => clearTimeout(timer); } - }, [status, currentStatus]); + }, [status]); useEffect(() => { - if (currentStatus === "CALLING" && calledAt) { + if (status === "CALLING" && calledAt) { const start = new Date(calledAt).getTime(); const updateElapsed = () => { const now = Date.now(); const diffSec = Math.floor((now - start) / 1000); - const min = String(Math.floor(diffSec / 60)).padStart(2, "0"); - const sec = String(diffSec % 60).padStart(2, "0"); + const remainingSec = Math.max(totalDurationSec - diffSec, 0); // 음수 방지 + + const min = String(Math.floor(remainingSec / 60)).padStart(2, "0"); + const sec = String(remainingSec % 60).padStart(2, "0"); setElapsed(`${min}:${sec}`); }; @@ -70,7 +69,7 @@ export function WaitingCard({ const interval = setInterval(updateElapsed, 1000); return () => clearInterval(interval); } - }, [currentStatus, calledAt]); + }, [status, calledAt]); return (
{/* 헤더 */} @@ -112,7 +111,7 @@ export function WaitingCard({ {/* 버튼 영역 */}
- {currentStatus === "WAITING" && ( + {status === "WAITING" && ( <> )} + {status === "CANCELLED" && + (isNoShow === true ? ( +
+ 미입장 +
+ ) : ( +
+ 취소된 입장 +
+ ))} - {currentStatus === "NO_SHOW" && ( -
- 미입장 -
- )} - - {currentStatus === "CONFIRMED" && ( + {status === "CONFIRMED" && (
완료된 입장
)} - - {currentStatus === "CANCELLED" && ( -
- 취소된 입장 -
- )}
); diff --git a/apps/nowait-admin/src/utils/AdminApi.tsx b/apps/nowait-admin/src/utils/AdminApi.tsx new file mode 100644 index 00000000..cc04263f --- /dev/null +++ b/apps/nowait-admin/src/utils/AdminApi.tsx @@ -0,0 +1,12 @@ +import axios from "axios"; + +const API_URL = import.meta.env.VITE_ADMIN_API_URL; +// 자유게시판 전체 데이터 +const AdminApi = axios.create({ + baseURL: API_URL, + headers: { + "Content-Type": "application/json", + }, +}); + +export default AdminApi; diff --git a/apps/nowait-admin/src/utils/UserApi.tsx b/apps/nowait-admin/src/utils/UserApi.tsx index aaa46f9d..c0a7976d 100644 --- a/apps/nowait-admin/src/utils/UserApi.tsx +++ b/apps/nowait-admin/src/utils/UserApi.tsx @@ -1,6 +1,6 @@ import axios from "axios"; -const API_URL = import.meta.env.VITE_API_URL; +const API_URL = import.meta.env.VITE_USER_API_URL; // 자유게시판 전체 데이터 const UserApi = axios.create({ baseURL: API_URL, @@ -9,12 +9,4 @@ const UserApi = axios.create({ }, }); -// UserApi.interceptors.request.use((config) => { -// const token = localStorage.getItem("adminToken"); // 또는 context에서 가져오기 -// if (token) { -// config.headers.Authorization = `Bearer ${token}`; -// } -// return config; -// }); - export default UserApi;