+
{name}
{status === "open" && (waitingCount || 0) > 0 ? (
diff --git a/apps/nowait-user/src/pages/home/components/MyWaitingCard.tsx b/apps/nowait-user/src/pages/home/components/MyWaitingCard.tsx
index 7756bcb2..a26a3956 100644
--- a/apps/nowait-user/src/pages/home/components/MyWaitingCard.tsx
+++ b/apps/nowait-user/src/pages/home/components/MyWaitingCard.tsx
@@ -1,5 +1,6 @@
-import { useState, useEffect, memo, useCallback, useMemo } from "react";
+import { useState, useEffect, memo, useCallback } from "react";
import { MemoizedRefresh } from "../../../components/icons/MemoizedIcons";
+import { useTimerStore } from "../../../stores/timerStore";
// 대기 중인 주점 데이터 타입
interface WaitingStoreData {
@@ -35,15 +36,12 @@ const MyWaitingCard = memo(
}: MyWaitingCardProps) => {
const [startX, setStartX] = useState(0);
const [isDragging, setIsDragging] = useState(false);
- const [timeLeft, setTimeLeft] = useState(0); // 현재 남은 시간 (초, CALLING 상태일 때만 설정)
- const totalSlides = waitingStores?.length || 0;
+ // 전역 타이머 상태 사용
+ const { setTimer, updateTimer, getTimeLeft, isTimerActive } =
+ useTimerStore();
- // 슬라이드별 초기 시간 (초 단위) - 모두 10분으로 통일
- const initialTimes = useMemo(
- () => Array(totalSlides).fill(600),
- [totalSlides]
- );
+ const totalSlides = waitingStores?.length || 0;
// 시간을 MM:SS 형식으로 포맷팅
const formatTime = useCallback((seconds: number) => {
@@ -57,31 +55,37 @@ const MyWaitingCard = memo(
// 현재 슬라이드의 데이터 가져오기
const currentStore = waitingStores[currentSlide];
- // 슬라이드가 변경될 때 타이머 리셋 (CALLING 상태일 때만)
+ // 현재 store의 남은 시간 가져오기
+ const timeLeft = currentStore ? getTimeLeft(currentStore.storeId) : 0;
+
+ // 슬라이드가 변경될 때 타이머 초기화 (CALLING 상태일 때만)
useEffect(() => {
- if (currentStore?.status === "CALLING") {
- setTimeLeft(initialTimes[currentSlide] || 600);
+ if (currentStore?.status === "CALLING" && currentStore?.storeId) {
+ // 타이머가 없거나 비활성화된 경우에만 새로 설정
+ if (!isTimerActive(currentStore.storeId)) {
+ setTimer(currentStore.storeId, 600); // 10분으로 설정
+ }
}
- }, [currentSlide, initialTimes, currentStore?.status]);
-
- // 타이머 로직 (CALLING 상태일 때만 작동)
+ }, [
+ currentSlide,
+ currentStore?.status,
+ currentStore?.storeId,
+ setTimer,
+ isTimerActive,
+ ]);
+
+ // 타이머 업데이트 로직 (CALLING 상태일 때만 작동)
useEffect(() => {
- // CALLING 상태가 아니면 타이머 비활성화
- if (currentStore?.status !== "CALLING" || timeLeft <= 0) {
+ if (currentStore?.status !== "CALLING" || !currentStore?.storeId) {
return;
}
const timer = setInterval(() => {
- setTimeLeft((prev: number) => {
- if (prev <= 1) {
- return 0;
- }
- return prev - 1;
- });
+ updateTimer(currentStore.storeId);
}, 1000);
return () => clearInterval(timer);
- }, [timeLeft, currentStore?.status]);
+ }, [currentStore?.status, currentStore?.storeId, updateTimer]);
// 터치 시작
const handleTouchStart = useCallback((e: React.TouchEvent) => {
diff --git a/apps/nowait-user/src/stores/timerStore.ts b/apps/nowait-user/src/stores/timerStore.ts
new file mode 100644
index 00000000..e3cac4ea
--- /dev/null
+++ b/apps/nowait-user/src/stores/timerStore.ts
@@ -0,0 +1,117 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+
+interface TimerState {
+ // 각 store별 타이머 상태를 저장
+ timers: Record<
+ number,
+ {
+ timeLeft: number;
+ startTime: number; // 타이머가 시작된 시간 (timestamp)
+ isActive: boolean;
+ }
+ >;
+
+ // 타이머 설정 (10분 = 600초)
+ setTimer: (storeId: number, timeLeft: number) => void;
+
+ // 타이머 업데이트 (1초씩 감소)
+ updateTimer: (storeId: number) => void;
+
+ // 타이머 초기화
+ resetTimer: (storeId: number) => void;
+
+ // 특정 store의 남은 시간 가져오기
+ getTimeLeft: (storeId: number) => number;
+
+ // 타이머가 활성화되어 있는지 확인
+ isTimerActive: (storeId: number) => boolean;
+
+ // 모든 타이머 정리
+ clearAllTimers: () => void;
+}
+
+export const useTimerStore = create
()(
+ persist(
+ (set, get) => ({
+ timers: {},
+
+ setTimer: (storeId: number, timeLeft: number) => {
+ set((state) => ({
+ timers: {
+ ...state.timers,
+ [storeId]: {
+ timeLeft,
+ startTime: Date.now(),
+ isActive: true,
+ },
+ },
+ }));
+ },
+
+ updateTimer: (storeId: number) => {
+ set((state) => {
+ const timer = state.timers[storeId];
+ if (!timer || !timer.isActive) return state;
+
+ const elapsed = Math.floor((Date.now() - timer.startTime) / 1000);
+ const newTimeLeft = Math.max(0, timer.timeLeft - elapsed);
+
+ return {
+ timers: {
+ ...state.timers,
+ [storeId]: {
+ ...timer,
+ timeLeft: newTimeLeft,
+ startTime: Date.now(), // 새로운 기준점 설정
+ isActive: newTimeLeft > 0,
+ },
+ },
+ };
+ });
+ },
+
+ resetTimer: (storeId: number) => {
+ set((state) => ({
+ timers: {
+ ...state.timers,
+ [storeId]: {
+ timeLeft: 600, // 10분으로 초기화
+ startTime: Date.now(),
+ isActive: true,
+ },
+ },
+ }));
+ },
+
+ getTimeLeft: (storeId: number) => {
+ const timer = get().timers[storeId];
+ if (!timer || !timer.isActive) return 0;
+
+ const elapsed = Math.floor((Date.now() - timer.startTime) / 1000);
+ return Math.max(0, timer.timeLeft - elapsed);
+ },
+
+ isTimerActive: (storeId: number) => {
+ const timer = get().timers[storeId];
+ return timer?.isActive && timer.timeLeft > 0;
+ },
+
+ clearAllTimers: () => {
+ set({ timers: {} });
+ },
+ }),
+ {
+ name: "timer-storage",
+ // 타이머는 새로고침 시에도 유지되지만, 너무 오래된 타이머는 정리
+ partialize: (state) => ({
+ timers: Object.fromEntries(
+ Object.entries(state.timers).filter(([_, timer]) => {
+ const elapsed = Math.floor((Date.now() - timer.startTime) / 1000);
+ return timer.timeLeft - elapsed > 0;
+ })
+ ),
+ }),
+ }
+ )
+);
diff --git a/apps/nowait-user/src/types/search.ts b/apps/nowait-user/src/types/search.ts
index 54ef39d5..c4f045da 100644
--- a/apps/nowait-user/src/types/search.ts
+++ b/apps/nowait-user/src/types/search.ts
@@ -1,13 +1,18 @@
+export interface ProfileImage {
+ id: number;
+ storeId: number;
+ imageUrl: string;
+ imageType: string;
+}
+
export interface SearchStore {
storeId: number;
- publicCode : string;
+ publicCode: string;
departmentId: number;
departmentName: string;
name: string;
- location: string;
- description: string;
- profileImage: string | null;
- bannerImages: any[];
+ openTime: string;
+ profileImage: ProfileImage | null;
isActive: boolean;
deleted: boolean;
createdAt: string;