"use client"; import React, { useState } from "react"; import { ComponentRendererProps } from "@/types/component"; import { ButtonPrimaryConfig } from "./types"; import { ButtonActionExecutor, ButtonActionContext, ButtonActionType, DEFAULT_BUTTON_ACTIONS, } from "@/lib/utils/buttonActions"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { toast } from "sonner"; import { filterDOMProps } from "@/lib/utils/domPropsFilter"; export interface ButtonPrimaryComponentProps extends ComponentRendererProps { config?: ButtonPrimaryConfig; // 추가 props screenId?: number; tableName?: string; onRefresh?: () => void; onClose?: () => void; // 폼 데이터 관련 originalData?: Record; // 부분 업데이트용 원본 데이터 // 테이블 선택된 행 정보 (다중 선택 액션용) selectedRows?: any[]; selectedRowsData?: any[]; } /** * ButtonPrimary 컴포넌트 * button-primary 컴포넌트입니다 */ export const ButtonPrimaryComponent: React.FC = ({ component, isDesignMode = false, isSelected = false, isInteractive = false, onClick, onDragStart, onDragEnd, config, className, style, formData, originalData, onFormDataChange, screenId, tableName, onRefresh, onClose, selectedRows, selectedRowsData, ...props }) => { // 확인 다이얼로그 상태 const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [pendingAction, setPendingAction] = useState<{ type: ButtonActionType; config: any; context: ButtonActionContext; } | null>(null); // 컴포넌트 설정 const componentConfig = { ...config, ...component.config, } as ButtonPrimaryConfig; // 액션 설정 처리 - DB에서 문자열로 저장된 액션을 객체로 변환 const processedConfig = { ...componentConfig }; if (componentConfig.action && typeof componentConfig.action === "string") { const actionType = componentConfig.action as ButtonActionType; processedConfig.action = { ...DEFAULT_BUTTON_ACTIONS[actionType], type: actionType, }; } console.log("🔧 버튼 컴포넌트 설정:", { originalConfig: componentConfig, processedConfig, component: component, screenId, tableName, onRefresh, onClose, selectedRows, selectedRowsData, }); // 스타일 계산 (위치는 RealtimePreviewDynamic에서 처리하므로 제외) const componentStyle: React.CSSProperties = { width: "100%", height: "100%", ...component.style, ...style, }; // 디자인 모드 스타일 if (isDesignMode) { componentStyle.border = "1px dashed #cbd5e1"; componentStyle.borderColor = isSelected ? "#3b82f6" : "#cbd5e1"; } // 확인 다이얼로그가 필요한 액션 타입들 const confirmationRequiredActions: ButtonActionType[] = ["save", "submit", "delete"]; // 실제 액션 실행 함수 const executeAction = async (actionConfig: any, context: ButtonActionContext) => { console.log("🚀 executeAction 시작:", { actionConfig, context }); let loadingToast: string | number | undefined; try { // edit 액션을 제외하고만 로딩 토스트 표시 if (actionConfig.type !== "edit") { console.log("📱 로딩 토스트 표시 시작"); loadingToast = toast.loading( actionConfig.type === "save" ? "저장 중..." : actionConfig.type === "delete" ? "삭제 중..." : actionConfig.type === "submit" ? "제출 중..." : "처리 중...", ); console.log("📱 로딩 토스트 ID:", loadingToast); } console.log("⚡ ButtonActionExecutor.executeAction 호출 시작"); const success = await ButtonActionExecutor.executeAction(actionConfig, context); console.log("⚡ ButtonActionExecutor.executeAction 완료, success:", success); // 로딩 토스트 제거 (있는 경우에만) if (loadingToast) { console.log("📱 로딩 토스트 제거"); toast.dismiss(loadingToast); } // edit 액션은 조용히 처리 (모달 열기만 하므로 토스트 불필요) if (actionConfig.type !== "edit") { const successMessage = actionConfig.successMessage || (actionConfig.type === "save" ? "저장되었습니다." : actionConfig.type === "delete" ? "삭제되었습니다." : actionConfig.type === "submit" ? "제출되었습니다." : "완료되었습니다."); console.log("🎉 성공 토스트 표시:", successMessage); toast.success(successMessage); } else { console.log("🔕 edit 액션은 조용히 처리 (토스트 없음)"); } console.log("✅ 버튼 액션 실행 성공:", actionConfig.type); } catch (error) { console.log("❌ executeAction catch 블록 진입:", error); // 로딩 토스트 제거 if (loadingToast) { console.log("📱 오류 시 로딩 토스트 제거"); toast.dismiss(loadingToast); } console.error("❌ 버튼 액션 실행 오류:", error); // 오류 토스트 표시 const errorMessage = actionConfig.errorMessage || (actionConfig.type === "save" ? "저장 중 오류가 발생했습니다." : actionConfig.type === "delete" ? "삭제 중 오류가 발생했습니다." : actionConfig.type === "submit" ? "제출 중 오류가 발생했습니다." : "처리 중 오류가 발생했습니다."); console.log("💥 오류 토스트 표시:", errorMessage); toast.error(errorMessage); } }; // 이벤트 핸들러 const handleClick = async (e: React.MouseEvent) => { e.stopPropagation(); // 디자인 모드에서는 기본 onClick만 실행 if (isDesignMode) { onClick?.(); return; } // 인터랙티브 모드에서 액션 실행 if (isInteractive && processedConfig.action) { const context: ButtonActionContext = { formData: formData || {}, originalData: originalData || {}, // 부분 업데이트용 원본 데이터 추가 screenId, tableName, onFormDataChange, onRefresh, onClose, // 테이블 선택된 행 정보 추가 selectedRows, selectedRowsData, }; // 확인이 필요한 액션인지 확인 if (confirmationRequiredActions.includes(processedConfig.action.type)) { // 확인 다이얼로그 표시 setPendingAction({ type: processedConfig.action.type, config: processedConfig.action, context, }); setShowConfirmDialog(true); } else { // 확인이 필요하지 않은 액션은 바로 실행 await executeAction(processedConfig.action, context); } } else { // 액션이 설정되지 않은 경우 기본 onClick 실행 onClick?.(); } }; // 확인 다이얼로그에서 확인 버튼 클릭 시 const handleConfirmAction = async () => { if (pendingAction) { await executeAction(pendingAction.config, pendingAction.context); } setShowConfirmDialog(false); setPendingAction(null); }; // 확인 다이얼로그에서 취소 버튼 클릭 시 const handleCancelAction = () => { setShowConfirmDialog(false); setPendingAction(null); }; // DOM에 전달하면 안 되는 React-specific props 필터링 const { selectedScreen, onZoneComponentDrop, onZoneClick, componentConfig: _componentConfig, component: _component, isSelected: _isSelected, onClick: _onClick, onDragStart: _onDragStart, onDragEnd: _onDragEnd, size: _size, position: _position, style: _style, screenId: _screenId, tableName: _tableName, onRefresh: _onRefresh, onClose: _onClose, selectedRows: _selectedRows, selectedRowsData: _selectedRowsData, onSelectedRowsChange: _onSelectedRowsChange, originalData: _originalData, // 부분 업데이트용 원본 데이터 필터링 refreshKey: _refreshKey, // 필터링 추가 isInModal: _isInModal, // 필터링 추가 mode: _mode, // 필터링 추가 ...domProps } = props; // 다이얼로그 메시지 생성 const getConfirmMessage = () => { if (!pendingAction) return ""; const customMessage = pendingAction.config.confirmMessage; if (customMessage) return customMessage; switch (pendingAction.type) { case "save": return "변경사항을 저장하시겠습니까?"; case "delete": return "정말로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다."; case "submit": return "제출하시겠습니까?"; default: return "이 작업을 실행하시겠습니까?"; } }; const getConfirmTitle = () => { if (!pendingAction) return ""; switch (pendingAction.type) { case "save": return "저장 확인"; case "delete": return "삭제 확인"; case "submit": return "제출 확인"; default: return "작업 확인"; } }; // DOM 안전한 props만 필터링 const safeDomProps = filterDOMProps(domProps); return ( <>
{/* 확인 다이얼로그 */} {getConfirmTitle()} {getConfirmMessage()} 취소 {pendingAction?.type === "save" ? "저장" : pendingAction?.type === "delete" ? "삭제" : pendingAction?.type === "submit" ? "제출" : "확인"} ); }; /** * ButtonPrimary 래퍼 컴포넌트 * 추가적인 로직이나 상태 관리가 필요한 경우 사용 */ export const ButtonPrimaryWrapper: React.FC = (props) => { return ; };