From 5c098a0395ed2ab5229c2f0d959c6bc4ede7f846 Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 15 Jan 2026 13:38:01 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A6=AC=ED=94=BC=ED=84=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../screen/panels/UnifiedPropertiesPanel.tsx | 1 + .../components/unified/UnifiedRepeater.tsx | 208 +++++++-- .../UnifiedRepeaterConfigPanel.tsx | 397 +++++++++++++++--- frontend/types/unified-repeater.ts | 25 ++ 4 files changed, 543 insertions(+), 88 deletions(-) diff --git a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx index 3bffcadc..128abac0 100644 --- a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx +++ b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx @@ -302,6 +302,7 @@ export const UnifiedPropertiesPanel: React.FC = ({ tableColumns={currentTable?.columns || []} // πŸ”§ ν…Œμ΄λΈ” 컬럼 정보 전달 allComponents={allComponents} // πŸ†• 연쇄 λ“œλ‘­λ‹€μš΄ λΆ€λͺ¨ κ°μ§€μš© currentComponent={selectedComponent} // πŸ†• ν˜„μž¬ μ»΄ν¬λ„ŒνŠΈ 정보 + menuObjid={menuObjid} // πŸ†• 메뉴 OBJID 전달 (μ±„λ²ˆ κ·œμΉ™ λ“±) /> diff --git a/frontend/components/unified/UnifiedRepeater.tsx b/frontend/components/unified/UnifiedRepeater.tsx index faae28c1..3ffe7545 100644 --- a/frontend/components/unified/UnifiedRepeater.tsx +++ b/frontend/components/unified/UnifiedRepeater.tsx @@ -10,7 +10,7 @@ * RepeaterTable 및 ItemSelectionModal μž¬μ‚¬μš© */ -import React, { useState, useEffect, useCallback, useMemo } from "react"; +import React, { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Plus } from "lucide-react"; import { cn } from "@/lib/utils"; @@ -21,6 +21,7 @@ import { DEFAULT_REPEATER_CONFIG, } from "@/types/unified-repeater"; import { apiClient } from "@/lib/api/client"; +import { allocateNumberingCode } from "@/lib/api/numberingRule"; // modal-repeater-table μ»΄ν¬λ„ŒνŠΈ μž¬μ‚¬μš© import { RepeaterTable } from "@/lib/registry/components/modal-repeater-table/RepeaterTable"; @@ -334,6 +335,8 @@ export const UnifiedRepeater: React.FC = ({ width: col.width === "auto" ? undefined : col.width, required: false, categoryRef, // πŸ†• μΉ΄ν…Œκ³ λ¦¬ μ°Έμ‘° ID 전달 + hidden: col.hidden, // πŸ†• νžˆλ“  처리 + autoFill: col.autoFill, // πŸ†• μžλ™ μž…λ ₯ μ„€μ • }; }); }, [config.columns, sourceColumnLabels, currentTableColumnInfo, resolvedSourceTable, config.dataSource?.tableName]); @@ -436,53 +439,133 @@ export const UnifiedRepeater: React.FC = ({ }, [data, selectedRows, handleDataChange]); // ν–‰ μΆ”κ°€ (inline λͺ¨λ“œ) - const handleAddRow = useCallback(() => { + // πŸ†• μžλ™ μž…λ ₯ κ°’ 생성 ν•¨μˆ˜ (동기 - μ±„λ²ˆ μ œμ™Έ) + const generateAutoFillValueSync = useCallback( + (col: any, rowIndex: number, mainFormData?: Record) => { + if (!col.autoFill || col.autoFill.type === "none") return undefined; + + const now = new Date(); + + switch (col.autoFill.type) { + case "currentDate": + return now.toISOString().split("T")[0]; // YYYY-MM-DD + + case "currentDateTime": + return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss + + case "sequence": + return rowIndex + 1; // 1λΆ€ν„° μ‹œμž‘ν•˜λŠ” 순번 + + case "numbering": + // μ±„λ²ˆμ€ 별도 비동기 처리 ν•„μš” + return null; // null λ°˜ν™˜ν•˜μ—¬ 비동기 처리 ν•„μš”ν•¨μ„ ν‘œμ‹œ + + case "fromMainForm": + if (col.autoFill.sourceField && mainFormData) { + return mainFormData[col.autoFill.sourceField]; + } + return ""; + + case "fixed": + return col.autoFill.fixedValue ?? ""; + + default: + return undefined; + } + }, + [], + ); + + // πŸ†• μ±„λ²ˆ API 호좜 (비동기) + const generateNumberingCode = useCallback(async (ruleId: string): Promise => { + try { + const result = await allocateNumberingCode(ruleId); + if (result.success && result.data?.generatedCode) { + return result.data.generatedCode; + } + console.error("μ±„λ²ˆ μ‹€νŒ¨:", result.error); + return ""; + } catch (error) { + console.error("μ±„λ²ˆ API 호좜 μ‹€νŒ¨:", error); + return ""; + } + }, []); + + // πŸ†• ν–‰ μΆ”κ°€ (inline λͺ¨λ“œ λ˜λŠ” λͺ¨λ‹¬ μ—΄κΈ°) - λΉ„λ™κΈ°λ‘œ λ³€κ²½ + const handleAddRow = useCallback(async () => { if (isModalMode) { setModalOpen(true); } else { const newRow: any = { _id: `new_${Date.now()}` }; - config.columns.forEach((col) => { - newRow[col.key] = ""; - }); - const newData = [...data, newRow]; - handleDataChange(newData); // πŸ†• handleDataChange μ‚¬μš© - } - }, [isModalMode, config.columns, data, handleDataChange]); - - // λͺ¨λ‹¬μ—μ„œ ν•­λͺ© 선택 - πŸ†• columns λ°°μ—΄μ—μ„œ isSourceDisplay ν”Œλž˜κ·Έλ‘œ ꡬ뢄 - const handleSelectItems = useCallback( - (items: Record[]) => { - const fkColumn = config.dataSource?.foreignKey; - - const newRows = items.map((item) => { - const row: any = { _id: `new_${Date.now()}_${Math.random()}` }; - - // FK κ°’ μ €μž₯ (resolvedReferenceKey μ‚¬μš©) - if (fkColumn && item[resolvedReferenceKey]) { - row[fkColumn] = item[resolvedReferenceKey]; + const currentRowCount = data.length; + + // λ¨Όμ € 동기적 μžλ™ μž…λ ₯ κ°’ 적용 + for (const col of config.columns) { + const autoValue = generateAutoFillValueSync(col, currentRowCount); + if (autoValue === null && col.autoFill?.type === "numbering" && col.autoFill.numberingRuleId) { + // μ±„λ²ˆ κ·œμΉ™: μ¦‰μ‹œ API 호좜 + newRow[col.key] = await generateNumberingCode(col.autoFill.numberingRuleId); + } else if (autoValue !== undefined) { + newRow[col.key] = autoValue; + } else { + newRow[col.key] = ""; } + } + + const newData = [...data, newRow]; + handleDataChange(newData); + } + }, [isModalMode, config.columns, data, handleDataChange, generateAutoFillValueSync, generateNumberingCode]); - // λͺ¨λ“  컬럼 처리 (μˆœμ„œλŒ€λ‘œ) - config.columns.forEach((col) => { - if (col.isSourceDisplay) { - // μ†ŒμŠ€ ν‘œμ‹œ 컬럼: μ†ŒμŠ€ ν…Œμ΄λΈ”μ—μ„œ κ°’ 볡사 (읽기 μ „μš©) - row[`_display_${col.key}`] = item[col.key] || ""; - } else { - // μž…λ ₯ 컬럼: 빈 κ°’μœΌλ‘œ μ΄ˆκΈ°ν™” - if (row[col.key] === undefined) { - row[col.key] = ""; + // λͺ¨λ‹¬μ—μ„œ ν•­λͺ© 선택 - λΉ„λ™κΈ°λ‘œ λ³€κ²½ + const handleSelectItems = useCallback( + async (items: Record[]) => { + const fkColumn = config.dataSource?.foreignKey; + const currentRowCount = data.length; + + // μ±„λ²ˆμ΄ ν•„μš”ν•œ 컬럼 μ°ΎκΈ° + const numberingColumns = config.columns.filter( + (col) => !col.isSourceDisplay && col.autoFill?.type === "numbering" && col.autoFill.numberingRuleId + ); + + const newRows = await Promise.all( + items.map(async (item, index) => { + const row: any = { _id: `new_${Date.now()}_${Math.random()}` }; + + // FK κ°’ μ €μž₯ (resolvedReferenceKey μ‚¬μš©) + if (fkColumn && item[resolvedReferenceKey]) { + row[fkColumn] = item[resolvedReferenceKey]; + } + + // λͺ¨λ“  컬럼 처리 (μˆœμ„œλŒ€λ‘œ) + for (const col of config.columns) { + if (col.isSourceDisplay) { + // μ†ŒμŠ€ ν‘œμ‹œ 컬럼: μ†ŒμŠ€ ν…Œμ΄λΈ”μ—μ„œ κ°’ 볡사 (읽기 μ „μš©) + row[`_display_${col.key}`] = item[col.key] || ""; + } else { + // μžλ™ μž…λ ₯ κ°’ 적용 + const autoValue = generateAutoFillValueSync(col, currentRowCount + index); + if (autoValue === null && col.autoFill?.type === "numbering" && col.autoFill.numberingRuleId) { + // μ±„λ²ˆ κ·œμΉ™: μ¦‰μ‹œ API 호좜 + row[col.key] = await generateNumberingCode(col.autoFill.numberingRuleId); + } else if (autoValue !== undefined) { + row[col.key] = autoValue; + } else if (row[col.key] === undefined) { + // μž…λ ₯ 컬럼: 빈 κ°’μœΌλ‘œ μ΄ˆκΈ°ν™” + row[col.key] = ""; + } } } - }); - return row; - }); + return row; + }) + ); const newData = [...data, ...newRows]; - handleDataChange(newData); // πŸ†• handleDataChange μ‚¬μš©ν•˜μ—¬ autoWidthTrigger도 증가 + handleDataChange(newData); setModalOpen(false); }, - [config.dataSource?.foreignKey, resolvedReferenceKey, config.columns, data, handleDataChange], + [config.dataSource?.foreignKey, resolvedReferenceKey, config.columns, data, handleDataChange, generateAutoFillValueSync, generateNumberingCode], ); // μ†ŒμŠ€ 컬럼 λͺ©λ‘ (λͺ¨λ‹¬μš©) - πŸ†• columns λ°°μ—΄μ—μ„œ isSourceDisplay인 κ²ƒλ§Œ 필터링 @@ -493,6 +576,61 @@ export const UnifiedRepeater: React.FC = ({ .filter((key) => key && key !== "none"); }, [config.columns]); + // πŸ†• beforeFormSave μ΄λ²€νŠΈμ—μ„œ μ±„λ²ˆ placeholderλ₯Ό μ‹€μ œ κ°’μœΌλ‘œ λ³€ν™˜ + const dataRef = useRef(data); + dataRef.current = data; + + useEffect(() => { + const handleBeforeFormSave = async (event: Event) => { + const customEvent = event as CustomEvent; + const formData = customEvent.detail?.formData; + + if (!formData || !dataRef.current.length) return; + + // μ±„λ²ˆ placeholderκ°€ μžˆλŠ” 행듀을 μ°Ύμ•„μ„œ μ‹€μ œ κ°’μœΌλ‘œ λ³€ν™˜ + const processedData = await Promise.all( + dataRef.current.map(async (row) => { + const newRow = { ...row }; + + for (const key of Object.keys(newRow)) { + const value = newRow[key]; + if (typeof value === "string" && value.startsWith("__NUMBERING_RULE__")) { + // __NUMBERING_RULE__ruleId__ ν˜•μ‹μ—μ„œ ruleId μΆ”μΆœ + const match = value.match(/__NUMBERING_RULE__(.+)__/); + if (match) { + const ruleId = match[1]; + try { + const result = await allocateNumberingCode(ruleId); + if (result.success && result.data?.generatedCode) { + newRow[key] = result.data.generatedCode; + } else { + console.error("μ±„λ²ˆ μ‹€νŒ¨:", result.error); + newRow[key] = ""; // μ±„λ²ˆ μ‹€νŒ¨ μ‹œ 빈 κ°’ + } + } catch (error) { + console.error("μ±„λ²ˆ API 호좜 μ‹€νŒ¨:", error); + newRow[key] = ""; + } + } + } + } + + return newRow; + }), + ); + + // 처리된 데이터λ₯Ό formData에 μΆ”κ°€ + const fieldName = config.fieldName || "repeaterData"; + formData[fieldName] = processedData; + }; + + window.addEventListener("beforeFormSave", handleBeforeFormSave); + + return () => { + window.removeEventListener("beforeFormSave", handleBeforeFormSave); + }; + }, [config.fieldName]); + return (
{/* 헀더 μ˜μ—­ */} diff --git a/frontend/components/unified/config-panels/UnifiedRepeaterConfigPanel.tsx b/frontend/components/unified/config-panels/UnifiedRepeaterConfigPanel.tsx index c5e2a628..62b78c19 100644 --- a/frontend/components/unified/config-panels/UnifiedRepeaterConfigPanel.tsx +++ b/frontend/components/unified/config-panels/UnifiedRepeaterConfigPanel.tsx @@ -24,8 +24,15 @@ import { GripVertical, ArrowRight, Calculator, + ChevronDown, + ChevronRight, + Eye, + EyeOff, + Wand2, } from "lucide-react"; import { tableTypeApi } from "@/lib/api/screen"; +import { getAvailableNumberingRules, getAvailableNumberingRulesForScreen } from "@/lib/api/numberingRule"; +import { NumberingRuleConfig } from "@/types/numbering-rule"; import { cn } from "@/lib/utils"; import { UnifiedRepeaterConfig, @@ -41,6 +48,7 @@ interface UnifiedRepeaterConfigPanelProps { currentTableName?: string; screenTableName?: string; tableColumns?: any[]; + menuObjid?: number | string; // πŸ†• 메뉴 ID (μ±„λ²ˆ κ·œμΉ™ 쑰회용) } interface ColumnOption { @@ -76,6 +84,7 @@ export const UnifiedRepeaterConfigPanel: React.FC { const currentTableName = screenTableName || propCurrentTableName; @@ -107,6 +116,88 @@ export const UnifiedRepeaterConfigPanel: React.FC(null); + + // πŸ†• μ±„λ²ˆ κ·œμΉ™ λͺ©λ‘ + const [numberingRules, setNumberingRules] = useState([]); + const [loadingNumberingRules, setLoadingNumberingRules] = useState(false); + + // πŸ†• λŒ€μƒ 메뉴 λͺ©λ‘ (μ±„λ²ˆ κ·œμΉ™ μ„ νƒμš©) + const [parentMenus, setParentMenus] = useState([]); + const [loadingMenus, setLoadingMenus] = useState(false); + + // πŸ†• μ„ νƒλœ 메뉴 OBJID (μ»¬λŸΌλ³„λ‘œ μ €μž₯, ν•œ 번 μ„ νƒν•˜λ©΄ 곡유) + const [selectedMenuObjid, setSelectedMenuObjid] = useState(() => { + // κΈ°μ‘΄ configμ—μ„œ μ €μž₯된 값이 있으면 볡원 + const existingAutoFill = config.columns.find(c => c.autoFill?.type === "numbering" && c.autoFill.selectedMenuObjid); + return existingAutoFill?.autoFill?.selectedMenuObjid || (menuObjid ? Number(menuObjid) : undefined); + }); + + // μžλ™ μž…λ ₯ νƒ€μž… μ˜΅μ…˜ + const autoFillOptions = [ + { value: "none", label: "μ—†μŒ" }, + { value: "currentDate", label: "ν˜„μž¬ λ‚ μ§œ" }, + { value: "currentDateTime", label: "ν˜„μž¬ λ‚ μ§œ+μ‹œκ°„" }, + { value: "sequence", label: "순번 (1, 2, 3...)" }, + { value: "numbering", label: "μ±„λ²ˆ κ·œμΉ™" }, + { value: "fromMainForm", label: "메인 νΌμ—μ„œ 볡사" }, + { value: "fixed", label: "κ³ μ •κ°’" }, + ]; + + // πŸ†• λŒ€μƒ 메뉴 λͺ©λ‘ λ‘œλ“œ (μ‚¬μš©μž λ©”λ‰΄μ˜ 레벨 2) + useEffect(() => { + const loadMenus = async () => { + setLoadingMenus(true); + try { + const { apiClient } = await import("@/lib/api/client"); + const response = await apiClient.get("/admin/menus"); + + if (response.data.success && response.data.data) { + const allMenus = response.data.data; + + // μ‚¬μš©μž 메뉴(menu_type='1')의 레벨 2만 필터링 + const level2UserMenus = allMenus.filter((menu: any) => + menu.menu_type === '1' && menu.lev === 2 + ); + + setParentMenus(level2UserMenus); + } + } catch (error) { + console.error("λΆ€λͺ¨ 메뉴 λ‘œλ“œ μ‹€νŒ¨:", error); + } finally { + setLoadingMenus(false); + } + }; + loadMenus(); + }, []); + + // πŸ†• μ±„λ²ˆ κ·œμΉ™ λ‘œλ“œ (μ„ νƒλœ 메뉴 κΈ°μ€€) + useEffect(() => { + const loadNumberingRules = async () => { + // 메뉴가 μ„ νƒλ˜μ§€ μ•Šμ•˜μœΌλ©΄ λ‘œλ“œν•˜μ§€ μ•ŠμŒ + if (!selectedMenuObjid) { + setNumberingRules([]); + return; + } + + setLoadingNumberingRules(true); + try { + const result = await getAvailableNumberingRules(selectedMenuObjid); + + if (result?.success && result.data) { + setNumberingRules(result.data); + } + } catch (error) { + console.error("μ±„λ²ˆ κ·œμΉ™ λ‘œλ“œ μ‹€νŒ¨:", error); + setNumberingRules([]); + } finally { + setLoadingNumberingRules(false); + } + }; + loadNumberingRules(); + }, [selectedMenuObjid]); // μ„€μ • μ—…λ°μ΄νŠΈ 헬퍼 const updateConfig = useCallback( @@ -639,62 +730,262 @@ export const UnifiedRepeaterConfigPanel: React.FC
{config.columns.map((col, index) => ( -
{ - e.dataTransfer.setData("columnIndex", String(index)); - }} - onDragOver={(e) => e.preventDefault()} - onDrop={(e) => { - e.preventDefault(); - const fromIndex = parseInt(e.dataTransfer.getData("columnIndex"), 10); - if (fromIndex !== index) { - const newColumns = [...config.columns]; - const [movedCol] = newColumns.splice(fromIndex, 1); - newColumns.splice(index, 0, movedCol); - updateConfig({ columns: newColumns }); - } - }} - > - - {col.isSourceDisplay ? ( - - ) : ( - - )} - updateColumnProp(col.key, "title", e.target.value)} - placeholder="제λͺ©" - className="h-6 flex-1 text-xs" - /> - {!col.isSourceDisplay && ( - updateColumnProp(col.key, "editable", !!checked)} - title="νŽΈμ§‘ κ°€λŠ₯" - /> - )} - + + + {/* ν™•μž₯/μΆ•μ†Œ λ²„νŠΌ (μž…λ ₯ 컬럼만) */} + {!col.isSourceDisplay && ( + + )} + + {col.isSourceDisplay ? ( + + ) : ( + + )} + + updateColumnProp(col.key, "title", e.target.value)} + placeholder="제λͺ©" + className="h-6 flex-1 text-xs" + /> + + {/* νžˆλ“  ν† κΈ€ (μž…λ ₯ 컬럼만) */} + {!col.isSourceDisplay && ( + + )} + + {/* μžλ™μž…λ ₯ ν‘œμ‹œ μ•„μ΄μ½˜ */} + {!col.isSourceDisplay && col.autoFill?.type && col.autoFill.type !== "none" && ( + + )} + + {/* νŽΈμ§‘ κ°€λŠ₯ μ²΄ν¬λ°•μŠ€ */} + {!col.isSourceDisplay && ( + updateColumnProp(col.key, "editable", !!checked)} + title="νŽΈμ§‘ κ°€λŠ₯" + /> + )} + + +
+ + {/* ν™•μž₯된 상세 μ„€μ • (μž…λ ₯ 컬럼만) */} + {!col.isSourceDisplay && expandedColumn === col.key && ( +
+ {/* μžλ™ μž…λ ₯ μ„€μ • */} +
+ + +
+ + {/* μ±„λ²ˆ κ·œμΉ™ 선택 */} + {col.autoFill?.type === "numbering" && ( +
+ {/* λŒ€μƒ 메뉴 선택 */} +
+ + +

+ 이 μž…λ ₯ ν•„λ“œκ°€ μ–΄λŠ 메뉴에 속할지 μ„ νƒν•˜μ„Έμš” (ν•΄λ‹Ή λ©”λ‰΄μ˜ μ±„λ²ˆκ·œμΉ™μ΄ μ μš©λ©λ‹ˆλ‹€) +

+
+ + {/* μ±„λ²ˆ κ·œμΉ™ 선택 (메뉴 선택 ν›„) */} + {selectedMenuObjid ? ( +
+ + {loadingNumberingRules ? ( +

κ·œμΉ™ λ‘œλ”© 쀑...

+ ) : numberingRules.length === 0 ? ( +
+ μ„ νƒλœ 메뉴에 μ‚¬μš© κ°€λŠ₯ν•œ μ±„λ²ˆ κ·œμΉ™μ΄ μ—†μŠ΅λ‹ˆλ‹€ +
+ ) : ( + + )} + {col.autoFill?.numberingRuleId && ( +

+ μ €μž₯ μ‹œ μ±„λ²ˆ APIλ₯Ό 톡해 μžλ™ μƒμ„±λ©λ‹ˆλ‹€. +

+ )} +
+ ) : ( +
+ λ¨Όμ € λŒ€μƒ 메뉴λ₯Ό μ„ νƒν•˜μ„Έμš” +
+ )} +
+ )} + + {/* 메인 νΌμ—μ„œ 볡사 μ„€μ • */} + {col.autoFill?.type === "fromMainForm" && ( +
+ + updateColumnProp(col.key, "autoFill", { + ...col.autoFill, + sourceField: e.target.value, + })} + placeholder="order_no" + className="h-6 text-xs" + /> +
+ )} + + {/* κ³ μ •κ°’ μ„€μ • */} + {col.autoFill?.type === "fixed" && ( +
+ + updateColumnProp(col.key, "autoFill", { + ...col.autoFill, + fixedValue: e.target.value, + })} + placeholder="κ³ μ •κ°’ μž…λ ₯" + className="h-6 text-xs" + /> +
+ )} +
+ )}
))}
diff --git a/frontend/types/unified-repeater.ts b/frontend/types/unified-repeater.ts index a3d92173..2ef5560c 100644 --- a/frontend/types/unified-repeater.ts +++ b/frontend/types/unified-repeater.ts @@ -16,6 +16,28 @@ export type ModalSize = "sm" | "md" | "lg" | "xl" | "full"; // 컬럼 λ„ˆλΉ„ μ˜΅μ…˜ export type ColumnWidthOption = "auto" | "60px" | "80px" | "100px" | "120px" | "150px" | "200px" | "250px" | "300px"; +// μžλ™ μž…λ ₯ νƒ€μž… +export type AutoFillType = + | "none" // μžλ™ μž…λ ₯ μ—†μŒ + | "currentDate" // ν˜„μž¬ λ‚ μ§œ + | "currentDateTime"// ν˜„μž¬ λ‚ μ§œ+μ‹œκ°„ + | "sequence" // 순번 (1, 2, 3...) + | "numbering" // μ±„λ²ˆ κ·œμΉ™ (κ΄€λ¦¬μžκ°€ λ“±λ‘ν•œ κ·œμΉ™ 선택) + | "fromMainForm" // 메인 νΌμ—μ„œ κ°’ 볡사 + | "fixed"; // κ³ μ •κ°’ + +// μžλ™ μž…λ ₯ μ„€μ • +export interface AutoFillConfig { + type: AutoFillType; + // fromMainForm νƒ€μž…μš© + sourceField?: string; // 메인 νΌμ—μ„œ 볡사할 ν•„λ“œλͺ… + // fixed νƒ€μž…μš© + fixedValue?: string | number | boolean; + // numbering νƒ€μž…μš© - κΈ°μ‘΄ μ±„λ²ˆ κ·œμΉ™ IDλ₯Ό μ°Έμ‘° + numberingRuleId?: string; // μ±„λ²ˆ κ·œμΉ™ ID (numbering_rules ν…Œμ΄λΈ”) + selectedMenuObjid?: number; // πŸ†• μ±„λ²ˆ κ·œμΉ™ 선택을 μœ„ν•œ λŒ€μƒ 메뉴 OBJID +} + // 컬럼 μ„€μ • export interface RepeaterColumnConfig { key: string; @@ -23,12 +45,15 @@ export interface RepeaterColumnConfig { width: ColumnWidthOption; visible: boolean; editable?: boolean; // νŽΈμ§‘ κ°€λŠ₯ μ—¬λΆ€ (inline λͺ¨λ“œ) + hidden?: boolean; // πŸ†• νžˆλ“  처리 (화면에 μ•ˆ λ³΄μ΄μ§€λ§Œ μ €μž₯됨) isJoinColumn?: boolean; sourceTable?: string; // πŸ†• μ†ŒμŠ€ ν…Œμ΄λΈ” ν‘œμ‹œ 컬럼 μ—¬λΆ€ (modal λͺ¨λ“œμ—μ„œ 읽기 μ „μš©μœΌλ‘œ ν‘œμ‹œ) isSourceDisplay?: boolean; // μž…λ ₯ νƒ€μž… (ν…Œμ΄λΈ” νƒ€μž… κ΄€λ¦¬μ˜ inputType을 따름) inputType?: string; // text, number, date, code, entity λ“± + // πŸ†• μžλ™ μž…λ ₯ μ„€μ • + autoFill?: AutoFillConfig; // μž…λ ₯ νƒ€μž…λ³„ 상세 μ„€μ • detailSettings?: { codeGroup?: string; // code νƒ€μž…μš©