feat(console): 점주 멀티매장 URL 모델(/{storeId}) 전환 및 매장 스위처 도입#80
Conversation
- COOKIE_TABLE에 LAST_ACCESSED_STORE_ID 추가하여 서버에서도 읽을 수 있도록 함 - localStorage.const 상수 및 index export 제거 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SIDEBAR_WIDTH 8rem→12rem: 사이드바 헤더 매장명 노출 대응 - TouchEventButton에 swallowEvent 옵션 추가하여 stopPropagation 여부를 선택 가능하도록 함 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
한 점주가 여러 매장을 관리할 수 있도록 매장 스코프를 URL로 통일한다.
- 라우트 재구성: orders·dashboard·tables·menus를 /{storeId}/* 하위로 이동
- 루트(/) 진입 리졸버 StoreEntryRedirect(RSC): 매장 목록 조회 후 마지막 접속 매장(쿠키) 또는 첫 매장의 /{storeId}/orders로 서버 리다이렉트
- [storeId]/layout: /stores/v1/{storeId} 조회로 매장 소유·유효성 검증, PersistLastStore로 마지막 접속 매장 쿠키 기록
- 사이드바 헤더 StoreSwitcher: 현재 매장명 노출, 매장 2개 이상 시 드롭다운 전환
- sidebarList→sidebarGroup 재구성 및 SidebarItemGroups가 useParams로 /{storeId}/{segment} 링크 생성
- next.config의 / → /stores 리다이렉트 제거, 로그인·로고 이동 경로를 /로 정리
- OrderNoticeDaemon·useStoreOrderSyncDaemon을 useParams storeId 기반으로 단순화(localStorage fallback 제거)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughChanges이번 변경은 Console의 매장 진입·사이드바 구조를 개편하고, 주문 보드와 주문 상세·추가 주문·결제 UI를 새로 구성합니다. React Query 캐시 동기화, 실시간 주문 이벤트 처리, 마지막 매장 쿠키 저장도 포함됩니다. Console 주문 관리
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (6)
apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts (1)
21-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
storeId타입을string으로 좁힌 후 잔존한 dead code guard를 제거해 주세요.
storeIdparameter 타입이string | undefined에서string으로 변경되었으므로, Line 47의if (!storeId) return;guard는 더 이상 도달할 수 없는 분기입니다. 혼란을 방지하기 위해 제거를 권장합니다.♻️ 제안: 불필요한 guard 제거
useEffect(() => { - if (!storeId) return; - const socket = getRealtimeSocket(); subscribeAdmin(storeId);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts` around lines 21 - 47, Remove the unreachable `if (!storeId) return;` guard from the second `useEffect` in the store order sync daemon, since `storeId` is typed as `string`; leave the remaining effect logic unchanged.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx (1)
19-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win결제 로직을 custom hook으로 추출하는 것을 권장합니다.
PaymentDialogControlbar에 상태 관리(isError), 비동기 결제 로직(payment,tryPayment), abort 처리가 혼재되어 있습니다. coding guidelines에 따라 복잡한 로직은 custom hook으로 분리하는 것을 권장합니다.As per coding guidelines,
apps/{order,console}/src/**/*.{ts,tsx}: "suggest custom hooks for complex logic when appropriate".♻️ 제안: usePayment hook 추출
// usePayment.ts export function usePayment(setOpen: (value: SetStateAction<boolean>) => void) { const [isError, setIsError] = useState(false); const payment = async ( signal: AbortSignal, e: React.MouseEvent<HTMLButtonElement> ) => { e.preventDefault(); const result: HealthCheckResponse = await httpMe.fetchHealthCheck({ signal }); return result; }; const paymentTransaction = useCancelableAsync< [React.MouseEvent<HTMLButtonElement>], HealthCheckResponse >(payment); const tryPayment = async (e: React.MouseEvent<HTMLButtonElement>) => { try { setIsError(false); await paymentTransaction(e); setOpen(false); } catch { setIsError(true); } }; return { tryPayment, abort: paymentTransaction.abort, isPending: paymentTransaction.isPending, isError }; }export default function PaymentDialogControlbar({ children, setOpen, }: PaymentDialogControlbarProps) { - const [isError, setIsError] = useState(false); - - const payment = async (...) => { ... }; - const paymentTransaction = useCancelableAsync(...); - const tryPayment = async (...) => { ... }; + const { tryPayment, abort, isPending, isError } = usePayment(setOpen); return ( <> - <AlertDialogCancel onClick={paymentTransaction.abort}> + <AlertDialogCancel onClick={abort}> 취소 </AlertDialogCancel> <AlertDialogAction onClick={tryPayment} className={isError ? "..." : ""} - disabled={paymentTransaction.isPending} + disabled={isPending} > <ActivityRender - value={!paymentTransaction.isPending} + value={!isPending} fallback={<LoadingTransaction />} > {() => (isError ? "결제 실패, 다시 시도" : children)} </ActivityRender> </AlertDialogAction> </> ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx around lines 19 - 75, Extract the payment state and async behavior from PaymentDialogControlbar into a dedicated usePayment custom hook, including isError, the health-check payment callback, useCancelableAsync integration, tryPayment, abort, and isPending. Keep PaymentDialogControlbar focused on rendering and consume the hook’s returned handlers and state while preserving the existing success, failure, cancellation, and loading behavior.Source: Coding guidelines
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx (1)
20-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win복잡한 로직을 custom hook으로 분리하는 것을 권장합니다.
OrderDetailProvider는 data fetching, mutation 설정, state 관리, derived data 계산, 5개의 action 함수를 포함하고 있습니다. 이 로직을useOrderDetail(params)같은 custom hook으로 분리하면 provider가 간결해지고, 비즈니스 로직의 testability가 향상됩니다.As per coding guidelines,
apps/{order,console}/src/**/*.{ts,tsx}경로의 파일에서는 복잡한 로직을 적절한 custom hook으로 분리할 것을 권장합니다.♻️ 제안하는 custom hook 분리 구조
+// useOrderDetail.ts +export function useOrderDetail(params: { storeId: string; tableId: string }) { + const { storeId, tableId } = params; + const fetchUrl = `/orders/v1/tables/${tableId}/active-session`; + const { data: session, isRefetching } = + useSuspenseWithAuth<ActiveSessionResponse>(fetchUrl); + const { update, remove } = useOrderItem({ storeId, tableId }); + const [editingItem, setEditingItem] = useState<OrderItemWithOrder | null>(null); + // ... derived data, actions ... + return { state, actions, meta }; +} + +// OrderDetailProvider.tsx export function OrderDetailProvider({ params, children }: OrderDetailProviderProps) { - const { storeId, tableId } = params; - const fetchUrl = `/orders/v1/tables/${tableId}/active-session`; - // ... 모든 로직 ... + const contextValue = useOrderDetail(params); return ( <OrderDetailContext.Provider value={contextValue}> {children} </OrderDetailContext.Provider> ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx around lines 20 - 131, Extract the data fetching, mutation setup, state, derived order data, and action functions from OrderDetailProvider into a dedicated useOrderDetail(params) custom hook. Return the existing context value structure from the hook, then keep OrderDetailProvider focused on invoking the hook and passing its result to OrderDetailContext.Provider without changing behavior.Source: Coding guidelines
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx (1)
16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winGrid column 클래스가 두 파일에 중복 정의되어 있습니다.
grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]클래스가OrderTableHeader.tsx의TableRow와OrderTableItem.tsx의TableCell에 각각 하드코딩되어 있습니다. 어느 한쪽만 변경되면 헤더와 바디 컬럼 정렬이 깨집니다.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx#L16-L16: 공통 상수를 import하여 사용하도록 변경apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableItem.tsx#L18-L18: 동일한 공통 상수를 import하여 사용하도록 변경♻️ 제안: 공통 상수 추출
order-table/디렉토리에 공통 상수를 추가하고 양쪽에서 import하여 사용:+// order-table/constants.ts +export const ORDER_TABLE_GRID_CLASS = + "grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]";
OrderTableHeader.tsx:- className="grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]" + className={ORDER_TABLE_GRID_CLASS}
OrderTableItem.tsx:- <TableCell className="grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr] cursor-pointer min-h-16 animate-fade-in-up"> + <TableCell className={`${ORDER_TABLE_GRID_CLASS} cursor-pointer min-h-16 animate-fade-in-up`}>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx at line 16, Extract the duplicated grid column class into a shared constant in the order-table directory, then update OrderTableHeader.tsx:16 and OrderTableItem.tsx:18 to import and use it instead of hardcoding the class. Keep both header and body layouts driven by the same constant.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts (1)
2-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
@spaceorder/db/types대신@spaceorder/db에서 database types를 import해야 합니다. coding guideline에 따르면 database types는@spaceorder/db패키지에서 import해야 하지만, 두 파일 모두@spaceorder/db/typessubpath에서 import하고 있습니다.layout.tsx는@spaceorder/db에서OrderBoardByStore를 import하고 있어 일관성이 깨집니다.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts#L2-L6:ActiveSessionResponse,OrderBoardByStore,PublicTable를@spaceorder/db에서 import하도록 변경apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx#L5-L5:OrderSyncEvent를@spaceorder/db에서 import하도록 변경🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts around lines 2 - 6, Update imports in apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts lines 2-6 so ActiveSessionResponse, OrderBoardByStore, and PublicTable come from `@spaceorder/db` instead of its /types subpath. Also update OrderSyncEvent in apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx line 5 to import from `@spaceorder/db`, preserving the existing symbol usage.Source: Coding guidelines
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx (1)
13-25: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
synchronize함수를useCallback으로 memoize하는 것을 권장합니다.
synchronize는 렌더링마다 새로운 함수 참조를 생성합니다.useStoreOrderSyncDaemon의useEffect의존성 배열에onCreatedAction,onUpdatedAction,onCancelledAction이 포함되어 있어,OrderSyncDaemon이 리렌더링될 때마다 socket listener가 불필요하게 unsubscribe/subscribe됩니다.board query가 invalidate되어
AwaitOrdersSummary가 리렌더링되는 시점(예: reconnect)에 이 cascade가 발생할 수 있으며, unsubscribe/subscribe 사이에 실시간 이벤트가 유실될 가능성이 있습니다.♻️ Proposed fix
+import { useCallback } from "react"; + export default function OrderSyncDaemon() { const params = useParams<{ storeId: string }>(); const queryClient = useQueryClient(); - const synchronize = (event: OrderSyncEvent) => { + const synchronize = useCallback((event: OrderSyncEvent) => { queryClient.invalidateQueries({ queryKey: pathToQueryKey( `/orders/v1/tables/${event.tablePublicId}/active-session` ), }); - }; + }, [queryClient]); useStoreOrderSyncDaemon(params.storeId, {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx around lines 13 - 25, OrderSyncDaemon의 synchronize 콜백을 useCallback으로 memoize하고, 내부에서 사용하는 queryClient를 의존성으로 포함하세요. useStoreOrderSyncDaemon에 전달되는 onCreatedAction, onUpdatedAction, onCancelledAction이 리렌더링마다 새로 생성되지 않도록 기존 synchronize 동작과 query key 생성을 유지하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx:
- Line 45: AddMenuButton의 가격 표시에서 transCurrencyFormat(price) 뒤에 붙는 “원”을 제거하고, 기존
“변경”/“추가” 문구는 그대로 유지하세요. 통화 기호는 transCurrencyFormat이 제공하는 값을 사용해 다른 사용처와 일관되게
표시합니다.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx:
- Around line 39-42: Update the button in CategoryList’s menu rendering to
include disabled={!menu.isAvailable}, preserving the existing onClick guard so
unavailable menus cannot be focused or activated via keyboard.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx:
- Around line 41-50: Remove the unnecessary e.preventDefault() call from the
catch block of tryPayment; keep setIsError(true) and the existing success
behavior unchanged.
- Around line 25-34: Update the payment function so the payment button invokes
the actual payment API endpoint instead of httpMe.fetchHealthCheck; preserve the
AbortSignal and event-prevention behavior, and use the payment API’s response
type. If no payment endpoint exists yet, separate this placeholder from the
payment action and ensure the UI does not present it as a successful payment.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx:
- Around line 68-76: Update the totalPrice column cell logic around editingData
and isSelected so the selected editing path also verifies editingData is truthy
before using editingData.totalPrice. Preserve the row.getValue fallback for
non-selected rows and ensure transCurrencyFormat always receives a number.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx:
- Line 30: Replace the useTransition-based loading in TableOrderAcceptAllButton
with a dedicated state such as isAccepting that remains true for the entire
async acceptance flow, including until Promise.all completes. Use this state to
keep the button disabled and spinner visible, and reset it after the mutations
finish so duplicate clicks cannot start another operation.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx:
- Around line 17-18: Update TableOrderCard to destructure storeId alongside
tableId from useParams, then build the order href/path using that storeId
instead of deriving a prefix with usePathname().split("orders"). Remove the
pathname-splitting dependency while preserving the existing tableId-based
routing.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx:
- Around line 38-41: Handle the rejection from
updateOrderByTableMutation.mutateAsync in onOrderStatusUpdate by attaching catch
handling consistent with TableOrderAcceptAllButton. Preserve the mutation’s
existing onError rollback and isError retry UI behavior while preventing an
unhandled promise rejection.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx:
- Around line 7-9: Rename the TableBoardProps type to TableOrderListProps and
update all references, including the TableOrderList component’s props
annotation, while preserving the existing sanitizedTable field and behavior.
---
Nitpick comments:
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx:
- Line 16: Extract the duplicated grid column class into a shared constant in
the order-table directory, then update OrderTableHeader.tsx:16 and
OrderTableItem.tsx:18 to import and use it instead of hardcoding the class. Keep
both header and body layouts driven by the same constant.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx:
- Around line 20-131: Extract the data fetching, mutation setup, state, derived
order data, and action functions from OrderDetailProvider into a dedicated
useOrderDetail(params) custom hook. Return the existing context value structure
from the hook, then keep OrderDetailProvider focused on invoking the hook and
passing its result to OrderDetailContext.Provider without changing behavior.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx:
- Around line 19-75: Extract the payment state and async behavior from
PaymentDialogControlbar into a dedicated usePayment custom hook, including
isError, the health-check payment callback, useCancelableAsync integration,
tryPayment, abort, and isPending. Keep PaymentDialogControlbar focused on
rendering and consume the hook’s returned handlers and state while preserving
the existing success, failure, cancellation, and loading behavior.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx:
- Around line 13-25: OrderSyncDaemon의 synchronize 콜백을 useCallback으로 memoize하고,
내부에서 사용하는 queryClient를 의존성으로 포함하세요. useStoreOrderSyncDaemon에 전달되는
onCreatedAction, onUpdatedAction, onCancelledAction이 리렌더링마다 새로 생성되지 않도록 기존
synchronize 동작과 query key 생성을 유지하세요.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts:
- Around line 2-6: Update imports in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
lines 2-6 so ActiveSessionResponse, OrderBoardByStore, and PublicTable come from
`@spaceorder/db` instead of its /types subpath. Also update OrderSyncEvent in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
line 5 to import from `@spaceorder/db`, preserving the existing symbol usage.
In `@apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts`:
- Around line 21-47: Remove the unreachable `if (!storeId) return;` guard from
the second `useEffect` in the store order sync daemon, since `storeId` is typed
as `string`; leave the remaining effect logic unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 150d4d1a-e37c-4b86-865a-801b7a40c885
📒 Files selected for processing (89)
apps/console/next.config.tsapps/console/src/app/(navigator)/(sidebar)/[storeId]/components/PersistLastStore.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/dashboard/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/dashboard/page.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/menus/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/menus/page.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/page.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/EmptyOrderDetail.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/TableOrderDetail.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/TableOrderDetailLayout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailContent.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailContext.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailControlbar.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailFooter.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailPayment.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailTable.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderDialog.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderProvider.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuDetail.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/OptionTags.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/AddedMenu.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/AddedMenuList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/CreateOrderButton.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryTabList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/index.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableBody.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableControlbar.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableFrame.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableItem.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableOptions.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableRow.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/index.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/AwaitOrdersSummary.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/GridLayout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrdersPageLayout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-board/TableBoard.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderListLayout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderContent.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderFooter.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderHeader.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderOrderList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderQrCode.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/index.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/hooks/useSetCacheByStoreBoard.tsapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/page.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.tsapps/console/src/app/(navigator)/(sidebar)/[storeId]/tables/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/tables/page.tsxapps/console/src/app/(navigator)/(sidebar)/components/HeaderLinkButton.tsxapps/console/src/app/(navigator)/(sidebar)/components/MainLayout.tsxapps/console/src/app/(navigator)/(sidebar)/components/NavSidebar.tsxapps/console/src/app/(navigator)/(sidebar)/components/PageTitle.tsxapps/console/src/app/(navigator)/(sidebar)/components/SidebarFooterLayout.tsxapps/console/src/app/(navigator)/(sidebar)/components/SidebarItemGroups.tsxapps/console/src/app/(navigator)/(sidebar)/components/StoreEntryRedirect.tsxapps/console/src/app/(navigator)/(sidebar)/components/StoreSwitcher.tsxapps/console/src/app/(navigator)/(sidebar)/components/UserName.tsxapps/console/src/app/(navigator)/(sidebar)/layout.tsxapps/console/src/app/(navigator)/(sidebar)/loading.tsxapps/console/src/app/(navigator)/(sidebar)/page.tsxapps/console/src/app/(navigator)/(sidebar)/stores/page.tsxapps/console/src/app/(navigator)/components/UserNameDropDown.tsxapps/console/src/app/(navigator)/layout.tsxapps/console/src/app/(navigator)/signin/components/form-card/FormCard.tsxapps/console/src/app/common/servers/getStores.tsapps/console/src/app/page.tsxapps/console/src/components/realtime/OrderNoticeDaemon.tsxapps/console/src/lib/realtime/useStoreOrderSyncDaemon.tsapps/console/src/shared/config/sidebarGroup.tsapps/console/src/shared/config/sidebarGroup.type.tsapps/console/src/shared/config/sidebarList.tspackages/db/constants/cookieTable.const.tspackages/db/constants/index.tspackages/db/constants/localStorage.const.tspackages/ui/src/components/buttons/TouchEventButton.tsxpackages/ui/src/components/layouts/sidebar.tsx
💤 Files with no reviewable changes (6)
- apps/console/src/shared/config/sidebarList.ts
- apps/console/src/app/page.tsx
- packages/db/constants/localStorage.const.ts
- packages/db/constants/index.ts
- apps/console/next.config.ts
- apps/console/src/app/(navigator)/(sidebar)/stores/page.tsx
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 9
🧹 Nitpick comments (6)
apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts (1)
21-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
storeId타입을string으로 좁힌 후 잔존한 dead code guard를 제거해 주세요.
storeIdparameter 타입이string | undefined에서string으로 변경되었으므로, Line 47의if (!storeId) return;guard는 더 이상 도달할 수 없는 분기입니다. 혼란을 방지하기 위해 제거를 권장합니다.♻️ 제안: 불필요한 guard 제거
useEffect(() => { - if (!storeId) return; - const socket = getRealtimeSocket(); subscribeAdmin(storeId);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts` around lines 21 - 47, Remove the unreachable `if (!storeId) return;` guard from the second `useEffect` in the store order sync daemon, since `storeId` is typed as `string`; leave the remaining effect logic unchanged.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx (1)
19-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win결제 로직을 custom hook으로 추출하는 것을 권장합니다.
PaymentDialogControlbar에 상태 관리(isError), 비동기 결제 로직(payment,tryPayment), abort 처리가 혼재되어 있습니다. coding guidelines에 따라 복잡한 로직은 custom hook으로 분리하는 것을 권장합니다.As per coding guidelines,
apps/{order,console}/src/**/*.{ts,tsx}: "suggest custom hooks for complex logic when appropriate".♻️ 제안: usePayment hook 추출
// usePayment.ts export function usePayment(setOpen: (value: SetStateAction<boolean>) => void) { const [isError, setIsError] = useState(false); const payment = async ( signal: AbortSignal, e: React.MouseEvent<HTMLButtonElement> ) => { e.preventDefault(); const result: HealthCheckResponse = await httpMe.fetchHealthCheck({ signal }); return result; }; const paymentTransaction = useCancelableAsync< [React.MouseEvent<HTMLButtonElement>], HealthCheckResponse >(payment); const tryPayment = async (e: React.MouseEvent<HTMLButtonElement>) => { try { setIsError(false); await paymentTransaction(e); setOpen(false); } catch { setIsError(true); } }; return { tryPayment, abort: paymentTransaction.abort, isPending: paymentTransaction.isPending, isError }; }export default function PaymentDialogControlbar({ children, setOpen, }: PaymentDialogControlbarProps) { - const [isError, setIsError] = useState(false); - - const payment = async (...) => { ... }; - const paymentTransaction = useCancelableAsync(...); - const tryPayment = async (...) => { ... }; + const { tryPayment, abort, isPending, isError } = usePayment(setOpen); return ( <> - <AlertDialogCancel onClick={paymentTransaction.abort}> + <AlertDialogCancel onClick={abort}> 취소 </AlertDialogCancel> <AlertDialogAction onClick={tryPayment} className={isError ? "..." : ""} - disabled={paymentTransaction.isPending} + disabled={isPending} > <ActivityRender - value={!paymentTransaction.isPending} + value={!isPending} fallback={<LoadingTransaction />} > {() => (isError ? "결제 실패, 다시 시도" : children)} </ActivityRender> </AlertDialogAction> </> ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx around lines 19 - 75, Extract the payment state and async behavior from PaymentDialogControlbar into a dedicated usePayment custom hook, including isError, the health-check payment callback, useCancelableAsync integration, tryPayment, abort, and isPending. Keep PaymentDialogControlbar focused on rendering and consume the hook’s returned handlers and state while preserving the existing success, failure, cancellation, and loading behavior.Source: Coding guidelines
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx (1)
20-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win복잡한 로직을 custom hook으로 분리하는 것을 권장합니다.
OrderDetailProvider는 data fetching, mutation 설정, state 관리, derived data 계산, 5개의 action 함수를 포함하고 있습니다. 이 로직을useOrderDetail(params)같은 custom hook으로 분리하면 provider가 간결해지고, 비즈니스 로직의 testability가 향상됩니다.As per coding guidelines,
apps/{order,console}/src/**/*.{ts,tsx}경로의 파일에서는 복잡한 로직을 적절한 custom hook으로 분리할 것을 권장합니다.♻️ 제안하는 custom hook 분리 구조
+// useOrderDetail.ts +export function useOrderDetail(params: { storeId: string; tableId: string }) { + const { storeId, tableId } = params; + const fetchUrl = `/orders/v1/tables/${tableId}/active-session`; + const { data: session, isRefetching } = + useSuspenseWithAuth<ActiveSessionResponse>(fetchUrl); + const { update, remove } = useOrderItem({ storeId, tableId }); + const [editingItem, setEditingItem] = useState<OrderItemWithOrder | null>(null); + // ... derived data, actions ... + return { state, actions, meta }; +} + +// OrderDetailProvider.tsx export function OrderDetailProvider({ params, children }: OrderDetailProviderProps) { - const { storeId, tableId } = params; - const fetchUrl = `/orders/v1/tables/${tableId}/active-session`; - // ... 모든 로직 ... + const contextValue = useOrderDetail(params); return ( <OrderDetailContext.Provider value={contextValue}> {children} </OrderDetailContext.Provider> ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx around lines 20 - 131, Extract the data fetching, mutation setup, state, derived order data, and action functions from OrderDetailProvider into a dedicated useOrderDetail(params) custom hook. Return the existing context value structure from the hook, then keep OrderDetailProvider focused on invoking the hook and passing its result to OrderDetailContext.Provider without changing behavior.Source: Coding guidelines
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx (1)
16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winGrid column 클래스가 두 파일에 중복 정의되어 있습니다.
grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]클래스가OrderTableHeader.tsx의TableRow와OrderTableItem.tsx의TableCell에 각각 하드코딩되어 있습니다. 어느 한쪽만 변경되면 헤더와 바디 컬럼 정렬이 깨집니다.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx#L16-L16: 공통 상수를 import하여 사용하도록 변경apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableItem.tsx#L18-L18: 동일한 공통 상수를 import하여 사용하도록 변경♻️ 제안: 공통 상수 추출
order-table/디렉토리에 공통 상수를 추가하고 양쪽에서 import하여 사용:+// order-table/constants.ts +export const ORDER_TABLE_GRID_CLASS = + "grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]";
OrderTableHeader.tsx:- className="grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr]" + className={ORDER_TABLE_GRID_CLASS}
OrderTableItem.tsx:- <TableCell className="grid grid-cols-[1.5fr_1fr_1fr] xl:grid-cols-[2fr_1fr_1fr] cursor-pointer min-h-16 animate-fade-in-up"> + <TableCell className={`${ORDER_TABLE_GRID_CLASS} cursor-pointer min-h-16 animate-fade-in-up`}>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx at line 16, Extract the duplicated grid column class into a shared constant in the order-table directory, then update OrderTableHeader.tsx:16 and OrderTableItem.tsx:18 to import and use it instead of hardcoding the class. Keep both header and body layouts driven by the same constant.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts (1)
2-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
@spaceorder/db/types대신@spaceorder/db에서 database types를 import해야 합니다. coding guideline에 따르면 database types는@spaceorder/db패키지에서 import해야 하지만, 두 파일 모두@spaceorder/db/typessubpath에서 import하고 있습니다.layout.tsx는@spaceorder/db에서OrderBoardByStore를 import하고 있어 일관성이 깨집니다.
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts#L2-L6:ActiveSessionResponse,OrderBoardByStore,PublicTable를@spaceorder/db에서 import하도록 변경apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx#L5-L5:OrderSyncEvent를@spaceorder/db에서 import하도록 변경🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts around lines 2 - 6, Update imports in apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts lines 2-6 so ActiveSessionResponse, OrderBoardByStore, and PublicTable come from `@spaceorder/db` instead of its /types subpath. Also update OrderSyncEvent in apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx line 5 to import from `@spaceorder/db`, preserving the existing symbol usage.Source: Coding guidelines
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx (1)
13-25: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
synchronize함수를useCallback으로 memoize하는 것을 권장합니다.
synchronize는 렌더링마다 새로운 함수 참조를 생성합니다.useStoreOrderSyncDaemon의useEffect의존성 배열에onCreatedAction,onUpdatedAction,onCancelledAction이 포함되어 있어,OrderSyncDaemon이 리렌더링될 때마다 socket listener가 불필요하게 unsubscribe/subscribe됩니다.board query가 invalidate되어
AwaitOrdersSummary가 리렌더링되는 시점(예: reconnect)에 이 cascade가 발생할 수 있으며, unsubscribe/subscribe 사이에 실시간 이벤트가 유실될 가능성이 있습니다.♻️ Proposed fix
+import { useCallback } from "react"; + export default function OrderSyncDaemon() { const params = useParams<{ storeId: string }>(); const queryClient = useQueryClient(); - const synchronize = (event: OrderSyncEvent) => { + const synchronize = useCallback((event: OrderSyncEvent) => { queryClient.invalidateQueries({ queryKey: pathToQueryKey( `/orders/v1/tables/${event.tablePublicId}/active-session` ), }); - }; + }, [queryClient]); useStoreOrderSyncDaemon(params.storeId, {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx around lines 13 - 25, OrderSyncDaemon의 synchronize 콜백을 useCallback으로 memoize하고, 내부에서 사용하는 queryClient를 의존성으로 포함하세요. useStoreOrderSyncDaemon에 전달되는 onCreatedAction, onUpdatedAction, onCancelledAction이 리렌더링마다 새로 생성되지 않도록 기존 synchronize 동작과 query key 생성을 유지하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx:
- Line 45: AddMenuButton의 가격 표시에서 transCurrencyFormat(price) 뒤에 붙는 “원”을 제거하고, 기존
“변경”/“추가” 문구는 그대로 유지하세요. 통화 기호는 transCurrencyFormat이 제공하는 값을 사용해 다른 사용처와 일관되게
표시합니다.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx:
- Around line 39-42: Update the button in CategoryList’s menu rendering to
include disabled={!menu.isAvailable}, preserving the existing onClick guard so
unavailable menus cannot be focused or activated via keyboard.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx:
- Around line 41-50: Remove the unnecessary e.preventDefault() call from the
catch block of tryPayment; keep setIsError(true) and the existing success
behavior unchanged.
- Around line 25-34: Update the payment function so the payment button invokes
the actual payment API endpoint instead of httpMe.fetchHealthCheck; preserve the
AbortSignal and event-prevention behavior, and use the payment API’s response
type. If no payment endpoint exists yet, separate this placeholder from the
payment action and ensure the UI does not present it as a successful payment.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx:
- Around line 68-76: Update the totalPrice column cell logic around editingData
and isSelected so the selected editing path also verifies editingData is truthy
before using editingData.totalPrice. Preserve the row.getValue fallback for
non-selected rows and ensure transCurrencyFormat always receives a number.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx:
- Line 30: Replace the useTransition-based loading in TableOrderAcceptAllButton
with a dedicated state such as isAccepting that remains true for the entire
async acceptance flow, including until Promise.all completes. Use this state to
keep the button disabled and spinner visible, and reset it after the mutations
finish so duplicate clicks cannot start another operation.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx:
- Around line 17-18: Update TableOrderCard to destructure storeId alongside
tableId from useParams, then build the order href/path using that storeId
instead of deriving a prefix with usePathname().split("orders"). Remove the
pathname-splitting dependency while preserving the existing tableId-based
routing.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx:
- Around line 38-41: Handle the rejection from
updateOrderByTableMutation.mutateAsync in onOrderStatusUpdate by attaching catch
handling consistent with TableOrderAcceptAllButton. Preserve the mutation’s
existing onError rollback and isError retry UI behavior while preventing an
unhandled promise rejection.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx:
- Around line 7-9: Rename the TableBoardProps type to TableOrderListProps and
update all references, including the TableOrderList component’s props
annotation, while preserving the existing sanitizedTable field and behavior.
---
Nitpick comments:
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsx:
- Line 16: Extract the duplicated grid column class into a shared constant in
the order-table directory, then update OrderTableHeader.tsx:16 and
OrderTableItem.tsx:18 to import and use it instead of hardcoding the class. Keep
both header and body layouts driven by the same constant.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsx:
- Around line 20-131: Extract the data fetching, mutation setup, state, derived
order data, and action functions from OrderDetailProvider into a dedicated
useOrderDetail(params) custom hook. Return the existing context value structure
from the hook, then keep OrderDetailProvider focused on invoking the hook and
passing its result to OrderDetailContext.Provider without changing behavior.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx:
- Around line 19-75: Extract the payment state and async behavior from
PaymentDialogControlbar into a dedicated usePayment custom hook, including
isError, the health-check payment callback, useCancelableAsync integration,
tryPayment, abort, and isPending. Keep PaymentDialogControlbar focused on
rendering and consume the hook’s returned handlers and state while preserving
the existing success, failure, cancellation, and loading behavior.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx:
- Around line 13-25: OrderSyncDaemon의 synchronize 콜백을 useCallback으로 memoize하고,
내부에서 사용하는 queryClient를 의존성으로 포함하세요. useStoreOrderSyncDaemon에 전달되는
onCreatedAction, onUpdatedAction, onCancelledAction이 리렌더링마다 새로 생성되지 않도록 기존
synchronize 동작과 query key 생성을 유지하세요.
In
`@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts:
- Around line 2-6: Update imports in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.ts
lines 2-6 so ActiveSessionResponse, OrderBoardByStore, and PublicTable come from
`@spaceorder/db` instead of its /types subpath. Also update OrderSyncEvent in
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsx
line 5 to import from `@spaceorder/db`, preserving the existing symbol usage.
In `@apps/console/src/lib/realtime/useStoreOrderSyncDaemon.ts`:
- Around line 21-47: Remove the unreachable `if (!storeId) return;` guard from
the second `useEffect` in the store order sync daemon, since `storeId` is typed
as `string`; leave the remaining effect logic unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 150d4d1a-e37c-4b86-865a-801b7a40c885
📒 Files selected for processing (89)
apps/console/next.config.tsapps/console/src/app/(navigator)/(sidebar)/[storeId]/components/PersistLastStore.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/dashboard/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/dashboard/page.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/menus/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/menus/page.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/page.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/EmptyOrderDetail.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/TableOrderDetail.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/TableOrderDetailLayout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailContent.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailContext.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailControlbar.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailFooter.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailPayment.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailProvider.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/OrderDetailTable.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderDialog.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderProvider.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuDetail.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/OptionTags.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/AddedMenu.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/AddedMenuList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/added-menu-list/CreateOrderButton.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryTabList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/index.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableBody.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableControlbar.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableFrame.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableHeader.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableItem.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableOptions.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/OrderTableRow.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/order-table/index.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/AwaitOrdersSummary.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/GridLayout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrderSyncDaemon.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/OrdersPageLayout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-board/TableBoard.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderListLayout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderContent.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderFooter.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderHeader.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderOrderList.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderQrCode.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/index.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/hooks/useSetCacheByStoreBoard.tsapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/page.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/utils/setTablesCache.tsapps/console/src/app/(navigator)/(sidebar)/[storeId]/tables/layout.tsxapps/console/src/app/(navigator)/(sidebar)/[storeId]/tables/page.tsxapps/console/src/app/(navigator)/(sidebar)/components/HeaderLinkButton.tsxapps/console/src/app/(navigator)/(sidebar)/components/MainLayout.tsxapps/console/src/app/(navigator)/(sidebar)/components/NavSidebar.tsxapps/console/src/app/(navigator)/(sidebar)/components/PageTitle.tsxapps/console/src/app/(navigator)/(sidebar)/components/SidebarFooterLayout.tsxapps/console/src/app/(navigator)/(sidebar)/components/SidebarItemGroups.tsxapps/console/src/app/(navigator)/(sidebar)/components/StoreEntryRedirect.tsxapps/console/src/app/(navigator)/(sidebar)/components/StoreSwitcher.tsxapps/console/src/app/(navigator)/(sidebar)/components/UserName.tsxapps/console/src/app/(navigator)/(sidebar)/layout.tsxapps/console/src/app/(navigator)/(sidebar)/loading.tsxapps/console/src/app/(navigator)/(sidebar)/page.tsxapps/console/src/app/(navigator)/(sidebar)/stores/page.tsxapps/console/src/app/(navigator)/components/UserNameDropDown.tsxapps/console/src/app/(navigator)/layout.tsxapps/console/src/app/(navigator)/signin/components/form-card/FormCard.tsxapps/console/src/app/common/servers/getStores.tsapps/console/src/app/page.tsxapps/console/src/components/realtime/OrderNoticeDaemon.tsxapps/console/src/lib/realtime/useStoreOrderSyncDaemon.tsapps/console/src/shared/config/sidebarGroup.tsapps/console/src/shared/config/sidebarGroup.type.tsapps/console/src/shared/config/sidebarList.tspackages/db/constants/cookieTable.const.tspackages/db/constants/index.tspackages/db/constants/localStorage.const.tspackages/ui/src/components/buttons/TouchEventButton.tsxpackages/ui/src/components/layouts/sidebar.tsx
💤 Files with no reviewable changes (6)
- apps/console/src/shared/config/sidebarList.ts
- apps/console/src/app/page.tsx
- packages/db/constants/localStorage.const.ts
- packages/db/constants/index.ts
- apps/console/next.config.ts
- apps/console/src/app/(navigator)/(sidebar)/stores/page.tsx
🛑 Comments failed to post (9)
apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx (1)
45-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
What is the output ofnew Intl.NumberFormat("ko-KR", { currency: "KRW", style: "currency" }).format(10000)in modern JavaScript engines?💡 Result:
The output of the command
new Intl.NumberFormat("ko-KR", { currency: "KRW", style: "currency" }).format(10000)in modern JavaScript engines is"₩10,000"[1][2][3]. Because the Korean Won (KRW) does not use minor currency units (i.e., it has no fractional subunits like cents), theIntl.NumberFormatAPI automatically excludes decimal places when formatting for theko-KRlocale [4][1][5]. The resulting string includes the Korean Won symbol (₩) followed by the formatted number with a thousands separator [1].Citations:
- 1: https://realcoding.blog/en/2025/06/25/javascript-intl-numberformat/
- 2: https://designerdk.tistory.com/44
- 3: https://github.com/mdn/translated-content/blob/main/files/ko/web/javascript/reference/global_objects/intl/numberformat/index.md
- 4: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
- 5: https://simplelocalize.io/blog/posts/handling-dates-times-numbers-localization/
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n[files]\n' git ls-files | rg 'transCurrencyFormat|CreateOrderButton|AddMenuButton' printf '\n[search]\n' rg -n "transCurrencyFormat|원 -|주문 생성|변경|추가" apps/console/src -g '!**/node_modules/**' printf '\n[file: transCurrencyFormat implementation]\n' file=$(rg -n "function transCurrencyFormat|const transCurrencyFormat|export .*transCurrencyFormat" -l apps/console/src packages 2>/dev/null | head -n 1 || true) if [ -n "${file:-}" ]; then echo "$file" wc -l "$file" sed -n '1,220p' "$file" fi printf '\n[file: AddMenuButton.tsx]\n' sed -n '1,120p' 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx' printf '\n[file: CreateOrderButton.tsx]\n' sed -n '1,120p' 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/CreateOrderButton.tsx'Repository: kisn3089/ssurak
Length of output: 8333
원을 제거해 주세요
transCurrencyFormat(price)는 이미₩10,000처럼 currency symbol을 포함합니다.- 여기에
원을 붙이면₩10,000원으로 단위가 중복됩니다.- 다른 사용처와도 동일하게 맞추는 게 좋습니다.
수정 예시
- {`${transCurrencyFormat(price)}원 - ${isEditing ? "변경" : "추가"}`} + {`${transCurrencyFormat(price)} - ${isEditing ? "변경" : "추가"}`}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.{`${transCurrencyFormat(price)} - ${isEditing ? "변경" : "추가"}`}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/add-menu-detail/AddMenuButton.tsx at line 45, AddMenuButton의 가격 표시에서 transCurrencyFormat(price) 뒤에 붙는 “원”을 제거하고, 기존 “변경”/“추가” 문구는 그대로 유지하세요. 통화 기호는 transCurrencyFormat이 제공하는 값을 사용해 다른 사용처와 일관되게 표시합니다.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx (1)
39-42: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
이용 불가능한 메뉴의 button에
disabled속성을 추가해 주세요.
menu.isAvailable이 false일 때 Card에pointer-events-none이 적용되어 마우스 클릭은 차단되지만, 키보드 사용자는 여전히 button에 focus하고 활성화할 수 있습니다.disabled={!menu.isAvailable}을 추가하면 키보드 접근성도 함께 차단됩니다.♿ 제안하는 접근성 개선
<button className="rounded-3xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" - onClick={() => menu.isAvailable && selectMenu(menu)} + type="button" + disabled={!menu.isAvailable} + onClick={() => selectMenu(menu)} >📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<button className="rounded-3xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" type="button" disabled={!menu.isAvailable} onClick={() => selectMenu(menu)} >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/create-order/category-list/CategoryList.tsx around lines 39 - 42, Update the button in CategoryList’s menu rendering to include disabled={!menu.isAvailable}, preserving the existing onClick guard so unavailable menus cannot be focused or activated via keyboard.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx (2)
25-34: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # Description: fetchHealthCheck의 정의와 실제 결제 API 존재 여부를 확인합니다. rg -n "fetchHealthCheck" packages/api/ --type=ts rg -n "payment\|pay\|checkout" packages/api/ --type=ts -i -lRepository: kisn3089/ssurak
Length of output: 292
🏁 Script executed:
#!/bin/bash set -euo pipefail # Inspect the API helper and the UI caller. sed -n '1,120p' packages/api/core/identity/me/httpMe.ts printf '\n---\n' sed -n '1,220p' 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx' printf '\n---\n' # Find payment/checkout/order-related API helpers in packages/api. rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \ "payment|checkout|pay|order" packages/api -iRepository: kisn3089/ssurak
Length of output: 27953
🏁 Script executed:
#!/bin/bash set -euo pipefail # 결제 상태를 다루는 실제 session/order API가 있는지 확인합니다. rg -n --hidden --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' \ "updateSessionPaymentSchema|PAYMENT_PENDING|updateCustomerSessionPayloadSchema|updateSessionPayloadSchema|tableSession|session.*payment|payment.*session" packages/api apps -i printf '\n---\n' # session 관련 HTTP helper가 있는지 확인합니다. fd -a 'http*session*.ts' packages/api printf '\n---\n' fd -a '*session*.ts' packages/api/coreRepository: kisn3089/ssurak
Length of output: 21524
결제 버튼에서는 health check가 아니라 실제 payment API를 호출해 주세요.
httpMe.fetchHealthCheck()는/health check용입니다.- 현재 구현은 결제 성공처럼 보여도 실제 결제 처리가 되지 않으므로, 결제 endpoint로 교체하거나 placeholder라면 UI/이름을 분리해 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx around lines 25 - 34, Update the payment function so the payment button invokes the actual payment API endpoint instead of httpMe.fetchHealthCheck; preserve the AbortSignal and event-prevention behavior, and use the payment API’s response type. If no payment endpoint exists yet, separate this placeholder from the payment action and ensure the UI does not present it as a successful payment.
41-50: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
catch 블록의
e.preventDefault()는 dead code입니다.
tryPayment에서await paymentTransaction(e)이후에 호출되는e.preventDefault()는 이미 event가 dispatch된 시점이므로 아무 효과가 없습니다. 성공 시setOpen(false)가 실행되지 않아 dialog가 열려있는 것은 정상 동작이지만, 이preventDefault호출이 그 원인이 아닙니다.🧹 제안: 불필요한 preventDefault 제거
const tryPayment = async (e: React.MouseEvent<HTMLButtonElement>) => { try { setIsError(false); await paymentTransaction(e); setOpen(false); } catch { - e.preventDefault(); setIsError(true); } };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const tryPayment = async (e: React.MouseEvent<HTMLButtonElement>) => { try { setIsError(false); await paymentTransaction(e); setOpen(false); } catch { setIsError(true); } };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/order-detail/PaymentDialogControlbar.tsx around lines 41 - 50, Remove the unnecessary e.preventDefault() call from the catch block of tryPayment; keep setIsError(true) and the existing success behavior unchanged.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx (1)
68-76: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
totalPrice컬럼에서editingData타입 narrowing이 누락되었습니다.
quantity컬럼(라인 33)에서는isSelected && meta.editingData를 사용하여 타입을 올바르게 좁히지만,totalPrice컬럼(라인 71)에서는isSelected만 확인하여editingData?.totalPrice가number | undefined가 됩니다.transCurrencyFormat은number만 받으므로 strict mode에서 type error가 발생합니다.🐛 제안하는 수정
const price = isSelected - ? editingData?.totalPrice + ? editingData?.totalPrice : row.getValue<number>("totalPrice");
isSelected조건에editingDatatruthy check를 추가하여 타입을 좁혀 주세요:- const isSelected = editingData?.publicId === row.original.publicId; - const price = isSelected - ? editingData?.totalPrice + const isSelected = editingData?.publicId === row.original.publicId; + const price = isSelected && editingData + ? editingData.totalPrice : row.getValue<number>("totalPrice");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.cell: ({ row, table }) => { const editingData = table.options.meta?.editingData; const isSelected = editingData?.publicId === row.original.publicId; const price = isSelected && editingData ? editingData.totalPrice : row.getValue<number>("totalPrice"); const formatted = transCurrencyFormat(price); return <div className="text-right w-full">{formatted}</div>;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/[tableId]/table-order-detail/tableOrderColumns.tsx around lines 68 - 76, Update the totalPrice column cell logic around editingData and isSelected so the selected editing path also verifies editingData is truthy before using editingData.totalPrice. Preserve the row.getValue fallback for non-selected rows and ensure transCurrencyFormat always receives a number.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx (1)
30-30: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # Description: React 18.3 useTransition이 async callback을 지원하는지 확인합니다. # 1. React 버전 확인 cat node_modules/react/package.json | jq '.version' # 2. useTransition 타입 시그니처 확인 rg -n 'startTransition' node_modules/@types/react/index.d.ts | head -20 # 3. 프로젝트 내 startTransition + async 사용 패턴 확인 rg -nP 'startTransition\(async' --type ts apps/ packages/Repository: kisn3089/ssurak
Length of output: 546
🏁 Script executed:
#!/bin/bash set -euo pipefail # 대상 파일과 관련 훅의 구조/해당 구간 확인 ast-grep outline 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx' --view expanded echo '---' ast-grep outline 'packages/api/hooks/useCancelableAsync.ts' --view expanded echo '--- FILE: TableOrderAcceptAllButton.tsx (1-220)' sed -n '1,220p' 'apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx' echo '--- FILE: useCancelableAsync.ts (1-220)' sed -n '1,220p' 'packages/api/hooks/useCancelableAsync.ts'Repository: kisn3089/ssurak
Length of output: 5759
🏁 Script executed:
#!/bin/bash set -euo pipefail # React/Next 버전과 관련 파일에서 async transition 사용 맥락 확인 rg -n '"react"|react@|next@|useTransition|startTransition\(async' package.json pnpm-lock.yaml yarn.lock package-lock.json apps packages -g '!**/dist/**' -g '!**/build/**' echo '--- package.json (top-level)' sed -n '1,220p' package.json 2>/dev/null || true echo '--- useCancelableAsync references' rg -n 'useCancelableAsync\(' apps packagesRepository: kisn3089/ssurak
Length of output: 39225
🌐 Web query:
React startTransition async callback isPending await promise official docs💡 Result:
In React 19, you can pass an asynchronous function (an "Action") to the startTransition function provided by the useTransition hook, which allows React to automatically track the pending state [1][2]. When you use startTransition with an async callback: 1. The isPending flag from useTransition becomes true immediately when the transition starts [3][1]. 2. It remains true until the asynchronous action completes and all triggered state updates are processed [3][1][2]. 3. You do not need to manually await the promise returned by the Action function inside the component logic for the transition to be tracked; simply passing the async function to startTransition is sufficient [1][2]. Important considerations: - Standalone startTransition vs. useTransition: While the useTransition hook's startTransition function handles async actions as described, the standalone startTransition function (imported from react) behaves differently and does not provide an isPending flag [4][5]. - State updates after await: In some cases, especially when using the standalone startTransition or complex asynchronous flows, state updates occurring after an await in the action might not be automatically included in the transition [1][6][2]. If you notice these updates are not marked as part of the transition, you may need to wrap those specific state updates in an additional startTransition call [1][6][5]. - Async Context: React's current ability to group updates across async boundaries is an implementation that works in many scenarios but may occasionally require explicit wrapping until future platform features like Async Context are fully adopted [3][6]. In summary, for most standard use cases in React 19, passing an async function to the startTransition provided by useTransition is the recommended way to handle pending states without manual loading flags [1][2].
Citations:
- 1: https://uk.react.dev/reference/react/useTransition
- 2: https://blog.openreplay.com/react-19-async-transitions/
- 3: react/react#26621
- 4: https://react.dev/reference/react/startTransition
- 5: reactjs/react.dev#8496
- 6: https://stackoverflow.com/questions/79923964/react-19-usetransition-behaves-weirdly-with-multiple-async-updates-to-state
🌐 Web query:
site:react.dev startTransition async function isPending promise React 18.3💡 Result:
In React, startTransition and useTransition are used to mark state updates as non-urgent transitions, allowing them to be interrupted by urgent updates like user input [1][2]. Key differences and behaviors: startTransition: This is a standalone function used when you cannot use the useTransition hook (e.g., outside of a component) [2]. It does not provide an isPending flag to track the transition status [3]. useTransition: This is a hook that returns both a startTransition function and an isPending boolean [4][5]. The isPending flag is automatically set to true when the transition starts and back to false once all transitions triggered by that call—including any updates scheduled synchronously—are completed [4][6]. Handling Async Functions: React's startTransition (and the one returned by useTransition) expects a synchronous function [2]. If you pass an async function to startTransition, React only marks the synchronous state updates scheduled immediately during the initial function call as transitions [4][2]. Any state updates that occur after an await statement (asynchronously) will not be automatically marked as transitions [7][2]. To correctly handle async updates: 1. You should perform the asynchronous work (e.g., fetching data) first [2]. 2. Once the asynchronous work is complete, you then call startTransition to wrap the resulting state updates [7][2]. 3. Alternatively, if you must use an async function inside a transition, you must explicitly wrap any state updates that happen after the await in an additional startTransition call [7]. Note: In newer versions of React (like React 19), async transitions are more natively supported, allowing the isPending state to correctly track the duration of the async request until the final state is rendered [6]. However, in standard React 18.x usage, the synchronous limitation for state updates remains the standard pattern [7][2].
Citations:
- 1: https://react.dev/blog/2022/03/29/react-v18
- 2: https://az.react.dev/reference/react/useTransition
- 3: https://react.dev/reference/react/startTransition
- 4: https://react.dev/reference/react/useTransition
- 5: https://react.dev/reference/react/useOptimistic
- 6: https://react.dev/blog/2024/12/05/react-19
- 7: https://uk.react.dev/reference/react/useTransition
startTransition(async () => ...)대신 별도 loading state를 사용해 주세요.
- 이 패턴은 React 18.3.1에서 async 작업 전체를
isPending으로 묶어주지 않아서,Promise.all이 끝나기 전에 버튼이 다시 활성화될 수 있습니다.- 그 경우 spinner가 먼저 사라지고 중복 클릭으로 mutation이 반복될 수 있습니다.
Promise.all완료까지isAccepting같은useState로 버튼을 잠그거나, async 작업은 transition 밖에서 처리하고 마지막setState만startTransition으로 감싸 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderAcceptAllButton.tsx at line 30, Replace the useTransition-based loading in TableOrderAcceptAllButton with a dedicated state such as isAccepting that remains true for the entire async acceptance flow, including until Promise.all completes. Use this state to keep the button disabled and spinner visible, and reset it after the mutations finish so duplicate clicks cannot start another operation.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx (1)
17-18: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
usePathname().split("orders")대신useParams의storeId를 직접 사용하는 것을 권장합니다.
split("orders")는storeId에 "orders"가 부분 문자열로 포함된 경우 잘못된 prefix를 생성합니다. 예: pathname이/myorders-store/orders/table-1인 경우split("orders")→["/my", "-store/", "/table-1"]→pathname = "/my"→ href가/myorders/table-1이 되어 라우팅이 깨집니다.
storeId를useParams에서 직접 가져와 href를 구성하면 더 단순하고 견고해집니다.♻️ Proposed fix
-import { useParams, usePathname } from "next/navigation"; +import { useParams } from "next/navigation";- const { tableId } = useParams<{ tableId: string }>(); - const [pathname] = usePathname().split("orders"); + const { storeId, tableId } = useParams<{ storeId: string; tableId: string }>();- href={`${pathname}orders/${sanitizedTable.publicId}`} + href={`/${storeId}/orders/${sanitizedTable.publicId}`}Also applies to: 33-33
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderCard.tsx around lines 17 - 18, Update TableOrderCard to destructure storeId alongside tableId from useParams, then build the order href/path using that storeId instead of deriving a prefix with usePathname().split("orders"). Remove the pathname-splitting dependency while preserving the existing tableId-based routing.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx (1)
38-41: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
mutateAsync호출의 에러 처리가 필요합니다.
onOrderStatusUpdate에서mutateAsync의 rejection이 처리되지 않아 unhandled promise rejection이 발생합니다. mutation의onError에서 캐시 롤백은 처리되고isError로 retry UI가 표시되지만, console에 에러가 출력됩니다.
TableOrderAcceptAllButton에서는 각mutateAsync에.catch()를 적용한 것과 일관성을 맞추는 것도 좋습니다.🔧 제안하는 수정
const onOrderStatusUpdate = async ( e: React.MouseEvent<HTMLButtonElement> ) => { e.preventDefault(); const nextStatus = nextStatusMap[order.status]; if (!nextStatus) { return; } - await updateOrderByTableMutation.mutateAsync({ + try { + await updateOrderByTableMutation.mutateAsync({ orderId: order.publicId, updateOrderPayload: { status: nextStatus }, }); + } catch { + // mutation의 onError에서 캐시 롤백 및 isError 상태 처리됨 + } };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.try { await updateOrderByTableMutation.mutateAsync({ orderId: order.publicId, updateOrderPayload: { status: nextStatus }, }); } catch { // mutation의 onError에서 캐시 롤백 및 isError 상태 처리됨 }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/table-order-card/TableOrderItem.tsx around lines 38 - 41, Handle the rejection from updateOrderByTableMutation.mutateAsync in onOrderStatusUpdate by attaching catch handling consistent with TableOrderAcceptAllButton. Preserve the mutation’s existing onError rollback and isError retry UI behavior while preventing an unhandled promise rejection.apps/console/src/app/(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx (1)
7-9: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
TableBoardProps타입명을TableOrderListProps로 변경해 주세요.이 타입은
TableOrderList컴포넌트의 props를 정의하지만, 이름이TableBoard를 참조하고 있어 혼동의 소지가 있습니다. As per coding guidelines, type, function, method, or variable names에 clear, descriptive names를 사용해야 합니다.♻️ 제안하는 수정
-type TableBoardProps = { +type TableOrderListProps = { sanitizedTable: BoardTableWithSession; }; -export default function TableOrderList({ sanitizedTable }: TableBoardProps) { +export default function TableOrderList({ sanitizedTable }: TableOrderListProps) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/app/`(navigator)/(sidebar)/[storeId]/orders/components/table-order-list/TableOrderList.tsx around lines 7 - 9, Rename the TableBoardProps type to TableOrderListProps and update all references, including the TableOrderList component’s props annotation, while preserving the existing sanitizedTable field and behavior.Source: Coding guidelines
설명 (Description)
점주 콘솔을 멀티매장 URL 모델(
/{storeId}) 로 전환하고, 이를 뒷받침하는 매장 스위처·사이드바·마지막 접속 매장 저장 방식을 정리합니다. 이후 테이블 관련 작업(tableNumberString 전환, 테이블 생성 기능)의 선행 기반입니다.1. 멀티매장 URL 모델 전환 (feat/console)
/{storeId}기반으로 전환하고 매장 스위처 도입2. 마지막 접속 매장 저장 방식 변경 (refactor/db)
localStorage→ 쿠키로 이관하여 SSR에서 접근 가능하도록 변경3. 사이드바·버튼 인터랙션 (feat/ui)
TouchEventButton에 이벤트 전파 제어(swallowEvent) 옵션 추가변경 유형 (Type of Change)
영향 범위 (Scope)
테스트 (Testing)
테스트 단계 (Test Steps)
/{storeId}/...로 갱신되는지 확인체크리스트 (Checklist)
데이터베이스 마이그레이션 (Database Migration)
배포 참고사항 (Deployment Notes)
🤖 Generated with Claude Code
Summary by CodeRabbit
새로운 기능
개선 사항