feat(pop): 후속 액션 화면 이동 구현 + 입고확정 버튼 선택 상태 피드백
- PopViewerWithModals에 __pop_navigate__ 이벤트 구독 추가 - targetScreenId가 있으면 해당 POP 화면으로 이동 - "back"이면 router.back(), params는 쿼리스트링 전달 - 입고확정 버튼에 카드리스트 선택 상태 시각 피드백 - 미선택: 기본 아이콘/색상 - 선택됨: emerald-600 배경 + 선택 개수 뱃지 - selected_items connectionMeta category를 "event"로 변경하여 자동 매칭 대상 포함
This commit is contained in:
parent
f12fca46be
commit
2e8300bbf5
|
|
@ -12,6 +12,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useCallback, useEffect, useMemo } from "react";
|
import { useState, useCallback, useEffect, useMemo } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
|
@ -61,6 +62,7 @@ export default function PopViewerWithModals({
|
||||||
overrideGap,
|
overrideGap,
|
||||||
overridePadding,
|
overridePadding,
|
||||||
}: PopViewerWithModalsProps) {
|
}: PopViewerWithModalsProps) {
|
||||||
|
const router = useRouter();
|
||||||
const [modalStack, setModalStack] = useState<OpenModal[]>([]);
|
const [modalStack, setModalStack] = useState<OpenModal[]>([]);
|
||||||
const { subscribe, publish } = usePopEvent(screenId);
|
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<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 () => {
|
return () => {
|
||||||
unsubOpen();
|
unsubOpen();
|
||||||
unsubClose();
|
unsubClose();
|
||||||
|
unsubNavigate();
|
||||||
};
|
};
|
||||||
}, [subscribe, publish, layout.modals]);
|
}, [subscribe, publish, layout.modals, router]);
|
||||||
|
|
||||||
// 최상위 모달만 닫기 (X 버튼, overlay 클릭, ESC)
|
// 최상위 모달만 닫기 (X 버튼, overlay 클릭, ESC)
|
||||||
const handleCloseTopModal = useCallback(() => {
|
const handleCloseTopModal = useCallback(() => {
|
||||||
|
|
|
||||||
|
|
@ -420,6 +420,21 @@ export function PopButtonComponent({
|
||||||
const [showCartConfirm, setShowCartConfirm] = useState(false);
|
const [showCartConfirm, setShowCartConfirm] = useState(false);
|
||||||
const [confirmProcessing, setConfirmProcessing] = useState(false);
|
const [confirmProcessing, setConfirmProcessing] = useState(false);
|
||||||
const [showInboundConfirm, setShowInboundConfirm] = 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 전달)
|
// 장바구니 상태 수신 (카드 목록에서 count/isDirty 전달)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -673,6 +688,20 @@ export function PopButtonComponent({
|
||||||
return "";
|
return "";
|
||||||
}, [isCartMode, cartCount, cartIsDirty]);
|
}, [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
|
@ -685,11 +714,12 @@ export function PopButtonComponent({
|
||||||
"transition-transform active:scale-95",
|
"transition-transform active:scale-95",
|
||||||
isIconOnly && "px-2",
|
isIconOnly && "px-2",
|
||||||
cartButtonClass,
|
cartButtonClass,
|
||||||
|
inboundButtonClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{(isCartMode ? cartIconName : iconName) && (
|
{(isCartMode ? cartIconName : isInboundConfirmMode ? inboundIconName : iconName) && (
|
||||||
<DynamicLucideIcon
|
<DynamicLucideIcon
|
||||||
name={isCartMode ? cartIconName : iconName}
|
name={isCartMode ? cartIconName : isInboundConfirmMode ? inboundIconName : iconName}
|
||||||
size={16}
|
size={16}
|
||||||
className={isIconOnly ? "" : "mr-1.5"}
|
className={isIconOnly ? "" : "mr-1.5"}
|
||||||
/>
|
/>
|
||||||
|
|
@ -711,6 +741,16 @@ export function PopButtonComponent({
|
||||||
{cartCount}
|
{cartCount}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 입고 확정 선택 개수 배지 */}
|
||||||
|
{isInboundConfirmMode && inboundSelectedCount > 0 && (
|
||||||
|
<div
|
||||||
|
className="absolute -top-2 -right-2 flex items-center justify-center rounded-full bg-emerald-600 text-white text-[10px] font-bold"
|
||||||
|
style={{ minWidth: 18, height: 18, padding: "0 4px" }}
|
||||||
|
>
|
||||||
|
{inboundSelectedCount}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -1990,6 +2030,7 @@ PopComponentRegistry.registerComponent({
|
||||||
{ key: "cart_updated", label: "장바구니 상태", type: "event", category: "event", description: "카드 목록에서 장바구니 변경 시 count/isDirty 수신" },
|
{ key: "cart_updated", label: "장바구니 상태", type: "event", category: "event", description: "카드 목록에서 장바구니 변경 시 count/isDirty 수신" },
|
||||||
{ key: "cart_save_completed", label: "저장 완료", type: "event", category: "event", description: "카드 목록에서 DB 저장 완료 시 수신" },
|
{ key: "cart_save_completed", label: "저장 완료", type: "event", category: "event", description: "카드 목록에서 DB 저장 완료 시 수신" },
|
||||||
{ key: "collected_data", label: "수집된 데이터", type: "event", category: "event", description: "컴포넌트에서 수집한 데이터+매핑 응답" },
|
{ key: "collected_data", label: "수집된 데이터", type: "event", category: "event", description: "컴포넌트에서 수집한 데이터+매핑 응답" },
|
||||||
|
{ key: "selected_items", label: "선택된 항목", type: "event", category: "event", description: "카드 목록에서 체크박스로 선택된 항목 수 수신" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
touchOptimized: true,
|
touchOptimized: true,
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ PopComponentRegistry.registerComponent({
|
||||||
{ key: "selected_row", label: "선택된 행", type: "selected_row", category: "data", description: "사용자가 선택한 카드의 행 데이터를 전달" },
|
{ key: "selected_row", label: "선택된 행", type: "selected_row", category: "data", description: "사용자가 선택한 카드의 행 데이터를 전달" },
|
||||||
{ key: "cart_updated", label: "장바구니 상태", type: "event", category: "event", description: "장바구니 변경 시 count/isDirty 전달" },
|
{ key: "cart_updated", label: "장바구니 상태", type: "event", category: "event", description: "장바구니 변경 시 count/isDirty 전달" },
|
||||||
{ key: "cart_save_completed", label: "저장 완료", type: "event", category: "event", description: "장바구니 DB 저장 완료 후 결과 전달" },
|
{ 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: "데이터 수집 요청에 대한 응답 (선택 항목 + 매핑)" },
|
{ key: "collected_data", label: "수집 응답", type: "event", category: "event", description: "데이터 수집 요청에 대한 응답 (선택 항목 + 매핑)" },
|
||||||
],
|
],
|
||||||
receivable: [
|
receivable: [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue