diff --git a/backend-node/package-lock.json b/backend-node/package-lock.json index c365a102..ae55e3c4 100644 --- a/backend-node/package-lock.json +++ b/backend-node/package-lock.json @@ -1045,7 +1045,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2373,7 +2372,6 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", "license": "MIT", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -3477,7 +3475,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz", "integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3714,7 +3711,6 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -3932,7 +3928,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4459,7 +4454,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -5670,7 +5664,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -5949,7 +5942,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -7443,7 +7435,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -8413,6 +8404,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", + "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -9301,7 +9293,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -10152,6 +10143,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -10960,7 +10952,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11066,7 +11057,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx b/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx index ad26004b..689a2407 100644 --- a/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx +++ b/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutComponent.tsx @@ -1993,6 +1993,88 @@ export const SplitPanelLayoutComponent: React.FC // 추가 버튼 핸들러 const handleAddClick = useCallback( (panel: "left" | "right") => { + // 좌측 패널 추가 시, addButton 모달 모드 확인 + if (panel === "left") { + const addButtonConfig = componentConfig.leftPanel?.addButton; + if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) { + const leftTableName = componentConfig.leftPanel?.tableName || ""; + + // ScreenModal 열기 이벤트 발생 + window.dispatchEvent( + new CustomEvent("openScreenModal", { + detail: { + screenId: addButtonConfig.modalScreenId, + urlParams: { + mode: "add", + tableName: leftTableName, + }, + }, + }), + ); + + console.log("✅ [SplitPanel] 좌측 추가 모달 화면 열기:", { + screenId: addButtonConfig.modalScreenId, + tableName: leftTableName, + }); + return; + } + } + + // 우측 패널 추가 시, addButton 모달 모드 확인 + if (panel === "right") { + const addButtonConfig = + activeTabIndex === 0 + ? componentConfig.rightPanel?.addButton + : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.addButton; + + if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) { + // 커스텀 모달 화면 열기 + const currentTableName = + activeTabIndex === 0 + ? componentConfig.rightPanel?.tableName || "" + : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.tableName || ""; + + // 좌측 선택 데이터를 modalDataStore에 저장 (추가 화면에서 참조 가능) + if (selectedLeftItem && componentConfig.leftPanel?.tableName) { + import("@/stores/modalDataStore").then(({ useModalDataStore }) => { + useModalDataStore.getState().setData(componentConfig.leftPanel!.tableName!, [selectedLeftItem]); + }); + } + + // ScreenModal 열기 이벤트 발생 + window.dispatchEvent( + new CustomEvent("openScreenModal", { + detail: { + screenId: addButtonConfig.modalScreenId, + urlParams: { + mode: "add", + tableName: currentTableName, + // 좌측 선택 항목의 연결 키 값 전달 + ...(selectedLeftItem && (() => { + const relation = activeTabIndex === 0 + ? componentConfig.rightPanel?.relation + : (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.relation; + const leftColumn = relation?.keys?.[0]?.leftColumn || relation?.leftColumn; + const rightColumn = relation?.keys?.[0]?.rightColumn || relation?.foreignKey; + if (leftColumn && rightColumn && selectedLeftItem[leftColumn] !== undefined) { + return { [rightColumn]: selectedLeftItem[leftColumn] }; + } + return {}; + })()), + }, + }, + }), + ); + + console.log("✅ [SplitPanel] 추가 모달 화면 열기:", { + screenId: addButtonConfig.modalScreenId, + tableName: currentTableName, + }); + return; + } + } + + // 기존 내장 추가 모달 로직 setAddModalPanel(panel); // 우측 패널 추가 시, 좌측에서 선택된 항목의 조인 컬럼 값을 자동으로 채움 @@ -2012,12 +2094,66 @@ export const SplitPanelLayoutComponent: React.FC setShowAddModal(true); }, - [selectedLeftItem, componentConfig], + [selectedLeftItem, componentConfig, activeTabIndex], ); // 수정 버튼 핸들러 const handleEditClick = useCallback( (panel: "left" | "right", item: any) => { + // 좌측 패널 수정 버튼 설정 확인 (모달 모드) + if (panel === "left") { + const editButtonConfig = componentConfig.leftPanel?.editButton; + if (editButtonConfig?.mode === "modal" && editButtonConfig?.modalScreenId) { + const leftTableName = componentConfig.leftPanel?.tableName || ""; + const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id"; + + // Primary Key 찾기 + let primaryKeyName = sourceColumn; + let primaryKeyValue = item[sourceColumn]; + + if (primaryKeyValue === undefined || primaryKeyValue === null) { + if (item.id !== undefined && item.id !== null) { + primaryKeyName = "id"; + primaryKeyValue = item.id; + } else if (item.ID !== undefined && item.ID !== null) { + primaryKeyName = "ID"; + primaryKeyValue = item.ID; + } else { + const firstKey = Object.keys(item)[0]; + primaryKeyName = firstKey; + primaryKeyValue = item[firstKey]; + } + } + + // modalDataStore에 저장 + import("@/stores/modalDataStore").then(({ useModalDataStore }) => { + useModalDataStore.getState().setData(leftTableName, [item]); + }); + + // ScreenModal 열기 이벤트 발생 + window.dispatchEvent( + new CustomEvent("openScreenModal", { + detail: { + screenId: editButtonConfig.modalScreenId, + urlParams: { + mode: "edit", + editId: primaryKeyValue, + tableName: leftTableName, + }, + }, + }), + ); + + console.log("✅ [SplitPanel] 좌측 수정 모달 화면 열기:", { + screenId: editButtonConfig.modalScreenId, + tableName: leftTableName, + primaryKeyName, + primaryKeyValue, + }); + return; + } + } + // 우측 패널 수정 버튼 설정 확인 (탭별 설정 지원) if (panel === "right") { const editButtonConfig = @@ -3339,29 +3475,33 @@ export const SplitPanelLayoutComponent: React.FC {/* 항목별 버튼들 */} {!isDesignMode && (
- {/* 수정 버튼 */} - + {/* 수정 버튼 (showEdit 활성화 시에만 표시) */} + {(componentConfig.leftPanel?.showEdit !== false) && ( + + )} - {/* 삭제 버튼 */} - + {/* 삭제 버튼 (showDelete 활성화 시에만 표시) */} + {(componentConfig.leftPanel?.showDelete !== false) && ( + + )} {/* 항목별 추가 버튼 */} {componentConfig.leftPanel?.showItemAddButton && ( diff --git a/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutConfigPanel.tsx b/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutConfigPanel.tsx index ab3e9af8..7c17c979 100644 --- a/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutConfigPanel.tsx +++ b/frontend/lib/registry/components/v2-split-panel-layout/SplitPanelLayoutConfigPanel.tsx @@ -1066,6 +1066,62 @@ const AdditionalTabConfigPanel: React.FC = ({
)} + {/* ===== 10-1. 추가 버튼 설정 ===== */} + {tab.showAdd && ( +
+

추가 버튼 설정

+
+
+ + +
+ + {tab.addButton?.mode === "modal" && ( +
+ + { + updateTab({ + addButton: { ...tab.addButton, enabled: true, mode: "modal", modalScreenId: screenId }, + }); + }} + /> +
+ )} + +
+ + { + updateTab({ + addButton: { ...tab.addButton, enabled: true, buttonLabel: e.target.value || undefined }, + }); + }} + placeholder="추가" + className="h-7 text-xs" + /> +
+
+
+ )} + {/* ===== 11. 삭제 버튼 설정 ===== */} {tab.showDelete && (
@@ -2071,6 +2127,169 @@ export const SplitPanelLayoutConfigPanel: React.FC
+ + {/* 좌측 패널 버튼 설정 */} +
+

좌측 패널 버튼 설정

+ + {/* 버튼 표시 체크박스 */} +
+
+ updateLeftPanel({ showSearch: !!checked })} + /> + +
+
+ updateLeftPanel({ showAdd: !!checked })} + /> + +
+
+ updateLeftPanel({ showEdit: !!checked })} + /> + +
+
+ updateLeftPanel({ showDelete: !!checked })} + /> + +
+
+ + {/* 추가 버튼 상세 설정 */} + {config.leftPanel?.showAdd && ( +
+

추가 버튼 설정

+
+
+ + +
+ + {config.leftPanel?.addButton?.mode === "modal" && ( +
+ + + updateLeftPanel({ + addButton: { ...config.leftPanel?.addButton, enabled: true, mode: "modal", modalScreenId: screenId }, + }) + } + /> +
+ )} + +
+ + + updateLeftPanel({ + addButton: { + ...config.leftPanel?.addButton, + enabled: true, + mode: config.leftPanel?.addButton?.mode || "auto", + buttonLabel: e.target.value || undefined, + }, + }) + } + placeholder="추가" + className="h-7 text-xs" + /> +
+
+
+ )} + + {/* 수정 버튼 상세 설정 */} + {(config.leftPanel?.showEdit ?? true) && ( +
+

수정 버튼 설정

+
+
+ + +
+ + {config.leftPanel?.editButton?.mode === "modal" && ( +
+ + + updateLeftPanel({ + editButton: { ...config.leftPanel?.editButton, enabled: true, mode: "modal", modalScreenId: screenId }, + }) + } + /> +
+ )} + +
+ + + updateLeftPanel({ + editButton: { + ...config.leftPanel?.editButton, + enabled: true, + mode: config.leftPanel?.editButton?.mode || "auto", + buttonLabel: e.target.value || undefined, + }, + }) + } + placeholder="수정" + className="h-7 text-xs" + /> +
+
+
+ )} +
@@ -2775,6 +2994,85 @@ export const SplitPanelLayoutConfigPanel: React.FC + {/* 🆕 우측 패널 추가 버튼 설정 */} + {config.rightPanel?.showAdd && ( +
+
+
+

추가 버튼 설정

+

우측 리스트의 추가 버튼 동작 방식 설정

+
+
+ +
+
+ + +

+ {config.rightPanel?.addButton?.mode === "modal" + ? "지정한 화면을 모달로 열어 데이터를 추가합니다" + : "내장 폼으로 데이터를 추가합니다"} +

+
+ + {config.rightPanel?.addButton?.mode === "modal" && ( +
+ + + updateRightPanel({ + addButton: { + ...config.rightPanel?.addButton!, + modalScreenId: screenId, + }, + }) + } + /> +
+ )} + +
+ + + updateRightPanel({ + addButton: { + ...config.rightPanel?.addButton!, + buttonLabel: e.target.value, + enabled: true, + mode: config.rightPanel?.addButton?.mode || "auto", + }, + }) + } + placeholder="추가" + className="h-8 text-xs" + /> +
+
+
+ )} + {/* 🆕 우측 패널 삭제 버튼 설정 */}
diff --git a/frontend/lib/registry/components/v2-split-panel-layout/types.ts b/frontend/lib/registry/components/v2-split-panel-layout/types.ts index a8e6618d..b738d317 100644 --- a/frontend/lib/registry/components/v2-split-panel-layout/types.ts +++ b/frontend/lib/registry/components/v2-split-panel-layout/types.ts @@ -115,6 +115,14 @@ export interface AdditionalTabConfig { groupByColumns?: string[]; }; + // 추가 버튼 설정 (모달 화면 연결 지원) + addButton?: { + enabled: boolean; + mode: "auto" | "modal"; // auto: 내장 폼, modal: 커스텀 모달 화면 + modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때) + buttonLabel?: string; // 버튼 라벨 (기본: "추가") + }; + deleteButton?: { enabled: boolean; buttonLabel?: string; @@ -141,6 +149,23 @@ export interface SplitPanelLayoutConfig { showAdd?: boolean; showEdit?: boolean; // 수정 버튼 showDelete?: boolean; // 삭제 버튼 + + // 수정 버튼 설정 (모달 화면 연결 지원) + editButton?: { + enabled: boolean; + mode: "auto" | "modal"; // auto: 내장 편집, modal: 커스텀 모달 화면 + modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때) + buttonLabel?: string; // 버튼 라벨 (기본: "수정") + }; + + // 추가 버튼 설정 (모달 화면 연결 지원) + addButton?: { + enabled: boolean; + mode: "auto" | "modal"; // auto: 내장 폼, modal: 커스텀 모달 화면 + modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때) + buttonLabel?: string; // 버튼 라벨 (기본: "추가") + }; + columns?: Array<{ name: string; label: string; @@ -307,6 +332,14 @@ export interface SplitPanelLayoutConfig { groupByColumns?: string[]; // 🆕 그룹핑 기준 컬럼들 (예: ["customer_id", "item_id"]) }; + // 🆕 추가 버튼 설정 (모달 화면 연결 지원) + addButton?: { + enabled: boolean; // 추가 버튼 표시 여부 (기본: true) + mode: "auto" | "modal"; // auto: 내장 폼, modal: 커스텀 모달 화면 + modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때) + buttonLabel?: string; // 버튼 라벨 (기본: "추가") + }; + // 🆕 삭제 버튼 설정 deleteButton?: { enabled: boolean; // 삭제 버튼 표시 여부 (기본: true)