diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index 3e043331..e6976844 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -84,7 +84,7 @@ const CascadingSelectField: React.FC = ({ return ( - updateField({ - selectOptions: { - ...localField.selectOptions, - type: value as "static" | "code", - }, - }) - } + onValueChange={(value) => { + // νƒ€μž… λ³€κ²½ μ‹œ κ΄€λ ¨ μ„€μ • μ΄ˆκΈ°ν™” + if (value === "cascading") { + updateField({ + selectOptions: { + type: "cascading", + cascading: { + parentField: "", + clearOnParentChange: true, + }, + }, + }); + } else { + updateField({ + selectOptions: { + ...localField.selectOptions, + type: value as "static" | "table" | "code", + cascading: undefined, + }, + }); + } + }} > @@ -382,6 +473,11 @@ export function FieldDetailSettingsModal({ ))} + + {localField.selectOptions?.type === "cascading" + ? "연쇄 λ“œλ‘­λ‹€μš΄: λΆ€λͺ¨ ν•„λ“œ 선택에 따라 μ˜΅μ…˜μ΄ λ™μ μœΌλ‘œ λ³€κ²½λ©λ‹ˆλ‹€" + : "ν…Œμ΄λΈ” μ°Έμ‘°: DB ν…Œμ΄λΈ”μ—μ„œ μ˜΅μ…˜ λͺ©λ‘μ„ κ°€μ Έμ˜΅λ‹ˆλ‹€."} + {localField.selectOptions?.type === "table" && ( @@ -594,6 +690,472 @@ export function FieldDetailSettingsModal({ )} + + {localField.selectOptions?.type === "cascading" && ( +
+ + 연쇄 λ“œλ‘­λ‹€μš΄: λΆ€λͺ¨ ν•„λ“œμ˜ 값에 따라 μ˜΅μ…˜μ΄ λ™μ μœΌλ‘œ ν•„ν„°λ§λ©λ‹ˆλ‹€. +
+ 예: 거래처 선택 β†’ ν•΄λ‹Ή 거래처의 λ‚©ν’ˆμ²˜λ§Œ ν‘œμ‹œ +
+ + {/* λΆ€λͺ¨ ν•„λ“œ 선택 - μ½€λ³΄λ°•μŠ€ (μ„Ήμ…˜λ³„ κ·Έλ£Ήν•‘) */} +
+ + {allFieldsWithSections.length > 0 ? ( + + + + + + + + + + 선택 κ°€λŠ₯ν•œ ν•„λ“œκ°€ μ—†μŠ΅λ‹ˆλ‹€. + + {allFieldsWithSections.map((section) => { + // 자기 μžμ‹  μ œμ™Έν•œ ν•„λ“œ λͺ©λ‘ + const availableFields = section.fields.filter( + (f) => f.columnName !== field.columnName + ); + if (availableFields.length === 0) return null; + + return ( + + {availableFields.map((f) => ( + { + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + parentField: f.columnName, + }, + }, + }); + setParentFieldOpen(false); + }} + className="text-xs" + > + +
+ {f.label} + + {f.columnName} ({f.fieldType}) + +
+
+ ))} +
+ ); + })} +
+
+
+
+ ) : ( + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + parentField: e.target.value, + }, + }, + }) + } + placeholder="customer_code" + className="h-7 text-xs mt-1" + /> + )} + + 이 λ“œλ‘­λ‹€μš΄μ˜ μ˜΅μ…˜μ„ κ²°μ •ν•  λΆ€λͺ¨ ν•„λ“œλ₯Ό μ„ νƒν•˜μ„Έμš” +
+ 예: 거래처 선택 β†’ λ‚©ν’ˆμ²˜ 필터링 +
+
+ + {/* 관계 μ½”λ“œ 선택 */} +
+ + + + + + + + + + + λ“±λ‘λœ 연쇄 관계가 μ—†μŠ΅λ‹ˆλ‹€. + + + {/* 직접 μ„€μ • μ˜΅μ…˜ */} + { + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + relationCode: undefined, + }, + }, + }); + setCascadingRelationOpen(false); + }} + className="text-xs" + > + + 직접 μ„€μ • + + + {cascadingRelations.map((relation) => ( + { + handleRelationCodeSelect(relation.relation_code); + setCascadingRelationOpen(false); + }} + className="text-xs" + > + +
+ {relation.relation_name} + + {relation.parent_table} β†’ {relation.child_table} + +
+
+ ))} +
+
+
+
+
+ + 미리 λ“±λ‘λœ 관계λ₯Ό μ„ νƒν•˜λ©΄ 섀정이 μžλ™μœΌλ‘œ μ±„μ›Œμ§‘λ‹ˆλ‹€. +
+ 직접 섀정을 μ„ νƒν•˜λ©΄ μ•„λž˜μ—μ„œ μˆ˜λ™μœΌλ‘œ μž…λ ₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€. +
+
+ + + + {/* 상세 μ„€μ • (μˆ˜μ • κ°€λŠ₯) */} +
+
+ + 상세 μ„€μ • (μˆ˜μ • κ°€λŠ₯) +
+ +
+ + + μ˜΅μ…˜μ„ κ°€μ Έμ˜¬ ν…Œμ΄λΈ” (예: delivery_destination) +
+ +
+ + {selectTableColumns.length > 0 ? ( + + ) : ( + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + parentKeyColumn: e.target.value, + }, + }, + }) + } + placeholder="customer_code" + className="h-7 text-xs mt-1" + /> + )} + λΆ€λͺ¨ κ°’κ³Ό λ§€μΉ­ν•  컬럼 (예: customer_code) +
+ +
+ + {selectTableColumns.length > 0 ? ( + + ) : ( + + updateField({ + selectOptions: { + ...localField.selectOptions, + valueColumn: e.target.value, + }, + }) + } + placeholder="destination_code" + className="h-7 text-xs mt-1" + /> + )} + λ“œλ‘­λ‹€μš΄ value둜 μ‚¬μš©ν•  컬럼 +
+ +
+ + {selectTableColumns.length > 0 ? ( + + ) : ( + + updateField({ + selectOptions: { + ...localField.selectOptions, + labelColumn: e.target.value, + }, + }) + } + placeholder="destination_name" + className="h-7 text-xs mt-1" + /> + )} + λ“œλ‘­λ‹€μš΄μ— ν‘œμ‹œν•  컬럼 +
+ + + +
+ + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + emptyParentMessage: e.target.value, + }, + }, + }) + } + placeholder="μƒμœ„ ν•­λͺ©μ„ λ¨Όμ € μ„ νƒν•˜μ„Έμš”" + className="h-7 text-xs mt-1" + /> +
+ +
+ + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + noOptionsMessage: e.target.value, + }, + }, + }) + } + placeholder="선택 κ°€λŠ₯ν•œ ν•­λͺ©μ΄ μ—†μŠ΅λ‹ˆλ‹€" + className="h-7 text-xs mt-1" + /> +
+ +
+ λΆ€λͺ¨ λ³€κ²½ μ‹œ κ°’ μ΄ˆκΈ°ν™” + + updateField({ + selectOptions: { + ...localField.selectOptions, + cascading: { + ...localField.selectOptions?.cascading, + clearOnParentChange: checked, + }, + }, + }) + } + /> +
+ λΆ€λͺ¨ ν•„λ“œ 값이 λ³€κ²½λ˜λ©΄ 이 ν•„λ“œμ˜ 값을 μžλ™μœΌλ‘œ μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€ +
+
+ )} )} @@ -929,7 +1491,7 @@ export function FieldDetailSettingsModal({ preview = `${subLabel} - ${mainLabel}`; } else if (format === "name_code" && subCol) { preview = `${mainLabel} (${subLabel})`; - } else if (format !== "name_only" && !subCol) { + } else if (!subCol) { preview = `${mainLabel} (μ„œλΈŒ μ»¬λŸΌμ„ μ„ νƒν•˜μ„Έμš”)`; } else { preview = mainLabel; diff --git a/frontend/lib/registry/components/universal-form-modal/types.ts b/frontend/lib/registry/components/universal-form-modal/types.ts index c5ecb1cc..72e29d58 100644 --- a/frontend/lib/registry/components/universal-form-modal/types.ts +++ b/frontend/lib/registry/components/universal-form-modal/types.ts @@ -7,7 +7,7 @@ // Select μ˜΅μ…˜ μ„€μ • export interface SelectOptionConfig { - type?: "static" | "table" | "code"; // μ˜΅μ…˜ νƒ€μž… (κΈ°λ³Έ: static) + type?: "static" | "table" | "code" | "cascading"; // μ˜΅μ…˜ νƒ€μž… (κΈ°λ³Έ: static) // 정적 μ˜΅μ…˜ staticOptions?: { value: string; label: string }[]; // ν…Œμ΄λΈ” 기반 μ˜΅μ…˜ @@ -19,6 +19,19 @@ export interface SelectOptionConfig { // μΉ΄ν…Œκ³ λ¦¬ 컬럼 기반 μ˜΅μ…˜ (table_column_category_values ν…Œμ΄λΈ”) // ν˜•μ‹: "tableName.columnName" (예: "sales_order_mng.incoterms") categoryKey?: string; + + // 연쇄 λ“œλ‘­λ‹€μš΄ μ„€μ • (type이 "cascading"일 λ•Œ μ‚¬μš©) + cascading?: { + parentField?: string; // λΆ€λͺ¨ ν•„λ“œλͺ… (같은 폼 λ‚΄) + relationCode?: string; // 관계 μ½”λ“œ (cascading_relation ν…Œμ΄λΈ”) + // 직접 μ„€μ • λ˜λŠ” 관계 μ½”λ“œμ—μ„œ κ°€μ Έμ˜¨ κ°’ μˆ˜μ • μ‹œ μ‚¬μš© + sourceTable?: string; // μ˜΅μ…˜μ„ μ‘°νšŒν•  ν…Œμ΄λΈ” + parentKeyColumn?: string; // λΆ€λͺ¨ κ°’κ³Ό λ§€μΉ­ν•  컬럼 + // valueColumn, labelColumn은 μƒμœ„ 속성 μ‚¬μš© + emptyParentMessage?: string; // λΆ€λͺ¨ 미선택 μ‹œ λ©”μ‹œμ§€ + noOptionsMessage?: string; // μ˜΅μ…˜ μ—†μŒ λ©”μ‹œμ§€ + clearOnParentChange?: boolean; // λΆ€λͺ¨ λ³€κ²½ μ‹œ κ°’ μ΄ˆκΈ°ν™” (κΈ°λ³Έ: true) + }; } // μ±„λ²ˆκ·œμΉ™ μ„€μ • @@ -873,6 +886,7 @@ export const SELECT_OPTION_TYPE_OPTIONS = [ { value: "static", label: "직접 μž…λ ₯" }, { value: "table", label: "ν…Œμ΄λΈ” μ°Έμ‘°" }, { value: "code", label: "κ³΅ν†΅μ½”λ“œ" }, + { value: "cascading", label: "연쇄 λ“œλ‘­λ‹€μš΄" }, ] as const; // 연동 ν•„λ“œ ν‘œμ‹œ ν˜•μ‹ μ˜΅μ…˜