refactor: finalize canonical data-view cleanup
Build & Deploy to K8s / build-and-deploy (push) Successful in 6m29s
Build & Deploy to K8s / build-and-deploy (push) Successful in 6m29s
This commit is contained in:
@@ -43,8 +43,13 @@ export function ComponentsPanel({
|
||||
const allComponents = useMemo(() => {
|
||||
const components = ComponentRegistry.getAllComponents();
|
||||
// ★ 새 생성 경로는 canonical 'table' (displayMode='table').
|
||||
// v2-table-list 는 옛 저장 화면 호환 hard blocker 로 자동 등록되지만
|
||||
// 팔레트에는 hidden 처리한다 (아래 hiddenComponents 참고).
|
||||
// v2-table-list / table-list registration shell 은 2026-05-20 cleanup 으로 삭제되어
|
||||
// ComponentRegistry 에는 더 이상 등록되지 않는다. 옛 저장 layout 의 v2-table-list /
|
||||
// table-list 는 BlockRenderer / DynamicComponentRenderer / templateMigrate 의 alias
|
||||
// 라우팅으로 canonical 'table' 정의를 통해 들어온 뒤, TableComponent 의 early
|
||||
// delegation 으로 _shared/{TableListComponent,V2TableListContainerWrapper} 본체에서
|
||||
// 기능 손실 없이 렌더된다. 아래 hiddenComponents 의 옛 ID 들은 만약 외부 코드가
|
||||
// register 를 추가하더라도 팔레트에는 노출되지 않도록 한 안전망이다.
|
||||
return components;
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -366,7 +366,7 @@ export interface LayoutItem {
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
componentKind: string; // 정확한 컴포넌트 종류 (table-list, button-primary 등)
|
||||
componentKind: string; // 정확한 컴포넌트 종류 (canonical 예: table / button — legacy 예: table-list / v2-table-list / button-primary)
|
||||
widgetType: string; // 일반적인 위젯 타입 (button, text 등)
|
||||
label?: string;
|
||||
bindField?: string; // 바인딩된 필드명 (컬럼명)
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
import type { FieldConfig } from "@/types/invyone-component";
|
||||
|
||||
/**
|
||||
* FieldConfig[] → v2-table-list 의 ColumnConfig[] 호환 배열.
|
||||
* FieldConfig[] → table-like 컴포넌트 (canonical 'table' / legacy 'table-list' /
|
||||
* hidden 'v2-table-list') 의 ColumnConfig[] 호환 배열.
|
||||
*
|
||||
* 상세 매핑 규칙은 v2-table-list 내부 포맷 확정 후 보강한다. 현재는 공통 필드
|
||||
* (column_name / column_label / visible / display_order / width / align / sortable)
|
||||
* 만 매핑.
|
||||
* 현재 공통 필드 (column_name / column_label / visible / display_order /
|
||||
* width / align / sortable) 만 매핑한다. shared `_shared/V2TableListContainerWrapper`
|
||||
* 가 본 어댑터를 호출해서 v2 본체에 columns 를 전달한다.
|
||||
*/
|
||||
export function fieldsToColumns(
|
||||
fields: FieldConfig[],
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useV2FormOptional } from "@/components/v2/V2FormContext";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
|
||||
import { getAdaptiveLabelColor } from "@/lib/utils/darkModeColor";
|
||||
import { isTableLikeComponentType } from "@/lib/utils/componentTypeUtils";
|
||||
|
||||
// 컬럼 메타데이터 캐시 (테이블명 → 컬럼 설정 맵)
|
||||
export const columnMetaCache: Record<string, Record<string, any>> = {};
|
||||
@@ -932,10 +933,9 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
const rendererInstance = new RendererClass(rendererProps);
|
||||
renderedElement = rendererInstance.render();
|
||||
} else {
|
||||
const needsKeyRefresh =
|
||||
componentType === "v2-table-list" ||
|
||||
componentType === "table-list" ||
|
||||
componentType === "v2-repeater";
|
||||
// canonical 'table' / legacy 'table-list' / hidden 'v2-table-list' / v2-repeater
|
||||
// 는 refreshKey 변동 시 강제 remount 가 필요 (data refetch 트리거).
|
||||
const needsKeyRefresh = isTableLikeComponentType(componentType) || componentType === "v2-repeater";
|
||||
renderedElement = <NewComponentRenderer key={needsKeyRefresh ? refreshKey : component.id} {...rendererProps} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,19 +5,25 @@ import { ComponentConfig } from "@/types/component";
|
||||
/**
|
||||
* Container 컴포넌트 통합 설정 타입
|
||||
*
|
||||
* 11개의 레이아웃/컨테이너 계열 컴포넌트를 통합.
|
||||
* containerType 으로 탭/섹션/아코디언/반복/조건부 분기.
|
||||
* canonical 새 생성 경로. containerType 으로 tabs / section / accordion / repeater /
|
||||
* conditional 분기. SectionVariant 로 card / paper / plain 분기.
|
||||
*
|
||||
* 흡수 대상 (11):
|
||||
* - v2-tabs-widget (탭)
|
||||
* - v2-section-card / v2-section-paper (섹션)
|
||||
* - v2-repeat-container / v2-repeater (반복)
|
||||
* - accordion-basic (아코디언)
|
||||
* - section-card / section-paper (legacy)
|
||||
* - tabs (legacy)
|
||||
* - conditional-container (조건부)
|
||||
* - repeat-container / repeat-screen-modal / repeater-field-group (legacy)
|
||||
* - screen-split-panel (legacy)
|
||||
* 흡수 완료 (alias 라우팅 — DynamicComponentRenderer.LEGACY_TO_UNIFIED + getComponentConfigPanel.CONFIG_PANEL_ALIAS):
|
||||
* - tabs-widget / v2-tabs-widget / tabs / v2-tabs → container(tabs)
|
||||
* - section-card / v2-section-card → container(section, sectionVariant=card)
|
||||
* - section-paper / v2-section-paper → container(section, sectionVariant=paper)
|
||||
*
|
||||
* 보존 (canonical skeleton 부족 또는 도메인 특화) — concrete blocker, 본 cleanup 범위 외:
|
||||
* - accordion-basic → canonical container.containerType=accordion skeleton 부족
|
||||
* - conditional-container → canonical container.containerType=conditional skeleton 부족
|
||||
* - repeat-container → canonical container.containerType=repeater skeleton 부족
|
||||
* - v2-repeat-container → 동일. basicV2Components palette item
|
||||
* - v2-repeater → 별도 데이터 조회/선택 도메인
|
||||
* - repeat-screen-modal / repeater-field-group → 도메인 특화
|
||||
* - screen-split-panel → 화면 임베딩 + 데이터 전달 (별도 도메인)
|
||||
*
|
||||
* split-panel-layout / v2-split-panel-layout / split-panel-layout2 는 table 의 split
|
||||
* displayMode 와 짝이고 SplitPanelContext provider 다수 사용처 때문에 별도 보존.
|
||||
*/
|
||||
|
||||
export type ContainerType =
|
||||
|
||||
@@ -103,8 +103,16 @@ import "./grouped-table/GroupedTableRenderer"; // Phase G.3.1 — canonical 그
|
||||
// form 컴포넌트는 롤백됨 (2026-04-11): "폼" 은 별도 컴포넌트가 아닌
|
||||
// 화면 디자이너의 3뷰 탭(목록/등록 팝업/수정 팝업) 구조로 처리할 예정.
|
||||
// 관련: notes/gbpark/2026-04-11-component-unification-plan.md §3.2
|
||||
import "./table/TableRenderer"; // v2-table-list + v2-table-grouped + v2-pivot-grid + v2-split-panel-layout + legacy 9종 흡수
|
||||
import "./container/ContainerRenderer"; // v2-tabs-widget + v2-section-card/paper + v2-repeat-container + accordion + conditional + legacy 11종 흡수
|
||||
// canonical 'table' — 새 생성 경로. table-list / v2-table-list 등 옛 ID 는
|
||||
// LEGACY_TO_UNIFIED + getComponentConfigPanel.CONFIG_PANEL_ALIAS 로 alias 라우팅 후
|
||||
// TableComponent early delegation 에서 _shared/{TableListComponent,V2TableListContainerWrapper}
|
||||
// 로 위임된다. shell 폴더 (table-list/, v2-table-list/) 는 2026-05-20 cleanup 으로 삭제됨.
|
||||
import "./table/TableRenderer";
|
||||
// canonical 'container' — 새 생성 경로. alias 흡수: tabs-widget / v2-tabs-widget /
|
||||
// section-card / v2-section-card / section-paper / v2-section-paper / tabs / v2-tabs.
|
||||
// 보존 (skeleton 부족 또는 도메인 특화): accordion-basic / conditional-container /
|
||||
// repeat-container / v2-repeat-container / screen-split-panel / split-panel-layout 계열.
|
||||
import "./container/ContainerRenderer";
|
||||
import "./v2-repeat-container/RepeatContainerRenderer"; // canonical container.containerType=repeater skeleton 부족 → 보존
|
||||
// v2-section-card / v2-section-paper → canonical container alias (containerType=section + sectionVariant) 로 라우팅 (auto-register 제거)
|
||||
import "./domain/v2-rack-structure/RackStructureRenderer";
|
||||
|
||||
@@ -14,6 +14,74 @@ import { GroupedView } from "./views/GroupedView";
|
||||
import { CardView } from "./views/CardView";
|
||||
import { PivotView } from "./views/PivotView";
|
||||
|
||||
/**
|
||||
* ─────────────────────────────────────────────────────────────────────────
|
||||
* Legacy / v2 table-list delegation (2026-05-20 canonical cleanup follow-up)
|
||||
*
|
||||
* canonical 새 생성 경로는 그대로 'table' (이 파일의 본체 `TableComponent`).
|
||||
* 단, old 저장 layout 중 componentType === 'table-list' / 'v2-table-list' 인
|
||||
* 컴포넌트가 BlockRenderer / DynamicComponentRenderer 의 alias 라우팅으로
|
||||
* 본 컴포넌트에 도달하면 기능 손실 없이 shared legacy/v2 runtime 으로 위임한다.
|
||||
* ─────────────────────────────────────────────────────────────────────────
|
||||
*/
|
||||
import { TableListWrapper as LegacyTableListWrapper } from "./_shared/TableListComponent";
|
||||
import { TableListContainerWrapper as V2TableListContainerWrapper } from "./_shared/V2TableListContainerWrapper";
|
||||
|
||||
/**
|
||||
* raw original componentType 탐색 — meaningful table type 만 즉시 채택.
|
||||
*
|
||||
* BlockRenderer / DynamicComponentRenderer 의 alias 라우팅을 통해 본 컴포넌트에 도달하면
|
||||
* `component.type` 이 layout JSON 의 일반 값 ("component", "widget", "container", "row" 등)
|
||||
* 으로 박혀 들어오는 경우가 있다. 그런 generic 값은 delegation 판단에 도움이 안 되므로
|
||||
* skip 하고 더 구체적인 후보를 계속 본다.
|
||||
*
|
||||
* 우선순위:
|
||||
* 1) MEANINGFUL_TABLE_TYPES 에 즉시 일치 → 채택
|
||||
* 2) GENERIC_TYPES 에 일치 → skip
|
||||
* 3) 그 외 첫 non-empty string → fallback 후보로 기억
|
||||
* 4) 모든 후보 + url last segment 검사 후 fallback 반환
|
||||
*/
|
||||
const _MEANINGFUL_TABLE_TYPES = new Set<string>([
|
||||
"table",
|
||||
"table-list",
|
||||
"v2-table-list",
|
||||
"data-table",
|
||||
"datatable",
|
||||
]);
|
||||
const _GENERIC_COMPONENT_TYPES = new Set<string>([
|
||||
"component", "widget", "container", "group", "row", "column", "area", "flow", "tabs",
|
||||
]);
|
||||
|
||||
function _resolveRawComponentType(component: any, props: any): string | undefined {
|
||||
const candidates: unknown[] = [
|
||||
component?.componentType,
|
||||
component?.component_type,
|
||||
component?.componentConfig?.type,
|
||||
component?.component_config?.type,
|
||||
props?.componentType,
|
||||
component?.type,
|
||||
props?.type,
|
||||
];
|
||||
let fallback: string | undefined;
|
||||
const consider = (v: unknown): string | undefined => {
|
||||
if (typeof v !== "string" || v.length === 0) return undefined;
|
||||
if (_MEANINGFUL_TABLE_TYPES.has(v)) return v;
|
||||
if (_GENERIC_COMPONENT_TYPES.has(v)) return undefined;
|
||||
if (!fallback) fallback = v;
|
||||
return undefined;
|
||||
};
|
||||
for (const v of candidates) {
|
||||
const hit = consider(v);
|
||||
if (hit) return hit;
|
||||
}
|
||||
if (typeof component?.url === "string") {
|
||||
const last = component.url.split("/").pop();
|
||||
const hit = consider(last);
|
||||
if (hit) return hit;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const VALID_MODES: TableDisplayMode[] = ["table", "split", "grouped", "pivot", "card"];
|
||||
const ROW_HEIGHT_PRESETS: Record<TableRowHeight, string> = {
|
||||
compact: "28px",
|
||||
@@ -38,7 +106,50 @@ export const TableComponent: React.FC<TableComponentProps> = ({
|
||||
style,
|
||||
...props
|
||||
}) => {
|
||||
// ─── 4경로 머지 ───
|
||||
// ─── Legacy / v2 delegation (early return) ───
|
||||
// canonical 새 생성 경로는 'table'. 그 외 라우팅된 old type 은 shared runtime 으로 위임.
|
||||
// props 손실 방지: 모든 props 를 ...rest 로 전달 + component/config/isDesignMode 등 명시.
|
||||
const _rawType = _resolveRawComponentType(component, props);
|
||||
if (_rawType === "table-list") {
|
||||
// old table-list layout — 기능 완전 보존: 필터/정렬/선택/체크박스/카드모드/
|
||||
// inline edit / toolbar / export / search / linked filter / exclude filter /
|
||||
// GroupSum / DataProvider / DataReceiver / FieldConfig adapter / tableName /
|
||||
// selectedTable / dbTable 모두 shared LegacyTableListWrapper 에서 그대로 처리.
|
||||
return (
|
||||
<LegacyTableListWrapper
|
||||
{...(props as any)}
|
||||
component={component}
|
||||
config={config as any}
|
||||
isDesignMode={isDesignMode}
|
||||
isSelected={isSelected}
|
||||
onClick={onClick}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
className={className}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (_rawType === "v2-table-list") {
|
||||
// old v2-table-list layout — V2TableListContainerWrapper 가
|
||||
// FieldConfig → columns 어댑터 + ResizeObserver + DataProvider/DataReceiver 포함.
|
||||
return (
|
||||
<V2TableListContainerWrapper
|
||||
{...(props as any)}
|
||||
component={component}
|
||||
config={config as any}
|
||||
isDesignMode={isDesignMode}
|
||||
isSelected={isSelected}
|
||||
onClick={onClick}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
className={className}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── 4경로 머지 (canonical 'table' 본체, 기존 코드 그대로) ───
|
||||
const fromProps: Partial<TableConfig> = {};
|
||||
const p = props as any;
|
||||
if (typeof p.displayMode === "string" && (VALID_MODES as string[]).includes(p.displayMode))
|
||||
|
||||
@@ -594,11 +594,11 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const [displayColumns, setDisplayColumns] = useState<ColumnConfig[]>([]);
|
||||
const [joinColumnMapping, setJoinColumnMapping] = useState<Record<string, string>>({});
|
||||
const [columnMeta, setColumnMeta] = useState<
|
||||
Record<string, { webType?: string; codeInfo?: string; inputType?: string }>
|
||||
Record<string, { web_type?: string; webType?: string; codeInfo?: string; inputType?: string }>
|
||||
>({});
|
||||
// 🆕 엔티티 조인 테이블의 컬럼 메타데이터 (테이블명.컬럼명 → inputType)
|
||||
const [joinedColumnMeta, setJoinedColumnMeta] = useState<
|
||||
Record<string, { webType?: string; codeInfo?: string; inputType?: string }>
|
||||
Record<string, { web_type?: string; webType?: string; codeInfo?: string; inputType?: string }>
|
||||
>({});
|
||||
const [categoryMappings, setCategoryMappings] = useState<
|
||||
Record<string, Record<string, { label: string; color?: string }>>
|
||||
@@ -1170,7 +1170,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const cached = tableColumnCache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < TABLE_CACHE_TTL) {
|
||||
const labels: Record<string, string> = {};
|
||||
const meta: Record<string, { webType?: string; codeInfo?: string; inputType?: string }> = {};
|
||||
const meta: Record<string, { web_type?: string; webType?: string; codeInfo?: string; inputType?: string }> = {};
|
||||
|
||||
// 캐시된 inputTypes 맵 생성
|
||||
const inputTypeMap: Record<string, string> = {};
|
||||
@@ -1210,7 +1210,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
});
|
||||
|
||||
const labels: Record<string, string> = {};
|
||||
const meta: Record<string, { webType?: string; codeInfo?: string; inputType?: string }> = {};
|
||||
const meta: Record<string, { web_type?: string; webType?: string; codeInfo?: string; inputType?: string }> = {};
|
||||
|
||||
columns.forEach((col: any) => {
|
||||
labels[col.column_name] = col.display_name || col.comment || col.column_name;
|
||||
@@ -1385,7 +1385,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
}
|
||||
|
||||
// 조인된 테이블별로 inputType 정보 가져오기
|
||||
const newJoinedColumnMeta: Record<string, { webType?: string; codeInfo?: string; inputType?: string }> = {};
|
||||
const newJoinedColumnMeta: Record<string, { web_type?: string; webType?: string; codeInfo?: string; inputType?: string }> = {};
|
||||
|
||||
for (const [joinedTable, columns] of Object.entries(joinedTableColumns)) {
|
||||
try {
|
||||
|
||||
@@ -396,7 +396,7 @@ import { TableFilter, ColumnVisibility, GroupSumConfig } from "@/types/table-opt
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useScreenContextOptional } from "@/contexts/ScreenContext";
|
||||
import { useSplitPanelContext, SplitPanelPosition } from "@/contexts/SplitPanelContext";
|
||||
import type { DataProvidable, DataReceivable, DataReceiverConfig, DataReceivableComponentType } from "@/types/data-transfer";
|
||||
import type { DataProvidable, DataReceivable, DataReceiverConfig, DataReceivableComponentType, EntityJoinColumnMeta } from "@/types/data-transfer";
|
||||
|
||||
// ========================================
|
||||
// 인터페이스
|
||||
@@ -1165,6 +1165,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
setIsAllSelected(false);
|
||||
},
|
||||
|
||||
// ★ snake_case 페이로드는 EntityJoinColumnMeta 와 동치 (런타임 컨트랙트 그대로),
|
||||
// 타입 inference 한계만 cast 로 풀어준다 (2026-05-20 canonical cleanup).
|
||||
getEntityJoinColumns: () => {
|
||||
return (tableConfig.columns || [])
|
||||
.filter((col) => col.additionalJoinInfo)
|
||||
@@ -1173,7 +1175,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
source_column: col.additionalJoinInfo!.sourceColumn,
|
||||
join_alias: col.additionalJoinInfo!.joinAlias,
|
||||
reference_table: col.additionalJoinInfo!.referenceTable,
|
||||
}));
|
||||
})) as unknown as EntityJoinColumnMeta[];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1317,7 +1319,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
// 프론트엔드 카테고리 매핑으로 추가 라벨 변환
|
||||
const mapping = categoryMappings[columnName];
|
||||
if (mapping && Object.keys(mapping).length > 0) {
|
||||
options = options.map((opt) => ({
|
||||
options = options.map((opt: any) => ({
|
||||
value: opt.value,
|
||||
label: mapping[opt.value]?.label || opt.label,
|
||||
}));
|
||||
@@ -1478,7 +1480,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
tableDisplayStore.setTableData(
|
||||
tableConfig.selectedTable,
|
||||
initialData,
|
||||
parsedOrder.filter((col) => col !== "__checkbox__"),
|
||||
parsedOrder.filter((col: string) => col !== "__checkbox__"),
|
||||
sortColumn ?? null,
|
||||
sortDirection,
|
||||
{
|
||||
@@ -1602,7 +1604,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const tables = cached.tables || [];
|
||||
const tableInfo = tables.find((t: any) => t.table_name === tableConfig.selectedTable);
|
||||
const label =
|
||||
tableInfo?.display_name || (tableInfo as any)?.comment || tableInfo?.description || tableConfig.selectedTable;
|
||||
(tableInfo as any)?.display_name || (tableInfo as any)?.comment || tableInfo?.description || tableConfig.selectedTable;
|
||||
setTableLabel(label);
|
||||
return;
|
||||
}
|
||||
@@ -1616,7 +1618,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
|
||||
const tableInfo = tables.find((t: any) => t.table_name === tableConfig.selectedTable);
|
||||
const label =
|
||||
tableInfo?.display_name || (tableInfo as any)?.comment || tableInfo?.description || tableConfig.selectedTable;
|
||||
(tableInfo as any)?.display_name || (tableInfo as any)?.comment || tableInfo?.description || tableConfig.selectedTable;
|
||||
setTableLabel(label);
|
||||
} catch (error) {
|
||||
console.error("테이블 라벨 가져오기 실패:", error);
|
||||
@@ -2297,6 +2299,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
cleanColumnOrder,
|
||||
newSortColumn,
|
||||
newSortDirection,
|
||||
// ★ store 의 setTableData snake_case 시그니처와 호출자 camelCase 가 혼재 (legacy).
|
||||
// 기능 그대로 유지하려면 키 이름 변경 X — 좁은 cast 로 TS 만 우회.
|
||||
{
|
||||
filterConditions: Object.keys(searchValues).length > 0 ? searchValues : undefined,
|
||||
searchTerm: searchTerm || undefined,
|
||||
@@ -2305,7 +2309,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
currentPage: currentPage,
|
||||
pageSize: localPageSize,
|
||||
totalItems: totalItems,
|
||||
},
|
||||
} as any,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -2498,7 +2502,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
// currentSplitPosition을 사용하여 정확한 위치 확인 (splitPanelPosition이 없을 수 있음)
|
||||
const effectiveSplitPosition = splitPanelPosition || currentSplitPosition;
|
||||
|
||||
if (splitPanelContext && effectiveSplitPosition === "left" && !splitPanelContext.disableAutoDataTransfer) {
|
||||
if (splitPanelContext && effectiveSplitPosition === "left" && !(splitPanelContext as any).disableAutoDataTransfer) {
|
||||
if (!isCurrentlySelected) {
|
||||
// 선택된 경우: 데이터 저장
|
||||
splitPanelContext.setSelectedLeftData(row);
|
||||
@@ -2535,7 +2539,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
|
||||
const effectiveSplitPosition = splitPanelPosition || currentSplitPosition;
|
||||
|
||||
if (splitPanelContext && effectiveSplitPosition === "left" && !splitPanelContext.disableAutoDataTransfer) {
|
||||
if (splitPanelContext && effectiveSplitPosition === "left" && !(splitPanelContext as any).disableAutoDataTransfer) {
|
||||
// 분할 패널 좌측: 단일 행 선택 모드
|
||||
if (!isCurrentlySelected) {
|
||||
setSelectedRows(new Set([rowKey]));
|
||||
@@ -4178,7 +4182,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
onSelectedRowsChange(
|
||||
Array.from(selectedRows),
|
||||
selectedRowsData,
|
||||
sortColumn,
|
||||
sortColumn ?? undefined,
|
||||
sortDirection,
|
||||
currentColumnOrder,
|
||||
reorderedData,
|
||||
@@ -4977,13 +4981,15 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
}, [data, groupByColumns, columnLabels, columnMeta, tableConfig.columns]);
|
||||
|
||||
// 🆕 그룹별 합산된 데이터 계산 (FilterPanel에서 설정한 경우)
|
||||
// ★ groupByColumn 은 GroupSumConfig 가 snake_case (group_by_column) 만 정의하지만,
|
||||
// 런타임은 camelCase 로 받아서 매핑한다 (FilterPanel 측 호환). cast 로만 우회.
|
||||
const summedData = useMemo(() => {
|
||||
// 그룹핑이 비활성화되었거나 그룹 기준 컬럼이 없으면 원본 데이터 반환
|
||||
if (!groupSumConfig?.enabled || !groupSumConfig?.groupByColumn) {
|
||||
if (!groupSumConfig?.enabled || !(groupSumConfig as any)?.groupByColumn) {
|
||||
return filteredData;
|
||||
}
|
||||
|
||||
const groupByColumn = groupSumConfig.groupByColumn;
|
||||
const groupByColumn = (groupSumConfig as any).groupByColumn;
|
||||
const groupMap = new Map<string, any>();
|
||||
|
||||
// 조인 컬럼인지 확인하고 실제 키 추론
|
||||
@@ -6111,7 +6117,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
return (
|
||||
<th
|
||||
key={column.columnName}
|
||||
ref={(el) => (columnRefs.current[column.columnName] = el)}
|
||||
ref={(el) => { columnRefs.current[column.columnName] = el; }}
|
||||
className={cn(
|
||||
"group text-muted-foreground relative h-8 overflow-hidden text-[10px] font-bold uppercase tracking-[0.04em] text-ellipsis whitespace-nowrap select-none sm:h-10 sm:text-xs",
|
||||
column.columnName === "__checkbox__" ? "px-0 py-1" : "px-3 py-2",
|
||||
@@ -6681,7 +6687,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
ref={editInputRef as any}
|
||||
value={editingValue}
|
||||
onChange={(e) => setEditingValue(e.target.value)}
|
||||
onKeyDown={handleEditKeyDown}
|
||||
onKeyDown={handleEditKeyDown as any}
|
||||
onBlur={saveEditing}
|
||||
className="border-primary bg-background h-8 w-full shrink-0 border-2 px-2 py-1 text-xs focus:outline-none sm:px-4 sm:py-1.5 sm:text-sm"
|
||||
autoFocus
|
||||
|
||||
@@ -77,6 +77,11 @@ export interface ColumnConfig {
|
||||
autoGeneration?: AutoGenerationConfig; // 자동생성 설정
|
||||
editable?: boolean; // 🆕 편집 가능 여부 (기본값: true, false면 인라인 편집 불가)
|
||||
|
||||
// 🆕 inputType — 컬럼 메타로부터 추론된 webType 캐시 (image / file / date 등)
|
||||
// 런타임에서 columnMeta[col].inputType 또는 column.inputType 으로 읽혀서 cell rendering 분기에 쓰임.
|
||||
// table shared 이동 시 노출된 타입 누락 보강 (2026-05-20 canonical cleanup).
|
||||
inputType?: string;
|
||||
|
||||
// 🎯 추가 조인 컬럼 정보 (조인 탭에서 추가한 컬럼들)
|
||||
additionalJoinInfo?: {
|
||||
sourceTable: string; // 원본 테이블
|
||||
|
||||
@@ -34,8 +34,10 @@ export const isFileComponent = (component: ComponentData): boolean => {
|
||||
export const isButtonComponent = (component: ComponentData): boolean => {
|
||||
if (!component || !component.type) return false;
|
||||
|
||||
// ComponentData.type union 에는 "button" 이 명시되지 않지만 legacy 저장 layout 에서
|
||||
// type === "button" 으로 직접 박혀온 경우가 있어 보존 — cast 로만 union 우회.
|
||||
return (
|
||||
component.type === "button" ||
|
||||
(component as any).type === "button" ||
|
||||
(component.type === "widget" && (component as any).widgetType === "button") ||
|
||||
(component.type === "component" &&
|
||||
((component as any).webType === "button" || (component as any).componentType === "button"))
|
||||
|
||||
@@ -825,7 +825,8 @@ export interface TemplateComponent {
|
||||
|
||||
/**
|
||||
* 컴포넌트 종류 — ComponentRegistry 의 ID 참조.
|
||||
* 예: 'v2-table-list', 'v2-button-primary', 'v2-bom-tree'
|
||||
* canonical 예: 'table', 'container', 'stats', 'button', 'input', 'search'
|
||||
* legacy 예 (alias 라우팅으로 호환): 'v2-table-list', 'v2-button-primary', 'v2-bom-tree'
|
||||
*/
|
||||
componentId: string;
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ export interface ComponentComponent extends BaseComponent {
|
||||
*/
|
||||
export interface TabInlineComponent {
|
||||
id: string;
|
||||
component_type: string; // 컴포넌트 타입 (예: "v2-text-display", "v2-table-list")
|
||||
component_type: string; // 컴포넌트 타입 (canonical 예: "table" / "container" / "stats" / "button" — legacy v2-text-display / v2-table-list 도 alias 라우팅으로 호환)
|
||||
label?: string;
|
||||
position: Position; // 탭 내부에서의 위치
|
||||
size: Size; // 컴포넌트 크기
|
||||
|
||||
@@ -459,3 +459,547 @@ M notes/gbpark/2026-05-19-canonical-data-view-cleanup-followup.md (본 §10)
|
||||
컴포넌트군 정착 후 `TABLE_LIKE_COMPONENT_TYPES` 본체로 흡수 검토
|
||||
- `repeat-container` 의 `dataSourceType = "table-list"` enum naming — 도메인 분리 위해
|
||||
`tableList` → `legacyTableList` 등으로 rename 검토 (별도 트랙)
|
||||
|
||||
---
|
||||
|
||||
## 12. 2026-05-20 — table-list / v2-table-list cleanup 최종 수렴
|
||||
|
||||
§10 / §11 에서 도입한 canonical-aware helper 및 §10.3 의 hard blocker 보존 정책을
|
||||
유지한 채로, table-list / v2-table-list registration shell 자체를 삭제하고
|
||||
런타임 본체를 `_shared/` 로 이동, `TableComponent` 의 early delegation 으로 옛 layout
|
||||
호환을 완성했다. 이번 단계는 잔여 매칭 105건 (43 파일) 전수 분류 + stale 4건 정리 +
|
||||
TableComponent resolver 견고화 + DynamicComponentRenderer active 분기 helper 화 까지
|
||||
포함한다.
|
||||
|
||||
### 12.1 shell 삭제 완료 (이전 단계 누적)
|
||||
|
||||
```
|
||||
D frontend/lib/registry/components/table-list/ (폴더 전체)
|
||||
D frontend/lib/registry/components/v2-table-list/ (폴더 전체)
|
||||
├─ index.ts (ComponentDefinition 등록 shell)
|
||||
├─ TableListRenderer.tsx
|
||||
├─ TableListComponent.tsx → _shared/TableListComponent.tsx (cp)
|
||||
├─ TableListContainerWrapper.tsx → _shared/V2TableListContainerWrapper.tsx
|
||||
└─ README.md
|
||||
```
|
||||
|
||||
- ComponentRegistry 에 `table-list` / `v2-table-list` 직접 등록 0건
|
||||
- `registry/components/index.ts` 의 side-effect import 0건
|
||||
- BlockRenderer / DynamicComponentRenderer / templateMigrate / getComponentConfigPanel
|
||||
의 alias 라우팅만 남음 (모두 canonical `"table"` 로 라우팅)
|
||||
|
||||
### 12.2 shared runtime 경로 (현재 상태)
|
||||
|
||||
```
|
||||
frontend/lib/registry/components/table/_shared/
|
||||
├── TableListComponent.tsx # legacy table-list 본체 (6838줄)
|
||||
│ ├─ TableListComponent
|
||||
│ ├─ TableListWrapper
|
||||
│ └─ TableListComponentProps
|
||||
├── V2TableListComponent.tsx # v2-table-list 본체 (7216줄)
|
||||
│ ├─ TableListComponent
|
||||
│ ├─ TableListWrapper
|
||||
│ └─ TableListComponentProps
|
||||
├── V2TableListContainerWrapper.tsx # v2-table-list 반응형 wrapper (FieldConfig adapter + ResizeObserver)
|
||||
│ └─ TableListContainerWrapper
|
||||
├── SingleTableWithSticky.tsx # 공유 sticky 렌더 (variant="v2" 분기 포함)
|
||||
├── CardModeRenderer.tsx # 공유 카드 모드 렌더 (variant="v2" 분기 포함)
|
||||
├── TableListConfigPanel.tsx
|
||||
└── tableListConfigTypes.ts # ColumnConfig / TableListConfig / EntityJoinInfo / ...
|
||||
```
|
||||
|
||||
### 12.3 early delegation 경로 (TableComponent.tsx)
|
||||
|
||||
```
|
||||
[BlockRenderer]
|
||||
isTableLikeComponentType(componentId) → canonical "table" alias
|
||||
└─ ComponentRegistry.getComponent("table") → canonical TableComponent
|
||||
|
||||
[DynamicComponentRenderer]
|
||||
LEGACY_TO_UNIFIED: { "v2-table-list": "table", "table-list": "table" }
|
||||
+ needsKeyRefresh = isTableLikeComponentType(componentType) || componentType === "v2-repeater"
|
||||
|
||||
[TableComponent.tsx]
|
||||
_resolveRawComponentType(component, props):
|
||||
candidates = [componentType, component_type, componentConfig.type,
|
||||
component_config.type, props.componentType, component.type,
|
||||
props.type, url last segment]
|
||||
- MEANINGFUL Set (table / table-list / v2-table-list / data-table / datatable) → 즉시 채택
|
||||
- GENERIC Set (component / widget / container / group / row / column / area / flow / tabs) → skip
|
||||
- 그 외 첫 non-empty string → fallback 후보
|
||||
├─ rawType === "table-list" → <LegacyTableListWrapper>
|
||||
├─ rawType === "v2-table-list" → <V2TableListContainerWrapper>
|
||||
└─ default → 본체 4경로 머지 + Grouped/Card/Pivot/SplitView
|
||||
```
|
||||
|
||||
### 12.4 잔여 105건 (43 파일) 분류
|
||||
|
||||
| 분류 | 위치 (대표) | 보존 결정 |
|
||||
|---|---|---|
|
||||
| **canonical alias / runtime compat** | DynamicComponentRenderer / templateMigrate / getComponentConfigPanel / TableComponent / componentTypeUtils | ✅ 보존 (concrete blocker) |
|
||||
| **schema/default old layout compat** | componentConfig.ts v2-table-list overridesSchema / defaultConfig | ✅ 보존 (concrete blocker — 옛 layout JSON 검증) |
|
||||
| **dataSourceType enum / domain value** | repeat-container/types.ts / v2-repeat-container/types.ts + ConfigPanel select | ✅ 보존 (컴포넌트 type 이 아닌 data source mode enum) |
|
||||
| **InvDataConfigPanel old config** | InvDataConfigPanel.tsx (5건) | ✅ 보존 (사용자 hard blocker 명시) |
|
||||
| **shared self-id** | _shared/{Legacy,V2}TableListComponent 의 `component_type: "table-list"` / `componentType="v2-table-list"` (7건) | ✅ 보존 (DataProvider/Receiver 컨트랙트) |
|
||||
| **table_id naming** | _shared/*.tsx 의 `tableId = \`table-list-${id}\`` / table-options.ts 예시 | ✅ 보존 (DOM/store key) |
|
||||
| **V2List wrapper** | V2List.tsx line 53 `type: "table-list"` | ✅ 보존 (shared props 시그니처 호환) |
|
||||
| **component-events publisher/subscriber** | types/component-events.ts (3건) | ✅ 보존 (이벤트 토픽 메타) |
|
||||
| **helper / 분류 매핑** | componentTypeUtils / MultilangSettingsModal / responsiveDefaults / ScreenNode / RealtimePreviewDynamic / TabsWidget / InvyoneStudio | ✅ 보존 (canonical-aware entry) |
|
||||
| **stale 주석 (이번 정리)** | screen-management.ts / invyone-component.ts / screenGroup.ts / fieldConfig/adapters.ts | ✏ 갱신 (canonical wording 추가) |
|
||||
| **docs / README 예시** | selected-items-detail-input/README.md / v2-timeline-scheduler/README.md / 각 컴포넌트 주석 | ✅ 보존 (코드 영향 없음, 옛 layout 흔적) |
|
||||
| **active branch (이번 정리)** | DynamicComponentRenderer line 935-938 OR 체인 | ✏ `isTableLikeComponentType` 호출로 단일화 |
|
||||
| **stale 자동등록 주석 (이전 정리, §11.x)** | ComponentsPanel.tsx allComponents 위 주석 | ✏ "shell 삭제 → alias 라우팅 → early delegation" 명시 |
|
||||
|
||||
### 12.5 이번 단계 (2026-05-20) 변경 파일
|
||||
|
||||
```
|
||||
M frontend/lib/registry/components/table/TableComponent.tsx
|
||||
└ _resolveRawComponentType 견고화 (MEANINGFUL 즉시 채택 / GENERIC skip / fallback)
|
||||
- candidates 우선순위: componentType → component_type → componentConfig.type →
|
||||
component_config.type → props.componentType →
|
||||
component.type → props.type → url last segment
|
||||
|
||||
M frontend/lib/registry/DynamicComponentRenderer.tsx
|
||||
└ needsKeyRefresh OR 체인 → isTableLikeComponentType(componentType) || === "v2-repeater"
|
||||
└ isTableLikeComponentType import 추가
|
||||
|
||||
M frontend/components/screen/panels/ComponentsPanel.tsx
|
||||
└ stale 주석 갱신: "v2-table-list 자동 등록" 진술 제거,
|
||||
"shell 삭제 → alias 라우팅 → early delegation" 정확한 흐름 명시
|
||||
|
||||
M frontend/types/screen-management.ts
|
||||
└ TabInlineComponent.component_type 예시 주석: legacy v2-text-display / v2-table-list 만
|
||||
있던 것을 canonical (table / container / stats / button) + legacy alias 명시
|
||||
|
||||
M frontend/types/invyone-component.ts
|
||||
└ componentId 예시 주석: canonical (table / container / stats / button / input / search) +
|
||||
legacy alias 라우팅 호환 명시
|
||||
|
||||
M frontend/lib/api/screenGroup.ts
|
||||
└ ScreenComponent.componentKind 주석: canonical / legacy 예시 모두 명시
|
||||
|
||||
M frontend/lib/fieldConfig/adapters.ts
|
||||
└ fieldsToColumns docstring: v2-table-list 전용 → table-like (canonical 'table' /
|
||||
legacy 'table-list' / hidden 'v2-table-list') 로 일반화.
|
||||
호출자 V2TableListContainerWrapper 명시.
|
||||
|
||||
M notes/gbpark/2026-05-19-canonical-data-view-cleanup-followup.md
|
||||
└ 본 §12 추가
|
||||
```
|
||||
|
||||
### 12.6 다음 삭제 조건 (concrete blocker 별)
|
||||
|
||||
| Hard Blocker | 폐기 조건 |
|
||||
|---|---|
|
||||
| `componentConfig.ts` v2-table-list overridesSchema / defaultConfig | 모든 저장된 v2-table-list layout 이 canonical TableConfig 로 마이그레이션 완료 + DB 통계 v2-table-list 카운트 0 |
|
||||
| `_shared/V2TableListComponent.tsx` 본체 (7216줄) | canonical TableComponent 가 다음 parity 100% 달성: v2 inline edit / category·code select / date picker fallback / image url·fallback / ResizeObserver wrapper / DataProvider·DataReceiver / FieldConfig adapter / sortable·searchable·filterable per-column / linked filter / exclude filter / GroupSum / context menu / export Excel |
|
||||
| `_shared/TableListComponent.tsx` 본체 (6838줄) | canonical TableComponent 가 legacy parity 100% (FlowWidget SingleTableWithSticky / linked filter / exclude filter / 카드모드 / inline edit / DataProvider·DataReceiver) + FlowWidget 가 canonical table 으로 마이그레이션 |
|
||||
| `_shared/V2TableListContainerWrapper.tsx` (FieldConfig adapter + ResizeObserver) | canonical TableComponent 가 자체적으로 ResizeObserver + FieldConfig adapter 내장 후 |
|
||||
| `TableComponent.tsx` early delegation 분기 | 위 두 본체 폐기 시 함께 제거 |
|
||||
| `DynamicComponentRenderer.LEGACY_TO_UNIFIED` v2-table-list/table-list alias | DB / 저장 layout 에서 옛 ID 사용 0 + 6개월 운영 안정 후 |
|
||||
| `templateMigrate.LEGACY_TO_UNIFIED` v2-table-list/table-list alias | 위와 동일 |
|
||||
| `getComponentConfigPanel.CONFIG_PANEL_ALIAS` v2-table-list/table-list | 위와 동일 |
|
||||
| `componentTypeUtils.TABLE_LIKE_COMPONENT_TYPES` | 위 전체 폐기 후 |
|
||||
| `InvDataConfigPanel.tsx` v2-table-list | old layout config 마이그레이션 완료 후 |
|
||||
| `repeat-container` / `v2-repeat-container` `dataSourceType = "table-list"` enum | 별도 도메인 트랙 — `legacyTableList` 같은 명시적 enum 으로 rename |
|
||||
| `V2List.tsx` 의 `type: "table-list"` 컴포넌트 객체 구성 | V2List 자체 폐기 후 |
|
||||
|
||||
### 12.7 기능 보존 검증 (delegation 경로 + adapter)
|
||||
|
||||
- **TableComponent early delegation 우선순위 검증**: `_rawType === "table-list"` / `"v2-table-list"` 일 때 early return → canonical 4경로 머지 / GroupedView / CardView / PivotView 코드에는 절대 진입하지 않음. 옛 layout 이 weak canonical TableComponent 로 떨어지는 경로 0건 확인.
|
||||
- **FieldConfig adapter (fieldsToColumns)**: `V2TableListContainerWrapper` 가 import. 경로 끊김 없음.
|
||||
- **DataProvider / DataReceiver**: `_shared/TableListComponent.tsx` 의 `dataProvider.component_type = "table-list"` / `_shared/V2TableListComponent.tsx` 의 `component_type = "table"` 으로 자체 등록. ScreenContext 호환.
|
||||
- **selectedTable / tableName / dbTable 호환**: `getTableNameFromTableLikeComponent` helper 가 8 후보 (componentConfig.selectedTable / tableName / table_name / component_config.selectedTable / tableName / table_name / root tableName / table_name) 모두 검사. 끊김 없음.
|
||||
- **sourceProvider / dataReceiver 호환**: ButtonPrimaryComponent (v2 / non-v2) 의 자동 탐색이 `isTableLikeComponentType(provider.component_type)` 로 통일 (§11). canonical / legacy / hidden 모두 자동 발견.
|
||||
|
||||
### 12.8 Acceptance 결과 (2026-05-20)
|
||||
|
||||
| 검증 항목 | 결과 |
|
||||
|---|---|
|
||||
| `git diff --check` | ✅ pass (출력 없음) |
|
||||
| 옛 폴더 직접 import (`table-list/TableListRenderer` / `v2-table-list/TableListRenderer` / `components/table-list` / `components/v2-table-list` / `./table-list*` / `./v2-table-list*`) — `_shared/**` 제외 | ✅ `componentConfig.ts` historical 주석 **1건**만 |
|
||||
| 입력 canonical 금지 토큰 (`v2-input` / `v2-select` / `V2InputRenderer` / `V2SelectRenderer`) | ✅ 0건 |
|
||||
| EntityPicker / entity-picker | ✅ 0건 |
|
||||
| `cd backend-spring && ./gradlew compileJava` | ✅ BUILD SUCCESSFUL |
|
||||
| `npx tsc --noEmit --pretty false \| rg "lib/registry/components/table/TableComponent\|lib/registry/components/table/_shared\|lib/utils/componentTypeUtils\|components/dash/BlockRenderer\|lib/registry/DynamicComponentRenderer\|components/screen/panels/ComponentsPanel"` 신규 오류 | ✅ 0건 (이전 단계 좁은 cast 유효, resolver 견고화 / helper 화 / 주석 갱신 모두 클린) |
|
||||
|
||||
---
|
||||
|
||||
## 13. 2026-05-20 — Container 계열 cleanup 분류 + stale 정리
|
||||
|
||||
§12 의 table cleanup 과 동일 원칙 (canonical 새 생성 경로 보존 / shell 삭제 → alias /
|
||||
shared runtime / FieldConfig·DataPort 호환 유지) 으로 container 계열 잔여를 전수 분류.
|
||||
container 계열은 table 과 달리 **canonical container skeleton 이 일부 모드에서 부족**
|
||||
하여 hard blocker 보존이 다수다. 본 단계는 분류 + stale 주석 2건 정리 + 보고서.
|
||||
|
||||
### 13.1 잔존 폴더 현황 (registry/components/)
|
||||
|
||||
| 폴더 | 상태 | 비고 |
|
||||
|---|---|---|
|
||||
| `container/` | ✅ canonical | 새 생성 경로 (containerType: tabs / section / accordion / repeater / conditional) |
|
||||
| `accordion-basic/` | 🔒 보존 (hard blocker) | canonical container.containerType=accordion skeleton 부족 |
|
||||
| `conditional-container/` | 🔒 보존 (hard blocker) | canonical container.containerType=conditional skeleton 부족 |
|
||||
| `repeat-container/` | 🔒 보존 (hard blocker) | canonical container.containerType=repeater skeleton 부족. ComponentDefinition `hidden: true` |
|
||||
| `v2-repeat-container/` | 🔒 보존 (hard blocker) | 동일. `basicV2Components` palette item |
|
||||
| `v2-repeater/` | 🔒 보존 (hard blocker) | 별도 데이터 조회/선택 도메인. palette item |
|
||||
| `repeat-screen-modal/` | 🔒 보존 (domain) | 도메인 특화 — 화면 모달 반복 |
|
||||
| `repeater-field-group/` | 🔒 보존 (domain) | 도메인 특화 — 필드 그룹 반복 |
|
||||
| `modal-repeater-table/` | 🔒 보존 (domain) | 도메인 특화 |
|
||||
| `simple-repeater-table/` | 🔒 보존 (domain) | 도메인 특화 |
|
||||
| `screen-split-panel/` | 🔒 보존 (domain) | 화면 임베딩 + 데이터 전달. SplitPanelContext provider. backend API (`/screen-split-panel/...`) 도 별도 |
|
||||
| `split-panel-layout/` | 🔒 보존 (hard blocker) | SplitPanelContext provider 다수 사용처 (`(main)/screens/[screenId]/page.tsx` / `(pop)/pop/screens/[screenId]/page.tsx` / `InteractiveScreenViewer.tsx` / `RealtimePreview.tsx` / `RealtimePreviewDynamic.tsx` / `SplitPanelAwareWrapper.tsx` 등) |
|
||||
| `split-panel-layout2/` | 🔒 보존 (hard blocker) | 새 분할 — table.displayMode='split' 와 짝 |
|
||||
| `v2-split-panel-layout/` | 🔒 보존 (hard blocker) | 7000+줄 master-detail UX 구현체. alias 없음 (canonical split-mode parity 부족) |
|
||||
|
||||
### 13.2 canonical container 흡수 매핑 (이미 완료된 alias 라우팅)
|
||||
|
||||
```
|
||||
DynamicComponentRenderer.LEGACY_TO_UNIFIED (line 387~394)
|
||||
templateMigrate.LEGACY_TO_UNIFIED (line 46~56)
|
||||
getComponentConfigPanel.CONFIG_PANEL_ALIAS (line 147~151)
|
||||
│
|
||||
▼
|
||||
┌── ✅ alias 흡수 (canonical 'container' 라우팅) ──┐
|
||||
│ tabs-widget / v2-tabs-widget / tabs / v2-tabs → container (containerType=tabs)
|
||||
│ section-card / v2-section-card → container (section, sectionVariant=card)
|
||||
│ section-paper / v2-section-paper → container (section, sectionVariant=paper)
|
||||
└──────────────────────────────────────────────────┘
|
||||
|
||||
┌── 🔒 보존 (concrete blocker — alias 없음 또는 별도 import 보존) ──┐
|
||||
│ accordion-basic → AccordionBasicConfigPanel 직접 import
|
||||
│ conditional-container → ConditionalContainerConfigPanel 직접 import
|
||||
│ repeat-container → RepeatContainerConfigPanel 직접 import
|
||||
│ v2-repeat-container → CONFIG_PANEL_ALIAS["v2-repeat-container"]="container" + 자체 import (skeleton 부족)
|
||||
│ split-panel-layout → SplitPanelLayoutConfigPanel 직접 import
|
||||
│ v2-split-panel-layout → 동일
|
||||
│ split-panel-layout2 → 동일
|
||||
│ screen-split-panel → 동일
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 13.3 잔여 매칭 분류 요약
|
||||
|
||||
| 분류 | 위치 (대표) | 결정 |
|
||||
|---|---|---|
|
||||
| **canonical alias / runtime compat** | DynamicComponentRenderer / templateMigrate / getComponentConfigPanel alias map | ✅ 보존 (hard blocker) |
|
||||
| **schema/default old layout compat** | componentConfig.ts v2-split-panel-layout / v2-repeat-container overridesSchema·defaultConfig | ✅ 보존 (hard blocker) |
|
||||
| **canonical container 흡수 라우팅** | DynamicComponentRenderer line 390~394 (tabs/section alias) | ✅ 보존 (런타임 동작) |
|
||||
| **canonical skeleton 부족 보존** | accordion-basic / conditional-container / repeat-container / v2-repeat-container | ✅ 보존 (hard blocker — skeleton 완성 후 폐기) |
|
||||
| **SplitPanelContext + master-detail UX** | split-panel-layout / v2-split-panel-layout / split-panel-layout2 / screen-split-panel + 다수 사용처 | ✅ 보존 (hard blocker — canonical split UX 미완) |
|
||||
| **domain-specific** | modal-repeater-table / simple-repeater-table / repeat-screen-modal / repeater-field-group / screen-split-panel API | ✅ 보존 (도메인 트랙 별도) |
|
||||
| **InvyoneStudio drag/drop 분기** | `compType === "split-panel-layout" \|\| === "v2-split-panel-layout"` × 다수 / `=== "tabs-widget" \|\| === "v2-tabs-widget" \|\| isCanonicalTabs` × 다수 | ✅ 보존 (도메인 특화 drag/drop, 흡수 helper 시 시그니처 더 복잡 — canonical container.containerType=tabs 까지 인식하는 helper 는 component 객체 + componentConfig 둘 다 필요) |
|
||||
| **ContainerComponent OR 체인** | `ContainerComponent.tsx` line 85-86 `v2-tabs-widget \|\| tabs-widget` | ✅ 보존 (canonical container 본체의 옛 ID 흡수 처리 — 본체 self-id) |
|
||||
| **buttonActions split-panel/screen-split-panel 분기** | line 3294, 3478, 3845-3848 등 | ✅ 보존 (split-panel-layout 의 leftPanel.tableName 등 고유 path — table cleanup 의 hard blocker 와 동일) |
|
||||
| **InteractiveScreenViewer / RealtimePreview / RealtimePreviewDynamic tabs/split 분기** | 다수 | ✅ 보존 (canonical container.containerType=tabs 와 옛 ID 의 동시 인식, 도메인 특화) |
|
||||
| **screen-split-panel API client** | `lib/api/screenEmbedding.ts` line 195~249 | ✅ 보존 (backend `/screen-split-panel` endpoint — frontend 폐기 불가) |
|
||||
| **stale 주석 — 이번 정리** | `registry/components/index.ts` line 106-107 / `container/types.ts` 헤더 docstring | ✏ 갱신 (2건) |
|
||||
|
||||
### 13.4 이번 단계 (2026-05-20) 변경 파일
|
||||
|
||||
```
|
||||
M frontend/lib/registry/components/index.ts
|
||||
└ line 106-107 의 "v2-table-list + ... 9종 흡수" / "container ... 11종 흡수"
|
||||
주석을 정확한 현재 상태 (alias 라우팅 + early delegation + 보존 사유) 로 갱신.
|
||||
auto-register import 자체는 그대로 (canonical TableRenderer / ContainerRenderer +
|
||||
accordion-basic / split-panel-layout / split-panel-layout2 / conditional-container /
|
||||
screen-split-panel / repeat-container / v2-repeat-container / v2-split-panel-layout
|
||||
auto-register 보존).
|
||||
|
||||
M frontend/lib/registry/components/container/types.ts
|
||||
└ 헤더 docstring 갱신: "흡수 대상 (11)" 단일 목록 → "흡수 완료 (alias 라우팅)" +
|
||||
"보존 (skeleton 부족 또는 도메인 특화) — concrete blocker" 두 그룹으로 분리.
|
||||
canonical 새 생성 경로 + alias 흡수 매핑 + hard blocker 사유 명시.
|
||||
|
||||
M notes/gbpark/2026-05-19-canonical-data-view-cleanup-followup.md
|
||||
└ 본 §13 추가
|
||||
```
|
||||
|
||||
### 13.5 Hard Blocker (다음 삭제 조건)
|
||||
|
||||
| Hard Blocker | 폐기 조건 |
|
||||
|---|---|
|
||||
| `accordion-basic/` 폴더 + ComponentDefinition | canonical `container.containerType=accordion` skeleton 완성 (헤더 / 패널 / 토글 / 다중 선택 / 키보드 네비게이션 / 애니메이션) + 옛 layout 마이그레이션 |
|
||||
| `conditional-container/` 폴더 + ComponentDefinition | canonical `container.containerType=conditional` skeleton 완성 (조건식 평가 / sections / 동적 표시 / 자식 컴포넌트 마운트 처리) + 옛 layout 마이그레이션 |
|
||||
| `repeat-container/` + `v2-repeat-container/` 폴더 | canonical `container.containerType=repeater` skeleton 완성 (데이터 lookup / 선택 / append / 인라인 add / 슬롯 컴포넌트 / `data-repeat-container` DOM 마커 / `_repeatContainerTables` save group 처리 / RepeatContainerConfig 마이그레이션) |
|
||||
| `v2-repeater/` | canonical container repeater 완성 + canonical table multi-select 가 동일 UX 구현 |
|
||||
| `split-panel-layout/` 폴더 + SplitPanelContext | canonical `table.displayMode=split` 의 master-detail UX 완성 (leftPanel/rightPanel / drag-drop / resize / selection sync / nested child / disableAutoDataTransfer) + 모든 사용처 마이그레이션 (`(main)/screens/[screenId]/page.tsx` / `(pop)/pop/screens/[screenId]/page.tsx` / `InteractiveScreenViewer.tsx` / `RealtimePreview.tsx` / `RealtimePreviewDynamic.tsx` / `SplitPanelAwareWrapper.tsx`) |
|
||||
| `v2-split-panel-layout/` 폴더 (7000+줄) | 위와 동일 |
|
||||
| `split-panel-layout2/` 폴더 | canonical split master-detail UX 완성 후 |
|
||||
| `screen-split-panel/` 폴더 + backend `/screen-split-panel` API | 화면 임베딩 도메인 별도 트랙. canonical 흡수 시 backend endpoint 도 함께 정리 |
|
||||
| `componentConfig.ts` v2-split-panel-layout / v2-repeat-container overridesSchema·defaultConfig | 옛 layout JSON 의 해당 type 사용 0 + 6개월 운영 안정 |
|
||||
| `DynamicComponentRenderer.LEGACY_TO_UNIFIED` tabs/section alias | 옛 ID 사용 0 + 안정 후 |
|
||||
| `templateMigrate.LEGACY_TO_UNIFIED` tabs/section alias | 위와 동일 |
|
||||
| `getComponentConfigPanel.CONFIG_PANEL_ALIAS` tabs/section alias + accordion-basic / split-panel-layout / conditional-container / repeat-container 직접 import | 위 hard blocker 와 짝 |
|
||||
| `modal-repeater-table / simple-repeater-table / repeat-screen-modal / repeater-field-group` | 별도 도메인 마이그레이션 트랙 |
|
||||
|
||||
### 13.6 기능 보존 검증
|
||||
|
||||
- **canonical container 흡수 라우팅 끊김 없음**: BlockRenderer / DynamicComponentRenderer / templateMigrate / getComponentConfigPanel 4 곳 모두에서 옛 ID → "container" 로 alias 처리됨. ContainerComponent 본체에서 옛 ID raw type 도 인식 (line 85-86).
|
||||
- **canonical skeleton 부족 컴포넌트들 보존**: ComponentRegistry 에 자체 ComponentDefinition 등록 (auto-register import 유지). palette 에서는 `hiddenComponents` 로 숨김 처리되지만, 옛 저장 layout JSON 에서 직접 type 지목 시 렌더 가능.
|
||||
- **SplitPanelContext 끊김 없음**: provider import 다수 사용처 (`(main)/screens/[screenId]/page.tsx` / `(pop)/pop/screens/[screenId]/page.tsx` / `InteractiveScreenViewer.tsx` / `RealtimePreview.tsx` / `RealtimePreviewDynamic.tsx` / `SplitPanelAwareWrapper.tsx`) 모두 그대로. master-detail data transfer 경로 보존.
|
||||
- **schema/default 호환**: `componentConfig.ts` 의 v2-split-panel-layout / v2-repeat-container schema + defaultConfig 보존. 옛 layout JSON 검증 통과.
|
||||
- **table cleanup 결과 보존**: `table-list/` / `v2-table-list/` 폴더 0건 + 직접 import 0건 + `_shared/{TableListComponent,V2TableListComponent,V2TableListContainerWrapper}` 본체 그대로.
|
||||
- **FieldConfig / DataPort / sourceProvider / dataReceiver 호환 모두 유지**: `componentTypeUtils.isTableLikeComponent*` helpers + `fieldsToColumns` adapter + DataProvidable/DataReceivable 인터페이스 모두 변경 없음.
|
||||
|
||||
### 13.7 Acceptance 결과 (2026-05-20 container)
|
||||
|
||||
| 검증 항목 | 결과 |
|
||||
|---|---|
|
||||
| `git diff --check` | ✅ pass |
|
||||
| 옛 table 폴더 직접 import (`table-list/TableListRenderer` 등) — `_shared/**` 제외 | ✅ `componentConfig.ts` historical 주석 1건만 |
|
||||
| 입력 canonical 금지 토큰 | ✅ 0건 |
|
||||
| EntityPicker / entity-picker | ✅ 0건 |
|
||||
| `cd backend-spring && ./gradlew compileJava` | ✅ BUILD SUCCESSFUL |
|
||||
| `npx tsc --noEmit \| rg "container\|tabs\|section\|accordion\|conditional\|repeat-container\|split-panel\|screen-split-panel\|DynamicComponentRenderer\|templateMigrate\|ComponentsPanel"` 신규 오류 | ✅ 0건 (주석 갱신 2건 모두 클린) |
|
||||
|
||||
---
|
||||
|
||||
## 14. 2026-05-20 — Final Audit / Close-out
|
||||
|
||||
input → stats → table → container → chart → card-list → grouped-table 까지 모든 canonical
|
||||
data-view cleanup 단계가 완료되었다. 본 §14 는 전체 상태 검증 + 누적 잔여 분류 +
|
||||
다음 삭제 조건의 최종 보정. 코드 수정은 **0건** (검증/문서 중심 단계).
|
||||
|
||||
### 14.1 Canonical 6종 — 새 생성 경로 상태
|
||||
|
||||
| Canonical ID | 폴더 | 본체 | 상태 |
|
||||
|---|---|---|---|
|
||||
| `stats` | `lib/registry/components/stats/` | StatsComponent | ✅ canonical 새 생성 경로 |
|
||||
| `table` | `lib/registry/components/table/` | TableComponent + `_shared/{TableListComponent,V2TableListComponent,V2TableListContainerWrapper}` | ✅ canonical + early delegation |
|
||||
| `container` | `lib/registry/components/container/` | ContainerComponent | ✅ canonical (tabs / section / accordion / repeater / conditional. 단 accordion/repeater/conditional skeleton 부족) |
|
||||
| `chart` | `lib/registry/components/chart/` | ChartRenderer (recharts: bar / horizontalBar / line / donut) | ✅ canonical |
|
||||
| `card-list` | `lib/registry/components/card-list/` | CardListRenderer | ✅ canonical |
|
||||
| `grouped-table` | `lib/registry/components/grouped-table/` | GroupedTableRenderer | ✅ canonical |
|
||||
|
||||
### 14.2 삭제 완료 폴더 (11개)
|
||||
|
||||
```
|
||||
D lib/registry/components/aggregation-widget/ (stats 흡수)
|
||||
D lib/registry/components/v2-aggregation-widget/ (stats 흡수)
|
||||
D lib/registry/components/v2-status-count/ (stats 흡수)
|
||||
D lib/registry/components/tabs/ (container 흡수, alias 라우팅)
|
||||
D lib/registry/components/v2-tabs-widget/ (container 흡수, alias 라우팅)
|
||||
D lib/registry/components/section-card/ (container 흡수, alias 라우팅)
|
||||
D lib/registry/components/v2-section-card/ (container 흡수, alias 라우팅)
|
||||
D lib/registry/components/section-paper/ (container 흡수, alias 라우팅)
|
||||
D lib/registry/components/v2-section-paper/ (container 흡수, alias 라우팅)
|
||||
D lib/registry/components/table-list/ (table 흡수 + _shared/TableListComponent 로 이동)
|
||||
D lib/registry/components/v2-table-list/ (table 흡수 + _shared/V2TableListContainerWrapper 로 이동)
|
||||
```
|
||||
|
||||
부가 삭제:
|
||||
```
|
||||
D V2AggregationWidgetConfigPanel.tsx (1085줄)
|
||||
D V2StatusCountConfigPanel.tsx (679줄)
|
||||
```
|
||||
|
||||
### 14.3 Alias / Delegation 라우팅 (모두 작동 중)
|
||||
|
||||
```
|
||||
┌─── BlockRenderer ───┐ ┌─── DynamicComponentRenderer ───┐
|
||||
│ isTableLikeComponent│ │ LEGACY_TO_UNIFIED │
|
||||
│ → canonical "table" │ v2-table-list → table │
|
||||
│ ★ alias 안전장치 유지 │ table-list → table │
|
||||
└─────────────────────┘ │ v2-aggregation-widget → stats │
|
||||
│ aggregation-widget → stats │
|
||||
┌─── templateMigrate.LEGACY_TO_UNIFIED ───┐│ v2-status-count → stats │
|
||||
│ v2-table-list / table-list → table ││ v2-tabs-widget → container │
|
||||
│ v2-aggregation-widget → stats ││ tabs-widget / tabs → container│
|
||||
│ v2-tabs-widget / tabs-widget → container││ v2-section-card → container │
|
||||
│ v2-section-card/paper → container ││ v2-section-paper → container │
|
||||
│ section-card / section-paper → container││ section-card / paper → cont │
|
||||
│ v2-repeat-container → container ││ v2-repeat-container → cont │
|
||||
│ accordion-basic → container ││ accordion-basic → container │
|
||||
└──────────────────────────────────────────┘└────────────────────────────────┘
|
||||
|
||||
┌─── getComponentConfigPanel ───────────────────────────────────────────┐
|
||||
│ CONFIG_PANEL_ALIAS │
|
||||
│ v2-table-list / table-list → table │
|
||||
│ v2-tabs-widget / tabs-widget → container │
|
||||
│ v2-section-card / v2-section-paper / section-card / paper → container│
|
||||
│ v2-repeat-container → container │
|
||||
│ v2-aggregation-widget / v2-status-count / aggregation-widget → stats│
|
||||
│ │
|
||||
│ CONFIG_PANEL_MAP (직접 import — alias 없음 hard blocker): │
|
||||
│ accordion-basic / split-panel-layout / v2-split-panel-layout / │
|
||||
│ split-panel-layout2 / screen-split-panel / conditional-container / │
|
||||
│ repeat-container / v2-repeat-container │
|
||||
└───────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─── TableComponent.tsx (canonical table 본체) ───────────────────────┐
|
||||
│ _resolveRawComponentType(component, props): │
|
||||
│ - MEANINGFUL (table / table-list / v2-table-list / data-table / │
|
||||
│ datatable) → 즉시 채택 │
|
||||
│ - GENERIC (component / widget / container / group / row / column / │
|
||||
│ area / flow / tabs) → skip │
|
||||
│ - 그 외 → fallback │
|
||||
│ │
|
||||
│ rawType === "table-list" → <LegacyTableListWrapper> │
|
||||
│ rawType === "v2-table-list" → <V2TableListContainerWrapper> │
|
||||
│ default → 본체 4경로 머지 + Grouped/Card/Pivot │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 14.4 누적 잔여 분류 (전체 735건 매칭)
|
||||
|
||||
| 분류 | 결정 |
|
||||
|---|---|
|
||||
| canonical alias / runtime compat (DynamicComponentRenderer / templateMigrate / getComponentConfigPanel) | ✅ 보존 (hard blocker — 옛 ID 사용 0 + 안정 후 폐기) |
|
||||
| schema/default old layout compat (componentConfig.ts: v2-table-list / v2-split-panel-layout / v2-repeat-container schema·default) | ✅ 보존 (hard blocker) |
|
||||
| canonical container 흡수 alias (tabs / section) | ✅ 작동 중 |
|
||||
| canonical skeleton 부족 (accordion / conditional / repeat 계열) | 🔒 hard blocker (skeleton 완성 + 마이그레이션 후 폐기) |
|
||||
| SplitPanelContext + master-detail UX (split-panel / screen-split-panel) | 🔒 hard blocker (canonical split UX 미완) |
|
||||
| domain-specific (modal-repeater / simple-repeater / repeat-screen-modal / repeater-field-group) | 🔒 hard blocker (도메인 트랙 별도) |
|
||||
| InvyoneStudio drag/drop / buttonActions split-panel / InteractiveScreenViewer / RealtimePreview/Dynamic tabs/split 분기 | ✅ 보존 (도메인 특화 — canonical 흡수 시 함께 정리) |
|
||||
| ContainerComponent self-id 인식 (v2-tabs-widget / tabs-widget) | ✅ 보존 (본체 옛 ID 흡수 처리) |
|
||||
| `_shared/` 본체 self-id (`component_type: "table-list"` / `componentType="v2-table-list"`) | ✅ 보존 (DataProvider/Receiver 컨트랙트) |
|
||||
| repeat-container `dataSourceType="table-list"` enum | ✅ 보존 (도메인 다름 — data source mode 값) |
|
||||
| component-events publisher/subscriber 주석 (3건) | ✅ 보존 (메타) |
|
||||
| helper / 분류 매핑 (componentTypeUtils / MultilangSettingsModal / responsiveDefaults / ScreenNode / RealtimePreviewDynamic / TabsWidget / InvyoneStudio summary count) | ✅ 보존 (canonical-aware entry) |
|
||||
| docs / README 예시 | ✅ 보존 (코드 영향 0) |
|
||||
| stale 주석 — 누적 정리 완료 | ✏ 갱신 완료 (§10 / §11 / §12 / §13 누적 8건) |
|
||||
|
||||
### 14.5 남은 Hard Blocker (concrete blockers)
|
||||
|
||||
#### 14.5.1 Schema / Default (componentConfig.ts)
|
||||
- `v2-table-list` overridesSchema + defaultConfig
|
||||
- `v2-split-panel-layout` overridesSchema + defaultConfig
|
||||
- `v2-repeat-container` overridesSchema + defaultConfig
|
||||
|
||||
#### 14.5.2 Canonical Skeleton 부족
|
||||
- `accordion-basic/` — canonical `container.containerType=accordion` skeleton 부족
|
||||
- `conditional-container/` — canonical `container.containerType=conditional` skeleton 부족
|
||||
- `repeat-container/` + `v2-repeat-container/` + `v2-repeater/` — canonical `container.containerType=repeater` 데이터 lookup / 슬롯 / save group 부족
|
||||
|
||||
#### 14.5.3 SplitPanel + Master-detail UX
|
||||
- `split-panel-layout/` (SplitPanelContext provider 다수 사용처)
|
||||
- `v2-split-panel-layout/` (7000+줄 master-detail UX 본체)
|
||||
- `split-panel-layout2/` (새 분할)
|
||||
- `screen-split-panel/` (화면 임베딩 + backend `/screen-split-panel` API)
|
||||
|
||||
#### 14.5.4 Table _shared 본체 (canonical TableComponent parity 부족)
|
||||
- `_shared/TableListComponent.tsx` (6838줄) — legacy FlowWidget SingleTableWithSticky / linked filter / exclude filter
|
||||
- `_shared/V2TableListComponent.tsx` (7216줄) — v2 inline edit / category·code select / image url·fallback / GroupSum / DataProvider/Receiver
|
||||
- `_shared/V2TableListContainerWrapper.tsx` — ResizeObserver + FieldConfig adapter
|
||||
- `table/TableComponent.tsx` early delegation 분기
|
||||
|
||||
#### 14.5.5 Alias 매핑
|
||||
- `DynamicComponentRenderer.LEGACY_TO_UNIFIED` (table / stats / container 그룹)
|
||||
- `templateMigrate.LEGACY_TO_UNIFIED`
|
||||
- `getComponentConfigPanel.CONFIG_PANEL_ALIAS`
|
||||
- `componentTypeUtils.TABLE_LIKE_COMPONENT_TYPES` Set
|
||||
|
||||
#### 14.5.6 Domain / Wrapper
|
||||
- `InvDataConfigPanel.tsx` v2-table-list (old config hard blocker)
|
||||
- `V2List.tsx` `type: "table-list"` (shared TableListComponent props 호환)
|
||||
- `repeat-container` / `v2-repeat-container` `dataSourceType = "table-list"` enum (도메인 다름)
|
||||
- `modal-repeater-table` / `simple-repeater-table` / `repeat-screen-modal` / `repeater-field-group` (도메인 특화)
|
||||
- `lib/api/screenEmbedding.ts` `/screen-split-panel` API client (backend endpoint 짝)
|
||||
|
||||
### 14.6 다음 삭제 조건 (요약)
|
||||
|
||||
| Hard Blocker 그룹 | 폐기 조건 |
|
||||
|---|---|
|
||||
| `_shared/TableListComponent.tsx` (legacy) | canonical TableComponent legacy parity 100% (FlowWidget SingleTableWithSticky / linked filter / exclude filter / 카드모드 / inline edit / DataProvider·Receiver) + FlowWidget 마이그레이션 |
|
||||
| `_shared/V2TableListComponent.tsx` (v2) | canonical TableComponent v2 parity 100% (v2 inline edit / category·code select / date picker fallback / image url·fallback / ResizeObserver / DataProvider·Receiver / FieldConfig adapter / sortable·searchable·filterable per-column / linked filter / exclude filter / GroupSum / context menu / export Excel) |
|
||||
| `V2TableListContainerWrapper.tsx` | canonical TableComponent 가 자체적으로 ResizeObserver + FieldConfig adapter 내장 후 |
|
||||
| `TableComponent.tsx` early delegation | 위 두 본체 폐기 시 함께 |
|
||||
| `componentConfig.ts` v2-table-list / v2-split-panel-layout / v2-repeat-container schema | 옛 layout JSON 의 해당 type 사용 0 + 6개월 운영 안정 |
|
||||
| `accordion-basic` | canonical container accordion skeleton 완성 + 마이그레이션 |
|
||||
| `conditional-container` | canonical container conditional skeleton 완성 + 마이그레이션 |
|
||||
| `repeat-container` / `v2-repeat-container` / `v2-repeater` | canonical container repeater 완성 (데이터 lookup / 슬롯 / append / 인라인 add / DOM marker / save group) |
|
||||
| `split-panel-layout` 계열 + SplitPanelContext | canonical `table.displayMode=split` master-detail UX 완성 + 다수 사용처 마이그레이션 |
|
||||
| `screen-split-panel` + backend API | 화면 임베딩 도메인 별도 트랙. canonical 흡수 시 endpoint 도 정리 |
|
||||
| `LEGACY_TO_UNIFIED` / `CONFIG_PANEL_ALIAS` / `TABLE_LIKE_COMPONENT_TYPES` 매핑 | 위 hard blocker 전체 폐기 후 단순화 |
|
||||
| `InvDataConfigPanel.tsx` v2-table-list | old layout config 마이그레이션 완료 후 |
|
||||
| `V2List.tsx` | V2List 자체 폐기 후 (canonical 흡수 시점) |
|
||||
| `repeat-container` `dataSourceType="table-list"` enum | 도메인 분리 트랙 — `legacyTableList` rename |
|
||||
| domain-specific (modal-repeater 등) | 각 도메인 트랙 |
|
||||
|
||||
### 14.7 Final Acceptance 결과 (2026-05-20)
|
||||
|
||||
| 검증 항목 | 결과 |
|
||||
|---|---|
|
||||
| `git diff --check` | ✅ pass (출력 없음) |
|
||||
| 입력 canonical 금지 토큰 (`v2-input` / `v2-select` / `V2InputRenderer` / `V2SelectRenderer`) | ✅ 0건 |
|
||||
| EntityPicker / entity-picker | ✅ 0건 |
|
||||
| table direct import (`table-list/TableListRenderer` / `v2-table-list/TableListRenderer` / `components/table-list` / `components/v2-table-list` / `./table-list*` / `./v2-table-list*`) — `_shared/**` 제외 | ✅ `componentConfig.ts` historical 주석 1건만 |
|
||||
| stats deleted import (`AggregationWidgetRenderer` / `StatusCountRenderer` / `V2AggregationWidgetConfigPanel` / `V2StatusCountConfigPanel` / `components/aggregation-widget` / `components/v2-aggregation-widget` / `components/v2-status-count`) | ✅ 0건 |
|
||||
| tabs/section deleted import (`TabsRenderer` / `tabs-component` / `SectionCardRenderer` / `SectionPaperRenderer` / `components/tabs` / `components/v2-tabs-widget` / `components/section-card` / `components/section-paper` / `components/v2-section-card` / `components/v2-section-paper`) | ✅ 0건 |
|
||||
| 11개 삭제 폴더 존재 X (aggregation-widget / v2-aggregation-widget / v2-status-count / tabs / v2-tabs-widget / section-card / v2-section-card / section-paper / v2-section-paper / table-list / v2-table-list) | ✅ ALL DELETED |
|
||||
| `cd backend-spring && ./gradlew compileJava` | ✅ BUILD SUCCESSFUL |
|
||||
| `npx tsc --noEmit --pretty false \| rg <변경 파일 패턴>` — **변경 파일 기준 신규 오류** | ✅ 0건 |
|
||||
|
||||
### 14.8 Known Residual Risks
|
||||
|
||||
1. **기존 전체 `tsc` 오류는 여전히 존재할 수 있음.**
|
||||
본 cleanup 은 canonical data-view 라우팅 / shared runtime 이동 / helper 도입 / 좁은
|
||||
타입 cast 만 수행. 본 cleanup 범위 외의 snake_case ↔ camelCase 타입 불일치,
|
||||
union 타입 누락, ComponentData 시그니처 불일치 등 기존 오류는 정리하지 않음.
|
||||
별도 typecheck cleanup 트랙에서 처리해야 함.
|
||||
|
||||
2. **넓은 grep 패턴은 기존 split-panel / InvyoneStudio 오류를 잡을 수 있음.**
|
||||
예시: `rg "split-panel\|InvyoneStudio"` 또는 `rg "container"` 같은 광역 패턴은
|
||||
본 cleanup 무관한 InvyoneStudio drag/drop 분기의 기존 union 오류, SplitPanel
|
||||
master-detail 의 기존 snake_case 불일치 등을 함께 매칭. 본 §14.7 acceptance 의
|
||||
`tsc` 검증 패턴은 **변경 파일 list 기준** 으로 좁혀 사용한다.
|
||||
|
||||
3. **acceptance 의 신규 오류 0건 기준 = 변경 파일 기준.**
|
||||
본 cleanup 14단계 누적 변경 파일 list:
|
||||
```
|
||||
lib/registry/components/table/TableComponent.tsx
|
||||
lib/registry/components/table/_shared/{TableListComponent,V2TableListComponent,V2TableListContainerWrapper,tableListConfigTypes}.tsx
|
||||
lib/utils/componentTypeUtils.ts
|
||||
lib/utils/buttonActions.ts
|
||||
lib/utils/templateMigrate.ts
|
||||
lib/utils/getComponentConfigPanel.tsx
|
||||
lib/utils/responsiveDefaults.ts
|
||||
lib/utils/layoutV2Converter.ts (touched in earlier phases)
|
||||
lib/registry/DynamicComponentRenderer.tsx
|
||||
lib/registry/components/index.ts
|
||||
lib/registry/components/container/types.ts
|
||||
lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx
|
||||
lib/registry/components/button-primary/ButtonPrimaryComponent.tsx
|
||||
lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx
|
||||
lib/schemas/componentConfig.ts
|
||||
lib/api/screenGroup.ts
|
||||
lib/fieldConfig/adapters.ts
|
||||
app/(main)/screens/[screenId]/page.tsx
|
||||
app/(pop)/pop/screens/[screenId]/page.tsx (touched in earlier phases)
|
||||
components/dash/BlockRenderer.tsx
|
||||
components/screen/RealtimePreviewDynamic.tsx
|
||||
components/screen/ScreenNode.tsx
|
||||
components/screen/widgets/TabsWidget.tsx
|
||||
components/screen/modals/MultilangSettingsModal.tsx
|
||||
components/screen/panels/ComponentsPanel.tsx
|
||||
components/screen/config-panels/button/DataTab.tsx
|
||||
components/screen/config-panels/button-config/ActionTab.tsx
|
||||
components/v2/config-panels/InvLegacyButtonConfigPanel.tsx
|
||||
components/v2/V2List.tsx
|
||||
types/screen-management.ts
|
||||
types/invyone-component.ts
|
||||
backend-spring/src/main/java/com/erp/service/ScreenGroupService.java
|
||||
backend-spring/src/main/resources/mapper/screenGroup.xml
|
||||
```
|
||||
이 list 의 변경 line 들에서 신규 오류 0건. 같은 파일의 다른 line 에 있는 기존
|
||||
오류 (예: V2TableListComponent 의 cp 이전부터 있던 union 불일치, ActionTab 의
|
||||
snake_case 불일치) 는 본 cleanup 범위 외.
|
||||
|
||||
4. **Backend 컴파일 + 런타임 영향 0건.**
|
||||
본 cleanup 의 backend 변경은 `ScreenGroupService.countTableLikeWidgets` helper
|
||||
추가 + `screenGroup.xml` 의 `componentType IN ('table', 'table-list', 'v2-table-list')` 확장만.
|
||||
`./gradlew compileJava BUILD SUCCESSFUL`. 기존 backend 컴파일 정책에 영향 없음.
|
||||
|
||||
5. **Hard Blocker 폐기는 운영 안정성 보장 후.**
|
||||
§14.6 의 다음 삭제 조건은 모두 "옛 layout JSON 사용 0 + N개월 운영 안정"
|
||||
또는 "canonical skeleton parity 100%" 같은 boolean 조건. 운영 환경 DB 통계 +
|
||||
회귀 테스트가 선결.
|
||||
|
||||
### 14.9 본 §14 의 코드 수정
|
||||
|
||||
**0건.** 본 단계는 검증 + 분류 + 보고서 보정 only. 기능 파일 / 주석 / 문서 / 코드
|
||||
모두 미변경.
|
||||
|
||||
```
|
||||
M notes/gbpark/2026-05-19-canonical-data-view-cleanup-followup.md (본 §14 추가)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user