Merge remote-tracking branch 'origin/ycshin-node' into ycshin-node

Resolve conflict in InteractiveScreenViewerDynamic.tsx:
- Keep horizontal label code (fd5c61b side)
- Remove old inline required field validation (replaced by useDialogAutoValidation hook)
- Clean up checkAllRequiredFieldsFilled usage from SaveModal, ButtonPrimaryComponent
- Remove isFieldEmpty, isInputComponent, checkAllRequiredFieldsFilled from formValidation.ts

Made-with: Cursor
This commit is contained in:
2026-03-03 13:12:48 +09:00
38 changed files with 1642 additions and 525 deletions

View File

@@ -3173,16 +3173,16 @@ export class ButtonActionExecutor {
return false;
}
// 1. 화면 설명 가져오기
let description = config.modalDescription || "";
if (!description) {
// 1. 화면 정보 가져오기 (제목/설명이 미설정 시 화면명에서 가져옴)
let screenInfo: any = null;
if (!config.modalTitle || !config.modalDescription) {
try {
const screenInfo = await screenApi.getScreen(config.targetScreenId);
description = screenInfo?.description || "";
screenInfo = await screenApi.getScreen(config.targetScreenId);
} catch (error) {
console.warn("화면 설명을 가져오지 못했습니다:", error);
console.warn("화면 정보를 가져오지 못했습니다:", error);
}
}
let description = config.modalDescription || screenInfo?.description || "";
// 2. 데이터 소스 및 선택된 데이터 수집
let selectedData: any[] = [];
@@ -3288,7 +3288,7 @@ export class ButtonActionExecutor {
}
// 3. 동적 모달 제목 생성
let finalTitle = config.modalTitle || "화면";
let finalTitle = config.modalTitle || screenInfo?.screenName || "데이터 등록";
// 블록 기반 제목 처리
if (config.modalTitleBlocks?.length) {

View File

@@ -662,75 +662,3 @@ const calculateStringSimilarity = (str1: string, str2: string): number => {
return maxLen === 0 ? 1 : (maxLen - distance) / maxLen;
};
/**
* 값이 비어있는지 판별
*/
export const isFieldEmpty = (value: any): boolean => {
return (
value === null ||
value === undefined ||
(typeof value === "string" && value.trim() === "") ||
(Array.isArray(value) && value.length === 0)
);
};
/**
* 입력 가능한 컴포넌트인지 판별 (위젯 또는 V2 입력 컴포넌트)
*/
const isInputComponent = (comp: any): boolean => {
if (comp.type === "widget") return true;
if (comp.type === "component") {
const ct = comp.componentType || "";
return ct.startsWith("v2-input") ||
ct.startsWith("v2-select") ||
ct.startsWith("v2-date") ||
ct.startsWith("v2-textarea") ||
ct.startsWith("v2-number") ||
ct === "entity-search-input" ||
ct === "autocomplete-search-input";
}
return false;
};
/**
* 모든 필수 필드가 채워졌는지 판별 (모달 저장 버튼 비활성화용)
* auto 입력 필드, readonly 필드는 검증 제외
*/
export const checkAllRequiredFieldsFilled = (
allComponents: any[],
formData: Record<string, any>,
): boolean => {
for (const comp of allComponents) {
if (!isInputComponent(comp)) continue;
const isRequired =
comp.required === true ||
comp.style?.required === true ||
comp.componentConfig?.required === true ||
comp.overrides?.required === true;
if (!isRequired) continue;
const isAutoInput =
comp.inputType === "auto" ||
comp.componentConfig?.inputType === "auto" ||
comp.overrides?.inputType === "auto";
const isReadonly =
comp.readonly === true ||
comp.componentConfig?.readonly === true ||
comp.overrides?.readonly === true;
if (isAutoInput || isReadonly) continue;
const fieldName =
comp.columnName ||
comp.componentConfig?.columnName ||
comp.overrides?.columnName ||
comp.id;
const value = formData[fieldName];
if (isFieldEmpty(value)) {
return false;
}
}
return true;
};