Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions apps/nowait-admin/src/hooks/useGetReservationList.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -27,11 +27,14 @@ const fetchReservations = async (
storeId: number
): Promise<ReservationResponse> => {
const token = localStorage.getItem("adminToken");
const res = await userApi.get<ApiResponse>(`/reservations/admin/${storeId}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const res = await adminApi.get<ApiResponse>(
`/reservations/admin/${storeId}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
return res.data.response;
};

Expand Down
4 changes: 2 additions & 2 deletions apps/nowait-admin/src/hooks/usePostAdminLogin.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => {
Expand Down
4 changes: 2 additions & 2 deletions apps/nowait-admin/src/hooks/useUpdateReservationStatus.tsx
Original file line number Diff line number Diff line change
@@ -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");

Expand All @@ -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 },
{
Expand Down
46 changes: 34 additions & 12 deletions apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +25,7 @@ interface Reservation {

const AdminHome = () => {
const width = useWindowWidth();
const [noShowIds, setNoShowIds] = useState<number[]>([]);
const { mutate: updateStatus } = useUpdateReservationStatus();

console.log(width);
Expand All @@ -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: "호출 중",
Expand All @@ -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(
Expand All @@ -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]);

// 호출 버튼 클릭 이벤트
Expand Down Expand Up @@ -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]);
},
}
);
Expand Down Expand Up @@ -188,8 +201,16 @@ const AdminHome = () => {
</button>
</div>
<div className="flex [@media(min-width:375px)_and_(max-width:431px)]:justify-between">
<CardBox title="대기 팀 수" count={14} bottomLabel="호출 중 3팀" />
<CardBox title="입장 완료" count={6} bottomLabel="대기 취소 1팀" />
<CardBox
title="대기 팀 수"
count={waitingCount}
bottomLabel={`호출 중 ${callingCount}팀`}
/>
<CardBox
title="입장 완료"
count={confirmedCount}
bottomLabel={`대기 취소 ${cancelledCount}팀`}
/>
</div>
</div>
</section>
Expand Down Expand Up @@ -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)}
Expand Down
61 changes: 29 additions & 32 deletions apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -35,42 +33,43 @@ 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}`);
};

updateElapsed(); // 최초 계산
const interval = setInterval(updateElapsed, 1000);
return () => clearInterval(interval);
}
}, [currentStatus, calledAt]);
}, [status, calledAt]);
return (
<div className="[@media(max-width:431px)]:w-[335px] [@media(min-width:768px)_and_(max-width:821px)]:w-[329px] relative lg:w-[372px] h-[200px] bg-white rounded-[16px] px-6 py-[18px]">
{/* 헤더 */}
Expand Down Expand Up @@ -112,7 +111,7 @@ export function WaitingCard({

{/* 버튼 영역 */}
<div className="flex justify-between">
{currentStatus === "WAITING" && (
{status === "WAITING" && (
<>
<button
onClick={onCall}
Expand All @@ -129,7 +128,7 @@ export function WaitingCard({
</>
)}

{currentStatus === "CALLING" && (
{status === "CALLING" && (
<>
<div className="w-[60%] bg-black-15 text-black-60 py-2 rounded-[8px] flex justify-center items-center gap-1">
⏱ {elapsed}
Expand All @@ -142,24 +141,22 @@ export function WaitingCard({
</button>
</>
)}
{status === "CANCELLED" &&
(isNoShow === true ? (
<div className="w-full bg-black-5 text-black-40 rounded-[8px] flex justify-center items-center py-2">
미입장
</div>
) : (
<div className="w-full bg-black-5 text-black-40 rounded-[8px] flex justify-center items-center py-2">
취소된 입장
</div>
))}

{currentStatus === "NO_SHOW" && (
<div className="w-full bg-black-5 text-black-40 rounded-[8px] flex justify-center items-center py-2">
미입장
</div>
)}

{currentStatus === "CONFIRMED" && (
{status === "CONFIRMED" && (
<div className="w-full bg-black-5 text-black-40 rounded-[8px] flex justify-center items-center py-2">
완료된 입장
</div>
)}

{currentStatus === "CANCELLED" && (
<div className="w-full bg-black-5 text-black-40 rounded-[8px] flex justify-center items-center py-2">
취소된 입장
</div>
)}
</div>
</div>
);
Expand Down
12 changes: 12 additions & 0 deletions apps/nowait-admin/src/utils/AdminApi.tsx
Original file line number Diff line number Diff line change
@@ -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;
10 changes: 1 addition & 9 deletions apps/nowait-admin/src/utils/UserApi.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;