Merge pull request 'fix/429error' (#75) from fix/429error into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/75
This commit is contained in:
commit
55f4c7fa26
|
|
@ -352,13 +352,13 @@ export default function ScreenViewPage() {
|
||||||
},
|
},
|
||||||
onFormDataChange: (fieldName, value) => {
|
onFormDataChange: (fieldName, value) => {
|
||||||
console.log(`🎯 page.tsx onFormDataChange 호출: ${fieldName} = "${value}"`);
|
console.log(`🎯 page.tsx onFormDataChange 호출: ${fieldName} = "${value}"`);
|
||||||
console.log(`📋 현재 formData:`, formData);
|
console.log("📋 현재 formData:", formData);
|
||||||
setFormData((prev) => {
|
setFormData((prev) => {
|
||||||
const newFormData = {
|
const newFormData = {
|
||||||
...prev,
|
...prev,
|
||||||
[fieldName]: value,
|
[fieldName]: value,
|
||||||
};
|
};
|
||||||
console.log(`📝 업데이트된 formData:`, newFormData);
|
console.log("📝 업데이트된 formData:", newFormData);
|
||||||
return newFormData;
|
return newFormData;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,107 @@
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { commonCodeApi } from "@/lib/api/commonCode";
|
import { commonCodeApi } from "@/lib/api/commonCode";
|
||||||
import { queryKeys } from "@/lib/queryKeys";
|
import { tableTypeApi } from "@/lib/api/screen";
|
||||||
import type { CodeFilter, CreateCodeData, UpdateCodeData } from "@/lib/schemas/commonCode";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
/**
|
// Query Keys
|
||||||
* 코드 목록 조회 훅
|
export const queryKeys = {
|
||||||
*/
|
codes: {
|
||||||
export function useCodes(categoryCode: string, filters?: CodeFilter) {
|
all: ["codes"] as const,
|
||||||
|
list: (categoryCode: string) => ["codes", "list", categoryCode] as const,
|
||||||
|
options: (categoryCode: string) => ["codes", "options", categoryCode] as const,
|
||||||
|
detail: (categoryCode: string, codeValue: string) =>
|
||||||
|
["codes", "detail", categoryCode, codeValue] as const,
|
||||||
|
infiniteList: (categoryCode: string, filters?: any) =>
|
||||||
|
["codes", "infiniteList", categoryCode, filters] as const,
|
||||||
|
},
|
||||||
|
tables: {
|
||||||
|
all: ["tables"] as const,
|
||||||
|
columns: (tableName: string) => ["tables", "columns", tableName] as const,
|
||||||
|
codeCategory: (tableName: string, columnName: string) =>
|
||||||
|
["tables", "codeCategory", tableName, columnName] as const,
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
all: ["categories"] as const,
|
||||||
|
list: (filters?: any) => ["categories", "list", filters] as const,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 테이블 컬럼의 코드 카테고리 조회
|
||||||
|
export function useTableCodeCategory(tableName?: string, columnName?: string) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: queryKeys.codes.list(categoryCode, filters),
|
queryKey: queryKeys.tables.codeCategory(tableName || "", columnName || ""),
|
||||||
queryFn: () => commonCodeApi.codes.getList(categoryCode, filters),
|
queryFn: async () => {
|
||||||
select: (data) => data.data || [],
|
if (!tableName || !columnName) return null;
|
||||||
enabled: !!categoryCode, // categoryCode가 있을 때만 실행
|
|
||||||
|
console.log(`🔍 [React Query] 테이블 코드 카테고리 조회: ${tableName}.${columnName}`);
|
||||||
|
const columns = await tableTypeApi.getColumns(tableName);
|
||||||
|
const targetColumn = columns.find((col) => col.columnName === columnName);
|
||||||
|
|
||||||
|
const codeCategory = targetColumn?.codeCategory && targetColumn.codeCategory !== "none"
|
||||||
|
? targetColumn.codeCategory
|
||||||
|
: null;
|
||||||
|
|
||||||
|
console.log(`✅ [React Query] 테이블 코드 카테고리 결과: ${tableName}.${columnName} -> ${codeCategory}`);
|
||||||
|
return codeCategory;
|
||||||
|
},
|
||||||
|
enabled: !!(tableName && columnName),
|
||||||
|
staleTime: 10 * 60 * 1000, // 10분 캐시
|
||||||
|
gcTime: 30 * 60 * 1000, // 30분 가비지 컬렉션
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 코드 옵션 조회 (select용)
|
||||||
* 코드 생성 뮤테이션 훅
|
export function useCodeOptions(codeCategory?: string, enabled: boolean = true) {
|
||||||
*/
|
const query = useQuery({
|
||||||
|
queryKey: queryKeys.codes.options(codeCategory || ""),
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!codeCategory || codeCategory === "none") return [];
|
||||||
|
|
||||||
|
console.log(`🔍 [React Query] 코드 옵션 조회: ${codeCategory}`);
|
||||||
|
const response = await commonCodeApi.codes.getList(codeCategory, { isActive: true });
|
||||||
|
|
||||||
|
if (response.success && response.data) {
|
||||||
|
const options = response.data.map((code: any) => {
|
||||||
|
const actualValue = code.code || code.CODE || code.value || code.code_value || code.codeValue;
|
||||||
|
const actualLabel = code.codeName || code.code_name || code.name || code.CODE_NAME ||
|
||||||
|
code.NAME || code.label || code.LABEL || code.text || code.title ||
|
||||||
|
code.description || actualValue;
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: actualValue,
|
||||||
|
label: actualLabel,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ [React Query] 코드 옵션 결과: ${codeCategory} (${options.length}개)`);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
enabled: enabled && !!(codeCategory && codeCategory !== "none"),
|
||||||
|
staleTime: 10 * 60 * 1000, // 10분 캐시
|
||||||
|
gcTime: 30 * 60 * 1000, // 30분 가비지 컬렉션
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
options: query.data || [],
|
||||||
|
isLoading: query.isLoading,
|
||||||
|
isFetching: query.isFetching,
|
||||||
|
error: query.error,
|
||||||
|
refetch: query.refetch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 코드 생성
|
||||||
export function useCreateCode() {
|
export function useCreateCode() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: ({ categoryCode, data }: { categoryCode: string; data: CreateCodeData }) =>
|
mutationFn: ({ categoryCode, data }: { categoryCode: string; data: any }) =>
|
||||||
commonCodeApi.codes.create(categoryCode, data),
|
commonCodeApi.codes.create(categoryCode, data),
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
// 해당 카테고리의 모든 코드 관련 쿼리 무효화 (일반 목록 + 무한 스크롤)
|
// 해당 카테고리의 모든 코드 관련 쿼리 무효화
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.codes.all,
|
queryKey: queryKeys.codes.all,
|
||||||
});
|
});
|
||||||
|
|
@ -34,15 +110,10 @@ export function useCreateCode() {
|
||||||
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
|
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
|
||||||
console.error("코드 생성 실패:", error);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 코드 수정
|
||||||
* 코드 수정 뮤테이션 훅
|
|
||||||
*/
|
|
||||||
export function useUpdateCode() {
|
export function useUpdateCode() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|
@ -54,14 +125,14 @@ export function useUpdateCode() {
|
||||||
}: {
|
}: {
|
||||||
categoryCode: string;
|
categoryCode: string;
|
||||||
codeValue: string;
|
codeValue: string;
|
||||||
data: UpdateCodeData;
|
data: any;
|
||||||
}) => commonCodeApi.codes.update(categoryCode, codeValue, data),
|
}) => commonCodeApi.codes.update(categoryCode, codeValue, data),
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
// 해당 코드 상세 쿼리 무효화
|
// 해당 코드 상세 쿼리 무효화
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue),
|
queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue),
|
||||||
});
|
});
|
||||||
// 해당 카테고리의 모든 코드 관련 쿼리 무효화 (일반 목록 + 무한 스크롤)
|
// 해당 카테고리의 모든 코드 관련 쿼리 무효화
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.codes.all,
|
queryKey: queryKeys.codes.all,
|
||||||
});
|
});
|
||||||
|
|
@ -70,15 +141,10 @@ export function useUpdateCode() {
|
||||||
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
|
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
|
||||||
console.error("코드 수정 실패:", error);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 코드 삭제
|
||||||
* 코드 삭제 뮤테이션 훅
|
|
||||||
*/
|
|
||||||
export function useDeleteCode() {
|
export function useDeleteCode() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|
@ -98,15 +164,10 @@ export function useDeleteCode() {
|
||||||
queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue),
|
queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
|
||||||
console.error("코드 삭제 실패:", error);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 코드 순서 변경
|
||||||
* 코드 순서 변경 뮤테이션 훅
|
|
||||||
*/
|
|
||||||
export function useReorderCodes() {
|
export function useReorderCodes() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
|
@ -126,7 +187,7 @@ export function useReorderCodes() {
|
||||||
const previousCodes = queryClient.getQueryData(queryKeys.codes.list(categoryCode));
|
const previousCodes = queryClient.getQueryData(queryKeys.codes.list(categoryCode));
|
||||||
|
|
||||||
// Optimistic update: 새로운 순서로 즉시 업데이트
|
// Optimistic update: 새로운 순서로 즉시 업데이트
|
||||||
if (previousCodes && (previousCodes as any).data && Array.isArray((previousCodes as any).data)) {
|
if (previousCodes && Array.isArray((previousCodes as any).data)) {
|
||||||
const previousCodesArray = (previousCodes as any).data;
|
const previousCodesArray = (previousCodes as any).data;
|
||||||
|
|
||||||
// 기존 데이터를 복사하고 sort_order만 업데이트
|
// 기존 데이터를 복사하고 sort_order만 업데이트
|
||||||
|
|
@ -135,8 +196,8 @@ export function useReorderCodes() {
|
||||||
return newCodeData ? { ...code, sort_order: newCodeData.sortOrder } : code;
|
return newCodeData ? { ...code, sort_order: newCodeData.sortOrder } : code;
|
||||||
});
|
});
|
||||||
|
|
||||||
// sort_order로 정렬
|
// 순서대로 정렬
|
||||||
updatedCodes.sort((a: any, b: any) => a.sort_order - b.sort_order);
|
updatedCodes.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0));
|
||||||
|
|
||||||
// API 응답 형태로 캐시에 저장 (기존 구조 유지)
|
// API 응답 형태로 캐시에 저장 (기존 구조 유지)
|
||||||
queryClient.setQueryData(queryKeys.codes.list(categoryCode), {
|
queryClient.setQueryData(queryKeys.codes.list(categoryCode), {
|
||||||
|
|
@ -145,11 +206,9 @@ export function useReorderCodes() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 롤백용 데이터 반환
|
|
||||||
return { previousCodes };
|
return { previousCodes };
|
||||||
},
|
},
|
||||||
onError: (error, variables, context) => {
|
onError: (err, variables, context) => {
|
||||||
console.error("코드 순서 변경 실패:", error);
|
|
||||||
// 에러 시 이전 데이터로 롤백
|
// 에러 시 이전 데이터로 롤백
|
||||||
if (context?.previousCodes) {
|
if (context?.previousCodes) {
|
||||||
queryClient.setQueryData(queryKeys.codes.list(variables.categoryCode), context.previousCodes);
|
queryClient.setQueryData(queryKeys.codes.list(variables.categoryCode), context.previousCodes);
|
||||||
|
|
@ -166,4 +225,4 @@ export function useReorderCodes() {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -18,13 +18,13 @@ export const DynamicWebTypeRenderer: React.FC<DynamicComponentProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
// 모든 hooks를 먼저 호출 (조건부 return 이전에)
|
// 모든 hooks를 먼저 호출 (조건부 return 이전에)
|
||||||
const { webTypes } = useWebTypes({ active: "Y" });
|
const { webTypes } = useWebTypes({ active: "Y" });
|
||||||
|
|
||||||
// 디버깅: 전달받은 웹타입과 props 정보 로깅
|
// 디버깅: 전달받은 웹타입과 props 정보 로깅
|
||||||
console.log("🔍 DynamicWebTypeRenderer 호출:", {
|
console.log("🔍 DynamicWebTypeRenderer 호출:", {
|
||||||
webType,
|
webType,
|
||||||
propsKeys: Object.keys(props),
|
propsKeys: Object.keys(props),
|
||||||
component: props.component,
|
component: props.component,
|
||||||
isFileComponent: props.component?.type === 'file' || webType === 'file'
|
isFileComponent: props.component?.type === "file" || webType === "file",
|
||||||
});
|
});
|
||||||
|
|
||||||
const webTypeDefinition = useMemo(() => {
|
const webTypeDefinition = useMemo(() => {
|
||||||
|
|
@ -70,21 +70,21 @@ export const DynamicWebTypeRenderer: React.FC<DynamicComponentProps> = ({
|
||||||
if (dbWebType?.component_name) {
|
if (dbWebType?.component_name) {
|
||||||
try {
|
try {
|
||||||
console.log(`웹타입 "${webType}" → DB 지정 컴포넌트 "${dbWebType.component_name}" 사용`);
|
console.log(`웹타입 "${webType}" → DB 지정 컴포넌트 "${dbWebType.component_name}" 사용`);
|
||||||
console.log(`DB 웹타입 정보:`, dbWebType);
|
console.log("DB 웹타입 정보:", dbWebType);
|
||||||
|
|
||||||
// FileWidget의 경우 FileUploadComponent 직접 사용
|
// FileWidget의 경우 FileUploadComponent 직접 사용
|
||||||
if (dbWebType.component_name === "FileWidget" || webType === "file") {
|
if (dbWebType.component_name === "FileWidget" || webType === "file") {
|
||||||
const { FileUploadComponent } = require("@/lib/registry/components/file-upload/FileUploadComponent");
|
const { FileUploadComponent } = require("@/lib/registry/components/file-upload/FileUploadComponent");
|
||||||
console.log(`✅ FileWidget → FileUploadComponent 사용`);
|
console.log("✅ FileWidget → FileUploadComponent 사용");
|
||||||
return <FileUploadComponent {...props} {...finalProps} />;
|
return <FileUploadComponent {...props} {...finalProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 다른 컴포넌트들은 기존 로직 유지
|
// 다른 컴포넌트들은 기존 로직 유지
|
||||||
// const ComponentByName = getWidgetComponentByName(dbWebType.component_name);
|
// const ComponentByName = getWidgetComponentByName(dbWebType.component_name);
|
||||||
// console.log(`컴포넌트 "${dbWebType.component_name}" 성공적으로 로드됨:`, ComponentByName);
|
// console.log(`컴포넌트 "${dbWebType.component_name}" 성공적으로 로드됨:`, ComponentByName);
|
||||||
// return <ComponentByName {...props} {...finalProps} />;
|
// return <ComponentByName {...props} {...finalProps} />;
|
||||||
console.warn(`DB 지정 컴포넌트 "${dbWebType.component_name}" 기능 임시 비활성화 (FileWidget 제외)`);
|
console.warn(`DB 지정 컴포넌트 "${dbWebType.component_name}" 기능 임시 비활성화 (FileWidget 제외)`);
|
||||||
|
|
||||||
// 로딩 중 메시지 대신 레지스트리로 폴백
|
// 로딩 중 메시지 대신 레지스트리로 폴백
|
||||||
// return <div>컴포넌트 로딩 중...</div>;
|
// return <div>컴포넌트 로딩 중...</div>;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -99,7 +99,7 @@ export const DynamicWebTypeRenderer: React.FC<DynamicComponentProps> = ({
|
||||||
// 파일 웹타입의 경우 FileUploadComponent 직접 사용
|
// 파일 웹타입의 경우 FileUploadComponent 직접 사용
|
||||||
if (webType === "file") {
|
if (webType === "file") {
|
||||||
const { FileUploadComponent } = require("@/lib/registry/components/file-upload/FileUploadComponent");
|
const { FileUploadComponent } = require("@/lib/registry/components/file-upload/FileUploadComponent");
|
||||||
console.log(`✅ 파일 웹타입 → FileUploadComponent 사용`);
|
console.log("✅ 파일 웹타입 → FileUploadComponent 사용");
|
||||||
return <FileUploadComponent {...props} {...finalProps} />;
|
return <FileUploadComponent {...props} {...finalProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,44 +127,39 @@ export const DynamicWebTypeRenderer: React.FC<DynamicComponentProps> = ({
|
||||||
// 3순위: 웹타입명으로 자동 매핑 (폴백)
|
// 3순위: 웹타입명으로 자동 매핑 (폴백)
|
||||||
try {
|
try {
|
||||||
console.warn(`웹타입 "${webType}" → 자동 매핑 폴백 사용`);
|
console.warn(`웹타입 "${webType}" → 자동 매핑 폴백 사용`);
|
||||||
|
|
||||||
// 파일 웹타입의 경우 FileUploadComponent 직접 사용 (최종 폴백)
|
// 파일 웹타입의 경우 FileUploadComponent 직접 사용 (최종 폴백)
|
||||||
if (webType === "file") {
|
if (webType === "file") {
|
||||||
const { FileUploadComponent } = require("@/lib/registry/components/file-upload/FileUploadComponent");
|
const { FileUploadComponent } = require("@/lib/registry/components/file-upload/FileUploadComponent");
|
||||||
console.log(`✅ 폴백: 파일 웹타입 → FileUploadComponent 사용`);
|
console.log("✅ 폴백: 파일 웹타입 → FileUploadComponent 사용");
|
||||||
return <FileUploadComponent {...props} {...finalProps} />;
|
return <FileUploadComponent {...props} {...finalProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 텍스트 입력 웹타입들
|
// 텍스트 입력 웹타입들
|
||||||
if (["text", "email", "password", "tel"].includes(webType)) {
|
if (["text", "email", "password", "tel"].includes(webType)) {
|
||||||
const { TextInputComponent } = require("@/lib/registry/components/text-input/TextInputComponent");
|
const { TextInputComponent } = require("@/lib/registry/components/text-input/TextInputComponent");
|
||||||
console.log(`✅ 폴백: ${webType} 웹타입 → TextInputComponent 사용`);
|
console.log(`✅ 폴백: ${webType} 웹타입 → TextInputComponent 사용`);
|
||||||
return <TextInputComponent {...props} {...finalProps} />;
|
return <TextInputComponent {...props} {...finalProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 숫자 입력 웹타입들
|
// 숫자 입력 웹타입들
|
||||||
if (["number", "decimal"].includes(webType)) {
|
if (["number", "decimal"].includes(webType)) {
|
||||||
const { NumberInputComponent } = require("@/lib/registry/components/number-input/NumberInputComponent");
|
const { NumberInputComponent } = require("@/lib/registry/components/number-input/NumberInputComponent");
|
||||||
console.log(`✅ 폴백: ${webType} 웹타입 → NumberInputComponent 사용`);
|
console.log(`✅ 폴백: ${webType} 웹타입 → NumberInputComponent 사용`);
|
||||||
return <NumberInputComponent {...props} {...finalProps} />;
|
return <NumberInputComponent {...props} {...finalProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 날짜 입력 웹타입들
|
// 날짜 입력 웹타입들
|
||||||
if (["date", "datetime", "time"].includes(webType)) {
|
if (["date", "datetime", "time"].includes(webType)) {
|
||||||
const { DateInputComponent } = require("@/lib/registry/components/date-input/DateInputComponent");
|
const { DateInputComponent } = require("@/lib/registry/components/date-input/DateInputComponent");
|
||||||
console.log(`✅ 폴백: ${webType} 웹타입 → DateInputComponent 사용`);
|
console.log(`✅ 폴백: ${webType} 웹타입 → DateInputComponent 사용`);
|
||||||
return <DateInputComponent {...props} {...finalProps} />;
|
return <DateInputComponent {...props} {...finalProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 기본 폴백: Input 컴포넌트 사용
|
// 기본 폴백: Input 컴포넌트 사용
|
||||||
const { Input } = require("@/components/ui/input");
|
const { Input } = require("@/components/ui/input");
|
||||||
console.log(`✅ 폴백: ${webType} 웹타입 → 기본 Input 사용`);
|
console.log(`✅ 폴백: ${webType} 웹타입 → 기본 Input 사용`);
|
||||||
return <Input
|
return <Input placeholder={`${webType}`} disabled={props.readonly} className="w-full" {...props} />;
|
||||||
placeholder={`${webType}`}
|
|
||||||
disabled={props.readonly}
|
|
||||||
className="w-full"
|
|
||||||
{...props}
|
|
||||||
/>;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`웹타입 "${webType}" 폴백 컴포넌트 렌더링 실패:`, error);
|
console.error(`웹타입 "${webType}" 폴백 컴포넌트 렌더링 실패:`, error);
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef, useMemo } from "react";
|
||||||
import { commonCodeApi } from "../../../api/commonCode";
|
|
||||||
import { tableTypeApi } from "../../../api/screen";
|
|
||||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||||
|
import { useCodeOptions, useTableCodeCategory } from "@/hooks/queries/useCodes";
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
value: string;
|
value: string;
|
||||||
|
|
@ -26,210 +25,10 @@ export interface SelectBasicComponentProps {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🚀 전역 상태 관리: 모든 컴포넌트가 공유하는 상태
|
// ✅ React Query를 사용하여 중복 요청 방지 및 자동 캐싱 처리
|
||||||
interface GlobalState {
|
// - 동일한 queryKey에 대해서는 자동으로 중복 요청 제거
|
||||||
tableCategories: Map<string, string>; // tableName.columnName -> codeCategory
|
// - 10분 staleTime으로 적절한 캐시 관리
|
||||||
codeOptions: Map<string, { options: Option[]; timestamp: number }>; // codeCategory -> options
|
// - 30분 gcTime으로 메모리 효율성 확보
|
||||||
activeRequests: Map<string, Promise<any>>; // 진행 중인 요청들
|
|
||||||
subscribers: Set<() => void>; // 상태 변경 구독자들
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalState: GlobalState = {
|
|
||||||
tableCategories: new Map(),
|
|
||||||
codeOptions: new Map(),
|
|
||||||
activeRequests: new Map(),
|
|
||||||
subscribers: new Set(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 전역 상태 변경 알림
|
|
||||||
const notifyStateChange = () => {
|
|
||||||
globalState.subscribers.forEach((callback) => callback());
|
|
||||||
};
|
|
||||||
|
|
||||||
// 캐시 유효 시간 (5분)
|
|
||||||
const CACHE_DURATION = 5 * 60 * 1000;
|
|
||||||
|
|
||||||
// 🔧 전역 테이블 코드 카테고리 로딩 (중복 방지)
|
|
||||||
const loadGlobalTableCodeCategory = async (tableName: string, columnName: string): Promise<string | null> => {
|
|
||||||
const key = `${tableName}.${columnName}`;
|
|
||||||
|
|
||||||
// 이미 진행 중인 요청이 있으면 대기
|
|
||||||
if (globalState.activeRequests.has(`table_${key}`)) {
|
|
||||||
try {
|
|
||||||
await globalState.activeRequests.get(`table_${key}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 테이블 설정 로딩 대기 중 오류:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 캐시된 값이 있으면 반환
|
|
||||||
if (globalState.tableCategories.has(key)) {
|
|
||||||
const cachedCategory = globalState.tableCategories.get(key);
|
|
||||||
console.log(`✅ 캐시된 테이블 설정 사용: ${key} -> ${cachedCategory}`);
|
|
||||||
return cachedCategory || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 새로운 요청 생성
|
|
||||||
const request = (async () => {
|
|
||||||
try {
|
|
||||||
console.log(`🔍 테이블 코드 카테고리 조회: ${key}`);
|
|
||||||
const columns = await tableTypeApi.getColumns(tableName);
|
|
||||||
const targetColumn = columns.find((col) => col.columnName === columnName);
|
|
||||||
|
|
||||||
const codeCategory =
|
|
||||||
targetColumn?.codeCategory && targetColumn.codeCategory !== "none" ? targetColumn.codeCategory : null;
|
|
||||||
|
|
||||||
// 전역 상태에 저장
|
|
||||||
globalState.tableCategories.set(key, codeCategory || "");
|
|
||||||
|
|
||||||
console.log(`✅ 테이블 설정 조회 완료: ${key} -> ${codeCategory}`);
|
|
||||||
|
|
||||||
// 상태 변경 알림
|
|
||||||
notifyStateChange();
|
|
||||||
|
|
||||||
return codeCategory;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ 테이블 코드 카테고리 조회 실패: ${key}`, error);
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
globalState.activeRequests.delete(`table_${key}`);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
globalState.activeRequests.set(`table_${key}`, request);
|
|
||||||
return request;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 🔧 전역 코드 옵션 로딩 (중복 방지)
|
|
||||||
const loadGlobalCodeOptions = async (codeCategory: string): Promise<Option[]> => {
|
|
||||||
if (!codeCategory || codeCategory === "none") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 이미 진행 중인 요청이 있으면 대기
|
|
||||||
if (globalState.activeRequests.has(`code_${codeCategory}`)) {
|
|
||||||
try {
|
|
||||||
await globalState.activeRequests.get(`code_${codeCategory}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 코드 옵션 로딩 대기 중 오류:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 캐시된 값이 유효하면 반환
|
|
||||||
const cached = globalState.codeOptions.get(codeCategory);
|
|
||||||
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
|
|
||||||
console.log(`✅ 캐시된 코드 옵션 사용: ${codeCategory} (${cached.options.length}개)`);
|
|
||||||
return cached.options;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 새로운 요청 생성
|
|
||||||
const request = (async () => {
|
|
||||||
try {
|
|
||||||
console.log(`🔄 코드 옵션 로딩: ${codeCategory}`);
|
|
||||||
const response = await commonCodeApi.codes.getList(codeCategory, { isActive: true });
|
|
||||||
|
|
||||||
console.log(`🔍 [API 응답 원본] ${codeCategory}:`, {
|
|
||||||
response,
|
|
||||||
success: response.success,
|
|
||||||
data: response.data,
|
|
||||||
dataType: typeof response.data,
|
|
||||||
isArray: Array.isArray(response.data),
|
|
||||||
dataLength: response.data?.length,
|
|
||||||
firstItem: response.data?.[0],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success && response.data) {
|
|
||||||
const options = response.data.map((code: any, index: number) => {
|
|
||||||
console.log(`🔍 [코드 매핑] ${index}:`, {
|
|
||||||
originalCode: code,
|
|
||||||
codeKeys: Object.keys(code),
|
|
||||||
values: Object.values(code),
|
|
||||||
// 가능한 모든 필드 확인
|
|
||||||
code: code.code,
|
|
||||||
codeName: code.codeName,
|
|
||||||
name: code.name,
|
|
||||||
label: code.label,
|
|
||||||
// 대문자 버전
|
|
||||||
CODE: code.CODE,
|
|
||||||
CODE_NAME: code.CODE_NAME,
|
|
||||||
NAME: code.NAME,
|
|
||||||
LABEL: code.LABEL,
|
|
||||||
// 스네이크 케이스
|
|
||||||
code_name: code.code_name,
|
|
||||||
code_value: code.code_value,
|
|
||||||
// 기타 가능한 필드들
|
|
||||||
value: code.value,
|
|
||||||
text: code.text,
|
|
||||||
title: code.title,
|
|
||||||
description: code.description,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 실제 값 찾기 시도 (우선순위 순)
|
|
||||||
const actualValue = code.code || code.CODE || code.value || code.code_value || `code_${index}`;
|
|
||||||
const actualLabel =
|
|
||||||
code.codeName ||
|
|
||||||
code.code_name || // 스네이크 케이스 추가!
|
|
||||||
code.name ||
|
|
||||||
code.CODE_NAME ||
|
|
||||||
code.NAME ||
|
|
||||||
code.label ||
|
|
||||||
code.LABEL ||
|
|
||||||
code.text ||
|
|
||||||
code.title ||
|
|
||||||
code.description ||
|
|
||||||
actualValue;
|
|
||||||
|
|
||||||
console.log(`✨ [최종 매핑] ${index}:`, {
|
|
||||||
actualValue,
|
|
||||||
actualLabel,
|
|
||||||
hasValue: !!actualValue,
|
|
||||||
hasLabel: !!actualLabel,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
value: actualValue,
|
|
||||||
label: actualLabel,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`🔍 [최종 옵션 배열] ${codeCategory}:`, {
|
|
||||||
optionsLength: options.length,
|
|
||||||
options: options.map((opt, idx) => ({
|
|
||||||
index: idx,
|
|
||||||
value: opt.value,
|
|
||||||
label: opt.label,
|
|
||||||
hasLabel: !!opt.label,
|
|
||||||
hasValue: !!opt.value,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 전역 상태에 저장
|
|
||||||
globalState.codeOptions.set(codeCategory, {
|
|
||||||
options,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`✅ 코드 옵션 로딩 완료: ${codeCategory} (${options.length}개)`);
|
|
||||||
|
|
||||||
// 상태 변경 알림
|
|
||||||
notifyStateChange();
|
|
||||||
|
|
||||||
return options;
|
|
||||||
} else {
|
|
||||||
console.log(`⚠️ 빈 응답: ${codeCategory}`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ 코드 옵션 로딩 실패: ${codeCategory}`, error);
|
|
||||||
return [];
|
|
||||||
} finally {
|
|
||||||
globalState.activeRequests.delete(`code_${codeCategory}`);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
globalState.activeRequests.set(`code_${codeCategory}`, request);
|
|
||||||
return request;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
component,
|
component,
|
||||||
|
|
@ -248,6 +47,22 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
value: externalValue, // 명시적으로 value prop 받기
|
value: externalValue, // 명시적으로 value prop 받기
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
|
// 🚨 최우선 디버깅: 컴포넌트가 실행되는지 확인
|
||||||
|
console.log("🚨🚨🚨 SelectBasicComponent 실행됨!!!", {
|
||||||
|
componentId: component?.id,
|
||||||
|
componentType: component?.type,
|
||||||
|
webType: component?.webType,
|
||||||
|
tableName: component?.tableName,
|
||||||
|
columnName: component?.columnName,
|
||||||
|
screenId,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 브라우저 알림으로도 확인
|
||||||
|
if (typeof window !== "undefined" && !(window as any).selectBasicAlerted) {
|
||||||
|
(window as any).selectBasicAlerted = true;
|
||||||
|
alert("SelectBasicComponent가 실행되었습니다!");
|
||||||
|
}
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
// webTypeConfig 또는 componentConfig 사용 (DynamicWebTypeRenderer 호환성)
|
// webTypeConfig 또는 componentConfig 사용 (DynamicWebTypeRenderer 호환성)
|
||||||
|
|
@ -257,23 +72,74 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
const [selectedValue, setSelectedValue] = useState(externalValue || config?.value || "");
|
const [selectedValue, setSelectedValue] = useState(externalValue || config?.value || "");
|
||||||
const [selectedLabel, setSelectedLabel] = useState("");
|
const [selectedLabel, setSelectedLabel] = useState("");
|
||||||
|
|
||||||
console.log("🔍 SelectBasicComponent 초기화:", {
|
console.log("🔍 SelectBasicComponent 초기화 (React Query):", {
|
||||||
componentId: component.id,
|
componentId: component.id,
|
||||||
externalValue,
|
externalValue,
|
||||||
componentConfigValue: componentConfig?.value,
|
componentConfigValue: componentConfig?.value,
|
||||||
webTypeConfigValue: (props as any).webTypeConfig?.value,
|
webTypeConfigValue: (props as any).webTypeConfig?.value,
|
||||||
configValue: config?.value,
|
configValue: config?.value,
|
||||||
finalSelectedValue: externalValue || config?.value || "",
|
finalSelectedValue: externalValue || config?.value || "",
|
||||||
props: Object.keys(props),
|
tableName: component.tableName,
|
||||||
|
columnName: component.columnName,
|
||||||
|
staticCodeCategory: config?.codeCategory,
|
||||||
|
// React Query 디버깅 정보
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
mountCount: ++(window as any).selectMountCount || ((window as any).selectMountCount = 1),
|
||||||
});
|
});
|
||||||
const [codeOptions, setCodeOptions] = useState<Option[]>([]);
|
|
||||||
const [isLoadingCodes, setIsLoadingCodes] = useState(false);
|
// 언마운트 시 로깅
|
||||||
const [dynamicCodeCategory, setDynamicCodeCategory] = useState<string | null>(null);
|
useEffect(() => {
|
||||||
const [globalStateVersion, setGlobalStateVersion] = useState(0); // 전역 상태 변경 감지용
|
const componentId = component.id;
|
||||||
|
console.log(`🔍 [${componentId}] SelectBasicComponent 마운트됨`);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
console.log(`🔍 [${componentId}] SelectBasicComponent 언마운트됨`);
|
||||||
|
};
|
||||||
|
}, [component.id]);
|
||||||
|
|
||||||
const selectRef = useRef<HTMLDivElement>(null);
|
const selectRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// 코드 카테고리 결정: 동적 카테고리 > 설정 카테고리
|
// 안정적인 쿼리 키를 위한 메모이제이션
|
||||||
const codeCategory = dynamicCodeCategory || config?.codeCategory;
|
const stableTableName = useMemo(() => component.tableName, [component.tableName]);
|
||||||
|
const stableColumnName = useMemo(() => component.columnName, [component.columnName]);
|
||||||
|
const staticCodeCategory = useMemo(() => config?.codeCategory, [config?.codeCategory]);
|
||||||
|
|
||||||
|
// 🚀 React Query: 테이블 코드 카테고리 조회
|
||||||
|
const { data: dynamicCodeCategory } = useTableCodeCategory(stableTableName, stableColumnName);
|
||||||
|
|
||||||
|
// 코드 카테고리 결정: 동적 카테고리 > 설정 카테고리 (메모이제이션)
|
||||||
|
const codeCategory = useMemo(() => {
|
||||||
|
const category = dynamicCodeCategory || staticCodeCategory;
|
||||||
|
console.log(`🔑 [${component.id}] 코드 카테고리 결정:`, {
|
||||||
|
dynamicCodeCategory,
|
||||||
|
staticCodeCategory,
|
||||||
|
finalCategory: category,
|
||||||
|
});
|
||||||
|
return category;
|
||||||
|
}, [dynamicCodeCategory, staticCodeCategory, component.id]);
|
||||||
|
|
||||||
|
// 🚀 React Query: 코드 옵션 조회 (안정적인 enabled 조건)
|
||||||
|
const isCodeCategoryValid = useMemo(() => {
|
||||||
|
return !!codeCategory && codeCategory !== "none";
|
||||||
|
}, [codeCategory]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
options: codeOptions,
|
||||||
|
isLoading: isLoadingCodes,
|
||||||
|
isFetching,
|
||||||
|
} = useCodeOptions(codeCategory, isCodeCategoryValid);
|
||||||
|
|
||||||
|
// React Query 상태 디버깅
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(`🎯 [${component.id}] React Query 상태:`, {
|
||||||
|
codeCategory,
|
||||||
|
isCodeCategoryValid,
|
||||||
|
codeOptionsLength: codeOptions.length,
|
||||||
|
isLoadingCodes,
|
||||||
|
isFetching,
|
||||||
|
cacheStatus: isFetching ? "FETCHING" : "FROM_CACHE",
|
||||||
|
});
|
||||||
|
}, [component.id, codeCategory, isCodeCategoryValid, codeOptions.length, isLoadingCodes, isFetching]);
|
||||||
|
|
||||||
// 외부 value prop 변경 시 selectedValue 업데이트
|
// 외부 value prop 변경 시 selectedValue 업데이트
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -293,109 +159,11 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
}
|
}
|
||||||
}, [externalValue, config?.value]);
|
}, [externalValue, config?.value]);
|
||||||
|
|
||||||
// 🚀 전역 상태 구독 및 동기화
|
// ✅ React Query가 자동으로 처리하므로 복잡한 전역 상태 관리 제거
|
||||||
useEffect(() => {
|
// - 캐싱: React Query가 자동 관리 (10분 staleTime, 30분 gcTime)
|
||||||
const updateFromGlobalState = () => {
|
// - 중복 요청 방지: 동일한 queryKey에 대해 자동 중복 제거
|
||||||
setGlobalStateVersion((prev) => prev + 1);
|
// - 상태 동기화: 모든 컴포넌트가 같은 캐시 공유
|
||||||
};
|
|
||||||
|
|
||||||
// 전역 상태 변경 구독
|
|
||||||
globalState.subscribers.add(updateFromGlobalState);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
globalState.subscribers.delete(updateFromGlobalState);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 🔧 테이블 코드 카테고리 로드 (전역 상태 사용)
|
|
||||||
const loadTableCodeCategory = async () => {
|
|
||||||
if (!component.tableName || !component.columnName) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log(`🔍 [${component.id}] 전역 테이블 코드 카테고리 조회`);
|
|
||||||
const category = await loadGlobalTableCodeCategory(component.tableName, component.columnName);
|
|
||||||
|
|
||||||
if (category !== dynamicCodeCategory) {
|
|
||||||
console.log(`🔄 [${component.id}] 코드 카테고리 변경: ${dynamicCodeCategory} → ${category}`);
|
|
||||||
setDynamicCodeCategory(category);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ [${component.id}] 테이블 코드 카테고리 조회 실패:`, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 🔧 코드 옵션 로드 (전역 상태 사용)
|
|
||||||
const loadCodeOptions = async (category: string) => {
|
|
||||||
if (!category || category === "none") {
|
|
||||||
setCodeOptions([]);
|
|
||||||
setIsLoadingCodes(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setIsLoadingCodes(true);
|
|
||||||
console.log(`🔄 [${component.id}] 전역 코드 옵션 로딩: ${category}`);
|
|
||||||
|
|
||||||
const options = await loadGlobalCodeOptions(category);
|
|
||||||
setCodeOptions(options);
|
|
||||||
|
|
||||||
console.log(`✅ [${component.id}] 코드 옵션 업데이트 완료: ${category} (${options.length}개)`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ [${component.id}] 코드 옵션 로딩 실패:`, error);
|
|
||||||
setCodeOptions([]);
|
|
||||||
} finally {
|
|
||||||
setIsLoadingCodes(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 초기 테이블 코드 카테고리 로드
|
|
||||||
useEffect(() => {
|
|
||||||
loadTableCodeCategory();
|
|
||||||
}, [component.tableName, component.columnName]);
|
|
||||||
|
|
||||||
// 전역 상태 변경 시 동기화
|
|
||||||
useEffect(() => {
|
|
||||||
if (component.tableName && component.columnName) {
|
|
||||||
const key = `${component.tableName}.${component.columnName}`;
|
|
||||||
const cachedCategory = globalState.tableCategories.get(key);
|
|
||||||
|
|
||||||
if (cachedCategory && cachedCategory !== dynamicCodeCategory) {
|
|
||||||
console.log(`🔄 [${component.id}] 전역 상태 동기화: ${dynamicCodeCategory} → ${cachedCategory}`);
|
|
||||||
setDynamicCodeCategory(cachedCategory || null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [globalStateVersion, component.tableName, component.columnName]);
|
|
||||||
|
|
||||||
// 코드 카테고리 변경 시 옵션 로드
|
|
||||||
useEffect(() => {
|
|
||||||
if (codeCategory && codeCategory !== "none") {
|
|
||||||
// 전역 캐시된 옵션부터 확인
|
|
||||||
const cached = globalState.codeOptions.get(codeCategory);
|
|
||||||
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
|
|
||||||
console.log(`🚀 [${component.id}] 전역 캐시 즉시 적용: ${codeCategory} (${cached.options.length}개)`);
|
|
||||||
setCodeOptions(cached.options);
|
|
||||||
setIsLoadingCodes(false);
|
|
||||||
} else {
|
|
||||||
loadCodeOptions(codeCategory);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setCodeOptions([]);
|
|
||||||
setIsLoadingCodes(false);
|
|
||||||
}
|
|
||||||
}, [codeCategory]);
|
|
||||||
|
|
||||||
// 전역 상태에서 코드 옵션 변경 감지
|
|
||||||
useEffect(() => {
|
|
||||||
if (codeCategory) {
|
|
||||||
const cached = globalState.codeOptions.get(codeCategory);
|
|
||||||
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
|
|
||||||
if (JSON.stringify(cached.options) !== JSON.stringify(codeOptions)) {
|
|
||||||
console.log(`🔄 [${component.id}] 전역 옵션 변경 감지: ${codeCategory}`);
|
|
||||||
setCodeOptions(cached.options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [globalStateVersion, codeCategory]);
|
|
||||||
|
|
||||||
// 선택된 값에 따른 라벨 업데이트
|
// 선택된 값에 따른 라벨 업데이트
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -438,41 +206,20 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
}
|
}
|
||||||
}, [selectedValue, codeOptions, config.options]);
|
}, [selectedValue, codeOptions, config.options]);
|
||||||
|
|
||||||
// 클릭 이벤트 핸들러 (전역 상태 새로고침)
|
// 클릭 이벤트 핸들러 (React Query로 간소화)
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
if (isDesignMode) return;
|
if (isDesignMode) return;
|
||||||
|
|
||||||
console.log(`🖱️ [${component.id}] 드롭다운 토글: ${isOpen} → ${!isOpen}`);
|
console.log(`🖱️ [${component.id}] 드롭다운 토글 (React Query): ${isOpen} → ${!isOpen}`);
|
||||||
console.log(`📊 [${component.id}] 현재 상태:`, {
|
console.log(`📊 [${component.id}] 현재 상태:`, {
|
||||||
isDesignMode,
|
codeCategory,
|
||||||
isLoadingCodes,
|
isLoadingCodes,
|
||||||
allOptionsLength: allOptions.length,
|
codeOptionsLength: codeOptions.length,
|
||||||
allOptions: allOptions.map((o) => ({ value: o.value, label: o.label })),
|
tableName: component.tableName,
|
||||||
|
columnName: component.columnName,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 드롭다운을 열 때 전역 상태 새로고침
|
// React Query가 자동으로 캐시 관리하므로 수동 새로고침 불필요
|
||||||
if (!isOpen) {
|
|
||||||
console.log(`🖱️ [${component.id}] 셀렉트박스 클릭 - 전역 상태 새로고침`);
|
|
||||||
|
|
||||||
// 테이블 설정 캐시 무효화 후 재로드
|
|
||||||
if (component.tableName && component.columnName) {
|
|
||||||
const key = `${component.tableName}.${component.columnName}`;
|
|
||||||
globalState.tableCategories.delete(key);
|
|
||||||
|
|
||||||
// 현재 코드 카테고리의 캐시도 무효화
|
|
||||||
if (dynamicCodeCategory) {
|
|
||||||
globalState.codeOptions.delete(dynamicCodeCategory);
|
|
||||||
console.log(`🗑️ [${component.id}] 코드 옵션 캐시 무효화: ${dynamicCodeCategory}`);
|
|
||||||
|
|
||||||
// 강제로 새로운 API 호출 수행
|
|
||||||
console.log(`🔄 [${component.id}] 강제 코드 옵션 재로드 시작: ${dynamicCodeCategory}`);
|
|
||||||
loadCodeOptions(dynamicCodeCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadTableCodeCategory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -519,45 +266,19 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
};
|
};
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
// 🚀 실시간 업데이트를 위한 이벤트 리스너
|
// ✅ React Query가 자동으로 처리하므로 수동 이벤트 리스너 불필요
|
||||||
useEffect(() => {
|
// - refetchOnWindowFocus: true (기본값)
|
||||||
const handleFocus = () => {
|
// - refetchOnReconnect: true (기본값)
|
||||||
console.log(`👁️ [${component.id}] 윈도우 포커스 - 전역 상태 새로고침`);
|
// - staleTime으로 적절한 캐시 관리
|
||||||
if (component.tableName && component.columnName) {
|
|
||||||
const key = `${component.tableName}.${component.columnName}`;
|
|
||||||
globalState.tableCategories.delete(key); // 캐시 무효화
|
|
||||||
loadTableCodeCategory();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleVisibilityChange = () => {
|
|
||||||
if (!document.hidden) {
|
|
||||||
console.log(`👁️ [${component.id}] 페이지 가시성 변경 - 전역 상태 새로고침`);
|
|
||||||
if (component.tableName && component.columnName) {
|
|
||||||
const key = `${component.tableName}.${component.columnName}`;
|
|
||||||
globalState.tableCategories.delete(key); // 캐시 무효화
|
|
||||||
loadTableCodeCategory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("focus", handleFocus);
|
|
||||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("focus", handleFocus);
|
|
||||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
||||||
};
|
|
||||||
}, [component.tableName, component.columnName]);
|
|
||||||
|
|
||||||
// 모든 옵션 가져오기
|
// 모든 옵션 가져오기
|
||||||
const getAllOptions = () => {
|
const getAllOptions = () => {
|
||||||
const configOptions = config.options || [];
|
const configOptions = config.options || [];
|
||||||
console.log(`🔧 [${component.id}] 옵션 병합:`, {
|
console.log(`🔧 [${component.id}] 옵션 병합:`, {
|
||||||
codeOptionsLength: codeOptions.length,
|
codeOptionsLength: codeOptions.length,
|
||||||
codeOptions: codeOptions.map((o) => ({ value: o.value, label: o.label })),
|
codeOptions: codeOptions.map((o: Option) => ({ value: o.value, label: o.label })),
|
||||||
configOptionsLength: configOptions.length,
|
configOptionsLength: configOptions.length,
|
||||||
configOptions: configOptions.map((o) => ({ value: o.value, label: o.label })),
|
configOptions: configOptions.map((o: Option) => ({ value: o.value, label: o.label })),
|
||||||
});
|
});
|
||||||
return [...codeOptions, ...configOptions];
|
return [...codeOptions, ...configOptions];
|
||||||
};
|
};
|
||||||
|
|
@ -649,7 +370,7 @@ const SelectBasicComponent: React.FC<SelectBasicComponentProps> = ({
|
||||||
isDesignMode,
|
isDesignMode,
|
||||||
isLoadingCodes,
|
isLoadingCodes,
|
||||||
allOptionsLength: allOptions.length,
|
allOptionsLength: allOptions.length,
|
||||||
allOptions: allOptions.map((o) => ({ value: o.value, label: o.label })),
|
allOptions: allOptions.map((o: Option) => ({ value: o.value, label: o.label })),
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
})()}
|
})()}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue