150 lines
6.4 KiB
TypeScript
150 lines
6.4 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer";
|
|
import { V2SelectDefinition } from "./index";
|
|
import { V2Select } from "@/components/v2/V2Select";
|
|
|
|
/**
|
|
* V2Select 렌더러
|
|
* 자동 등록 시스템을 사용하여 컴포넌트를 레지스트리에 등록
|
|
*/
|
|
export class V2SelectRenderer extends AutoRegisteringComponentRenderer {
|
|
static componentDefinition = V2SelectDefinition;
|
|
|
|
render(): React.ReactElement {
|
|
const { component, formData, onFormDataChange, isDesignMode, isSelected, isInteractive, ...restProps } = this.props;
|
|
|
|
// 컴포넌트 설정 추출
|
|
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에 설정
|
|
setTimeout(() => {
|
|
if (!formData?.[columnName]) {
|
|
onFormDataChange(columnName, defaultValue);
|
|
}
|
|
}, 0);
|
|
currentValue = defaultValue;
|
|
}
|
|
|
|
// 값 변경 핸들러 (배열 → 쉼표 구분 문자열로 변환하여 저장)
|
|
const handleChange = (value: any) => {
|
|
if (isInteractive && onFormDataChange && columnName) {
|
|
// 🔧 배열이면 무조건 쉼표 구분 문자열로 변환 (PostgreSQL 배열 형식 방지)
|
|
if (Array.isArray(value)) {
|
|
const stringValue = value.map(v => typeof v === "number" ? String(v) : v).join(",");
|
|
onFormDataChange(columnName, stringValue);
|
|
} else {
|
|
onFormDataChange(columnName, value);
|
|
}
|
|
}
|
|
};
|
|
|
|
// 🔧 DynamicComponentRenderer에서 전달한 style/size를 우선 사용 (height 포함)
|
|
// restProps.style에 mergedStyle(height 변환됨)이 있고, restProps.size에도 size가 있음
|
|
const effectiveStyle = restProps.style || component.style;
|
|
const effectiveSize = restProps.size || component.size;
|
|
|
|
// 디버깅 필요시 주석 해제
|
|
// console.log("🔍 [V2SelectRenderer]", { componentId: component.id, effectiveStyle, effectiveSize });
|
|
|
|
// 🔧 restProps에서 style, size 제외 (effectiveStyle/effectiveSize가 우선되어야 함)
|
|
const { style: _style, size: _size, ...restPropsClean } = restProps as any;
|
|
|
|
return (
|
|
<V2Select
|
|
id={component.id}
|
|
label={component.label}
|
|
required={component.required}
|
|
readonly={config.readonly || component.readonly}
|
|
disabled={config.disabled || component.disabled}
|
|
value={currentValue}
|
|
onChange={handleChange}
|
|
config={{
|
|
mode: config.mode || "dropdown",
|
|
// 🔧 카테고리 타입이면 source를 "category"로 설정
|
|
source: config.source || (isCategoryType ? "category" : "distinct"),
|
|
multiple: config.multiple || false,
|
|
searchable: config.searchable ?? true,
|
|
placeholder: config.placeholder || "선택하세요",
|
|
options: config.options || [],
|
|
codeGroup: config.codeGroup,
|
|
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}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
// 자동 등록 실행
|
|
V2SelectRenderer.registerSelf();
|
|
|
|
// Hot Reload 지원 (개발 모드)
|
|
if (process.env.NODE_ENV === "development") {
|
|
V2SelectRenderer.enableHotReload();
|
|
}
|