"use client"; import React, { useState, useEffect } from "react"; import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, ResizableDialogTitle } from "@/components/ui/resizable-dialog"; import { Button } from "@/components/ui/button"; import { X, Save, Loader2 } from "lucide-react"; import { toast } from "sonner"; import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer"; import { InteractiveScreenViewer } from "./InteractiveScreenViewer"; import { screenApi } from "@/lib/api/screen"; import { dynamicFormApi, DynamicFormData } from "@/lib/api/dynamicForm"; import { ComponentData } from "@/lib/types/screen"; import { useAuth } from "@/hooks/useAuth"; interface SaveModalProps { isOpen: boolean; onClose: () => void; screenId?: number; modalSize?: "sm" | "md" | "lg" | "xl" | "full"; initialData?: any; // 수정 모드일 때 기존 데이터 onSaveSuccess?: () => void; // 저장 성공 시 콜백 (테이블 새로고침용) } /** * 저장 전용 모달 컴포넌트 * - 저장 성공 시: 메시지 표시 → 모달 닫기 → 테이블 새로고침 * - 저장 실패 시: 에러 메시지 표시, 모달 유지 */ export const SaveModal: React.FC = ({ isOpen, onClose, screenId, modalSize = "lg", initialData, onSaveSuccess, }) => { const { user, userName } = useAuth(); // 현재 사용자 정보 가져오기 const [formData, setFormData] = useState>(initialData || {}); const [originalData, setOriginalData] = useState>(initialData || {}); const [screenData, setScreenData] = useState(null); const [components, setComponents] = useState([]); const [loading, setLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); // 모달 크기 설정 const modalSizeClasses = { sm: "max-w-md", md: "max-w-2xl", lg: "max-w-4xl", xl: "max-w-6xl", full: "max-w-[95vw]", }; // 화면 데이터 로드 useEffect(() => { const loadScreenData = async () => { if (!screenId || !isOpen) return; try { setLoading(true); // 화면 정보 로드 const screen = await screenApi.getScreen(screenId); setScreenData(screen); // 레이아웃 로드 const layout = await screenApi.getLayout(screenId); setComponents(layout.components || []); // initialData가 있으면 폼에 채우기 if (initialData) { setFormData(initialData); setOriginalData(initialData); } } catch (error) { console.error("화면 로드 실패:", error); toast.error("화면을 불러오는데 실패했습니다."); } finally { setLoading(false); } }; loadScreenData(); }, [screenId, isOpen, initialData]); // closeSaveModal 이벤트 리스너 useEffect(() => { const handleCloseSaveModal = () => { console.log("🚪 SaveModal 닫기 이벤트 수신"); onClose(); }; if (typeof window !== "undefined") { window.addEventListener("closeSaveModal", handleCloseSaveModal); } return () => { if (typeof window !== "undefined") { window.removeEventListener("closeSaveModal", handleCloseSaveModal); } }; }, [onClose]); // 저장 핸들러 const handleSave = async () => { if (!screenData || !screenId) return; // ✅ 사용자 정보가 로드되지 않았으면 저장 불가 if (!user?.userId) { toast.error("사용자 정보를 불러오는 중입니다. 잠시 후 다시 시도해주세요."); return; } try { setIsSaving(true); // 변경된 데이터만 추출 (수정 모드일 때) const changedData: Record = {}; if (initialData) { // 수정 모드: 변경된 필드만 전송 Object.keys(formData).forEach((key) => { if (formData[key] !== originalData[key]) { changedData[key] = formData[key]; } }); // 변경사항이 없으면 저장하지 않음 if (Object.keys(changedData).length === 0) { toast.info("변경된 내용이 없습니다."); setIsSaving(false); return; } } // 저장할 데이터 준비 const dataToSave = initialData ? changedData : formData; // 🆕 자동으로 작성자 정보 추가 (user.userId가 확실히 있음) const writerValue = user.userId; const companyCodeValue = user.companyCode || ""; console.log("👤 현재 사용자 정보:", { userId: user.userId, userName: userName, companyCode: user.companyCode, // ✅ 회사 코드 formDataWriter: dataToSave.writer, // ✅ 폼에서 입력한 writer 값 formDataCompanyCode: dataToSave.company_code, // ✅ 폼에서 입력한 company_code 값 defaultWriterValue: writerValue, companyCodeValue, // ✅ 최종 회사 코드 값 }); const dataWithUserInfo = { ...dataToSave, writer: dataToSave.writer || writerValue, // ✅ 입력값 우선, 없으면 userId created_by: writerValue, // created_by는 항상 로그인한 사람 updated_by: writerValue, // updated_by는 항상 로그인한 사람 company_code: dataToSave.company_code || companyCodeValue, // ✅ 입력값 우선, 없으면 user.companyCode }; // 테이블명 결정 const tableName = screenData.tableName || components.find((c) => c.columnName)?.tableName || "dynamic_form_data"; const saveData: DynamicFormData = { screenId: screenId, tableName: tableName, data: dataWithUserInfo, }; console.log("💾 저장 요청 데이터:", saveData); // API 호출 const result = await dynamicFormApi.saveFormData(saveData); if (result.success) { // ✅ 저장 성공 toast.success(initialData ? "수정되었습니다!" : "저장되었습니다!"); // 모달 닫기 onClose(); // 테이블 새로고침 콜백 호출 if (onSaveSuccess) { setTimeout(() => { onSaveSuccess(); }, 300); // 모달 닫힘 애니메이션 후 실행 } } else { throw new Error(result.message || "저장에 실패했습니다."); } } catch (error: any) { // ❌ 저장 실패 - 모달은 닫히지 않음 console.error("저장 실패:", error); toast.error(`저장 중 오류가 발생했습니다: ${error.message || "알 수 없는 오류"}`); } finally { setIsSaving(false); } }; // 동적 크기 계산 (컴포넌트들의 위치 기반) const calculateDynamicSize = () => { if (!components.length) return { width: 800, height: 600 }; const maxX = Math.max(...components.map((c) => { const x = c.position?.x || 0; const width = typeof c.size?.width === 'number' ? c.size.width : parseInt(String(c.size?.width || 200), 10); return x + width; })); const maxY = Math.max(...components.map((c) => { const y = c.position?.y || 0; const height = typeof c.size?.height === 'number' ? c.size.height : parseInt(String(c.size?.height || 40), 10); return y + height; })); const padding = 40; return { width: Math.max(maxX + padding, 400), height: Math.max(maxY + padding, 300), }; }; const dynamicSize = calculateDynamicSize(); return ( !isSaving && !open && onClose()}>
{initialData ? "데이터 수정" : "데이터 등록"}
{loading ? (
) : screenData && components.length > 0 ? (
{components.map((component, index) => { // ✅ 격자 시스템 잔재 제거: size의 픽셀 값만 사용 const widthPx = typeof component.size?.width === 'number' ? component.size.width : parseInt(String(component.size?.width || 200), 10); const heightPx = typeof component.size?.height === 'number' ? component.size.height : parseInt(String(component.size?.height || 40), 10); // 디버깅: 실제 크기 확인 if (index === 0) { console.log('🔍 SaveModal 컴포넌트 크기:', { componentId: component.id, 'size.width (원본)': component.size?.width, 'size.width 타입': typeof component.size?.width, 'widthPx (계산)': widthPx, 'style.width': component.style?.width, }); } return (
{component.type === "widget" ? ( { setFormData((prev) => ({ ...prev, [fieldName]: value, })); }} hideLabel={false} /> ) : ( { setFormData((prev) => ({ ...prev, [fieldName]: value, })); }} mode={initialData ? "edit" : "create"} isInModal={true} isInteractive={true} /> )}
); })}
) : (
화면에 컴포넌트가 없습니다.
)}
); };