feat(universal-form-modal): 연쇄 드롭다운(Cascading Dropdown) 기능 구현

- SelectOptionConfig에 cascading 타입 및 설정 객체 추가
- FieldDetailSettingsModal에 연쇄 드롭다운 설정 UI 구현
  - 부모 필드 선택 (섹션별 그룹핑 콤보박스)
  - 관계 코드 선택 시 상세 설정 자동 채움
  - 소스 테이블, 부모 키 컬럼, 값/라벨 컬럼 설정
- UniversalFormModalComponent에 자식 필드 초기화 로직 추가
- selectOptions.cascading 방식 CascadingSelectField 렌더링 지원
This commit is contained in:
SeongHyun Kim
2026-01-13 18:26:41 +09:00
parent d7d7dabe84
commit ef27e0e38f
4 changed files with 653 additions and 12 deletions

View File

@@ -84,7 +84,7 @@ const CascadingSelectField: React.FC<CascadingSelectFieldProps> = ({
return (
<Select value={value || ""} onValueChange={onChange} disabled={isDisabled}>
<SelectTrigger id={fieldId} className="w-full">
<SelectTrigger id={fieldId} className="w-full" size="default">
{loading ? (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
@@ -958,6 +958,26 @@ export function UniversalFormModalComponent({
if (fieldConfig) break;
}
// 🆕 연쇄 드롭다운: 부모 필드 변경 시 자식 필드 초기화
const childFieldsToReset: string[] = [];
for (const section of config.sections) {
if (section.type === "table" || section.repeatable) continue;
for (const field of section.fields || []) {
// field.cascading 방식 체크
if (field.cascading?.enabled && field.cascading?.parentField === columnName) {
if (field.cascading.clearOnParentChange !== false) {
childFieldsToReset.push(field.columnName);
}
}
// selectOptions.cascading 방식 체크
if (field.selectOptions?.type === "cascading" && field.selectOptions?.cascading?.parentField === columnName) {
if (field.selectOptions.cascading.clearOnParentChange !== false) {
childFieldsToReset.push(field.columnName);
}
}
}
}
setFormData((prev) => {
const newData = { ...prev, [columnName]: value };
@@ -976,6 +996,12 @@ export function UniversalFormModalComponent({
}
}
// 🆕 연쇄 드롭다운 자식 필드 초기화
for (const childField of childFieldsToReset) {
newData[childField] = "";
console.log(`[연쇄 드롭다운] 부모 ${columnName} 변경 → 자식 ${childField} 초기화`);
}
// onChange는 렌더링 외부에서 호출해야 함 (setTimeout 사용)
if (onChange) {
setTimeout(() => onChange(newData), 0);
@@ -1463,7 +1489,7 @@ export function UniversalFormModalComponent({
);
case "select": {
// 🆕 연쇄 드롭다운 처리
// 🆕 연쇄 드롭다운 처리 (기존 field.cascading 방식)
if (field.cascading?.enabled) {
const cascadingConfig = field.cascading;
const parentValue = formData[cascadingConfig.parentField];
@@ -1480,6 +1506,37 @@ export function UniversalFormModalComponent({
/>
);
}
// 🆕 연쇄 드롭다운 처리 (selectOptions.type === "cascading" 방식)
if (field.selectOptions?.type === "cascading" && field.selectOptions?.cascading?.parentField) {
const cascadingOpts = field.selectOptions.cascading;
const parentValue = formData[cascadingOpts.parentField];
// selectOptions 기반 cascading config를 CascadingDropdownConfig 형태로 변환
const cascadingConfig: CascadingDropdownConfig = {
enabled: true,
parentField: cascadingOpts.parentField,
sourceTable: cascadingOpts.sourceTable || field.selectOptions.tableName || "",
parentKeyColumn: cascadingOpts.parentKeyColumn || "",
valueColumn: field.selectOptions.valueColumn || "",
labelColumn: field.selectOptions.labelColumn || "",
emptyParentMessage: cascadingOpts.emptyParentMessage,
noOptionsMessage: cascadingOpts.noOptionsMessage,
clearOnParentChange: cascadingOpts.clearOnParentChange !== false,
};
return (
<CascadingSelectField
fieldId={fieldKey}
config={cascadingConfig}
parentValue={parentValue}
value={value}
onChange={onChangeHandler}
placeholder={field.placeholder || "선택하세요"}
disabled={isDisabled}
/>
);
}
// 다중 컬럼 저장이 활성화된 경우
const lfgMappings = field.linkedFieldGroup?.mappings;