버튼 액션 안되는 버그 수정

This commit is contained in:
kjs
2025-10-23 13:15:52 +09:00
parent b66b7c66f0
commit 4996dd5562
6 changed files with 156 additions and 93 deletions

View File

@@ -64,6 +64,15 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
selectedRowsData,
...props
}) => {
console.log("🔵 ButtonPrimaryComponent 렌더링, 받은 props:", {
componentId: component.id,
hasSelectedRowsData: !!selectedRowsData,
selectedRowsDataLength: selectedRowsData?.length,
selectedRowsData,
tableName,
screenId,
});
// 확인 다이얼로그 상태
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [pendingAction, setPendingAction] = useState<{
@@ -204,7 +213,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
}
// 확인 다이얼로그가 필요한 액션 타입들
const confirmationRequiredActions: ButtonActionType[] = ["save", "submit", "delete"];
const confirmationRequiredActions: ButtonActionType[] = ["save", "delete"];
// 실제 액션 실행 함수
const executeAction = async (actionConfig: any, context: ButtonActionContext) => {
@@ -221,8 +230,9 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// 추가 안전장치: 모든 로딩 토스트 제거
toast.dismiss();
// edit 액션을 제외하고만 로딩 토스트 표시
if (actionConfig.type !== "edit") {
// UI 전환 액션(edit, modal, navigate)을 제외하고만 로딩 토스트 표시
const silentActions = ["edit", "modal", "navigate"];
if (!silentActions.includes(actionConfig.type)) {
console.log("📱 로딩 토스트 표시 시작");
currentLoadingToastRef.current = toast.loading(
actionConfig.type === "save"
@@ -237,9 +247,16 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
},
);
console.log("📱 로딩 토스트 ID:", currentLoadingToastRef.current);
} else {
console.log("🔕 UI 전환 액션은 로딩 토스트 표시 안함:", actionConfig.type);
}
console.log("⚡ ButtonActionExecutor.executeAction 호출 시작");
console.log("🔍 actionConfig 확인:", {
type: actionConfig.type,
successMessage: actionConfig.successMessage,
errorMessage: actionConfig.errorMessage,
});
const success = await ButtonActionExecutor.executeAction(actionConfig, context);
console.log("⚡ ButtonActionExecutor.executeAction 완료, success:", success);
@@ -252,37 +269,70 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// 실패한 경우 오류 처리
if (!success) {
// UI 전환 액션(edit, modal, navigate)은 에러도 조용히 처리
const silentActions = ["edit", "modal", "navigate"];
if (silentActions.includes(actionConfig.type)) {
console.log("🔕 UI 전환 액션 실패지만 에러 토스트 표시 안함:", actionConfig.type);
return;
}
console.log("❌ 액션 실패, 오류 토스트 표시");
const errorMessage =
actionConfig.errorMessage ||
(actionConfig.type === "save"
// 기본 에러 메시지 결정
const defaultErrorMessage =
actionConfig.type === "save"
? "저장 중 오류가 발생했습니다."
: actionConfig.type === "delete"
? "삭제 중 오류가 발생했습니다."
: actionConfig.type === "submit"
? "제출 중 오류가 발생했습니다."
: "처리 중 오류가 발생했습니다.");
: "처리 중 오류가 발생했습니다.";
// 커스텀 메시지 사용 조건:
// 1. 커스텀 메시지가 있고
// 2. (액션 타입이 save이거나 OR 메시지에 "저장"이 포함되지 않은 경우)
const useCustomMessage =
actionConfig.errorMessage &&
(actionConfig.type === "save" || !actionConfig.errorMessage.includes("저장"));
const errorMessage = useCustomMessage ? actionConfig.errorMessage : defaultErrorMessage;
console.log("🔍 에러 메시지 결정:", {
actionType: actionConfig.type,
customMessage: actionConfig.errorMessage,
useCustom: useCustomMessage,
finalMessage: errorMessage
});
toast.error(errorMessage);
return;
}
// 성공한 경우에만 성공 토스트 표시
// edit 액션은 조용히 처리 (모달 열기만 하므로 토스트 불필요)
if (actionConfig.type !== "edit") {
const successMessage =
actionConfig.successMessage ||
(actionConfig.type === "save"
// edit, modal, navigate 액션은 조용히 처리 (UI 전환만 하므로 토스트 불필요)
if (actionConfig.type !== "edit" && actionConfig.type !== "modal" && actionConfig.type !== "navigate") {
// 기본 성공 메시지 결정
const defaultSuccessMessage =
actionConfig.type === "save"
? "저장되었습니다."
: actionConfig.type === "delete"
? "삭제되었습니다."
: actionConfig.type === "submit"
? "제출되었습니다."
: "완료되었습니다.");
: "완료되었습니다.";
// 커스텀 메시지 사용 조건:
// 1. 커스텀 메시지가 있고
// 2. (액션 타입이 save이거나 OR 메시지에 "저장"이 포함되지 않은 경우)
const useCustomMessage =
actionConfig.successMessage &&
(actionConfig.type === "save" || !actionConfig.successMessage.includes("저장"));
const successMessage = useCustomMessage ? actionConfig.successMessage : defaultSuccessMessage;
console.log("🎉 성공 토스트 표시:", successMessage);
toast.success(successMessage);
} else {
console.log("🔕 edit 액션은 조용히 처리 (토스트 없음)");
console.log("🔕 UI 전환 액션은 조용히 처리 (토스트 없음):", actionConfig.type);
}
console.log("✅ 버튼 액션 실행 성공:", actionConfig.type);
@@ -357,6 +407,13 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
requiresConfirmation: confirmationRequiredActions.includes(processedConfig.action.type),
});
// 삭제 액션인데 선택된 데이터가 없으면 경고 메시지 표시하고 중단
if (processedConfig.action.type === "delete" && (!selectedRowsData || selectedRowsData.length === 0)) {
console.log("⚠️ 삭제할 데이터가 선택되지 않았습니다.");
toast.warning("삭제할 항목을 먼저 선택해주세요.");
return;
}
const context: ButtonActionContext = {
formData: formData || {},
originalData: originalData || {}, // 부분 업데이트용 원본 데이터 추가
@@ -370,6 +427,15 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
selectedRowsData,
};
console.log("🔍 버튼 액션 실행 전 context 확인:", {
hasSelectedRowsData: !!selectedRowsData,
selectedRowsDataLength: selectedRowsData?.length,
selectedRowsData,
tableName,
screenId,
formData,
});
// 확인이 필요한 액션인지 확인
if (confirmationRequiredActions.includes(processedConfig.action.type)) {
console.log("📋 확인 다이얼로그 표시 중...");

View File

@@ -11,18 +11,11 @@ import type { ExtendedControlContext } from "@/types/control-management";
*/
export type ButtonActionType =
| "save" // 저장
| "cancel" // 취소
| "delete" // 삭제
| "edit" // 편집
| "add" // 추가
| "search" // 검색
| "reset" // 초기화
| "submit" // 제출
| "close" // 닫기
| "popup" // 팝업 열기
| "navigate" // 페이지 이동
| "modal" // 모달 열기
| "newWindow"; // 새 창 열기
| "control"; // 제어 흐름
/**
* 버튼 액션 설정
@@ -92,42 +85,18 @@ export class ButtonActionExecutor {
case "save":
return await this.handleSave(config, context);
case "submit":
return await this.handleSubmit(config, context);
case "delete":
return await this.handleDelete(config, context);
case "reset":
return this.handleReset(config, context);
case "cancel":
return this.handleCancel(config, context);
case "navigate":
return this.handleNavigate(config, context);
case "modal":
return this.handleModal(config, context);
case "newWindow":
return this.handleNewWindow(config, context);
case "popup":
return this.handlePopup(config, context);
case "search":
return this.handleSearch(config, context);
case "add":
return this.handleAdd(config, context);
case "edit":
return this.handleEdit(config, context);
case "close":
return this.handleClose(config, context);
case "control":
return this.handleControl(config, context);
@@ -515,9 +484,9 @@ export class ButtonActionExecutor {
});
window.dispatchEvent(modalEvent);
toast.success("모달 화면이 열렸습니다.");
// 모달 열기는 조용히 처리 (토스트 불필요)
} else {
toast.error("모달로 열 화면이 지정되지 않았습니다.");
console.error("모달로 열 화면이 지정되지 않았습니다.");
return false;
}
@@ -1421,26 +1390,12 @@ export const DEFAULT_BUTTON_ACTIONS: Record<ButtonActionType, Partial<ButtonActi
successMessage: "저장되었습니다.",
errorMessage: "저장 중 오류가 발생했습니다.",
},
submit: {
type: "submit",
validateForm: true,
successMessage: "제출되었습니다.",
errorMessage: "제출 중 오류가 발생했습니다.",
},
delete: {
type: "delete",
confirmMessage: "정말 삭제하시겠습니까?",
successMessage: "삭제되었습니다.",
errorMessage: "삭제 중 오류가 발생했습니다.",
},
reset: {
type: "reset",
confirmMessage: "초기화하시겠습니까?",
successMessage: "초기화되었습니다.",
},
cancel: {
type: "cancel",
},
navigate: {
type: "navigate",
},
@@ -1448,29 +1403,11 @@ export const DEFAULT_BUTTON_ACTIONS: Record<ButtonActionType, Partial<ButtonActi
type: "modal",
modalSize: "md",
},
newWindow: {
type: "newWindow",
popupWidth: 800,
popupHeight: 600,
},
popup: {
type: "popup",
popupWidth: 600,
popupHeight: 400,
},
search: {
type: "search",
successMessage: "검색을 실행했습니다.",
},
add: {
type: "add",
successMessage: "추가되었습니다.",
},
edit: {
type: "edit",
successMessage: "편집되었습니다.",
},
close: {
type: "close",
control: {
type: "control",
},
};