diff --git a/frontend/components/pop/viewer/PopViewerWithModals.tsx b/frontend/components/pop/viewer/PopViewerWithModals.tsx index 78d1b647..f322d4c0 100644 --- a/frontend/components/pop/viewer/PopViewerWithModals.tsx +++ b/frontend/components/pop/viewer/PopViewerWithModals.tsx @@ -12,6 +12,7 @@ "use client"; import { useState, useCallback, useEffect, useMemo } from "react"; +import { useRouter } from "next/navigation"; import { Dialog, DialogContent, @@ -61,6 +62,7 @@ export default function PopViewerWithModals({ overrideGap, overridePadding, }: PopViewerWithModalsProps) { + const router = useRouter(); const [modalStack, setModalStack] = useState([]); const { subscribe, publish } = usePopEvent(screenId); @@ -126,11 +128,30 @@ export default function PopViewerWithModals({ }); }); + const unsubNavigate = subscribe("__pop_navigate__", (payload: unknown) => { + const data = payload as { + screenId?: string; + params?: Record; + }; + + if (!data?.screenId) return; + + if (data.screenId === "back") { + router.back(); + } else { + const query = data.params + ? "?" + new URLSearchParams(data.params).toString() + : ""; + window.location.href = `/pop/screens/${data.screenId}${query}`; + } + }); + return () => { unsubOpen(); unsubClose(); + unsubNavigate(); }; - }, [subscribe, publish, layout.modals]); + }, [subscribe, publish, layout.modals, router]); // 최상위 모달만 닫기 (X 버튼, overlay 클릭, ESC) const handleCloseTopModal = useCallback(() => { diff --git a/frontend/lib/registry/pop-components/pop-button.tsx b/frontend/lib/registry/pop-components/pop-button.tsx index a3d7f4b9..0dc5aa50 100644 --- a/frontend/lib/registry/pop-components/pop-button.tsx +++ b/frontend/lib/registry/pop-components/pop-button.tsx @@ -420,6 +420,21 @@ export function PopButtonComponent({ const [showCartConfirm, setShowCartConfirm] = useState(false); const [confirmProcessing, setConfirmProcessing] = useState(false); const [showInboundConfirm, setShowInboundConfirm] = useState(false); + const [inboundSelectedCount, setInboundSelectedCount] = useState(0); + + // 입고 확정 모드: 선택 항목 수 수신 + useEffect(() => { + if (!isInboundConfirmMode || !componentId) return; + const unsub = subscribe( + `__comp_input__${componentId}__selected_items`, + (payload: unknown) => { + const data = payload as { value?: unknown[] } | undefined; + const items = Array.isArray(data?.value) ? data.value : []; + setInboundSelectedCount(items.length); + } + ); + return unsub; + }, [isInboundConfirmMode, componentId, subscribe]); // 장바구니 상태 수신 (카드 목록에서 count/isDirty 전달) useEffect(() => { @@ -673,6 +688,20 @@ export function PopButtonComponent({ return ""; }, [isCartMode, cartCount, cartIsDirty]); + // 입고 확정 2상태 아이콘: 미선택(기본 아이콘) / 선택됨(체크 아이콘) + const inboundIconName = useMemo(() => { + if (!isInboundConfirmMode) return iconName; + return inboundSelectedCount > 0 ? (config?.icon || "PackageCheck") : (config?.icon || "PackageCheck"); + }, [isInboundConfirmMode, inboundSelectedCount, config?.icon, iconName]); + + // 입고 확정 2상태 버튼 색상: 미선택(기본) / 선택됨(초록) + const inboundButtonClass = useMemo(() => { + if (!isInboundConfirmMode) return ""; + return inboundSelectedCount > 0 + ? "bg-emerald-600 hover:bg-emerald-700 text-white border-emerald-600" + : ""; + }, [isInboundConfirmMode, inboundSelectedCount]); + return ( <>
@@ -685,11 +714,12 @@ export function PopButtonComponent({ "transition-transform active:scale-95", isIconOnly && "px-2", cartButtonClass, + inboundButtonClass, )} > - {(isCartMode ? cartIconName : iconName) && ( + {(isCartMode ? cartIconName : isInboundConfirmMode ? inboundIconName : iconName) && ( @@ -711,6 +741,16 @@ export function PopButtonComponent({ {cartCount}
)} + + {/* 입고 확정 선택 개수 배지 */} + {isInboundConfirmMode && inboundSelectedCount > 0 && ( +
+ {inboundSelectedCount} +
+ )} @@ -1990,6 +2030,7 @@ PopComponentRegistry.registerComponent({ { key: "cart_updated", label: "장바구니 상태", type: "event", category: "event", description: "카드 목록에서 장바구니 변경 시 count/isDirty 수신" }, { key: "cart_save_completed", label: "저장 완료", type: "event", category: "event", description: "카드 목록에서 DB 저장 완료 시 수신" }, { key: "collected_data", label: "수집된 데이터", type: "event", category: "event", description: "컴포넌트에서 수집한 데이터+매핑 응답" }, + { key: "selected_items", label: "선택된 항목", type: "event", category: "event", description: "카드 목록에서 체크박스로 선택된 항목 수 수신" }, ], }, touchOptimized: true, diff --git a/frontend/lib/registry/pop-components/pop-card-list/index.tsx b/frontend/lib/registry/pop-components/pop-card-list/index.tsx index 01b9bf64..b9b769af 100644 --- a/frontend/lib/registry/pop-components/pop-card-list/index.tsx +++ b/frontend/lib/registry/pop-components/pop-card-list/index.tsx @@ -63,7 +63,7 @@ PopComponentRegistry.registerComponent({ { key: "selected_row", label: "선택된 행", type: "selected_row", category: "data", description: "사용자가 선택한 카드의 행 데이터를 전달" }, { key: "cart_updated", label: "장바구니 상태", type: "event", category: "event", description: "장바구니 변경 시 count/isDirty 전달" }, { key: "cart_save_completed", label: "저장 완료", type: "event", category: "event", description: "장바구니 DB 저장 완료 후 결과 전달" }, - { key: "selected_items", label: "선택된 항목", type: "value", category: "data", description: "장바구니 모드에서 체크박스로 선택된 항목 배열" }, + { key: "selected_items", label: "선택된 항목", type: "event", category: "event", description: "장바구니 모드에서 체크박스로 선택된 항목 배열" }, { key: "collected_data", label: "수집 응답", type: "event", category: "event", description: "데이터 수집 요청에 대한 응답 (선택 항목 + 매핑)" }, ], receivable: [