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:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user