Consolidate canonical input migration
Build & Deploy to K8s / build-and-deploy (push) Failing after 11m17s

Remove legacy v2 input/select and file/media runtimes, add canonical option/file loaders, and document Codex handoff.
This commit is contained in:
DDD1542
2026-05-12 18:36:43 +09:00
parent 90035dd5c6
commit 4a8413000b
79 changed files with 2359 additions and 14028 deletions
@@ -336,7 +336,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
}, [screenTableName]);
// 컴포넌트 타입 추출 - 새 시스템에서는 componentType 속성 사용, 레거시는 type 사용
// 🆕 V2 레이아웃의 경우 url에서 컴포넌트 타입 추출 (예: "@/lib/registry/components/v2-input" → "v2-input")
// 레거시 저장 데이터의 url 마지막 세그먼트를 컴포넌트 타입으로 사용
const extractTypeFromUrl = (url: string | undefined): string | undefined => {
if (!url) return undefined;
// url의 마지막 세그먼트를 컴포넌트 타입으로 사용
@@ -349,8 +349,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
// 레거시 타입을 v2 컴포넌트로 매핑 (v2 컴포넌트가 없으면 원본 유지)
// ★ 2026-04-11: INVYONE 통합 컴포넌트(Phase A~) 는 v2- 매핑에서 제외.
// 예: 'input' 을 'v2-input' 으로 리다이렉트하면 새 통합 Input 대신 기존
// v2-input 이 렌더되어 설정 변경이 안 반영됨.
// ★ 2026-05-12: V2 입력/선택은 완전 폐기 — 매핑/alias/fallback 모두 제거.
const INVYONE_UNIFIED_IDS = new Set([
"divider",
"title",
@@ -373,8 +372,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
"v2-button-primary": "button", "button-primary": "button",
// search
"v2-table-search-widget": "search", "table-search-widget": "search",
// input
"v2-input": "input", "v2-select": "input",
// input (V2 입력/선택은 Phase D.2 에서 완전 폐기 — alias 제거)
"text-input": "input", "number-input": "input", "date-input": "input",
"select-basic": "input", "checkbox-basic": "input", "textarea-basic": "input",
"slider-basic": "input", "radio-basic": "input", "toggle-switch": "input",
@@ -415,9 +413,8 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
const mappedComponentType = mapToV2ComponentType(rawComponentType);
// ★ canonical 라우팅: fieldType / dbInputType → v2-input/v2-select 강제 swap 제거됨.
// InvField (kind/type/format) 모델이 진실의 원천. mappedComponentType 그대로 사용.
// (이전 분기는 brumb 변경 시 InputComponent → V2Input swap 의 원인이었음)
// ★ canonical 라우팅: InvField (kind/type/format) 모델이 진실의 원천.
// fieldType / dbInputType 기반 강제 swap 분기는 Phase 3 / D.2 에서 제거됨.
const componentType = mappedComponentType;
// 🆕 조건부 렌더링 체크 (conditionalConfig)
@@ -478,7 +475,7 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
}
// 🆕 모든 v2- 컴포넌트는 ComponentRegistry에서 통합 처리
// (v2-input, v2-select, v2-repeat-container 등 모두 동일하게 처리)
// (v2-repeat-container 등. V2 입력/선택은 Phase D.2 에서 폐기됨)
// 🎯 카테고리 타입 우선 처리 (inputType 또는 webType 확인)
// DB input_type이 "text" 등 비-카테고리로 변경된 경우 이 분기를 건너뜀
@@ -502,117 +499,11 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
// webType도 DB 값으로 대체 (레이아웃에 webType: "category" 하드코딩되어 있을 수 있음)
const effectiveWebType = dbFieldInputType || webType;
const componentMode = (component as any).componentConfig?.mode || (component as any).config?.mode;
const isMultipleSelect = (component as any).componentConfig?.multiple;
const nonDropdownModes = ["radio", "check", "checkbox", "tag", "tagbox", "toggle", "swap", "combobox"];
const isNonDropdownMode = componentMode && nonDropdownModes.includes(componentMode);
const shouldUseV2Select =
componentType === "select-basic" || componentType === "v2-select" || isNonDropdownMode || isMultipleSelect;
// DB input_type이 비-카테고리(text 등)로 확인된 경우, 레이아웃에 category가 남아있어도 카테고리 분기 강제 스킵
// dbFieldInputType이 있으면(캐시 로드됨) 그 값으로 판단, 없으면 기존 로직 유지
const isDbConfirmedNonCategory = dbFieldInputType && !["category", "entity", "select"].includes(dbFieldInputType);
if (!isDbConfirmedNonCategory && (inputType === "category" || effectiveWebType === "category") && tableName && columnName && shouldUseV2Select) {
// V2SelectRenderer로 직접 렌더링 (카테고리 + 고급 모드)
try {
const { V2SelectRenderer } = require("@/lib/registry/components/v2-select/V2SelectRenderer");
const fieldName = columnName || component.id;
// 수평 라벨 감지
const catLabelDisplay = component.style?.label_display ?? (component as any).labelDisplay;
const catLabelPosition = component.style?.label_position;
const catLabelText =
catLabelDisplay === true || catLabelDisplay === "true"
? component.style?.label_text || (component as any).label || component.component_config?.label
: undefined;
const catNeedsExternalHorizLabel = !!(
catLabelText &&
(catLabelPosition === "left" || catLabelPosition === "right")
);
const selectComponent = {
...component,
component_config: {
...component.component_config,
mode: componentMode || "dropdown",
source: "category",
categoryTable: tableName,
categoryColumn: columnName,
},
tableName,
columnName,
inputType: "category",
web_type: "category",
};
const catStyle = catNeedsExternalHorizLabel
? {
...(component as any).style,
label_display: false,
label_position: "top" as const,
width: "100%",
height: "100%",
borderWidth: undefined,
borderColor: undefined,
borderStyle: undefined,
border: undefined,
borderRadius: undefined,
}
: (component as any).style;
const catSize = catNeedsExternalHorizLabel
? { ...(component as any).size, width: undefined }
: (component as any).size;
const rendererProps = {
component: selectComponent,
formData: props.formData,
onFormDataChange: props.onFormDataChange,
isDesignMode: props.isDesignMode,
isInteractive: props.isInteractive ?? !props.isDesignMode,
tableName,
style: catStyle,
size: catSize,
};
const rendererInstance = new V2SelectRenderer(rendererProps);
const renderedCatSelect = rendererInstance.render();
if (catNeedsExternalHorizLabel) {
const labelGap = component.style?.label_gap || "8px";
const labelFontSize = component.style?.label_font_size || "14px";
const labelColor = getAdaptiveLabelColor(component.style?.label_color);
const labelFontWeight = component.style?.label_font_weight || "500";
const isRequired =
component.required || (component as any).required || isColumnRequiredByMeta(tableName, columnName);
const isLeft = catLabelPosition === "left";
return (
<div style={{ position: "relative", width: "100%", height: "100%" }}>
<label
style={{
position: "absolute",
top: "50%",
transform: "translateY(-50%)",
...(isLeft ? { right: "100%", marginRight: labelGap } : { left: "100%", marginLeft: labelGap }),
fontSize: labelFontSize,
color: labelColor,
fontWeight: labelFontWeight,
whiteSpace: "nowrap",
}}
className="text-sm font-medium"
>
{catLabelText}
{isRequired && <span className="ml-0.5 text-amber-500">*</span>}
</label>
<div style={{ width: "100%", height: "100%" }}>{renderedCatSelect}</div>
</div>
);
}
return renderedCatSelect;
} catch (error) {
console.error("❌ V2SelectRenderer 로드 실패:", error);
}
} else if (!isDbConfirmedNonCategory && (inputType === "category" || effectiveWebType === "category") && tableName && columnName) {
if (!isDbConfirmedNonCategory && (inputType === "category" || effectiveWebType === "category") && tableName && columnName) {
try {
const { CategorySelectComponent } = require("@/lib/registry/components/category-select/CategorySelectComponent");
const fieldName = columnName || component.id;
@@ -821,14 +712,13 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
};
// 🆕 엔티티 검색 컴포넌트는 componentConfig.tableName을 사용해야 함 (화면 테이블이 아닌 검색 대상 테이블)
// 🆕 v2-input도 포함 (채번 규칙 조회 시 tableName 필요)
// (V2 입력 폐기로 채번 규칙 조회용 분기 제거됨 — input canonical 은 자체 props 로 tableName 받음)
const useConfigTableName =
componentType === "entity-search-input" ||
componentType === "autocomplete-search-input" ||
componentType === "modal-repeater-table" ||
componentType === "v2-input";
componentType === "modal-repeater-table";
// 🆕 v2-input 등의 라벨 표시 로직 (InteractiveScreenViewerDynamic과 동일한 부정형 체크)
// 🆕 라벨 표시 로직 (InteractiveScreenViewerDynamic 과 동일한 부정형 체크)
const labelDisplay = component.style?.label_display ?? (component as any).labelDisplay;
const effectiveLabel =
labelDisplay !== false && labelDisplay !== "false"