feat: V2 레이아웃 동기화 및 컴포넌트 개선
- TableManagementService에서 V2 레이아웃 동기화 로직을 추가하여, 새로운 입력 타입에 따라 화면 레이아웃을 자동으로 업데이트하도록 개선하였습니다. - syncScreenLayoutsV2InputType 메서드를 통해 V2 레이아웃의 컴포넌트 source를 동기화하는 기능을 구현하였습니다. - EditModal에서 배열 데이터를 쉼표 구분 문자열로 변환하는 로직을 추가하여, 손상된 값을 필터링하고 데이터 저장 시 일관성을 높였습니다. - CategorySelectComponent에서 불필요한 스타일 및 높이 관련 props를 제거하여 코드 간결성을 개선하였습니다. - V2Select 및 관련 컴포넌트에서 height 스타일을 통일하여 사용자 경험을 향상시켰습니다.
This commit is contained in:
@@ -91,11 +91,6 @@ export const CategorySelectComponent: React.FC<
|
||||
|
||||
useEffect(() => {
|
||||
if (!tableName || !columnName) {
|
||||
console.warn("CategorySelectComponent: tableName 또는 columnName이 없습니다", {
|
||||
tableName,
|
||||
columnName,
|
||||
component,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,7 +123,6 @@ export const CategorySelectComponent: React.FC<
|
||||
};
|
||||
|
||||
const handleValueChange = (newValue: string) => {
|
||||
console.log("🔄 카테고리 값 변경:", { oldValue: value, newValue });
|
||||
onChange?.(newValue);
|
||||
};
|
||||
|
||||
@@ -216,7 +210,7 @@ export const CategorySelectComponent: React.FC<
|
||||
<Select
|
||||
value={value}
|
||||
onValueChange={handleValueChange}
|
||||
disabled={disabled || readonly}
|
||||
disabled={disabled || readonly || isDesignMode}
|
||||
required={required}
|
||||
>
|
||||
<SelectTrigger className={`w-full h-full ${className}`} style={heightStyle}>
|
||||
|
||||
@@ -19,11 +19,55 @@ export class V2SelectRenderer extends AutoRegisteringComponentRenderer {
|
||||
const config = component.componentConfig || component.config || {};
|
||||
const columnName = component.columnName;
|
||||
const tableName = component.tableName || this.props.tableName;
|
||||
|
||||
// 🔧 카테고리 타입 감지 (inputType 또는 webType이 category인 경우)
|
||||
const inputType = component.componentConfig?.inputType || component.inputType;
|
||||
const webType = component.componentConfig?.webType || component.webType;
|
||||
const isCategoryType = inputType === "category" || webType === "category";
|
||||
|
||||
// formData에서 현재 값 가져오기 (기본값 지원)
|
||||
const defaultValue = config.defaultValue || "";
|
||||
// 🔧 tagbox, check, tag, swap 모드는 본질적으로 다중 선택
|
||||
const multiSelectModes = ["tagbox", "check", "checkbox", "tag", "swap"];
|
||||
const isMultiple = config.multiple || multiSelectModes.includes(config.mode);
|
||||
let currentValue = formData?.[columnName] ?? component.value ?? "";
|
||||
|
||||
// 🔧 다중 선택 시 값 정규화 (잘못된 형식 필터링)
|
||||
if (isMultiple) {
|
||||
// 헬퍼: 유효한 값인지 체크 (중괄호, 따옴표, 백슬래시 없어야 함)
|
||||
// 숫자도 유효한 값으로 처리
|
||||
const isValidValue = (v: any): boolean => {
|
||||
// 숫자면 유효
|
||||
if (typeof v === "number" && !isNaN(v)) return true;
|
||||
if (typeof v !== "string") return false;
|
||||
if (!v || v.trim() === "") return false;
|
||||
if (v.includes("{") || v.includes("}") || v.includes('"') || v.includes("\\")) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (typeof currentValue === "string" && currentValue) {
|
||||
// 🔧 PostgreSQL 배열 형식 또는 중첩된 잘못된 형식 감지
|
||||
if (currentValue.startsWith("{") || currentValue.includes('{"') || currentValue.includes('\\"')) {
|
||||
currentValue = [];
|
||||
} else if (currentValue.includes(",")) {
|
||||
// 쉼표 구분 문자열 파싱 후 유효한 값만 필터링
|
||||
currentValue = currentValue.split(",").map(v => v.trim()).filter(isValidValue);
|
||||
} else if (isValidValue(currentValue)) {
|
||||
currentValue = [currentValue];
|
||||
} else {
|
||||
currentValue = [];
|
||||
}
|
||||
} else if (Array.isArray(currentValue)) {
|
||||
// 🔧 배열일 때도 잘못된 값 필터링 + 숫자→문자열 변환!
|
||||
const filtered = currentValue
|
||||
.map(v => typeof v === "number" ? String(v) : v)
|
||||
.filter(isValidValue);
|
||||
currentValue = filtered;
|
||||
} else {
|
||||
currentValue = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 formData에 값이 없고 기본값이 설정된 경우, 기본값 적용
|
||||
if ((currentValue === "" || currentValue === undefined || currentValue === null) && defaultValue && isInteractive && onFormDataChange && columnName) {
|
||||
// 초기 렌더링 시 기본값을 formData에 설정
|
||||
@@ -35,10 +79,16 @@ export class V2SelectRenderer extends AutoRegisteringComponentRenderer {
|
||||
currentValue = defaultValue;
|
||||
}
|
||||
|
||||
// 값 변경 핸들러
|
||||
// 값 변경 핸들러 (배열 → 쉼표 구분 문자열로 변환하여 저장)
|
||||
const handleChange = (value: any) => {
|
||||
if (isInteractive && onFormDataChange && columnName) {
|
||||
onFormDataChange(columnName, value);
|
||||
// 🔧 배열이면 무조건 쉼표 구분 문자열로 변환 (PostgreSQL 배열 형식 방지)
|
||||
if (Array.isArray(value)) {
|
||||
const stringValue = value.map(v => typeof v === "number" ? String(v) : v).join(",");
|
||||
onFormDataChange(columnName, stringValue);
|
||||
} else {
|
||||
onFormDataChange(columnName, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,16 +97,8 @@ export class V2SelectRenderer extends AutoRegisteringComponentRenderer {
|
||||
const effectiveStyle = restProps.style || component.style;
|
||||
const effectiveSize = restProps.size || component.size;
|
||||
|
||||
// 🔍 디버깅: props 확인 (warn으로 변경하여 캡처되도록)
|
||||
console.warn("🔍 [V2SelectRenderer] props 디버깅:", {
|
||||
componentId: component.id,
|
||||
"component.style": component.style,
|
||||
"component.size": component.size,
|
||||
"restProps.style": restProps.style,
|
||||
"restProps.size": restProps.size,
|
||||
effectiveStyle,
|
||||
effectiveSize,
|
||||
});
|
||||
// 디버깅 필요시 주석 해제
|
||||
// console.log("🔍 [V2SelectRenderer]", { componentId: component.id, effectiveStyle, effectiveSize });
|
||||
|
||||
// 🔧 restProps에서 style, size 제외 (effectiveStyle/effectiveSize가 우선되어야 함)
|
||||
const { style: _style, size: _size, ...restPropsClean } = restProps as any;
|
||||
@@ -72,7 +114,8 @@ export class V2SelectRenderer extends AutoRegisteringComponentRenderer {
|
||||
onChange={handleChange}
|
||||
config={{
|
||||
mode: config.mode || "dropdown",
|
||||
source: config.source || "distinct",
|
||||
// 🔧 카테고리 타입이면 source를 "category"로 설정
|
||||
source: config.source || (isCategoryType ? "category" : "distinct"),
|
||||
multiple: config.multiple || false,
|
||||
searchable: config.searchable ?? true,
|
||||
placeholder: config.placeholder || "선택하세요",
|
||||
@@ -81,10 +124,14 @@ export class V2SelectRenderer extends AutoRegisteringComponentRenderer {
|
||||
entityTable: config.entityTable,
|
||||
entityLabelColumn: config.entityLabelColumn,
|
||||
entityValueColumn: config.entityValueColumn,
|
||||
// 🔧 카테고리 소스 지원 (tableName, columnName 폴백)
|
||||
categoryTable: config.categoryTable || (isCategoryType ? tableName : undefined),
|
||||
categoryColumn: config.categoryColumn || (isCategoryType ? columnName : undefined),
|
||||
}}
|
||||
tableName={tableName}
|
||||
columnName={columnName}
|
||||
formData={formData}
|
||||
isDesignMode={isDesignMode}
|
||||
{...restPropsClean}
|
||||
style={effectiveStyle}
|
||||
size={effectiveSize}
|
||||
|
||||
@@ -43,6 +43,7 @@ export const V2SelectDefinition = createComponentDefinition({
|
||||
{ value: "radio", label: "라디오 버튼" },
|
||||
{ value: "check", label: "체크박스" },
|
||||
{ value: "tag", label: "태그" },
|
||||
{ value: "tagbox", label: "태그박스 (태그+드롭다운)" },
|
||||
{ value: "toggle", label: "토글" },
|
||||
{ value: "swap", label: "스왑 (좌우 이동)" },
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user