- Replaced existing toast error messages with the new `showErrorToast` utility across multiple components, improving consistency in error reporting. - Updated error messages to provide more specific guidance for users, enhancing the overall user experience during error scenarios. - Ensured that all relevant error handling in batch management, external call configurations, cascading management, and screen management components now utilizes the new utility for better maintainability.
220 lines
6.0 KiB
TypeScript
220 lines
6.0 KiB
TypeScript
/**
|
|
* 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";
|
|
import { showErrorToast } from "@/lib/utils/toastUtils";
|
|
|
|
// ========================================
|
|
// 타입 정의
|
|
// ========================================
|
|
|
|
/** 확인 대기 중인 액션 상태 */
|
|
export interface PendingConfirmState {
|
|
action: ButtonMainAction;
|
|
rowData?: Record<string, unknown>;
|
|
fieldMapping?: Record<string, string>;
|
|
confirm: ConfirmConfig;
|
|
followUpActions?: FollowUpAction[];
|
|
}
|
|
|
|
/** execute 호출 시 옵션 */
|
|
interface ExecuteActionOptions {
|
|
/** 대상 행 데이터 */
|
|
rowData?: Record<string, unknown>;
|
|
/** 필드 매핑 */
|
|
fieldMapping?: Record<string, string>;
|
|
/** 확인 다이얼로그 설정 */
|
|
confirm?: ConfirmConfig;
|
|
/** 후속 액션 */
|
|
followUpActions?: FollowUpAction[];
|
|
}
|
|
|
|
// ========================================
|
|
// 상수
|
|
// ========================================
|
|
|
|
/** 액션 성공 시 토스트 메시지 */
|
|
const ACTION_SUCCESS_MESSAGES: Record<string, string> = {
|
|
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<PendingConfirmState | null>(null);
|
|
|
|
const { publish } = usePopEvent(screenId);
|
|
|
|
// publish 안정성 보장 (콜백 내에서 최신 참조 사용)
|
|
const publishRef = useRef(publish);
|
|
publishRef.current = publish;
|
|
|
|
/**
|
|
* 실제 실행 (확인 다이얼로그 이후 or 확인 불필요 시)
|
|
*/
|
|
const runAction = useCallback(
|
|
async (
|
|
action: ButtonMainAction,
|
|
rowData?: Record<string, unknown>,
|
|
fieldMapping?: Record<string, string>,
|
|
followUpActions?: FollowUpAction[]
|
|
): Promise<ActionResult> => {
|
|
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 {
|
|
showErrorToast("작업에 실패했습니다", result.error, { guidance: "잠시 후 다시 시도해 주세요." });
|
|
}
|
|
|
|
// 성공 시 후속 액션 실행
|
|
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<ActionResult> => {
|
|
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;
|
|
}
|