diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index 0e7cc4ec..776eea83 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -24,6 +24,8 @@ interface EditModalState { modalSize: "sm" | "md" | "lg" | "xl"; editData: Record; onSave?: () => void; + groupByColumns?: string[]; // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ (์˜ˆ: ["order_no"]) + tableName?: string; // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… (๊ทธ๋ฃน ์กฐํšŒ์šฉ) } interface EditModalProps { @@ -40,6 +42,8 @@ export const EditModal: React.FC = ({ className }) => { modalSize: "md", editData: {}, onSave: undefined, + groupByColumns: undefined, + tableName: undefined, }); const [screenData, setScreenData] = useState<{ @@ -58,6 +62,10 @@ export const EditModal: React.FC = ({ className }) => { // ํผ ๋ฐ์ดํ„ฐ ์ƒํƒœ (ํŽธ์ง‘ ๋ฐ์ดํ„ฐ๋กœ ์ดˆ๊ธฐํ™”๋จ) const [formData, setFormData] = useState>({}); const [originalData, setOriginalData] = useState>({}); + + // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ƒํƒœ (๊ฐ™์€ order_no์˜ ๋ชจ๋“  ํ’ˆ๋ชฉ) + const [groupData, setGroupData] = useState[]>([]); + const [originalGroupData, setOriginalGroupData] = useState[]>([]); // ํ™”๋ฉด์˜ ์‹ค์ œ ํฌ๊ธฐ ๊ณ„์‚ฐ ํ•จ์ˆ˜ (ScreenModal๊ณผ ๋™์ผ) const calculateScreenDimensions = (components: ComponentData[]) => { @@ -110,7 +118,7 @@ export const EditModal: React.FC = ({ className }) => { // ์ „์—ญ ๋ชจ๋‹ฌ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ useEffect(() => { const handleOpenEditModal = (event: CustomEvent) => { - const { screenId, title, description, modalSize, editData, onSave } = event.detail; + const { screenId, title, description, modalSize, editData, onSave, groupByColumns, tableName } = event.detail; setModalState({ isOpen: true, @@ -120,6 +128,8 @@ export const EditModal: React.FC = ({ className }) => { modalSize: modalSize || "lg", editData: editData || {}, onSave, + groupByColumns, // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ + tableName, // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… }); // ํŽธ์ง‘ ๋ฐ์ดํ„ฐ๋กœ ํผ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” @@ -154,9 +164,78 @@ export const EditModal: React.FC = ({ className }) => { 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); @@ -208,10 +287,14 @@ export const EditModal: React.FC = ({ className }) => { modalSize: "md", editData: {}, onSave: undefined, + groupByColumns: undefined, + tableName: undefined, }); setScreenData(null); setFormData({}); setOriginalData({}); + setGroupData([]); // ๐Ÿ†• + setOriginalGroupData([]); // ๐Ÿ†• }; // ์ €์žฅ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ - UPDATE ์•ก์…˜ ์‹คํ–‰ @@ -222,7 +305,104 @@ export const EditModal: React.FC = ({ className }) => { } 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]) { @@ -339,6 +519,13 @@ export const EditModal: React.FC = ({ className }) => { maxHeight: "100%", }} > + {/* ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์œผ๋ฉด ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ */} + {groupData.length > 1 && ( +
+ {groupData.length}๊ฐœ์˜ ๊ด€๋ จ ํ’ˆ๋ชฉ์„ ํ•จ๊ป˜ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค +
+ )} + {screenData.components.map((component) => { // ์ปดํฌ๋„ŒํŠธ ์œ„์น˜๋ฅผ offset๋งŒํผ ์กฐ์ • const offsetX = screenDimensions?.offsetX || 0; @@ -353,23 +540,51 @@ export const EditModal: React.FC = ({ className }) => { }, }; + // ๐Ÿ” ๋””๋ฒ„๊น…: ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์‹œ์ ์˜ 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) => { - setFormData((prev) => ({ - ...prev, - [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} /> ); })} diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx index df134685..d1cd2a5f 100644 --- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx +++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx @@ -46,6 +46,8 @@ interface InteractiveScreenViewerProps { userId?: string; userName?: string; companyCode?: string; + // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (EditModal์—์„œ ์ „๋‹ฌ) + groupedData?: Record[]; } export const InteractiveScreenViewerDynamic: React.FC = ({ @@ -61,6 +63,7 @@ export const InteractiveScreenViewerDynamic: React.FC { const { isPreviewMode } = useScreenPreview(); // ํ”„๋ฆฌ๋ทฐ ๋ชจ๋“œ ํ™•์ธ const { userName: authUserName, user: authUser } = useAuth(); @@ -332,6 +335,8 @@ export const InteractiveScreenViewerDynamic: React.FC { diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 3792518e..92fd89e8 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -107,6 +107,8 @@ export interface DynamicComponentRendererProps { onClose?: () => void; // ํ…Œ์ด๋ธ” ์„ ํƒ๋œ ํ–‰ ์ •๋ณด (๋‹ค์ค‘ ์„ ํƒ ์•ก์…˜์šฉ) selectedRows?: any[]; + // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (EditModal โ†’ ModalRepeaterTable) + groupedData?: Record[]; selectedRowsData?: any[]; onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[], sortBy?: string, sortOrder?: "asc" | "desc", columnOrder?: string[], tableDisplayData?: any[]) => void; // ํ…Œ์ด๋ธ” ์ •๋ ฌ ์ •๋ณด (์—‘์…€ ๋‹ค์šด๋กœ๋“œ์šฉ) @@ -279,7 +281,17 @@ export const DynamicComponentRenderer: React.FC = // modal-repeater-table์€ ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋ฏ€๋กœ ๋นˆ ๋ฐฐ์—ด๋กœ ์ดˆ๊ธฐํ™” let currentValue; if (componentType === "modal-repeater-table") { - currentValue = formData?.[fieldName] || []; + // ๐Ÿ†• EditModal์—์„œ ์ „๋‹ฌ๋œ groupedData๊ฐ€ ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ + currentValue = props.groupedData || formData?.[fieldName] || []; + + // ๋””๋ฒ„๊น… ๋กœ๊ทธ + console.log("๐Ÿ” [DynamicComponentRenderer] ModalRepeaterTable value ์„ค์ •:", { + hasGroupedData: !!props.groupedData, + groupedDataLength: props.groupedData?.length || 0, + fieldName, + formDataValue: formData?.[fieldName], + finalValueLength: Array.isArray(currentValue) ? currentValue.length : 0, + }); } else { currentValue = formData?.[fieldName] || ""; } @@ -380,6 +392,8 @@ export const DynamicComponentRenderer: React.FC = isPreview, // ๋””์ž์ธ ๋ชจ๋“œ ํ”Œ๋ž˜๊ทธ ์ „๋‹ฌ - isPreview์™€ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ isDesignMode: props.isDesignMode !== undefined ? props.isDesignMode : false, + // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ ์ „๋‹ฌ (EditModal โ†’ ConditionalContainer โ†’ ModalRepeaterTable) + groupedData: props.groupedData, }; // ๋ Œ๋”๋Ÿฌ๊ฐ€ ํด๋ž˜์Šค์ธ์ง€ ํ•จ์ˆ˜์ธ์ง€ ํ™•์ธ diff --git a/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx b/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx index d1aff6de..2589026f 100644 --- a/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx +++ b/frontend/lib/registry/components/conditional-container/ConditionalContainerComponent.tsx @@ -40,6 +40,7 @@ export function ConditionalContainerComponent({ componentId, style, className, + groupedData, // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ }: ConditionalContainerProps) { console.log("๐ŸŽฏ ConditionalContainerComponent ๋ Œ๋”๋ง!", { isDesignMode, @@ -177,6 +178,7 @@ export function ConditionalContainerComponent({ showBorder={showBorder} formData={formData} onFormDataChange={onFormDataChange} + groupedData={groupedData} /> ))} @@ -196,6 +198,7 @@ export function ConditionalContainerComponent({ showBorder={showBorder} formData={formData} onFormDataChange={onFormDataChange} + groupedData={groupedData} /> ) : null ) diff --git a/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx index c660a0a8..d402f4df 100644 --- a/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx +++ b/frontend/lib/registry/components/conditional-container/ConditionalSectionViewer.tsx @@ -3,6 +3,7 @@ import React, { useState, useEffect } from "react"; import { ConditionalSectionViewerProps } from "./types"; import { RealtimePreview } from "@/components/screen/RealtimePreviewDynamic"; +import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer"; import { cn } from "@/lib/utils"; import { Loader2 } from "lucide-react"; import { screenApi } from "@/lib/api/screen"; @@ -24,6 +25,7 @@ export function ConditionalSectionViewer({ showBorder = true, formData, onFormDataChange, + groupedData, // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ }: ConditionalSectionViewerProps) { const { userId, userName, user } = useAuth(); const [isLoading, setIsLoading] = useState(false); @@ -135,22 +137,36 @@ export function ConditionalSectionViewer({ minHeight: "200px", }} > - {components.map((component) => ( - {}} - screenId={screenInfo?.id} - tableName={screenInfo?.tableName} - userId={userId} - userName={userName} - companyCode={user?.companyCode} - formData={formData} - onFormDataChange={onFormDataChange} - /> - ))} + {components.map((component) => { + const { position = { x: 0, y: 0, z: 1 }, size = { width: 200, height: 40 } } = component; + + return ( +
+ +
+ ); + })} )} diff --git a/frontend/lib/registry/components/conditional-container/types.ts b/frontend/lib/registry/components/conditional-container/types.ts index 6f1964e9..0cf741b2 100644 --- a/frontend/lib/registry/components/conditional-container/types.ts +++ b/frontend/lib/registry/components/conditional-container/types.ts @@ -45,6 +45,7 @@ export interface ConditionalContainerProps { onChange?: (value: string) => void; formData?: Record; onFormDataChange?: (fieldName: string, value: any) => void; + groupedData?: Record[]; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ (EditModal โ†’ ModalRepeaterTable) // ํ™”๋ฉด ํŽธ์ง‘๊ธฐ ๊ด€๋ จ isDesignMode?: boolean; // ๋””์ž์ธ ๋ชจ๋“œ ์—ฌ๋ถ€ @@ -75,5 +76,6 @@ export interface ConditionalSectionViewerProps { // ํผ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ formData?: Record; onFormDataChange?: (fieldName: string, value: any) => void; + groupedData?: Record[]; // ๐Ÿ†• ๊ทธ๋ฃน ๋ฐ์ดํ„ฐ } diff --git a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx index d903cc9f..3941a89f 100644 --- a/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx +++ b/frontend/lib/registry/components/modal-repeater-table/ModalRepeaterTableComponent.tsx @@ -291,13 +291,47 @@ export function ModalRepeaterTableComponent({ return; } + // ๐Ÿ”ฅ sourceColumns์— ํฌํ•จ๋œ ์ปฌ๋Ÿผ ์ œ์™ธ (์กฐ์ธ๋œ ์ปฌ๋Ÿผ ์ œ๊ฑฐ) + console.log("๐Ÿ” [ModalRepeaterTable] ํ•„ํ„ฐ๋ง ์ „ ๋ฐ์ดํ„ฐ:", { + sourceColumns, + sourceTable, + targetTable, + sampleItem: value[0], + itemKeys: value[0] ? Object.keys(value[0]) : [], + }); + + const filteredData = value.map((item: any) => { + const filtered: Record = {}; + + Object.keys(item).forEach((key) => { + // sourceColumns์— ํฌํ•จ๋œ ์ปฌ๋Ÿผ์€ ์ œ์™ธ (item_info ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ) + if (sourceColumns.includes(key)) { + console.log(` โ›” ${key} ์ œ์™ธ (sourceColumn)`); + return; + } + // ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ•„๋“œ๋„ ์ œ์™ธ + if (key.startsWith("_")) { + console.log(` โ›” ${key} ์ œ์™ธ (๋ฉ”ํƒ€๋ฐ์ดํ„ฐ)`); + return; + } + filtered[key] = item[key]; + }); + + return filtered; + }); + + console.log("โœ… [ModalRepeaterTable] ํ•„ํ„ฐ๋ง ํ›„ ๋ฐ์ดํ„ฐ:", { + filteredItemKeys: filteredData[0] ? Object.keys(filteredData[0]) : [], + sampleFilteredItem: filteredData[0], + }); + // ๐Ÿ”ฅ targetTable ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๋ฐฐ์—ด ํ•ญ๋ชฉ์— ์ถ”๊ฐ€ const dataWithTargetTable = targetTable - ? value.map(item => ({ + ? filteredData.map((item: any) => ({ ...item, _targetTable: targetTable, // ๋ฐฑ์—”๋“œ๊ฐ€ ์ธ์‹ํ•  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ })) - : value; + : filteredData; // โœ… CustomEvent์˜ detail์— ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ if (event instanceof CustomEvent && event.detail) { @@ -333,9 +367,10 @@ export function ModalRepeaterTableComponent({ const calculated = calculateAll(value); // ๊ฐ’์ด ์‹ค์ œ๋กœ ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ๋งŒ ์—…๋ฐ์ดํŠธ if (JSON.stringify(calculated) !== JSON.stringify(value)) { - onChange(calculated); + handleChange(calculated); } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleAddItems = async (items: any[]) => { diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 5f825cdc..3b9b9d9e 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -41,7 +41,8 @@ export interface ButtonActionConfig { // ๋ชจ๋‹ฌ/ํŒ์—… ๊ด€๋ จ modalTitle?: string; - modalTitleBlocks?: Array<{ // ๐Ÿ†• ๋ธ”๋ก ๊ธฐ๋ฐ˜ ์ œ๋ชฉ (์šฐ์„ ์ˆœ์œ„ ๋†’์Œ) + modalTitleBlocks?: Array<{ + // ๐Ÿ†• ๋ธ”๋ก ๊ธฐ๋ฐ˜ ์ œ๋ชฉ (์šฐ์„ ์ˆœ์œ„ ๋†’์Œ) id: string; type: "text" | "field"; value: string; // type=text: ํ…์ŠคํŠธ ๋‚ด์šฉ, type=field: ์ปฌ๋Ÿผ๋ช… @@ -88,6 +89,12 @@ export interface ButtonActionConfig { // ์ฝ”๋“œ ๋ณ‘ํ•ฉ ๊ด€๋ จ mergeColumnName?: string; // ๋ณ‘ํ•ฉํ•  ์ปฌ๋Ÿผ๋ช… (์˜ˆ: "item_code") mergeShowPreview?: boolean; // ๋ณ‘ํ•ฉ ์ „ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ‘œ์‹œ ์—ฌ๋ถ€ (๊ธฐ๋ณธ: true) + + // ํŽธ์ง‘ ๊ด€๋ จ (์ˆ˜์ฃผ๊ด€๋ฆฌ ๋“ฑ ๊ทธ๋ฃน๋ณ„ ๋‹ค์ค‘ ๋ ˆ์ฝ”๋“œ ํŽธ์ง‘) + editMode?: "modal" | "navigate" | "inline"; // ํŽธ์ง‘ ๋ชจ๋“œ + editModalTitle?: string; // ํŽธ์ง‘ ๋ชจ๋‹ฌ ์ œ๋ชฉ + editModalDescription?: string; // ํŽธ์ง‘ ๋ชจ๋‹ฌ ์„ค๋ช… + groupByColumns?: string[]; // ๊ฐ™์€ ๊ทธ๋ฃน์˜ ์—ฌ๋Ÿฌ ํ–‰์„ ํ•จ๊ป˜ ํŽธ์ง‘ (์˜ˆ: ["order_no"]) } /** @@ -1256,14 +1263,6 @@ export class ButtonActionExecutor { // ํ”Œ๋กœ์šฐ ์„ ํƒ ๋ฐ์ดํ„ฐ ์šฐ์„  ์‚ฌ์šฉ let dataToEdit = flowSelectedData && flowSelectedData.length > 0 ? flowSelectedData : selectedRowsData; - console.log("๐Ÿ” handleEdit - ๋ฐ์ดํ„ฐ ์†Œ์Šค ํ™•์ธ:", { - hasFlowSelectedData: !!(flowSelectedData && flowSelectedData.length > 0), - flowSelectedDataLength: flowSelectedData?.length || 0, - hasSelectedRowsData: !!(selectedRowsData && selectedRowsData.length > 0), - selectedRowsDataLength: selectedRowsData?.length || 0, - dataToEditLength: dataToEdit?.length || 0, - }); - // ์„ ํƒ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ if (!dataToEdit || dataToEdit.length === 0) { toast.error("์ˆ˜์ •ํ•  ํ•ญ๋ชฉ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); @@ -1276,26 +1275,15 @@ export class ButtonActionExecutor { return false; } - console.log(`๐Ÿ“ ํŽธ์ง‘ ์•ก์…˜ ์‹คํ–‰: ${dataToEdit.length}๊ฐœ ํ•ญ๋ชฉ`, { - dataToEdit, - targetScreenId: config.targetScreenId, - editMode: config.editMode, - }); - if (dataToEdit.length === 1) { // ๋‹จ์ผ ํ•ญ๋ชฉ ํŽธ์ง‘ const rowData = dataToEdit[0]; - console.log("๐Ÿ“ ๋‹จ์ผ ํ•ญ๋ชฉ ํŽธ์ง‘:", rowData); await this.openEditForm(config, rowData, context); } else { // ๋‹ค์ค‘ ํ•ญ๋ชฉ ํŽธ์ง‘ - ํ˜„์žฌ๋Š” ๋‹จ์ผ ํŽธ์ง‘๋งŒ ์ง€์› toast.error("ํ˜„์žฌ ๋‹จ์ผ ํ•ญ๋ชฉ ํŽธ์ง‘๋งŒ ์ง€์›๋ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํ•ญ๋ชฉ๋งŒ ์„ ํƒํ•ด์ฃผ์„ธ์š”."); return false; - - // TODO: ํ–ฅํ›„ ๋‹ค์ค‘ ํŽธ์ง‘ ์ง€์› - // console.log("๐Ÿ“ ๋‹ค์ค‘ ํ•ญ๋ชฉ ํŽธ์ง‘:", selectedRowsData); - // this.openBulkEditForm(config, selectedRowsData, context); } return true; @@ -1329,7 +1317,7 @@ export class ButtonActionExecutor { default: // ๊ธฐ๋ณธ๊ฐ’: ๋ชจ๋‹ฌ - this.openEditModal(config, rowData, context); + await this.openEditModal(config, rowData, context); } } @@ -1341,11 +1329,17 @@ export class ButtonActionExecutor { rowData: any, context: ButtonActionContext, ): Promise { - console.log("๐ŸŽญ ํŽธ์ง‘ ๋ชจ๋‹ฌ ์—ด๊ธฐ:", { - targetScreenId: config.targetScreenId, - modalSize: config.modalSize, - rowData, - }); + const { groupByColumns = [] } = config; + + // PK ๊ฐ’ ์ถ”์ถœ (์šฐ์„ ์ˆœ์œ„: id > ID > ์ฒซ ๋ฒˆ์งธ ํ•„๋“œ) + let primaryKeyValue: any; + if (rowData.id !== undefined && rowData.id !== null) { + primaryKeyValue = rowData.id; + } else if (rowData.ID !== undefined && rowData.ID !== null) { + primaryKeyValue = rowData.ID; + } else { + primaryKeyValue = Object.values(rowData)[0]; + } // 1. config์— editModalDescription์ด ์žˆ์œผ๋ฉด ์šฐ์„  ์‚ฌ์šฉ let description = config.editModalDescription || ""; @@ -1360,7 +1354,7 @@ export class ButtonActionExecutor { } } - // ๋ชจ๋‹ฌ ์—ด๊ธฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ + // ๐Ÿ”ง ํ•ญ์ƒ EditModal ์‚ฌ์šฉ (groupByColumns๋Š” EditModal์—์„œ ์ฒ˜๋ฆฌ) const modalEvent = new CustomEvent("openEditModal", { detail: { screenId: config.targetScreenId, @@ -1368,16 +1362,15 @@ export class ButtonActionExecutor { description: description, modalSize: config.modalSize || "lg", editData: rowData, + groupByColumns: groupByColumns.length > 0 ? groupByColumns : undefined, // ๐Ÿ†• ๊ทธ๋ฃนํ•‘ ์ปฌ๋Ÿผ ์ „๋‹ฌ + tableName: context.tableName, // ๐Ÿ†• ํ…Œ์ด๋ธ”๋ช… ์ „๋‹ฌ onSave: () => { - // ์ €์žฅ ํ›„ ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ - console.log("๐Ÿ’พ ํŽธ์ง‘ ์ €์žฅ ์™„๋ฃŒ - ํ…Œ์ด๋ธ” ์ƒˆ๋กœ๊ณ ์นจ"); context.onRefresh?.(); }, }, }); window.dispatchEvent(modalEvent); - // ํŽธ์ง‘ ๋ชจ๋‹ฌ ์—ด๊ธฐ๋Š” ์กฐ์šฉํžˆ ์ฒ˜๋ฆฌ (ํ† ์ŠคํŠธ ์—†์Œ) } /**