diff --git a/frontend/hooks/pop/useConnectionResolver.ts b/frontend/hooks/pop/useConnectionResolver.ts index ca408692..14bd321a 100644 --- a/frontend/hooks/pop/useConnectionResolver.ts +++ b/frontend/hooks/pop/useConnectionResolver.ts @@ -11,7 +11,8 @@ * * _auto 모드: * sourceOutput="_auto"인 연결은 소스/타겟의 connectionMeta를 비교하여 - * key가 같고 category="event"인 쌍을 모두 자동 라우팅한다. + * key가 같고 category="event"인 쌍을 양방향으로 자동 라우팅한다. + * (정방향: 소스->타겟, 역방향: 타겟->소스) */ import { useEffect, useRef } from "react"; @@ -85,9 +86,9 @@ export function useConnectionResolver({ if (!sourceType || !targetType) continue; - const pairs = getAutoMatchPairs(sourceType, targetType); - - for (const pair of pairs) { + // 정방향: 소스 sendable -> 타겟 receivable + const forwardPairs = getAutoMatchPairs(sourceType, targetType); + for (const pair of forwardPairs) { const sourceEvent = `__comp_output__${conn.sourceComponent}__${pair.sourceKey}`; const targetEvent = `__comp_input__${conn.targetComponent}__${pair.targetKey}`; @@ -99,6 +100,21 @@ export function useConnectionResolver({ }); unsubscribers.push(unsub); } + + // 역방향: 타겟 sendable -> 소스 receivable + const reversePairs = getAutoMatchPairs(targetType, sourceType); + for (const pair of reversePairs) { + const sourceEvent = `__comp_output__${conn.targetComponent}__${pair.sourceKey}`; + const targetEvent = `__comp_input__${conn.sourceComponent}__${pair.targetKey}`; + + const unsub = subscribe(sourceEvent, (payload: unknown) => { + publish(targetEvent, { + value: payload, + _connectionId: conn.id, + }); + }); + unsubscribers.push(unsub); + } } else { const sourceEvent = `__comp_output__${conn.sourceComponent}__${conn.sourceOutput || conn.sourceField}`; diff --git a/frontend/lib/registry/pop-components/pop-button.tsx b/frontend/lib/registry/pop-components/pop-button.tsx index df9a4fc8..a3d7f4b9 100644 --- a/frontend/lib/registry/pop-components/pop-button.tsx +++ b/frontend/lib/registry/pop-components/pop-button.tsx @@ -547,7 +547,7 @@ export function PopButtonComponent({ const cardListMapping = cardListData?.mapping ?? null; const fieldMapping = fieldData?.mapping ?? null; - const result = await apiClient.post("/api/pop/execute-action", { + const result = await apiClient.post("/pop/execute-action", { action: "inbound-confirm", data: { items: selectedItems, @@ -567,6 +567,24 @@ export function PopButtonComponent({ success: true, count: selectedItems.length, }); + + // 후속 액션 실행 (navigate, refresh 등) + const followUps = config?.followUpActions ?? []; + for (const fa of followUps) { + switch (fa.type) { + case "navigate": + if (fa.targetScreenId) { + publish("__pop_navigate__", { screenId: fa.targetScreenId, params: fa.params }); + } + break; + case "refresh": + publish("__pop_refresh__"); + break; + case "event": + if (fa.eventName) publish(fa.eventName, fa.eventPayload); + break; + } + } } else { toast.error(result.data?.message || "입고 확정에 실패했습니다."); } @@ -577,7 +595,7 @@ export function PopButtonComponent({ setConfirmProcessing(false); setShowInboundConfirm(false); } - }, [componentId, subscribe, publish, config?.statusChangeRules, config?.inboundConfirm?.statusChangeRules]); + }, [componentId, subscribe, publish, config?.statusChangeRules, config?.inboundConfirm?.statusChangeRules, config?.followUpActions]); // 클릭 핸들러 const handleClick = useCallback(async () => {