From b3cd771b99bee4b4094385ee4f4a0ddd3f014d2d Mon Sep 17 00:00:00 2001 From: leeheejin Date: Thu, 6 Nov 2025 17:32:24 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EA=B3=BC=20=EA=B7=B8=EB=A3=B9=EB=93=9C=EB=A1=AD=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4,=20=ED=92=88=EB=AA=A9=EB=B3=B5=EC=82=AC=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5,=20=EC=97=B0=EC=86=8D=EC=9E=85=EB=A0=A5=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config-panels/ButtonConfigPanel-fixed.tsx | 70 ++++- .../config-panels/ButtonConfigPanel.tsx | 154 ++++++++++ .../ButtonDataflowConfigPanel.tsx | 1 + .../table-list/TableListComponent.tsx | 267 ++++++++++++------ frontend/lib/utils/buttonActions.ts | 152 ++++++++++ frontend/types/unified-core.ts | 1 + 6 files changed, 551 insertions(+), 94 deletions(-) diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel-fixed.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel-fixed.tsx index a70a0633..dc991519 100644 --- a/frontend/components/screen/config-panels/ButtonConfigPanel-fixed.tsx +++ b/frontend/components/screen/config-panels/ButtonConfigPanel-fixed.tsx @@ -242,6 +242,7 @@ export const ButtonConfigPanel: React.FC = ({ component, 취소 삭제 수정 + 복사 (품목코드 초기화) 추가 검색 초기화 @@ -386,6 +387,71 @@ export const ButtonConfigPanel: React.FC = ({ component, + +
+
+ + setModalSearchTerm(e.target.value)} + className="border-0 p-0 focus-visible:ring-0" + /> +
+
+ {(() => { + const filteredScreens = filterScreens(modalSearchTerm); + if (screensLoading) { + return
화면 목록을 불러오는 중...
; + } + if (filteredScreens.length === 0) { + return
검색 결과가 없습니다.
; + } + return filteredScreens.map((screen, index) => ( +
{ + onUpdateProperty("componentConfig.action.targetScreenId", screen.id); + setModalScreenOpen(false); + setModalSearchTerm(""); + }} + > + {screen.name} +
+ )); + })()} +
+
+
+ + + + )} + + {/* 복사 액션 설정 */} + {localSelects.actionType === "copy" && ( +
+

복사 설정 (품목코드 자동 초기화)

+ +
+ + + + +
@@ -434,12 +500,12 @@ export const ButtonConfigPanel: React.FC = ({ component,

- 선택된 데이터가 이 폼 화면에 자동으로 로드되어 수정할 수 있습니다 + 선택된 데이터가 복사되며, 품목코드는 자동으로 초기화됩니다

- + setModalSearchTerm(e.target.value)} + className="border-0 p-0 focus-visible:ring-0" + /> +
+
+ {(() => { + const filteredScreens = filterScreens(modalSearchTerm); + if (screensLoading) { + return
화면 목록을 불러오는 중...
; + } + if (filteredScreens.length === 0) { + return
검색 결과가 없습니다.
; + } + return filteredScreens.map((screen, index) => ( +
{ + onUpdateProperty("componentConfig.action.targetScreenId", screen.id); + setModalScreenOpen(false); + setModalSearchTerm(""); + }} + > + +
+ {screen.name} + {screen.description && {screen.description}} +
+
+ )); + })()} +
+
+
+
+

+ 선택된 데이터가 복사되며, 품목코드는 자동으로 초기화됩니다 +

+
+ +
+ + +
+ + {(component.componentConfig?.action?.editMode || "modal") === "modal" && ( + <> +
+ + { + const newValue = e.target.value; + setLocalInputs((prev) => ({ ...prev, editModalTitle: newValue })); + onUpdateProperty("componentConfig.action.editModalTitle", newValue); + onUpdateProperty("webTypeConfig.editModalTitle", newValue); + }} + /> +

비워두면 기본 제목이 표시됩니다

+
+ +
+ + { + const newValue = e.target.value; + setLocalInputs((prev) => ({ ...prev, editModalDescription: newValue })); + onUpdateProperty("componentConfig.action.editModalDescription", newValue); + onUpdateProperty("webTypeConfig.editModalDescription", newValue); + }} + /> +

비워두면 설명이 표시되지 않습니다

+
+ +
+ + +
+ + )} +
+ )} + {/* 테이블 이력 보기 액션 설정 */} {(component.componentConfig?.action?.type || "save") === "view_table_history" && (
diff --git a/frontend/components/screen/config-panels/ButtonDataflowConfigPanel.tsx b/frontend/components/screen/config-panels/ButtonDataflowConfigPanel.tsx index afa181f8..e3226a10 100644 --- a/frontend/components/screen/config-panels/ButtonDataflowConfigPanel.tsx +++ b/frontend/components/screen/config-panels/ButtonDataflowConfigPanel.tsx @@ -188,6 +188,7 @@ export const ButtonDataflowConfigPanel: React.FC save: "저장", delete: "삭제", edit: "수정", + copy: "복사", add: "추가", search: "검색", reset: "초기화", diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 13865051..d53798d1 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -35,6 +35,11 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { Label } from "@/components/ui/label"; import { AdvancedSearchFilters } from "@/components/screen/filters/AdvancedSearchFilters"; import { SingleTableWithSticky } from "./SingleTableWithSticky"; @@ -274,7 +279,6 @@ export const TableListComponent: React.FC = ({ const [visibleFilterColumns, setVisibleFilterColumns] = useState>(new Set()); // 그룹 설정 관련 상태 - const [isGroupSettingOpen, setIsGroupSettingOpen] = useState(false); const [groupByColumns, setGroupByColumns] = useState([]); const [collapsedGroups, setCollapsedGroups] = useState>(new Set()); @@ -1281,17 +1285,14 @@ export const TableListComponent: React.FC = ({ })); }, [visibleColumns, visibleFilterColumns, columnLabels]); - // 그룹 설정 저장 - const saveGroupSettings = useCallback(() => { + // 그룹 설정 자동 저장 (localStorage) + useEffect(() => { if (!groupSettingKey) return; try { localStorage.setItem(groupSettingKey, JSON.stringify(groupByColumns)); - setIsGroupSettingOpen(false); - toast.success("그룹 설정이 저장되었습니다"); } catch (error) { console.error("그룹 설정 저장 실패:", error); - toast.error("설정 저장에 실패했습니다"); } }, [groupSettingKey, groupByColumns]); @@ -1542,10 +1543,6 @@ export const TableListComponent: React.FC = ({ > - - - 전체 {totalItems.toLocaleString()}개 -
{/* 우측 새로고침 버튼 */} @@ -1607,7 +1604,12 @@ export const TableListComponent: React.FC = ({ onClearFilters={handleClearAdvancedFilters} /> -
+
+ {/* 전체 개수 */} +
+ 전체 {totalItems.toLocaleString()}개 +
+ - + + + + + +
+
+

그룹 설정

+

+ 데이터를 그룹화할 컬럼을 선택하세요 +

+
+ + {/* 컬럼 목록 */} +
+ {visibleColumns + .filter((col) => col.columnName !== "__checkbox__") + .map((col) => ( +
+ toggleGroupColumn(col.columnName)} + /> + +
+ ))} +
+ + {/* 선택된 그룹 안내 */} + {groupByColumns.length > 0 && ( +
+ + {groupByColumns.map((col) => columnLabels[col] || col).join(" → ")} + +
+ )} + + {/* 초기화 버튼 */} + {groupByColumns.length > 0 && ( + + )} +
+
+
@@ -1714,7 +1785,12 @@ export const TableListComponent: React.FC = ({ onClearFilters={handleClearAdvancedFilters} /> -
+
+ {/* 전체 개수 */} +
+ 전체 {totalItems.toLocaleString()}개 +
+ - + + + + + +
+
+

그룹 설정

+

+ 데이터를 그룹화할 컬럼을 선택하세요 +

+
+ + {/* 컬럼 목록 */} +
+ {visibleColumns + .filter((col) => col.columnName !== "__checkbox__") + .map((col) => ( +
+ toggleGroupColumn(col.columnName)} + /> + +
+ ))} +
+ + {/* 선택된 그룹 안내 */} + {groupByColumns.length > 0 && ( +
+ + {groupByColumns.map((col) => columnLabels[col] || col).join(" → ")} + +
+ )} + + {/* 초기화 버튼 */} + {groupByColumns.length > 0 && ( + + )} +
+
+
@@ -2206,68 +2351,6 @@ export const TableListComponent: React.FC = ({ - {/* 그룹 설정 다이얼로그 */} - - - - 그룹 설정 - - 데이터를 그룹화할 컬럼을 선택하세요. 여러 컬럼을 선택하면 계층적으로 그룹화됩니다. - - - -
- {/* 컬럼 목록 */} -
- {visibleColumns - .filter((col) => col.columnName !== "__checkbox__") - .map((col) => ( -
- toggleGroupColumn(col.columnName)} - /> - -
- ))} -
- - {/* 선택된 그룹 안내 */} -
- {groupByColumns.length === 0 ? ( - 그룹화할 컬럼을 선택하세요 - ) : ( - - 선택된 그룹:{" "} - - {groupByColumns.map((col) => columnLabels[col] || col).join(" → ")} - - - )} -
-
- - - - - -
-
- {/* 테이블 옵션 모달 */} { + try { + const { selectedRowsData, flowSelectedData } = context; + + // 플로우 선택 데이터 우선 사용 + let dataToCopy = flowSelectedData && flowSelectedData.length > 0 ? flowSelectedData : selectedRowsData; + + console.log("📋 handleCopy - 데이터 소스 확인:", { + hasFlowSelectedData: !!(flowSelectedData && flowSelectedData.length > 0), + flowSelectedDataLength: flowSelectedData?.length || 0, + hasSelectedRowsData: !!(selectedRowsData && selectedRowsData.length > 0), + selectedRowsDataLength: selectedRowsData?.length || 0, + dataToCopyLength: dataToCopy?.length || 0, + }); + + // 선택된 데이터가 없는 경우 + if (!dataToCopy || dataToCopy.length === 0) { + toast.error("복사할 항목을 선택해주세요."); + return false; + } + + // 복사 화면이 설정되지 않은 경우 + if (!config.targetScreenId) { + toast.error("복사 폼 화면이 설정되지 않았습니다. 버튼 설정에서 복사 폼 화면을 선택해주세요."); + return false; + } + + console.log(`📋 복사 액션 실행: ${dataToCopy.length}개 항목`, { + dataToCopy, + targetScreenId: config.targetScreenId, + editMode: config.editMode, + }); + + if (dataToCopy.length === 1) { + // 단일 항목 복사 + const rowData = dataToCopy[0]; + console.log("📋 단일 항목 복사:", rowData); + console.log("📋 원본 데이터 키 목록:", Object.keys(rowData)); + + // 품목코드 필드 초기화 (여러 가능한 필드명 확인) + const copiedData = { ...rowData }; + const itemCodeFields = [ + "item_code", + "itemCode", + "item_no", + "itemNo", + "품목코드", + "품번", + "code", + ]; + + // 품목코드 필드를 찾아서 초기화 + let resetFieldName = ""; + for (const field of itemCodeFields) { + if (copiedData[field] !== undefined) { + // 품목코드 필드를 빈 문자열로 초기화 + // (저장 시점에 채번 규칙이 자동으로 적용됨) + copiedData[field] = ""; + + // 채번 규칙 ID도 함께 저장 (formData에 있을 경우) + const ruleIdKey = `${field}_numberingRuleId`; + if (rowData[ruleIdKey]) { + copiedData[ruleIdKey] = rowData[ruleIdKey]; + console.log(`📋 채번 규칙 ID 복사: ${ruleIdKey} = ${rowData[ruleIdKey]}`); + } + + resetFieldName = field; + console.log(`✅ 품목코드 필드 초기화: ${field} (기존값: ${rowData[field]})`); + break; + } + } + + if (resetFieldName) { + toast.success(`품목코드(${resetFieldName})가 초기화되었습니다. 저장 시 자동으로 새 코드가 생성됩니다.`); + } else { + console.warn("⚠️ 품목코드 필드를 찾을 수 없습니다. 전체 데이터를 복사합니다."); + console.warn("⚠️ 사용 가능한 필드:", Object.keys(copiedData)); + toast.info("복사본이 생성됩니다. (품목코드 필드를 찾을 수 없음)"); + } + + console.log("📋 복사된 데이터:", copiedData); + await this.openCopyForm(config, copiedData, context); + } else { + // 다중 항목 복사 - 현재는 단일 복사만 지원 + toast.error("현재 단일 항목 복사만 지원됩니다. 하나의 항목만 선택해주세요."); + return false; + } + + return true; + } catch (error: any) { + console.error("❌ 복사 액션 실행 중 오류:", error); + toast.error(`복사 중 오류가 발생했습니다: ${error.message || "알 수 없는 오류"}`); + return false; + } + } + + /** + * 복사 폼 열기 (단일 항목) + */ + private static async openCopyForm( + config: ButtonActionConfig, + rowData: any, + context: ButtonActionContext, + ): Promise { + try { + const editMode = config.editMode || "modal"; + console.log("📋 openCopyForm 실행:", { editMode, targetScreenId: config.targetScreenId }); + + switch (editMode) { + case "modal": + // 모달로 복사 폼 열기 (편집 모달 재사용) + console.log("📋 모달로 복사 폼 열기"); + await this.openEditModal(config, rowData, context); + break; + + case "navigate": + // 새 페이지로 이동 + console.log("📋 새 페이지로 복사 화면 이동"); + this.navigateToCopyScreen(config, rowData, context); + break; + + default: + // 기본값: 모달 + console.log("📋 기본 모달로 복사 폼 열기"); + this.openEditModal(config, rowData, context); + } + } catch (error: any) { + console.error("❌ openCopyForm 실행 중 오류:", error); + throw error; + } + } + + /** + * 복사 화면으로 네비게이션 + */ + private static navigateToCopyScreen(config: ButtonActionConfig, rowData: any, context: ButtonActionContext): void { + const copyUrl = `/screens/${config.targetScreenId}?mode=copy`; + console.log("🔄 복사 화면으로 이동:", copyUrl); + + // 복사할 데이터를 sessionStorage에 저장 + sessionStorage.setItem("copyData", JSON.stringify(rowData)); + + window.location.href = copyUrl; + } + /** * 닫기 액션 처리 */ diff --git a/frontend/types/unified-core.ts b/frontend/types/unified-core.ts index 6f7c2b40..cba1c3f7 100644 --- a/frontend/types/unified-core.ts +++ b/frontend/types/unified-core.ts @@ -55,6 +55,7 @@ export type ButtonActionType = | "cancel" | "delete" | "edit" + | "copy" // 복사 (품목코드 초기화) | "add" // 검색 및 초기화 | "search"