feat: 중첩 구조 지원을 위한 컴포넌트 선택 및 기본값 적용 기능 추가

- ScreenManagementService에서 company_code 저장 로직을 개선하여 SUPER_ADMIN의 경우 화면 정의에 따라 company_code를 저장하도록 수정하였습니다.
- ScreenDesigner에서 중첩 구조를 지원하는 탭 내부 컴포넌트 선택 상태 및 핸들러를 추가하였습니다.
- SplitPanelLayoutComponent에서 분할 패널 내부 컴포넌트의 기본값을 재귀적으로 적용하는 헬퍼 함수를 구현하였습니다.
- TimelineSchedulerConfigPanel에서 필드 매핑 업데이트 로직을 개선하여 이전 형식과 새 형식을 모두 지원하도록 하였습니다.
- useTimelineData 훅에서 필드 매핑을 JSON 문자열로 안정화하여 객체 참조 변경 방지를 위한 메모이제이션을 적용하였습니다.
This commit is contained in:
kjs
2026-02-02 17:11:00 +09:00
parent 4e7aa0c3b9
commit 7043f26ac8
9 changed files with 1079 additions and 231 deletions

View File

@@ -67,10 +67,38 @@ export function useTimelineData(
const resourceTableName = config.resourceTable;
// 필드 매핑
const fieldMapping = config.fieldMapping || defaultTimelineSchedulerConfig.fieldMapping!;
const resourceFieldMapping =
config.resourceFieldMapping || defaultTimelineSchedulerConfig.resourceFieldMapping!;
// 필드 매핑을 JSON 문자열로 안정화 (객체 참조 변경 방지)
const fieldMappingKey = useMemo(() => {
return JSON.stringify(config.fieldMapping || {});
}, [config.fieldMapping]);
const resourceFieldMappingKey = useMemo(() => {
return JSON.stringify(config.resourceFieldMapping || {});
}, [config.resourceFieldMapping]);
// 🆕 필드 매핑 정규화 (이전 형식 → 새 형식 변환) - useMemo로 메모이제이션
const fieldMapping = useMemo(() => {
const mapping = config.fieldMapping;
if (!mapping) return defaultTimelineSchedulerConfig.fieldMapping!;
return {
id: mapping.id || mapping.idField || "id",
resourceId: mapping.resourceId || mapping.resourceIdField || "resource_id",
title: mapping.title || mapping.titleField || "title",
startDate: mapping.startDate || mapping.startDateField || "start_date",
endDate: mapping.endDate || mapping.endDateField || "end_date",
status: mapping.status || mapping.statusField || undefined,
progress: mapping.progress || mapping.progressField || undefined,
color: mapping.color || mapping.colorField || undefined,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fieldMappingKey]);
// 리소스 필드 매핑 - useMemo로 메모이제이션
const resourceFieldMapping = useMemo(() => {
return config.resourceFieldMapping || defaultTimelineSchedulerConfig.resourceFieldMapping!;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [resourceFieldMappingKey]);
// 스케줄 데이터 로드
const fetchSchedules = useCallback(async () => {
@@ -125,13 +153,10 @@ export function useTimelineData(
} finally {
setIsLoading(false);
}
}, [
tableName,
externalSchedules,
fieldMapping,
viewStartDate,
viewEndDate,
]);
// fieldMappingKey를 의존성으로 사용하여 객체 참조 변경 방지
// viewStartDate, viewEndDate는 API 호출에 사용되지 않으므로 제거
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tableName, externalSchedules, fieldMappingKey]);
// 리소스 데이터 로드
const fetchResources = useCallback(async () => {
@@ -173,7 +198,9 @@ export function useTimelineData(
console.error("리소스 로드 오류:", err);
setResources([]);
}
}, [resourceTableName, externalResources, resourceFieldMapping]);
// resourceFieldMappingKey를 의존성으로 사용하여 객체 참조 변경 방지
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [resourceTableName, externalResources, resourceFieldMappingKey]);
// 초기 로드
useEffect(() => {