diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index ba0f721f..93111ba8 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -352,13 +352,13 @@ export default function ScreenViewPage() { }, onFormDataChange: (fieldName, value) => { console.log(`๐ŸŽฏ page.tsx onFormDataChange ํ˜ธ์ถœ: ${fieldName} = "${value}"`); - console.log(`๐Ÿ“‹ ํ˜„์žฌ formData:`, formData); + console.log("๐Ÿ“‹ ํ˜„์žฌ formData:", formData); setFormData((prev) => { const newFormData = { ...prev, [fieldName]: value, }; - console.log(`๐Ÿ“ ์—…๋ฐ์ดํŠธ๋œ formData:`, newFormData); + console.log("๐Ÿ“ ์—…๋ฐ์ดํŠธ๋œ formData:", newFormData); return newFormData; }); }, diff --git a/frontend/hooks/queries/useCodes.ts b/frontend/hooks/queries/useCodes.ts index 48b20641..14c6ee5d 100644 --- a/frontend/hooks/queries/useCodes.ts +++ b/frontend/hooks/queries/useCodes.ts @@ -1,31 +1,107 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { commonCodeApi } from "@/lib/api/commonCode"; -import { queryKeys } from "@/lib/queryKeys"; -import type { CodeFilter, CreateCodeData, UpdateCodeData } from "@/lib/schemas/commonCode"; +import { tableTypeApi } from "@/lib/api/screen"; +import { useMemo } from "react"; -/** - * ์ฝ”๋“œ ๋ชฉ๋ก ์กฐํšŒ ํ›… - */ -export function useCodes(categoryCode: string, filters?: CodeFilter) { +// Query Keys +export const queryKeys = { + codes: { + 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({ - queryKey: queryKeys.codes.list(categoryCode, filters), - queryFn: () => commonCodeApi.codes.getList(categoryCode, filters), - select: (data) => data.data || [], - enabled: !!categoryCode, // categoryCode๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ์‹คํ–‰ + queryKey: queryKeys.tables.codeCategory(tableName || "", columnName || ""), + queryFn: async () => { + if (!tableName || !columnName) return null; + + 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() { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ categoryCode, data }: { categoryCode: string; data: CreateCodeData }) => + mutationFn: ({ categoryCode, data }: { categoryCode: string; data: any }) => commonCodeApi.codes.create(categoryCode, data), onSuccess: (_, variables) => { - // ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋ชจ๋“  ์ฝ”๋“œ ๊ด€๋ จ ์ฟผ๋ฆฌ ๋ฌดํšจํ™” (์ผ๋ฐ˜ ๋ชฉ๋ก + ๋ฌดํ•œ ์Šคํฌ๋กค) + // ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋ชจ๋“  ์ฝ”๋“œ ๊ด€๋ จ ์ฟผ๋ฆฌ ๋ฌดํšจํ™” queryClient.invalidateQueries({ queryKey: queryKeys.codes.all, }); @@ -34,15 +110,10 @@ export function useCreateCode() { queryKey: queryKeys.codes.infiniteList(variables.categoryCode), }); }, - onError: (error) => { - console.error("์ฝ”๋“œ ์ƒ์„ฑ ์‹คํŒจ:", error); - }, }); } -/** - * ์ฝ”๋“œ ์ˆ˜์ • ๋ฎคํ…Œ์ด์…˜ ํ›… - */ +// ์ฝ”๋“œ ์ˆ˜์ • export function useUpdateCode() { const queryClient = useQueryClient(); @@ -54,14 +125,14 @@ export function useUpdateCode() { }: { categoryCode: string; codeValue: string; - data: UpdateCodeData; + data: any; }) => commonCodeApi.codes.update(categoryCode, codeValue, data), onSuccess: (_, variables) => { // ํ•ด๋‹น ์ฝ”๋“œ ์ƒ์„ธ ์ฟผ๋ฆฌ ๋ฌดํšจํ™” queryClient.invalidateQueries({ queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue), }); - // ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋ชจ๋“  ์ฝ”๋“œ ๊ด€๋ จ ์ฟผ๋ฆฌ ๋ฌดํšจํ™” (์ผ๋ฐ˜ ๋ชฉ๋ก + ๋ฌดํ•œ ์Šคํฌ๋กค) + // ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋ชจ๋“  ์ฝ”๋“œ ๊ด€๋ จ ์ฟผ๋ฆฌ ๋ฌดํšจํ™” queryClient.invalidateQueries({ queryKey: queryKeys.codes.all, }); @@ -70,15 +141,10 @@ export function useUpdateCode() { queryKey: queryKeys.codes.infiniteList(variables.categoryCode), }); }, - onError: (error) => { - console.error("์ฝ”๋“œ ์ˆ˜์ • ์‹คํŒจ:", error); - }, }); } -/** - * ์ฝ”๋“œ ์‚ญ์ œ ๋ฎคํ…Œ์ด์…˜ ํ›… - */ +// ์ฝ”๋“œ ์‚ญ์ œ export function useDeleteCode() { const queryClient = useQueryClient(); @@ -98,15 +164,10 @@ export function useDeleteCode() { queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue), }); }, - onError: (error) => { - console.error("์ฝ”๋“œ ์‚ญ์ œ ์‹คํŒจ:", error); - }, }); } -/** - * ์ฝ”๋“œ ์ˆœ์„œ ๋ณ€๊ฒฝ ๋ฎคํ…Œ์ด์…˜ ํ›… - */ +// ์ฝ”๋“œ ์ˆœ์„œ ๋ณ€๊ฒฝ export function useReorderCodes() { const queryClient = useQueryClient(); @@ -126,7 +187,7 @@ export function useReorderCodes() { const previousCodes = queryClient.getQueryData(queryKeys.codes.list(categoryCode)); // 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; // ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์‚ฌํ•˜๊ณ  sort_order๋งŒ ์—…๋ฐ์ดํŠธ @@ -135,8 +196,8 @@ export function useReorderCodes() { 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 ์‘๋‹ต ํ˜•ํƒœ๋กœ ์บ์‹œ์— ์ €์žฅ (๊ธฐ์กด ๊ตฌ์กฐ ์œ ์ง€) queryClient.setQueryData(queryKeys.codes.list(categoryCode), { @@ -145,11 +206,9 @@ export function useReorderCodes() { }); } - // ๋กค๋ฐฑ์šฉ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜ return { previousCodes }; }, - onError: (error, variables, context) => { - console.error("์ฝ”๋“œ ์ˆœ์„œ ๋ณ€๊ฒฝ ์‹คํŒจ:", error); + onError: (err, variables, context) => { // ์—๋Ÿฌ ์‹œ ์ด์ „ ๋ฐ์ดํ„ฐ๋กœ ๋กค๋ฐฑ if (context?.previousCodes) { queryClient.setQueryData(queryKeys.codes.list(variables.categoryCode), context.previousCodes); @@ -166,4 +225,4 @@ export function useReorderCodes() { }); }, }); -} +} \ No newline at end of file diff --git a/frontend/lib/registry/DynamicWebTypeRenderer.tsx b/frontend/lib/registry/DynamicWebTypeRenderer.tsx index a90b045e..0319e44a 100644 --- a/frontend/lib/registry/DynamicWebTypeRenderer.tsx +++ b/frontend/lib/registry/DynamicWebTypeRenderer.tsx @@ -18,13 +18,13 @@ export const DynamicWebTypeRenderer: React.FC = ({ }) => { // ๋ชจ๋“  hooks๋ฅผ ๋จผ์ € ํ˜ธ์ถœ (์กฐ๊ฑด๋ถ€ return ์ด์ „์—) const { webTypes } = useWebTypes({ active: "Y" }); - + // ๋””๋ฒ„๊น…: ์ „๋‹ฌ๋ฐ›์€ ์›นํƒ€์ž…๊ณผ props ์ •๋ณด ๋กœ๊น… console.log("๐Ÿ” DynamicWebTypeRenderer ํ˜ธ์ถœ:", { webType, propsKeys: Object.keys(props), component: props.component, - isFileComponent: props.component?.type === 'file' || webType === 'file' + isFileComponent: props.component?.type === "file" || webType === "file", }); const webTypeDefinition = useMemo(() => { @@ -70,21 +70,21 @@ export const DynamicWebTypeRenderer: React.FC = ({ if (dbWebType?.component_name) { try { console.log(`์›นํƒ€์ž… "${webType}" โ†’ DB ์ง€์ • ์ปดํฌ๋„ŒํŠธ "${dbWebType.component_name}" ์‚ฌ์šฉ`); - console.log(`DB ์›นํƒ€์ž… ์ •๋ณด:`, dbWebType); - + console.log("DB ์›นํƒ€์ž… ์ •๋ณด:", dbWebType); + // FileWidget์˜ ๊ฒฝ์šฐ FileUploadComponent ์ง์ ‘ ์‚ฌ์šฉ if (dbWebType.component_name === "FileWidget" || webType === "file") { const { FileUploadComponent } = require("@/lib/registry/components/file-upload/FileUploadComponent"); - console.log(`โœ… FileWidget โ†’ FileUploadComponent ์‚ฌ์šฉ`); + console.log("โœ… FileWidget โ†’ FileUploadComponent ์‚ฌ์šฉ"); return ; } - + // ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ๊ธฐ์กด ๋กœ์ง ์œ ์ง€ // const ComponentByName = getWidgetComponentByName(dbWebType.component_name); // console.log(`์ปดํฌ๋„ŒํŠธ "${dbWebType.component_name}" ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๋“œ๋จ:`, ComponentByName); // return ; console.warn(`DB ์ง€์ • ์ปดํฌ๋„ŒํŠธ "${dbWebType.component_name}" ๊ธฐ๋Šฅ ์ž„์‹œ ๋น„ํ™œ์„ฑํ™” (FileWidget ์ œ์™ธ)`); - + // ๋กœ๋”ฉ ์ค‘ ๋ฉ”์‹œ์ง€ ๋Œ€์‹  ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋กœ ํด๋ฐฑ // return
์ปดํฌ๋„ŒํŠธ ๋กœ๋”ฉ ์ค‘...
; } catch (error) { @@ -99,7 +99,7 @@ export const DynamicWebTypeRenderer: React.FC = ({ // ํŒŒ์ผ ์›นํƒ€์ž…์˜ ๊ฒฝ์šฐ FileUploadComponent ์ง์ ‘ ์‚ฌ์šฉ if (webType === "file") { const { FileUploadComponent } = require("@/lib/registry/components/file-upload/FileUploadComponent"); - console.log(`โœ… ํŒŒ์ผ ์›นํƒ€์ž… โ†’ FileUploadComponent ์‚ฌ์šฉ`); + console.log("โœ… ํŒŒ์ผ ์›นํƒ€์ž… โ†’ FileUploadComponent ์‚ฌ์šฉ"); return ; } @@ -127,44 +127,39 @@ export const DynamicWebTypeRenderer: React.FC = ({ // 3์ˆœ์œ„: ์›นํƒ€์ž…๋ช…์œผ๋กœ ์ž๋™ ๋งคํ•‘ (ํด๋ฐฑ) try { console.warn(`์›นํƒ€์ž… "${webType}" โ†’ ์ž๋™ ๋งคํ•‘ ํด๋ฐฑ ์‚ฌ์šฉ`); - + // ํŒŒ์ผ ์›นํƒ€์ž…์˜ ๊ฒฝ์šฐ FileUploadComponent ์ง์ ‘ ์‚ฌ์šฉ (์ตœ์ข… ํด๋ฐฑ) if (webType === "file") { const { FileUploadComponent } = require("@/lib/registry/components/file-upload/FileUploadComponent"); - console.log(`โœ… ํด๋ฐฑ: ํŒŒ์ผ ์›นํƒ€์ž… โ†’ FileUploadComponent ์‚ฌ์šฉ`); + console.log("โœ… ํด๋ฐฑ: ํŒŒ์ผ ์›นํƒ€์ž… โ†’ FileUploadComponent ์‚ฌ์šฉ"); return ; } - + // ํ…์ŠคํŠธ ์ž…๋ ฅ ์›นํƒ€์ž…๋“ค if (["text", "email", "password", "tel"].includes(webType)) { const { TextInputComponent } = require("@/lib/registry/components/text-input/TextInputComponent"); console.log(`โœ… ํด๋ฐฑ: ${webType} ์›นํƒ€์ž… โ†’ TextInputComponent ์‚ฌ์šฉ`); return ; } - + // ์ˆซ์ž ์ž…๋ ฅ ์›นํƒ€์ž…๋“ค if (["number", "decimal"].includes(webType)) { const { NumberInputComponent } = require("@/lib/registry/components/number-input/NumberInputComponent"); console.log(`โœ… ํด๋ฐฑ: ${webType} ์›นํƒ€์ž… โ†’ NumberInputComponent ์‚ฌ์šฉ`); return ; } - + // ๋‚ ์งœ ์ž…๋ ฅ ์›นํƒ€์ž…๋“ค if (["date", "datetime", "time"].includes(webType)) { const { DateInputComponent } = require("@/lib/registry/components/date-input/DateInputComponent"); console.log(`โœ… ํด๋ฐฑ: ${webType} ์›นํƒ€์ž… โ†’ DateInputComponent ์‚ฌ์šฉ`); return ; } - + // ๊ธฐ๋ณธ ํด๋ฐฑ: Input ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ const { Input } = require("@/components/ui/input"); console.log(`โœ… ํด๋ฐฑ: ${webType} ์›นํƒ€์ž… โ†’ ๊ธฐ๋ณธ Input ์‚ฌ์šฉ`); - return ; + return ; } catch (error) { console.error(`์›นํƒ€์ž… "${webType}" ํด๋ฐฑ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง ์‹คํŒจ:`, error); return ( diff --git a/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx b/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx index dedba749..7d87a4a9 100644 --- a/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx +++ b/frontend/lib/registry/components/select-basic/SelectBasicComponent.tsx @@ -1,7 +1,6 @@ -import React, { useState, useEffect, useRef } from "react"; -import { commonCodeApi } from "../../../api/commonCode"; -import { tableTypeApi } from "../../../api/screen"; +import React, { useState, useEffect, useRef, useMemo } from "react"; import { filterDOMProps } from "@/lib/utils/domPropsFilter"; +import { useCodeOptions, useTableCodeCategory } from "@/hooks/queries/useCodes"; interface Option { value: string; @@ -26,210 +25,10 @@ export interface SelectBasicComponentProps { [key: string]: any; } -// ๐Ÿš€ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ: ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ณต์œ ํ•˜๋Š” ์ƒํƒœ -interface GlobalState { - tableCategories: Map; // tableName.columnName -> codeCategory - codeOptions: Map; // codeCategory -> options - activeRequests: Map>; // ์ง„ํ–‰ ์ค‘์ธ ์š”์ฒญ๋“ค - 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 => { - 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 => { - 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; -}; +// โœ… React Query๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ค‘๋ณต ์š”์ฒญ ๋ฐฉ์ง€ ๋ฐ ์ž๋™ ์บ์‹ฑ ์ฒ˜๋ฆฌ +// - ๋™์ผํ•œ queryKey์— ๋Œ€ํ•ด์„œ๋Š” ์ž๋™์œผ๋กœ ์ค‘๋ณต ์š”์ฒญ ์ œ๊ฑฐ +// - 10๋ถ„ staleTime์œผ๋กœ ์ ์ ˆํ•œ ์บ์‹œ ๊ด€๋ฆฌ +// - 30๋ถ„ gcTime์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ ํ™•๋ณด const SelectBasicComponent: React.FC = ({ component, @@ -248,6 +47,22 @@ const SelectBasicComponent: React.FC = ({ value: externalValue, // ๋ช…์‹œ์ ์œผ๋กœ value prop ๋ฐ›๊ธฐ ...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); // webTypeConfig ๋˜๋Š” componentConfig ์‚ฌ์šฉ (DynamicWebTypeRenderer ํ˜ธํ™˜์„ฑ) @@ -257,23 +72,74 @@ const SelectBasicComponent: React.FC = ({ const [selectedValue, setSelectedValue] = useState(externalValue || config?.value || ""); const [selectedLabel, setSelectedLabel] = useState(""); - console.log("๐Ÿ” SelectBasicComponent ์ดˆ๊ธฐํ™”:", { + console.log("๐Ÿ” SelectBasicComponent ์ดˆ๊ธฐํ™” (React Query):", { componentId: component.id, externalValue, componentConfigValue: componentConfig?.value, webTypeConfigValue: (props as any).webTypeConfig?.value, configValue: 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([]); - const [isLoadingCodes, setIsLoadingCodes] = useState(false); - const [dynamicCodeCategory, setDynamicCodeCategory] = useState(null); - const [globalStateVersion, setGlobalStateVersion] = useState(0); // ์ „์—ญ ์ƒํƒœ ๋ณ€๊ฒฝ ๊ฐ์ง€์šฉ + + // ์–ธ๋งˆ์šดํŠธ ์‹œ ๋กœ๊น… + useEffect(() => { + const componentId = component.id; + console.log(`๐Ÿ” [${componentId}] SelectBasicComponent ๋งˆ์šดํŠธ๋จ`); + + return () => { + console.log(`๐Ÿ” [${componentId}] SelectBasicComponent ์–ธ๋งˆ์šดํŠธ๋จ`); + }; + }, [component.id]); + const selectRef = useRef(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 ์—…๋ฐ์ดํŠธ useEffect(() => { @@ -293,109 +159,11 @@ const SelectBasicComponent: React.FC = ({ } }, [externalValue, config?.value]); - // ๐Ÿš€ ์ „์—ญ ์ƒํƒœ ๊ตฌ๋… ๋ฐ ๋™๊ธฐํ™” - useEffect(() => { - const updateFromGlobalState = () => { - setGlobalStateVersion((prev) => prev + 1); - }; + // โœ… React Query๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ๋ณต์žกํ•œ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ ์ œ๊ฑฐ + // - ์บ์‹ฑ: React Query๊ฐ€ ์ž๋™ ๊ด€๋ฆฌ (10๋ถ„ staleTime, 30๋ถ„ gcTime) + // - ์ค‘๋ณต ์š”์ฒญ ๋ฐฉ์ง€: ๋™์ผํ•œ queryKey์— ๋Œ€ํ•ด ์ž๋™ ์ค‘๋ณต ์ œ๊ฑฐ + // - ์ƒํƒœ ๋™๊ธฐํ™”: ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฐ™์€ ์บ์‹œ ๊ณต์œ  - // ์ „์—ญ ์ƒํƒœ ๋ณ€๊ฒฝ ๊ตฌ๋… - 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(() => { @@ -438,41 +206,20 @@ const SelectBasicComponent: React.FC = ({ } }, [selectedValue, codeOptions, config.options]); - // ํด๋ฆญ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ (์ „์—ญ ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ) + // ํด๋ฆญ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ (React Query๋กœ ๊ฐ„์†Œํ™”) const handleToggle = () => { if (isDesignMode) return; - console.log(`๐Ÿ–ฑ๏ธ [${component.id}] ๋“œ๋กญ๋‹ค์šด ํ† ๊ธ€: ${isOpen} โ†’ ${!isOpen}`); + console.log(`๐Ÿ–ฑ๏ธ [${component.id}] ๋“œ๋กญ๋‹ค์šด ํ† ๊ธ€ (React Query): ${isOpen} โ†’ ${!isOpen}`); console.log(`๐Ÿ“Š [${component.id}] ํ˜„์žฌ ์ƒํƒœ:`, { - isDesignMode, + codeCategory, isLoadingCodes, - allOptionsLength: allOptions.length, - allOptions: allOptions.map((o) => ({ value: o.value, label: o.label })), + codeOptionsLength: codeOptions.length, + tableName: component.tableName, + columnName: component.columnName, }); - // ๋“œ๋กญ๋‹ค์šด์„ ์—ด ๋•Œ ์ „์—ญ ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ - 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(); - } - } - + // React Query๊ฐ€ ์ž๋™์œผ๋กœ ์บ์‹œ ๊ด€๋ฆฌํ•˜๋ฏ€๋กœ ์ˆ˜๋™ ์ƒˆ๋กœ๊ณ ์นจ ๋ถˆํ•„์š” setIsOpen(!isOpen); }; @@ -519,45 +266,19 @@ const SelectBasicComponent: React.FC = ({ }; }, [isOpen]); - // ๐Ÿš€ ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ - useEffect(() => { - const handleFocus = () => { - console.log(`๐Ÿ‘๏ธ [${component.id}] ์œˆ๋„์šฐ ํฌ์ปค์Šค - ์ „์—ญ ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ`); - 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]); + // โœ… React Query๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ์ˆ˜๋™ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋ถˆํ•„์š” + // - refetchOnWindowFocus: true (๊ธฐ๋ณธ๊ฐ’) + // - refetchOnReconnect: true (๊ธฐ๋ณธ๊ฐ’) + // - staleTime์œผ๋กœ ์ ์ ˆํ•œ ์บ์‹œ ๊ด€๋ฆฌ // ๋ชจ๋“  ์˜ต์…˜ ๊ฐ€์ ธ์˜ค๊ธฐ const getAllOptions = () => { const configOptions = config.options || []; console.log(`๐Ÿ”ง [${component.id}] ์˜ต์…˜ ๋ณ‘ํ•ฉ:`, { 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, - configOptions: configOptions.map((o) => ({ value: o.value, label: o.label })), + configOptions: configOptions.map((o: Option) => ({ value: o.value, label: o.label })), }); return [...codeOptions, ...configOptions]; }; @@ -649,7 +370,7 @@ const SelectBasicComponent: React.FC = ({ isDesignMode, isLoadingCodes, 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; })()} diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 03754e7f..6d0067c1 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -1,11 +1,85 @@ "use client"; -import React, { useState, useEffect, useMemo } from "react"; +import React, { useState, useEffect, useMemo, useCallback, useRef } from "react"; import { TableListConfig, ColumnConfig } from "./types"; import { WebType } from "@/types/common"; import { tableTypeApi } from "@/lib/api/screen"; import { entityJoinApi } from "@/lib/api/entityJoin"; import { codeCache } from "@/lib/caching/codeCache"; + +// ์ „์—ญ ํ…Œ์ด๋ธ” ์บ์‹œ +const tableColumnCache = new Map(); +const tableInfoCache = new Map(); +const TABLE_CACHE_TTL = 5 * 60 * 1000; // 5๋ถ„ + +// ์บ์‹œ ์ •๋ฆฌ ํ•จ์ˆ˜ +const cleanupTableCache = () => { + const now = Date.now(); + + // ์ปฌ๋Ÿผ ์บ์‹œ ์ •๋ฆฌ + for (const [key, entry] of tableColumnCache.entries()) { + if (now - entry.timestamp > TABLE_CACHE_TTL) { + tableColumnCache.delete(key); + } + } + + // ํ…Œ์ด๋ธ” ์ •๋ณด ์บ์‹œ ์ •๋ฆฌ + for (const [key, entry] of tableInfoCache.entries()) { + if (now - entry.timestamp > TABLE_CACHE_TTL) { + tableInfoCache.delete(key); + } + } +}; + +// ์ฃผ๊ธฐ์ ์œผ๋กœ ์บ์‹œ ์ •๋ฆฌ (10๋ถ„๋งˆ๋‹ค) +if (typeof window !== "undefined") { + setInterval(cleanupTableCache, 10 * 60 * 1000); +} + +// ์š”์ฒญ ๋””๋ฐ”์šด์‹ฑ์„ ์œ„ํ•œ ์ „์—ญ ํƒ€์ด๋จธ +const debounceTimers = new Map(); + +// ์ง„ํ–‰ ์ค‘์ธ ์š”์ฒญ ์ถ”์  (์ค‘๋ณต ์š”์ฒญ ๋ฐฉ์ง€) +const activeRequests = new Map>(); + +// ๋””๋ฐ”์šด์‹ฑ๋œ API ํ˜ธ์ถœ ํ•จ์ˆ˜ (์ค‘๋ณต ์š”์ฒญ ๋ฐฉ์ง€ ํฌํ•จ) +const debouncedApiCall = (key: string, fn: (...args: T) => Promise, delay: number = 300) => { + return (...args: T): Promise => { + // ์ด๋ฏธ ์ง„ํ–‰ ์ค‘์ธ ๋™์ผํ•œ ์š”์ฒญ์ด ์žˆ์œผ๋ฉด ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ + const activeRequest = activeRequests.get(key); + if (activeRequest) { + console.log(`๐Ÿ”„ ์ง„ํ–‰ ์ค‘์ธ ์š”์ฒญ ์žฌ์‚ฌ์šฉ: ${key}`); + return activeRequest as Promise; + } + + return new Promise((resolve, reject) => { + // ๊ธฐ์กด ํƒ€์ด๋จธ ์ œ๊ฑฐ + const existingTimer = debounceTimers.get(key); + if (existingTimer) { + clearTimeout(existingTimer); + } + + // ์ƒˆ ํƒ€์ด๋จธ ์„ค์ • + const timer = setTimeout(async () => { + try { + // ์š”์ฒญ ์‹œ์ž‘ ์‹œ ํ™œ์„ฑ ์š”์ฒญ์œผ๋กœ ๋“ฑ๋ก + const requestPromise = fn(...args); + activeRequests.set(key, requestPromise); + + const result = await requestPromise; + resolve(result); + } catch (error) { + reject(error); + } finally { + debounceTimers.delete(key); + activeRequests.delete(key); + } + }, delay); + + debounceTimers.set(key, timer); + }); + }; +}; import { useEntityJoinOptimization } from "@/lib/hooks/useEntityJoinOptimization"; import { Button } from "@/components/ui/button"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; @@ -90,12 +164,12 @@ export const TableListComponent: React.FC = ({ } as TableListConfig; // ๐ŸŽจ ๋™์  ์ƒ‰์ƒ ์„ค์ • (์†์„ฑํŽธ์ง‘ ๋ชจ๋‹ฌ์˜ "์ƒ‰์ƒ" ํ•„๋“œ์™€ ์—ฐ๋™) - const buttonColor = component.style?.labelColor || '#3b83f6'; // ๊ธฐ๋ณธ ํŒŒ๋ž€์ƒ‰ - const buttonTextColor = component.config?.buttonTextColor || '#ffffff'; + const buttonColor = component.style?.labelColor || "#3b83f6"; // ๊ธฐ๋ณธ ํŒŒ๋ž€์ƒ‰ + const buttonTextColor = component.config?.buttonTextColor || "#ffffff"; const buttonStyle = { backgroundColor: buttonColor, color: buttonTextColor, - borderColor: buttonColor + borderColor: buttonColor, }; // ๋””๋ฒ„๊น… ๋กœ๊ทธ ์ œ๊ฑฐ (์„ฑ๋Šฅ์ƒ ์ด์œ ๋กœ) @@ -119,6 +193,15 @@ export const TableListComponent: React.FC = ({ const [joinColumnMapping, setJoinColumnMapping] = useState>({}); const [columnMeta, setColumnMeta] = useState>({}); // ๐ŸŽฏ ์ปฌ๋Ÿผ ๋ฉ”ํƒ€์ •๋ณด (์›นํƒ€์ž…, ์ฝ”๋“œ์นดํ…Œ๊ณ ๋ฆฌ) + // ์ปฌ๋Ÿผ ์ •๋ณด ๋ฉ”๋ชจ์ด์ œ์ด์…˜ + const memoizedColumnInfo = useMemo(() => { + return { + labels: columnLabels, + meta: columnMeta, + visibleColumns: (tableConfig.columns || []).filter((col) => col.visible !== false), + }; + }, [columnLabels, columnMeta, tableConfig.columns]); + // ๊ณ ๊ธ‰ ํ•„ํ„ฐ ๊ด€๋ จ state const [searchValues, setSearchValues] = useState>({}); @@ -183,7 +266,7 @@ export const TableListComponent: React.FC = ({ // ๐ŸŽฏ ๊ฐ•์ œ๋กœ ๊ทธ๋ฆฌ๋“œ ์ปฌ๋Ÿผ์ˆ˜์— ๋งž๋Š” ํฌ๊ธฐ ์ ์šฉ (๋””์ž์ธ ๋ชจ๋“œ์—์„œ๋Š” ๋” ํฐ ํฌ๊ธฐ ํ—ˆ์šฉ) const gridColumns = component.gridColumns || 1; let calculatedWidth: string; - + if (isDesignMode) { // ๋””์ž์ธ ๋ชจ๋“œ์—์„œ๋Š” ๋” ํฐ ์ตœ์†Œ ํฌ๊ธฐ ์ ์šฉ if (gridColumns === 1) { @@ -208,8 +291,7 @@ export const TableListComponent: React.FC = ({ } } - // ๋””๋ฒ„๊น… ๋กœ๊ทธ ์ œ๊ฑฐ (์„ฑ๋Šฅ์ƒ ์ด์œ ๋กœ) - + // ๋””๋ฒ„๊น… ๋กœ๊ทธ ์ œ๊ฑฐ (์„ฑ๋Šฅ์ƒ ์ด์œ ๋กœ) // ์Šคํƒ€์ผ ๊ณ„์‚ฐ (์ปจํ…Œ์ด๋„ˆ์— ๋งž์ถค) const componentStyle: React.CSSProperties = { @@ -244,69 +326,117 @@ export const TableListComponent: React.FC = ({ // ์ž๋™ ๋†’์ด๋กœ ํ…Œ์ด๋ธ” ์ „์ฒด๋ฅผ ๊ฐ์Œˆ } - // ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ + // ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (์บ์‹ฑ ์ ์šฉ) const fetchColumnLabels = async () => { if (!tableConfig.selectedTable) return; - try { - const response = await tableTypeApi.getColumns(tableConfig.selectedTable); - // API ์‘๋‹ต ๊ตฌ์กฐ ํ™•์ธ ๋ฐ ์ปฌ๋Ÿผ ๋ฐฐ์—ด ์ถ”์ถœ - const columns = Array.isArray(response) ? response : (response as any).columns || []; - const labels: Record = {}; - const meta: Record = {}; + // ์บ์‹œ ํ™•์ธ + const cacheKey = tableConfig.selectedTable; + const cached = tableColumnCache.get(cacheKey); + const now = Date.now(); - columns.forEach((column: any) => { - // ๐ŸŽฏ Entity ์กฐ์ธ๋œ ์ปฌ๋Ÿผ์˜ ๊ฒฝ์šฐ ํ‘œ์‹œ ์ปฌ๋Ÿผ๋ช… ์‚ฌ์šฉ - let displayLabel = column.displayName || column.columnName; + let columns: any[] = []; - // Entity ํƒ€์ž…์ธ ๊ฒฝ์šฐ - if (column.webType === "entity") { - // ์šฐ์„  ๊ธฐ์ค€ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ๋ผ๋ฒจ์„ ์‚ฌ์šฉ - displayLabel = column.displayName || column.columnName; - console.log( - `๐ŸŽฏ Entity ์กฐ์ธ ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์„ค์ •: ${column.columnName} โ†’ "${displayLabel}" (๊ธฐ์ค€ ํ…Œ์ด๋ธ” ๋ผ๋ฒจ ์‚ฌ์šฉ)`, - ); - } + if (cached && now - cached.timestamp < TABLE_CACHE_TTL) { + console.log(`๐Ÿš€ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์บ์‹œ ์‚ฌ์šฉ: ${cacheKey}`); + columns = cached.columns; + } else { + try { + console.log(`๐Ÿ”„ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ API ํ˜ธ์ถœ: ${cacheKey}`); + const response = await tableTypeApi.getColumns(tableConfig.selectedTable); + // API ์‘๋‹ต ๊ตฌ์กฐ ํ™•์ธ ๋ฐ ์ปฌ๋Ÿผ ๋ฐฐ์—ด ์ถ”์ถœ + columns = Array.isArray(response) ? response : (response as any).columns || []; - labels[column.columnName] = displayLabel; - // ๐ŸŽฏ ์›นํƒ€์ž…๊ณผ ์ฝ”๋“œ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด ์ €์žฅ - meta[column.columnName] = { - webType: column.webType, - codeCategory: column.codeCategory, - }; - }); - - setColumnLabels(labels); - setColumnMeta(meta); - console.log("๐Ÿ” ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์„ค์ • ์™„๋ฃŒ:", labels); - console.log("๐Ÿ” ์ปฌ๋Ÿผ ๋ฉ”ํƒ€์ •๋ณด ์„ค์ • ์™„๋ฃŒ:", meta); - } catch (error) { - console.log("์ปฌ๋Ÿผ ๋ผ๋ฒจ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:", error); + // ์บ์‹œ ์ €์žฅ + tableColumnCache.set(cacheKey, { columns, timestamp: now }); + console.log(`โœ… ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ ์บ์‹œ ์ €์žฅ: ${cacheKey} (${columns.length}๊ฐœ ์ปฌ๋Ÿผ)`); + } catch (error) { + console.log("์ปฌ๋Ÿผ ๋ผ๋ฒจ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:", error); + return; + } } + + const labels: Record = {}; + const meta: Record = {}; + + columns.forEach((column: any) => { + // ๐ŸŽฏ Entity ์กฐ์ธ๋œ ์ปฌ๋Ÿผ์˜ ๊ฒฝ์šฐ ํ‘œ์‹œ ์ปฌ๋Ÿผ๋ช… ์‚ฌ์šฉ + let displayLabel = column.displayName || column.columnName; + + // Entity ํƒ€์ž…์ธ ๊ฒฝ์šฐ + if (column.webType === "entity") { + // ์šฐ์„  ๊ธฐ์ค€ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ ๋ผ๋ฒจ์„ ์‚ฌ์šฉ + displayLabel = column.displayName || column.columnName; + console.log(`๐ŸŽฏ Entity ์กฐ์ธ ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์„ค์ •: ${column.columnName} โ†’ "${displayLabel}" (๊ธฐ์ค€ ํ…Œ์ด๋ธ” ๋ผ๋ฒจ ์‚ฌ์šฉ)`); + } + + labels[column.columnName] = displayLabel; + // ๐ŸŽฏ ์›นํƒ€์ž…๊ณผ ์ฝ”๋“œ์นดํ…Œ๊ณ ๋ฆฌ ์ •๋ณด ์ €์žฅ + meta[column.columnName] = { + webType: column.webType, + codeCategory: column.codeCategory, + }; + }); + + setColumnLabels(labels); + setColumnMeta(meta); + console.log("๐Ÿ” ์ปฌ๋Ÿผ ๋ผ๋ฒจ ์„ค์ • ์™„๋ฃŒ:", labels); + console.log("๐Ÿ” ์ปฌ๋Ÿผ ๋ฉ”ํƒ€์ •๋ณด ์„ค์ • ์™„๋ฃŒ:", meta); }; // ๐ŸŽฏ ์ „์—ญ ์ฝ”๋“œ ์บ์‹œ ์‚ฌ์šฉ์œผ๋กœ ํ•จ์ˆ˜ ์ œ๊ฑฐ (codeCache.convertCodeToName ์‚ฌ์šฉ) - // ํ…Œ์ด๋ธ” ๋ผ๋ฒจ๋ช… ๊ฐ€์ ธ์˜ค๊ธฐ + // ํ…Œ์ด๋ธ” ๋ผ๋ฒจ๋ช… ๊ฐ€์ ธ์˜ค๊ธฐ (์บ์‹ฑ ์ ์šฉ) const fetchTableLabel = async () => { if (!tableConfig.selectedTable) return; - try { - const tables = await tableTypeApi.getTables(); - const table = tables.find((t: any) => t.tableName === tableConfig.selectedTable); - if (table && table.displayName && table.displayName !== table.tableName) { - setTableLabel(table.displayName); - } else { + // ์บ์‹œ ํ™•์ธ + const cacheKey = "all_tables"; + const cached = tableInfoCache.get(cacheKey); + const now = Date.now(); + + let tables: any[] = []; + + if (cached && now - cached.timestamp < TABLE_CACHE_TTL) { + console.log(`๐Ÿš€ ํ…Œ์ด๋ธ” ์ •๋ณด ์บ์‹œ ์‚ฌ์šฉ: ${cacheKey}`); + tables = cached.tables; + } else { + try { + console.log(`๐Ÿ”„ ํ…Œ์ด๋ธ” ์ •๋ณด API ํ˜ธ์ถœ: ${cacheKey}`); + tables = await tableTypeApi.getTables(); + + // ์บ์‹œ ์ €์žฅ + tableInfoCache.set(cacheKey, { tables, timestamp: now }); + console.log(`โœ… ํ…Œ์ด๋ธ” ์ •๋ณด ์บ์‹œ ์ €์žฅ: ${cacheKey} (${tables.length}๊ฐœ ํ…Œ์ด๋ธ”)`); + } catch (error) { + console.log("ํ…Œ์ด๋ธ” ๋ผ๋ฒจ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:", error); setTableLabel(tableConfig.selectedTable); + return; } - } catch (error) { - console.log("ํ…Œ์ด๋ธ” ๋ผ๋ฒจ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:", error); + } + + const table = tables.find((t: any) => t.tableName === tableConfig.selectedTable); + if (table && table.displayName && table.displayName !== table.tableName) { + setTableLabel(table.displayName); + } else { setTableLabel(tableConfig.selectedTable); } }; - // ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ - const fetchTableData = async () => { + // ๋””๋ฐ”์šด์‹ฑ๋œ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ + const fetchTableDataDebounced = useCallback( + debouncedApiCall( + `fetchTableData_${tableConfig.selectedTable}_${currentPage}_${localPageSize}`, + async () => { + return fetchTableDataInternal(); + }, + 200, // 200ms ๋””๋ฐ”์šด์Šค + ), + [tableConfig.selectedTable, currentPage, localPageSize, searchTerm, sortColumn, sortDirection, searchValues], + ); + + // ์‹ค์ œ ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ํ•จ์ˆ˜ + const fetchTableDataInternal = async () => { if (!tableConfig.selectedTable) { setData([]); return; @@ -494,14 +624,24 @@ export const TableListComponent: React.FC = ({ codeColumns.map(([col, meta]) => `${col}(${meta.codeCategory})`), ); - // ํ•„์š”ํ•œ ์ฝ”๋“œ ์นดํ…Œ๊ณ ๋ฆฌ๋“ค์„ ์ถ”์ถœํ•˜์—ฌ ๋ฐฐ์น˜ ๋กœ๋“œ - const categoryList = codeColumns.map(([, meta]) => meta.codeCategory).filter(Boolean) as string[]; + // ํ•„์š”ํ•œ ์ฝ”๋“œ ์นดํ…Œ๊ณ ๋ฆฌ๋“ค์„ ์ถ”์ถœํ•˜์—ฌ ๋ฐฐ์น˜ ๋กœ๋“œ (์ค‘๋ณต ์ œ๊ฑฐ) + const categoryList = [ + ...new Set(codeColumns.map(([, meta]) => meta.codeCategory).filter(Boolean)), + ] as string[]; - try { - await codeCache.preloadCodes(categoryList); - console.log("๐Ÿ“‹ ๋ชจ๋“  ์ฝ”๋“œ ์บ์‹œ ๋กœ๋“œ ์™„๋ฃŒ (์ „์—ญ ์บ์‹œ)"); - } catch (error) { - console.error("โŒ ์ฝ”๋“œ ์บ์‹œ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜:", error); + // ์ด๋ฏธ ์บ์‹œ๋œ ์นดํ…Œ๊ณ ๋ฆฌ๋Š” ์ œ์™ธ + const uncachedCategories = categoryList.filter((category) => !codeCache.getCodeSync(category)); + + if (uncachedCategories.length > 0) { + try { + console.log(`๐Ÿ“‹ ์ƒˆ๋กœ์šด ์ฝ”๋“œ ์นดํ…Œ๊ณ ๋ฆฌ ๋กœ๋”ฉ: ${uncachedCategories.join(", ")}`); + await codeCache.preloadCodes(uncachedCategories); + console.log("๐Ÿ“‹ ๋ชจ๋“  ์ฝ”๋“œ ์บ์‹œ ๋กœ๋“œ ์™„๋ฃŒ (์ „์—ญ ์บ์‹œ)"); + } catch (error) { + console.error("โŒ ์ฝ”๋“œ ์บ์‹œ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜:", error); + } + } else { + console.log("๐Ÿ“‹ ๋ชจ๋“  ์ฝ”๋“œ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์ด๋ฏธ ์บ์‹œ๋จ"); } } @@ -659,7 +799,7 @@ export const TableListComponent: React.FC = ({ // ํŽ˜์ด์ง€ ๋ณ€๊ฒฝ const handlePageChange = (newPage: number) => { setCurrentPage(newPage); - + // ์ƒ์„ธ์„ค์ •์— ํ˜„์žฌ ํŽ˜์ด์ง€ ์ •๋ณด ์•Œ๋ฆผ (ํ•„์š”ํ•œ ๊ฒฝ์šฐ) if (onConfigChange && tableConfig.pagination) { // console.log("๐Ÿ“ค ํ…Œ์ด๋ธ”์—์„œ ํŽ˜์ด์ง€ ๋ณ€๊ฒฝ์„ ์ƒ์„ธ์„ค์ •์— ์•Œ๋ฆผ:", newPage); @@ -695,18 +835,18 @@ export const TableListComponent: React.FC = ({ const handleAdvancedSearch = () => { setCurrentPage(1); - fetchTableData(); + fetchTableDataDebounced(); }; const handleClearAdvancedFilters = () => { setSearchValues({}); setCurrentPage(1); - fetchTableData(); + fetchTableDataDebounced(); }; // ์ƒˆ๋กœ๊ณ ์นจ const handleRefresh = () => { - fetchTableData(); + fetchTableDataDebounced(); }; // ์ฒดํฌ๋ฐ•์Šค ํ•ธ๋“ค๋Ÿฌ๋“ค @@ -808,7 +948,7 @@ export const TableListComponent: React.FC = ({ useEffect(() => { if (tableConfig.autoLoad && !isDesignMode) { - fetchTableData(); + fetchTableDataDebounced(); } }, [ tableConfig.selectedTable, @@ -832,7 +972,7 @@ export const TableListComponent: React.FC = ({ console.log("๐Ÿ”„ ์„ ํƒ ์ƒํƒœ ์ดˆ๊ธฐํ™” - ๋นˆ ๋ฐฐ์—ด ์ „๋‹ฌ"); onSelectedRowsChange?.([], []); // ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ๊ณ ์นจ - fetchTableData(); + fetchTableDataDebounced(); } }, [refreshKey]); @@ -844,7 +984,7 @@ export const TableListComponent: React.FC = ({ setLocalPageSize(tableConfig.pagination.pageSize); setCurrentPage(1); // ํŽ˜์ด์ง€๋ฅผ 1๋กœ ๋ฆฌ์…‹ } - + // ํ˜„์žฌ ํŽ˜์ด์ง€ ๋™๊ธฐํ™” (์ƒ์„ธ์„ค์ •์—์„œ ํŽ˜์ด์ง€๋ฅผ ์ง์ ‘ ๋ณ€๊ฒฝํ•œ ๊ฒฝ์šฐ) if (tableConfig.pagination?.currentPage && tableConfig.pagination.currentPage !== currentPage) { // console.log("๐Ÿ”„ ์ƒ์„ธ์„ค์ •์—์„œ ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ณ€๊ฒฝ ๊ฐ์ง€:", tableConfig.pagination.currentPage); @@ -987,7 +1127,14 @@ export const TableListComponent: React.FC = ({ return null; } - return ; + return ( + + ); }; // ์ฒดํฌ๋ฐ•์Šค ์…€ ๋ Œ๋”๋ง @@ -1066,41 +1213,41 @@ export const TableListComponent: React.FC = ({ const handleRowDragStart = (e: React.DragEvent, row: any, index: number) => { setIsDragging(true); setDraggedRowIndex(index); - + // ๋“œ๋ž˜๊ทธ ๋ฐ์ดํ„ฐ์— ๊ทธ๋ฆฌ๋“œ ์ •๋ณด ํฌํ•จ const dragData = { ...row, - _dragType: 'table-row', + _dragType: "table-row", _gridSize: { width: 4, height: 1 }, // ๊ธฐ๋ณธ ๊ทธ๋ฆฌ๋“œ ํฌ๊ธฐ (4์นธ ๋„ˆ๋น„, 1์นธ ๋†’์ด) - _snapToGrid: true + _snapToGrid: true, }; - - e.dataTransfer.setData('application/json', JSON.stringify(dragData)); - e.dataTransfer.effectAllowed = 'copy'; // move ๋Œ€์‹  copy๋กœ ๋ณ€๊ฒฝ - + + e.dataTransfer.setData("application/json", JSON.stringify(dragData)); + e.dataTransfer.effectAllowed = "copy"; // move ๋Œ€์‹  copy๋กœ ๋ณ€๊ฒฝ + // ๋“œ๋ž˜๊ทธ ์ด๋ฏธ์ง€๋ฅผ ๋” ๊น”๋”ํ•˜๊ฒŒ const dragElement = e.currentTarget as HTMLElement; - + // ์ปค์Šคํ…€ ๋“œ๋ž˜๊ทธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (์ €์žฅ ๋ฒ„ํŠผ๊ณผ ์–ด์šธ๋ฆฌ๋Š” ์Šคํƒ€์ผ) - const dragImage = document.createElement('div'); - dragImage.style.position = 'absolute'; - dragImage.style.top = '-1000px'; - dragImage.style.left = '-1000px'; - dragImage.style.background = 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)'; - dragImage.style.color = 'white'; - dragImage.style.padding = '12px 16px'; - dragImage.style.borderRadius = '8px'; - dragImage.style.fontSize = '14px'; - dragImage.style.fontWeight = '600'; - dragImage.style.boxShadow = '0 4px 12px rgba(59, 130, 246, 0.4)'; - dragImage.style.display = 'flex'; - dragImage.style.alignItems = 'center'; - dragImage.style.gap = '8px'; - dragImage.style.minWidth = '200px'; - dragImage.style.whiteSpace = 'nowrap'; - + const dragImage = document.createElement("div"); + dragImage.style.position = "absolute"; + dragImage.style.top = "-1000px"; + dragImage.style.left = "-1000px"; + dragImage.style.background = "linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)"; + dragImage.style.color = "white"; + dragImage.style.padding = "12px 16px"; + dragImage.style.borderRadius = "8px"; + dragImage.style.fontSize = "14px"; + dragImage.style.fontWeight = "600"; + dragImage.style.boxShadow = "0 4px 12px rgba(59, 130, 246, 0.4)"; + dragImage.style.display = "flex"; + dragImage.style.alignItems = "center"; + dragImage.style.gap = "8px"; + dragImage.style.minWidth = "200px"; + dragImage.style.whiteSpace = "nowrap"; + // ์•„์ด์ฝ˜๊ณผ ํ…์ŠคํŠธ ์ถ”๊ฐ€ - const firstValue = Object.values(row)[0] || 'Row'; + const firstValue = Object.values(row)[0] || "Row"; dragImage.innerHTML = `
4ร—1
`; - + document.body.appendChild(dragImage); e.dataTransfer.setDragImage(dragImage, 20, 20); - + // ์ •๋ฆฌ setTimeout(() => { if (document.body.contains(dragImage)) { @@ -1150,12 +1297,12 @@ export const TableListComponent: React.FC = ({ return (
-
-
+
+
-
ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ
-
+
ํ…Œ์ด๋ธ” ๋ฆฌ์ŠคํŠธ
+
์„ค์ • ํŒจ๋„์—์„œ ํ…Œ์ด๋ธ”์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”
@@ -1165,59 +1312,52 @@ export const TableListComponent: React.FC = ({ } return ( -
{/* ํ—ค๋” */} {tableConfig.showHeader && ( -
{(tableConfig.title || tableLabel) && ( -

- {tableConfig.title || tableLabel} -

+

{tableConfig.title || tableLabel}

)}
{/* ์„ ํƒ๋œ ํ•ญ๋ชฉ ์ •๋ณด ํ‘œ์‹œ */} {selectedRows.size > 0 && ( -
+
{selectedRows.size}๊ฐœ ์„ ํƒ๋จ
)} {/* ์ƒˆ๋กœ๊ณ ์นจ */} - @@ -1604,19 +1748,15 @@ export const TableListComponent: React.FC = ({ size="sm" onClick={() => handlePageChange(currentPage - 1)} disabled={currentPage === 1} - className="h-8 w-8 p-0 disabled:opacity-50 hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300" + className="h-8 w-8 p-0 hover:border-blue-300 hover:bg-blue-50 hover:text-blue-600 disabled:opacity-50" > -
- - {currentPage} - - / - - {totalPages} - +
+ {currentPage} + / + {totalPages}
@@ -1633,7 +1773,7 @@ export const TableListComponent: React.FC = ({ size="sm" onClick={() => handlePageChange(totalPages)} disabled={currentPage === totalPages} - className="h-8 w-8 p-0 disabled:opacity-50 hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300" + className="h-8 w-8 p-0 hover:border-blue-300 hover:bg-blue-50 hover:text-blue-600 disabled:opacity-50" >