"use client"; import React, { useState, useEffect } from "react"; import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, ResizableDialogTitle, ResizableDialogDescription, ResizableDialogFooter, } from "@/components/ui/resizable-dialog"; import { InteractiveScreenViewerDynamic } from "@/components/screen/InteractiveScreenViewerDynamic"; import { screenApi } from "@/lib/api/screen"; import { ComponentData } from "@/types/screen"; import { toast } from "sonner"; import { dynamicFormApi } from "@/lib/api/dynamicForm"; import { useAuth } from "@/hooks/useAuth"; interface EditModalState { isOpen: boolean; screenId: number | null; title: string; description?: string; modalSize: "sm" | "md" | "lg" | "xl"; editData: Record; onSave?: () => void; groupByColumns?: string[]; // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ (์˜ˆ: ["order_no"]) tableName?: string; // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… (๊ทธ๋ฃน ์กฐํšŒ์šฉ) } interface EditModalProps { className?: string; } export const EditModal: React.FC = ({ className }) => { const { user } = useAuth(); const [modalState, setModalState] = useState({ isOpen: false, screenId: null, title: "", description: "", modalSize: "md", editData: {}, onSave: undefined, groupByColumns: undefined, tableName: undefined, }); const [screenData, setScreenData] = useState<{ components: ComponentData[]; screenInfo: any; } | null>(null); const [loading, setLoading] = useState(false); const [screenDimensions, setScreenDimensions] = useState<{ width: number; height: number; offsetX?: number; offsetY?: number; } | null>(null); // ํผ ๋ฐ์ดํ„ฐ ์ƒํƒœ (ํŽธ์ง‘ ๋ฐ์ดํ„ฐ๋กœ ์ดˆ๊ธฐํ™”๋จ) const [formData, setFormData] = useState>({}); const [originalData, setOriginalData] = useState>({}); // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ƒํƒœ (๊ฐ™์€ order_no์˜ ๋ชจ๋“  ํ’ˆ๋ชฉ) const [groupData, setGroupData] = useState[]>([]); const [originalGroupData, setOriginalGroupData] = useState[]>([]); // ํ™”๋ฉด์˜ ์‹ค์ œ ํฌ๊ธฐ ๊ณ„์‚ฐ ํ•จ์ˆ˜ (ScreenModal๊ณผ ๋™์ผ) const calculateScreenDimensions = (components: ComponentData[]) => { if (components.length === 0) { return { width: 400, height: 300, offsetX: 0, offsetY: 0, }; } // ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์˜ ๊ฒฝ๊ณ„ ์ฐพ๊ธฐ let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; components.forEach((component) => { const x = parseFloat(component.position?.x?.toString() || "0"); const y = parseFloat(component.position?.y?.toString() || "0"); const width = parseFloat(component.size?.width?.toString() || "100"); const height = parseFloat(component.size?.height?.toString() || "40"); minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x + width); maxY = Math.max(maxY, y + height); }); // ์‹ค์ œ ์ปจํ…์ธ  ํฌ๊ธฐ ๊ณ„์‚ฐ const contentWidth = maxX - minX; const contentHeight = maxY - minY; // ์ ์ ˆํ•œ ์—ฌ๋ฐฑ ์ถ”๊ฐ€ (์ฃผ์„์ฒ˜๋ฆฌ - ์‚ฌ์šฉ์ž ์„ค์ • ํฌ๊ธฐ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ) // const paddingX = 40; // const paddingY = 40; const finalWidth = Math.max(contentWidth, 400); // padding ์ œ๊ฑฐ const finalHeight = Math.max(contentHeight, 300); // padding ์ œ๊ฑฐ return { width: Math.min(finalWidth, window.innerWidth * 0.95), height: Math.min(finalHeight, window.innerHeight * 0.9), offsetX: Math.max(0, minX), // paddingX ์ œ๊ฑฐ offsetY: Math.max(0, minY), // paddingY ์ œ๊ฑฐ }; }; // ์ „์—ญ ๋ชจ๋‹ฌ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ useEffect(() => { const handleOpenEditModal = (event: CustomEvent) => { const { screenId, title, description, modalSize, editData, onSave, groupByColumns, tableName } = event.detail; setModalState({ isOpen: true, screenId, title, description: description || "", modalSize: modalSize || "lg", editData: editData || {}, onSave, groupByColumns, // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ tableName, // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… }); // ํŽธ์ง‘ ๋ฐ์ดํ„ฐ๋กœ ํผ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” setFormData(editData || {}); setOriginalData(editData || {}); }; const handleCloseEditModal = () => { // ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ onSave ์ฝœ๋ฐฑ ์‹คํ–‰ (ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ) if (modalState.onSave) { try { modalState.onSave(); } catch (callbackError) { console.error("โš ๏ธ onSave ์ฝœ๋ฐฑ ์—๋Ÿฌ:", callbackError); } } // ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ handleClose(); }; window.addEventListener("openEditModal", handleOpenEditModal as EventListener); window.addEventListener("closeEditModal", handleCloseEditModal); return () => { window.removeEventListener("openEditModal", handleOpenEditModal as EventListener); window.removeEventListener("closeEditModal", handleCloseEditModal); }; }, [modalState.onSave]); // modalState.onSave๋ฅผ ์˜์กด์„ฑ์— ์ถ”๊ฐ€ํ•˜์—ฌ ์ตœ์‹  ์ฝœ๋ฐฑ ์ฐธ์กฐ // ํ™”๋ฉด ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ useEffect(() => { if (modalState.isOpen && modalState.screenId) { loadScreenData(modalState.screenId); // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ (groupByColumns๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ) if (modalState.groupByColumns && modalState.groupByColumns.length > 0 && modalState.tableName) { loadGroupData(); } } }, [modalState.isOpen, modalState.screenId]); // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ํ•จ์ˆ˜ const loadGroupData = async () => { if (!modalState.tableName || !modalState.groupByColumns || modalState.groupByColumns.length === 0) { console.warn("ํ…Œ์ด๋ธ”๋ช… ๋˜๋Š” ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ์ด ์—†์Šต๋‹ˆ๋‹ค."); return; } try { console.log("๐Ÿ” ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ์ž‘:", { tableName: modalState.tableName, groupByColumns: modalState.groupByColumns, editData: modalState.editData, }); // ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ ๊ฐ’ ์ถ”์ถœ (์˜ˆ: order_no = "ORD-20251124-001") const groupValues: Record = {}; modalState.groupByColumns.forEach((column) => { if (modalState.editData[column]) { groupValues[column] = modalState.editData[column]; } }); if (Object.keys(groupValues).length === 0) { console.warn("๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ ๊ฐ’์ด ์—†์Šต๋‹ˆ๋‹ค:", modalState.groupByColumns); return; } console.log("๐Ÿ” ๊ทธ๋ฃน ์กฐํšŒ ์š”์ฒญ:", { tableName: modalState.tableName, groupValues, }); // ๊ฐ™์€ ๊ทธ๋ฃน์˜ ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ ์กฐํšŒ (entityJoinApi ์‚ฌ์šฉ) const { entityJoinApi } = await import("@/lib/api/entityJoin"); const response = await entityJoinApi.getTableDataWithJoins(modalState.tableName, { page: 1, size: 1000, search: groupValues, // search ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ (๋ฐฑ์—”๋“œ์—์„œ WHERE ์กฐ๊ฑด์œผ๋กœ ์ฒ˜๋ฆฌ) enableEntityJoin: true, }); console.log("๐Ÿ” ๊ทธ๋ฃน ์กฐํšŒ ์‘๋‹ต:", response); // entityJoinApi๋Š” ๋ฐฐ์—ด ๋˜๋Š” { data: [] } ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜ const dataArray = Array.isArray(response) ? response : response?.data || []; if (dataArray.length > 0) { console.log("โœ… ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ์„ฑ๊ณต:", dataArray); setGroupData(dataArray); setOriginalGroupData(JSON.parse(JSON.stringify(dataArray))); // Deep copy toast.info(`${dataArray.length}๊ฐœ์˜ ๊ด€๋ จ ํ’ˆ๋ชฉ์„ ๋ถˆ๋Ÿฌ์™”์Šต๋‹ˆ๋‹ค.`); } else { console.warn("๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค:", response); setGroupData([modalState.editData]); // ๊ธฐ๋ณธ๊ฐ’: ์„ ํƒ๋œ ํ–‰๋งŒ setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]); } } catch (error: any) { console.error("โŒ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์กฐํšŒ ์˜ค๋ฅ˜:", error); toast.error("๊ด€๋ จ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); setGroupData([modalState.editData]); // ๊ธฐ๋ณธ๊ฐ’: ์„ ํƒ๋œ ํ–‰๋งŒ setOriginalGroupData([JSON.parse(JSON.stringify(modalState.editData))]); } }; const loadScreenData = async (screenId: number) => { try { setLoading(true); console.log("ํ™”๋ฉด ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์‹œ์ž‘:", screenId); // ํ™”๋ฉด ์ •๋ณด์™€ ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ const [screenInfo, layoutData] = await Promise.all([ screenApi.getScreen(screenId), screenApi.getLayout(screenId), ]); console.log("API ์‘๋‹ต:", { screenInfo, layoutData }); if (screenInfo && layoutData) { const components = layoutData.components || []; // ํ™”๋ฉด์˜ ์‹ค์ œ ํฌ๊ธฐ ๊ณ„์‚ฐ const dimensions = calculateScreenDimensions(components); setScreenDimensions(dimensions); setScreenData({ components, screenInfo: screenInfo, }); console.log("ํ™”๋ฉด ๋ฐ์ดํ„ฐ ์„ค์ • ์™„๋ฃŒ:", { componentsCount: components.length, dimensions, screenInfo, }); } else { throw new Error("ํ™”๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"); } } catch (error) { console.error("ํ™”๋ฉด ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์˜ค๋ฅ˜:", error); toast.error("ํ™”๋ฉด์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); handleClose(); } finally { setLoading(false); } }; const handleClose = () => { setModalState({ isOpen: false, screenId: null, title: "", description: "", modalSize: "md", editData: {}, onSave: undefined, groupByColumns: undefined, tableName: undefined, }); setScreenData(null); setFormData({}); setOriginalData({}); setGroupData([]); // ๐Ÿ†• setOriginalGroupData([]); // ๐Ÿ†• }; // ์ €์žฅ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ - UPDATE ์•ก์…˜ ์‹คํ–‰ const handleSave = async () => { if (!screenData?.screenInfo?.tableName) { toast.error("ํ…Œ์ด๋ธ” ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."); return; } try { // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ: ๋ชจ๋“  ํ’ˆ๋ชฉ ์ผ๊ด„ ์ˆ˜์ • if (groupData.length > 0) { console.log("๐Ÿ”„ ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ผ๊ด„ ์ˆ˜์ • ์‹œ์ž‘:", { groupDataLength: groupData.length, originalGroupDataLength: originalGroupData.length, }); let updatedCount = 0; for (let i = 0; i < groupData.length; i++) { const currentData = groupData[i]; const originalItemData = originalGroupData[i]; if (!originalItemData) { console.warn(`์›๋ณธ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค (index: ${i})`); continue; } // ๋ณ€๊ฒฝ๋œ ํ•„๋“œ๋งŒ ์ถ”์ถœ const changedData: Record = {}; // ๐Ÿ†• sales_order_mng ํ…Œ์ด๋ธ”์˜ ์‹ค์ œ ์ปฌ๋Ÿผ๋งŒ ํฌํ•จ (์กฐ์ธ๋œ ์ปฌ๋Ÿผ ์ œ์™ธ) const salesOrderColumns = [ "id", "order_no", "customer_code", "customer_name", "order_date", "delivery_date", "item_code", "quantity", "unit_price", "amount", "status", "notes", "created_at", "updated_at", "company_code", ]; Object.keys(currentData).forEach((key) => { // sales_order_mng ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋งŒ ์ฒ˜๋ฆฌ (์กฐ์ธ ์ปฌ๋Ÿผ ์ œ์™ธ) if (!salesOrderColumns.includes(key)) { return; } if (currentData[key] !== originalItemData[key]) { changedData[key] = currentData[key]; } }); // ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†์œผ๋ฉด ์Šคํ‚ต if (Object.keys(changedData).length === 0) { console.log(`๋ณ€๊ฒฝ์‚ฌํ•ญ ์—†์Œ (index: ${i})`); continue; } // ๊ธฐ๋ณธํ‚ค ํ™•์ธ const recordId = originalItemData.id || Object.values(originalItemData)[0]; // UPDATE ์‹คํ–‰ const response = await dynamicFormApi.updateFormDataPartial( recordId, originalItemData, changedData, screenData.screenInfo.tableName, ); if (response.success) { updatedCount++; console.log(`โœ… ํ’ˆ๋ชฉ ${i + 1} ์ˆ˜์ • ์„ฑ๊ณต (id: ${recordId})`); } else { console.error(`โŒ ํ’ˆ๋ชฉ ${i + 1} ์ˆ˜์ • ์‹คํŒจ (id: ${recordId}):`, response.message); } } if (updatedCount > 0) { toast.success(`${updatedCount}๊ฐœ์˜ ํ’ˆ๋ชฉ์ด ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); // ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ onSave ์ฝœ๋ฐฑ ์‹คํ–‰ (ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ) if (modalState.onSave) { try { modalState.onSave(); } catch (callbackError) { console.error("โš ๏ธ onSave ์ฝœ๋ฐฑ ์—๋Ÿฌ:", callbackError); } } handleClose(); } else { toast.info("๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์ด ์—†์Šต๋‹ˆ๋‹ค."); handleClose(); } return; } // ๊ธฐ์กด ๋กœ์ง: ๋‹จ์ผ ๋ ˆ์ฝ”๋“œ ์ˆ˜์ • const changedData: Record = {}; Object.keys(formData).forEach((key) => { if (formData[key] !== originalData[key]) { changedData[key] = formData[key]; } }); if (Object.keys(changedData).length === 0) { toast.info("๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์ด ์—†์Šต๋‹ˆ๋‹ค."); handleClose(); return; } // ๊ธฐ๋ณธํ‚ค ํ™•์ธ (id ๋˜๋Š” ์ฒซ ๋ฒˆ์งธ ํ‚ค) const recordId = originalData.id || Object.values(originalData)[0]; // UPDATE ์•ก์…˜ ์‹คํ–‰ const response = await dynamicFormApi.updateFormDataPartial( recordId, originalData, changedData, screenData.screenInfo.tableName, ); if (response.success) { toast.success("๋ฐ์ดํ„ฐ๊ฐ€ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); // ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์˜ onSave ์ฝœ๋ฐฑ ์‹คํ–‰ (ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ) if (modalState.onSave) { try { modalState.onSave(); } catch (callbackError) { console.error("โš ๏ธ onSave ์ฝœ๋ฐฑ ์—๋Ÿฌ:", callbackError); } } handleClose(); } else { throw new Error(response.message || "์ˆ˜์ •์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."); } } catch (error: any) { console.error("โŒ ์ˆ˜์ • ์‹คํŒจ:", error); toast.error(error.message || "๋ฐ์ดํ„ฐ ์ˆ˜์ • ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."); } }; // ๋ชจ๋‹ฌ ํฌ๊ธฐ ์„ค์ • - ํ™”๋ฉด๊ด€๋ฆฌ ์„ค์ • ํฌ๊ธฐ + ํ—ค๋” const getModalStyle = () => { if (!screenDimensions) { return { className: "w-fit min-w-[400px] max-w-4xl max-h-[90vh] overflow-hidden p-0", style: undefined, // undefined๋กœ ๋ณ€๊ฒฝ - defaultWidth/defaultHeight ์‚ฌ์šฉ }; } // ํ™”๋ฉด๊ด€๋ฆฌ์—์„œ ์„ค์ •ํ•œ ํฌ๊ธฐ = ์ปจํ…์ธ  ์˜์—ญ ํฌ๊ธฐ // ์‹ค์ œ ๋ชจ๋‹ฌ ํฌ๊ธฐ = ์ปจํ…์ธ  + ํ—ค๋” const headerHeight = 60; // DialogHeader const totalHeight = screenDimensions.height + headerHeight; return { className: "overflow-hidden p-0", style: { width: `${Math.min(screenDimensions.width, window.innerWidth * 0.98)}px`, height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`, maxWidth: "98vw", maxHeight: "95vh", }, }; }; const modalStyle = getModalStyle(); return (
{modalState.title || "๋ฐ์ดํ„ฐ ์ˆ˜์ •"} {modalState.description && !loading && ( {modalState.description} )} {loading && ( {loading ? "ํ™”๋ฉด์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค..." : ""} )}
{loading ? (

ํ™”๋ฉด์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...

) : screenData ? (
{/* ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ */} {groupData.length > 1 && (
{groupData.length}๊ฐœ์˜ ๊ด€๋ จ ํ’ˆ๋ชฉ์„ ํ•จ๊ป˜ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค
)} {screenData.components.map((component) => { // ์ปดํฌ๋„ŒํŠธ ์œ„์น˜๋ฅผ offset๋งŒํผ ์กฐ์ • const offsetX = screenDimensions?.offsetX || 0; const offsetY = screenDimensions?.offsetY || 0; const adjustedComponent = { ...component, position: { ...component.position, x: parseFloat(component.position?.x?.toString() || "0") - offsetX, y: parseFloat(component.position?.y?.toString() || "0") - offsetY, }, }; // ๐Ÿ” ๋””๋ฒ„๊น…: ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์‹œ์ ์˜ groupData ํ™•์ธ if (component.id === screenData.components[0]?.id) { console.log("๐Ÿ” [EditModal] InteractiveScreenViewerDynamic props:", { componentId: component.id, groupDataLength: groupData.length, groupData: groupData, formData: groupData.length > 0 ? groupData[0] : formData, }); } return ( 0 ? groupData[0] : formData} onFormDataChange={(fieldName, value) => { // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์ฒ˜๋ฆฌ if (groupData.length > 0) { // ModalRepeaterTable์˜ ๊ฒฝ์šฐ ๋ฐฐ์—ด ์ „์ฒด๋ฅผ ๋ฐ›์Œ if (Array.isArray(value)) { setGroupData(value); } else { // ์ผ๋ฐ˜ ํ•„๋“œ๋Š” ๋ชจ๋“  ํ•ญ๋ชฉ์— ๋™์ผํ•˜๊ฒŒ ์ ์šฉ setGroupData((prev) => prev.map((item) => ({ ...item, [fieldName]: value, })) ); } } else { setFormData((prev) => ({ ...prev, [fieldName]: value, })); } }} screenInfo={{ id: modalState.screenId!, tableName: screenData.screenInfo?.tableName, }} onSave={handleSave} // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๋ฅผ ModalRepeaterTable์— ์ „๋‹ฌ groupedData={groupData.length > 0 ? groupData : undefined} /> ); })}
) : (

ํ™”๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

)}
); }; export default EditModal;