Skip to content
Closed
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
14 changes: 10 additions & 4 deletions apps/nowait-admin/src/components/NewOrderToast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,16 @@ export default function NewOrderToast() {
onTap={() => {
const params = new URLSearchParams();
params.set("order", String(t.orderId));
navigate({
pathname: `/admin/orders/${storeId}`,
search: params.toString(),
});
params.set("status", t.status);
navigate(
{
pathname: `/admin/orders/${storeId}`,
search: params.toString(),
},
{ replace: true }
);

removeToast(t.id);
}}
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,6 @@ const MenuSection = ({ isTablet }: { isTablet: boolean }) => {
);

return editMode ? (
// ✅ 편집 모드: SwipeableRow 사용 안 함 (충돌 차단)
<div
ref={provided.innerRef}
{...provided.draggableProps}
Expand Down
36 changes: 15 additions & 21 deletions apps/nowait-admin/src/pages/AdminOrders/DetailCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ const DetailCard = ({
setShowModal(false);
};

const handleModalSuccess = () => {
setShowModal(false);
onSuccess?.();
onClose(); // 디테일 화면도 닫기
};

// 타입에 따른 설정
const getConfig = () => {
if (type === "payment") {
Expand Down Expand Up @@ -168,27 +174,15 @@ const DetailCard = ({
className="fixed inset-0 bg-black/30 flex items-center justify-center z-50"
onClick={handleCloseModal}
>
{type === "cooking" ? (
<ModalComponent
orderId={orderId}
tableNumber={tableNumber}
depositorName={depositorName}
totalAmount={totalAmount}
timeText={timeText}
onClose={handleCloseModal}
onSuccess={onSuccess}
/>
) : (
<ModalComponent
orderId={orderId}
tableNumber={tableNumber}
depositorName={depositorName}
totalAmount={totalAmount}
timeText={timeText}
onClose={handleCloseModal}
onSuccess={onSuccess}
/>
)}
<ModalComponent
orderId={orderId}
tableNumber={tableNumber}
depositorName={depositorName}
totalAmount={totalAmount}
timeText={timeText}
onClose={handleCloseModal}
onSuccess={handleModalSuccess}
/>
</div>
)}
</>
Expand Down
3 changes: 2 additions & 1 deletion apps/nowait-admin/src/pages/AdminOrders/OrderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ const CookCard = ({
return 7; // sm
};

const handleCookCompleteClick = () => {
const handleCookCompleteClick = (e: React.MouseEvent) => {
e.stopPropagation(); // 부모 onClick 이벤트 방지
setShowCookCompleteModal(true);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const SearchModal = ({ isOpen, onClose }: SearchModalProps) => {
{store.profileImage ? (
<img
alt={`${store.name} 주점 이미지`}
src={store.profileImage}
src={store.profileImage?.imageUrl}
className="w-full h-full object-cover rounded-full"
/>
) : (
Expand Down
4 changes: 2 additions & 2 deletions apps/nowait-user/src/pages/home/components/MainCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface WaitingCardProps {
interface StoreCardProps {
type: "store";
storeId: number;
publicCode : string;
publicCode: string;
name: string;
departmentName: string;
profileImageUrl: string;
Expand Down Expand Up @@ -319,7 +319,7 @@ const StoreCardComponent = ({
</div>
<div className="flex flex-col flex-1 min-w-0">
<div className="flex flex-row gap-2 items-center min-w-0">
<div className="text-title-16-semibold text-black-90 text-start truncate flex-shrink min-w-0">
<div className="text-title-16-semibold text-black-90 text-start truncate flex-shrink max-sm:max-w-[200px]">
{name}
</div>
{status === "open" && (waitingCount || 0) > 0 ? (
Expand Down
50 changes: 27 additions & 23 deletions apps/nowait-user/src/pages/home/components/MyWaitingCard.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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) => {
Expand All @@ -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) => {
Expand Down
117 changes: 117 additions & 0 deletions apps/nowait-user/src/stores/timerStore.ts
Original file line number Diff line number Diff line change
@@ -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<TimerState>()(
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;
})
),
}),
}
)
);
15 changes: 10 additions & 5 deletions apps/nowait-user/src/types/search.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down