/** * usePopAction - POP 액션 실행 React 훅 * * executePopAction (순수 함수)를 래핑하여 React UI 관심사를 처리: * - 로딩 상태 (isLoading) * - 확인 다이얼로그 (pendingConfirm) * - 토스트 알림 * - 후속 액션 체이닝 (followUpActions) * * 사용처: * - PopButtonComponent (메인 버튼) * * pop-string-list 등 리스트 컴포넌트는 executePopAction을 직접 호출하여 * 훅 인스턴스 폭발 문제를 회피함. */ import { useState, useCallback, useRef } from "react"; import type { ButtonMainAction, FollowUpAction, ConfirmConfig, } from "@/lib/registry/pop-components/pop-button"; import { usePopEvent } from "./usePopEvent"; import { executePopAction } from "./executePopAction"; import type { ActionResult } from "./executePopAction"; import { toast } from "sonner"; // ======================================== // 타입 정의 // ======================================== /** 확인 대기 중인 액션 상태 */ export interface PendingConfirmState { action: ButtonMainAction; rowData?: Record; fieldMapping?: Record; confirm: ConfirmConfig; followUpActions?: FollowUpAction[]; } /** execute 호출 시 옵션 */ interface ExecuteActionOptions { /** 대상 행 데이터 */ rowData?: Record; /** 필드 매핑 */ fieldMapping?: Record; /** 확인 다이얼로그 설정 */ confirm?: ConfirmConfig; /** 후속 액션 */ followUpActions?: FollowUpAction[]; } // ======================================== // 상수 // ======================================== /** 액션 성공 시 토스트 메시지 */ const ACTION_SUCCESS_MESSAGES: Record = { save: "저장되었습니다.", delete: "삭제되었습니다.", api: "요청이 완료되었습니다.", modal: "", event: "", }; // ======================================== // 메인 훅 // ======================================== /** * POP 액션 실행 훅 * * @param screenId - 화면 ID (이벤트 버스 연결용) * @returns execute, isLoading, pendingConfirm, confirmExecute, cancelConfirm */ export function usePopAction(screenId: string) { const [isLoading, setIsLoading] = useState(false); const [pendingConfirm, setPendingConfirm] = useState(null); const { publish } = usePopEvent(screenId); // publish 안정성 보장 (콜백 내에서 최신 참조 사용) const publishRef = useRef(publish); publishRef.current = publish; /** * 실제 실행 (확인 다이얼로그 이후 or 확인 불필요 시) */ const runAction = useCallback( async ( action: ButtonMainAction, rowData?: Record, fieldMapping?: Record, followUpActions?: FollowUpAction[] ): Promise => { setIsLoading(true); try { const result = await executePopAction(action, rowData, { fieldMapping, screenId, publish: publishRef.current, }); // 결과에 따른 토스트 if (result.success) { const msg = ACTION_SUCCESS_MESSAGES[action.type]; if (msg) toast.success(msg); } else { toast.error(result.error || "작업에 실패했습니다."); } // 성공 시 후속 액션 실행 if (result.success && followUpActions?.length) { await executeFollowUpActions(followUpActions); } return result; } finally { setIsLoading(false); } }, [screenId] ); /** * 후속 액션 실행 */ const executeFollowUpActions = useCallback( async (actions: FollowUpAction[]) => { for (const followUp of actions) { switch (followUp.type) { case "event": if (followUp.eventName) { publishRef.current(followUp.eventName, followUp.eventPayload); } break; case "refresh": // 새로고침 이벤트 발행 (구독하는 컴포넌트가 refetch) publishRef.current("__pop_refresh__"); break; case "navigate": if (followUp.targetScreenId) { publishRef.current("__pop_navigate__", { screenId: followUp.targetScreenId, params: followUp.params, }); } break; case "close-modal": publishRef.current("__pop_modal_close__"); break; } } }, [] ); /** * 외부에서 호출하는 실행 함수 * confirm이 활성화되어 있으면 pendingConfirm에 저장하고 대기. * 비활성화이면 즉시 실행. */ const execute = useCallback( async ( action: ButtonMainAction, options?: ExecuteActionOptions ): Promise => { const { rowData, fieldMapping, confirm, followUpActions } = options || {}; // 확인 다이얼로그 필요 시 대기 if (confirm?.enabled) { setPendingConfirm({ action, rowData, fieldMapping, confirm, followUpActions, }); return { success: true }; // 대기 상태이므로 일단 success } // 즉시 실행 return runAction(action, rowData, fieldMapping, followUpActions); }, [runAction] ); /** * 확인 다이얼로그에서 "확인" 클릭 시 */ const confirmExecute = useCallback(async () => { if (!pendingConfirm) return; const { action, rowData, fieldMapping, followUpActions } = pendingConfirm; setPendingConfirm(null); await runAction(action, rowData, fieldMapping, followUpActions); }, [pendingConfirm, runAction]); /** * 확인 다이얼로그에서 "취소" 클릭 시 */ const cancelConfirm = useCallback(() => { setPendingConfirm(null); }, []); return { execute, isLoading, pendingConfirm, confirmExecute, cancelConfirm, } as const; }