2026-01-28 11:24:25 +09:00
|
|
|
/**
|
|
|
|
|
* 컴포넌트 기본값 및 복원 유틸리티
|
|
|
|
|
*
|
|
|
|
|
* screen_layouts_v2 테이블의 config_overrides를 기본값과 병합하여
|
|
|
|
|
* 전체 componentConfig를 복원합니다.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// 컴포넌트별 기본값 맵
|
|
|
|
|
export const componentDefaults: Record<string, any> = {
|
|
|
|
|
"button-primary": {
|
|
|
|
|
type: "button-primary",
|
|
|
|
|
text: "저장",
|
|
|
|
|
actionType: "button",
|
|
|
|
|
variant: "primary",
|
|
|
|
|
webType: "button",
|
|
|
|
|
},
|
|
|
|
|
"v2-button-primary": {
|
|
|
|
|
type: "v2-button-primary",
|
|
|
|
|
text: "저장",
|
|
|
|
|
actionType: "button",
|
|
|
|
|
variant: "primary",
|
|
|
|
|
webType: "button",
|
|
|
|
|
},
|
|
|
|
|
"text-input": {
|
|
|
|
|
type: "text-input",
|
|
|
|
|
webType: "text",
|
|
|
|
|
format: "none",
|
|
|
|
|
multiline: false,
|
|
|
|
|
placeholder: "텍스트를 입력하세요",
|
|
|
|
|
},
|
|
|
|
|
"number-input": {
|
|
|
|
|
type: "number-input",
|
|
|
|
|
webType: "number",
|
|
|
|
|
placeholder: "숫자를 입력하세요",
|
|
|
|
|
},
|
|
|
|
|
"date-input": {
|
|
|
|
|
type: "date-input",
|
|
|
|
|
webType: "date",
|
|
|
|
|
format: "YYYY-MM-DD",
|
|
|
|
|
showTime: false,
|
|
|
|
|
placeholder: "날짜를 선택하세요",
|
|
|
|
|
},
|
|
|
|
|
"select-basic": {
|
|
|
|
|
type: "select-basic",
|
|
|
|
|
webType: "code",
|
|
|
|
|
placeholder: "선택하세요",
|
|
|
|
|
options: [],
|
|
|
|
|
},
|
|
|
|
|
"file-upload": {
|
|
|
|
|
type: "file-upload",
|
|
|
|
|
webType: "file",
|
|
|
|
|
placeholder: "입력하세요",
|
|
|
|
|
},
|
|
|
|
|
"table-list": {
|
|
|
|
|
type: "table-list",
|
|
|
|
|
webType: "table",
|
|
|
|
|
displayMode: "table",
|
|
|
|
|
showHeader: true,
|
|
|
|
|
showFooter: true,
|
|
|
|
|
autoLoad: true,
|
|
|
|
|
autoWidth: true,
|
|
|
|
|
stickyHeader: false,
|
|
|
|
|
height: "auto",
|
|
|
|
|
columns: [],
|
|
|
|
|
pagination: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
pageSize: 20,
|
|
|
|
|
showSizeSelector: true,
|
|
|
|
|
showPageInfo: true,
|
|
|
|
|
pageSizeOptions: [10, 20, 50, 100],
|
|
|
|
|
},
|
|
|
|
|
checkbox: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
multiple: true,
|
|
|
|
|
position: "left",
|
|
|
|
|
selectAll: true,
|
|
|
|
|
},
|
|
|
|
|
horizontalScroll: {
|
|
|
|
|
enabled: false,
|
|
|
|
|
},
|
|
|
|
|
filter: {
|
|
|
|
|
enabled: false,
|
|
|
|
|
filters: [],
|
|
|
|
|
},
|
|
|
|
|
actions: {
|
|
|
|
|
showActions: false,
|
|
|
|
|
actions: [],
|
|
|
|
|
bulkActions: false,
|
|
|
|
|
bulkActionList: [],
|
|
|
|
|
},
|
|
|
|
|
tableStyle: {
|
|
|
|
|
theme: "default",
|
|
|
|
|
headerStyle: "default",
|
|
|
|
|
rowHeight: "normal",
|
|
|
|
|
alternateRows: false,
|
|
|
|
|
hoverEffect: true,
|
|
|
|
|
borderStyle: "light",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"v2-table-list": {
|
|
|
|
|
type: "v2-table-list",
|
|
|
|
|
webType: "table",
|
|
|
|
|
displayMode: "table",
|
|
|
|
|
showHeader: true,
|
|
|
|
|
showFooter: true,
|
|
|
|
|
autoLoad: true,
|
|
|
|
|
autoWidth: true,
|
|
|
|
|
stickyHeader: false,
|
|
|
|
|
height: "auto",
|
|
|
|
|
columns: [],
|
|
|
|
|
pagination: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
pageSize: 20,
|
|
|
|
|
showSizeSelector: true,
|
|
|
|
|
showPageInfo: true,
|
|
|
|
|
pageSizeOptions: [10, 20, 50, 100],
|
|
|
|
|
},
|
|
|
|
|
checkbox: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
multiple: true,
|
|
|
|
|
position: "left",
|
|
|
|
|
selectAll: true,
|
|
|
|
|
},
|
|
|
|
|
horizontalScroll: { enabled: false },
|
|
|
|
|
filter: { enabled: false, filters: [] },
|
|
|
|
|
actions: { showActions: false, actions: [], bulkActions: false, bulkActionList: [] },
|
|
|
|
|
tableStyle: { theme: "default", headerStyle: "default", rowHeight: "normal", alternateRows: false, hoverEffect: true, borderStyle: "light" },
|
|
|
|
|
},
|
|
|
|
|
"table-search-widget": { type: "table-search-widget", webType: "custom" },
|
|
|
|
|
"split-panel-layout": { type: "split-panel-layout", webType: "text", autoLoad: true, resizable: true, splitRatio: 30 },
|
|
|
|
|
"v2-split-panel-layout": { type: "v2-split-panel-layout", webType: "custom" },
|
|
|
|
|
"tabs-widget": { type: "tabs-widget", webType: "text", tabs: [] },
|
|
|
|
|
"v2-tabs-widget": { type: "v2-tabs-widget", webType: "custom", tabs: [] },
|
|
|
|
|
"flow-widget": { type: "flow-widget", webType: "text", displayMode: "horizontal", allowDataMove: false, showStepCount: true },
|
|
|
|
|
"entity-search-input": { type: "entity-search-input", webType: "entity" },
|
|
|
|
|
"autocomplete-search-input": { type: "autocomplete-search-input", webType: "entity" },
|
2026-01-28 17:36:19 +09:00
|
|
|
"v2-list": { type: "v2-list", webType: "table" },
|
2026-01-28 11:24:25 +09:00
|
|
|
"modal-repeater-table": { type: "modal-repeater-table", webType: "table", columns: [], multiSelect: true },
|
|
|
|
|
"category-manager": { type: "category-manager", webType: "custom" },
|
|
|
|
|
"numbering-rule": { type: "numbering-rule", webType: "text" },
|
|
|
|
|
"conditional-container": { type: "conditional-container", webType: "custom" },
|
|
|
|
|
"selected-items-detail-input": { type: "selected-items-detail-input", webType: "custom" },
|
|
|
|
|
"text-display": { type: "text-display", webType: "text" },
|
|
|
|
|
"image-widget": { type: "image-widget", webType: "image" },
|
|
|
|
|
"textarea-basic": { type: "textarea-basic", webType: "textarea", placeholder: "내용을 입력하세요" },
|
|
|
|
|
"checkbox-basic": { type: "checkbox-basic", webType: "checkbox" },
|
|
|
|
|
"radio-basic": { type: "radio-basic", webType: "radio" },
|
|
|
|
|
"divider-line": { type: "divider-line", webType: "custom" },
|
|
|
|
|
"section-paper": { type: "section-paper", webType: "custom" },
|
|
|
|
|
"section-card": { type: "section-card", webType: "custom" },
|
|
|
|
|
"card-display": { type: "card-display", webType: "custom" },
|
|
|
|
|
"pivot-grid": { type: "pivot-grid", webType: "table" },
|
|
|
|
|
"rack-structure": { type: "rack-structure", webType: "custom" },
|
|
|
|
|
"v2-rack-structure": { type: "v2-rack-structure", webType: "custom" },
|
|
|
|
|
"location-swap-selector": { type: "location-swap-selector", webType: "custom" },
|
|
|
|
|
"screen-split-panel": { type: "screen-split-panel", webType: "custom" },
|
|
|
|
|
"universal-form-modal": { type: "universal-form-modal", webType: "custom" },
|
|
|
|
|
"repeater-field-group": { type: "repeater-field-group", webType: "custom" },
|
|
|
|
|
"repeat-screen-modal": { type: "repeat-screen-modal", webType: "custom" },
|
|
|
|
|
"related-data-buttons": { type: "related-data-buttons", webType: "custom" },
|
|
|
|
|
"split-panel-layout2": { type: "split-panel-layout2", webType: "custom" },
|
2026-01-28 17:36:19 +09:00
|
|
|
"v2-input": { type: "v2-input", webType: "text" },
|
|
|
|
|
"v2-select": { type: "v2-select", webType: "select" },
|
|
|
|
|
"v2-date": { type: "v2-date", webType: "date" },
|
|
|
|
|
"v2-repeater": { type: "v2-repeater", webType: "custom" },
|
2026-01-28 11:24:25 +09:00
|
|
|
"v2-repeat-container": { type: "v2-repeat-container", webType: "custom" },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 컴포넌트 기본값 조회
|
|
|
|
|
*/
|
|
|
|
|
export function getComponentDefaults(componentType: string): any {
|
|
|
|
|
return componentDefaults[componentType] || {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 설정 복원: 기본값 + overrides 병합
|
|
|
|
|
*
|
|
|
|
|
* @param componentType 컴포넌트 타입
|
|
|
|
|
* @param overrides 저장된 차이값 (config_overrides)
|
|
|
|
|
* @returns 복원된 전체 설정
|
|
|
|
|
*/
|
|
|
|
|
export function reconstructConfig(componentType: string, overrides: any): any {
|
|
|
|
|
const defaults = getComponentDefaults(componentType);
|
|
|
|
|
|
|
|
|
|
if (!overrides || Object.keys(overrides).length === 0) {
|
|
|
|
|
return { ...defaults };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// _originalKeys가 있으면 해당 키만 복원
|
|
|
|
|
const originalKeys = overrides._originalKeys;
|
|
|
|
|
|
|
|
|
|
if (originalKeys && Array.isArray(originalKeys)) {
|
|
|
|
|
const result: any = {};
|
|
|
|
|
for (const key of originalKeys) {
|
|
|
|
|
if (key === "_originalKeys") continue;
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(overrides, key)) {
|
|
|
|
|
result[key] = overrides[key];
|
|
|
|
|
} else if (Object.prototype.hasOwnProperty.call(defaults, key)) {
|
|
|
|
|
result[key] = defaults[key];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// _originalKeys가 없으면 단순 병합
|
|
|
|
|
return { ...defaults, ...overrides };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 깊은 비교 함수
|
|
|
|
|
*/
|
|
|
|
|
export function isDeepEqual(a: any, b: any): boolean {
|
|
|
|
|
if (a === b) return true;
|
|
|
|
|
if (a == null || b == null) return a === b;
|
|
|
|
|
if (typeof a !== typeof b) return false;
|
|
|
|
|
if (typeof a !== "object") return a === b;
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
|
|
|
if (Array.isArray(a)) {
|
|
|
|
|
if (a.length !== b.length) return false;
|
|
|
|
|
for (let i = 0; i < a.length; i++) {
|
|
|
|
|
if (!isDeepEqual(a[i], b[i])) return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const keysA = Object.keys(a);
|
|
|
|
|
const keysB = Object.keys(b);
|
|
|
|
|
|
|
|
|
|
if (keysA.length !== keysB.length) return false;
|
|
|
|
|
|
|
|
|
|
for (const key of keysA) {
|
|
|
|
|
if (!keysB.includes(key)) return false;
|
|
|
|
|
if (!isDeepEqual(a[key], b[key])) return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 차이값 추출: 현재 설정에서 기본값과 다른 것만 추출
|
|
|
|
|
*/
|
|
|
|
|
export function extractConfigDiff(componentType: string, currentConfig: any): any {
|
|
|
|
|
const defaults = getComponentDefaults(componentType);
|
|
|
|
|
|
|
|
|
|
if (!currentConfig) return {};
|
|
|
|
|
|
|
|
|
|
const diff: any = {
|
|
|
|
|
_originalKeys: Object.keys(currentConfig),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const key of Object.keys(currentConfig)) {
|
|
|
|
|
const defaultVal = defaults[key];
|
|
|
|
|
const currentVal = currentConfig[key];
|
|
|
|
|
|
|
|
|
|
if (!isDeepEqual(defaultVal, currentVal)) {
|
|
|
|
|
diff[key] = currentVal;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return diff;
|
|
|
|
|
}
|