import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { commonCodeApi } from "@/lib/api/commonCode"; import { tableTypeApi } from "@/lib/api/screen"; import { useMemo } from "react"; // 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.tables.codeCategory(tableName || "", columnName || ""), queryFn: async () => { if (!tableName || !columnName) return null; const columns = await tableTypeApi.getColumns(tableName); const targetColumn = columns.find((col) => col.columnName === columnName); const codeCategory = targetColumn?.codeCategory && targetColumn.codeCategory !== "none" ? targetColumn.codeCategory : null; return codeCategory; }, enabled: !!(tableName && columnName), staleTime: 10 * 60 * 1000, // 10분 캐시 gcTime: 30 * 60 * 1000, // 30분 가비지 컬렉션 }); } // 🆕 테이블 컬럼의 계층구조 설정 조회 (대분류/중분류/소분류) interface ColumnHierarchyInfo { hierarchyRole?: "large" | "medium" | "small"; hierarchyParentField?: string; codeCategory?: string; } export function useTableColumnHierarchy(tableName?: string, columnName?: string) { return useQuery({ queryKey: ["tables", "hierarchy", tableName || "", columnName || ""], queryFn: async () => { if (!tableName || !columnName) return null; const columns = await tableTypeApi.getColumns(tableName); const targetColumn = columns.find((col) => col.columnName === columnName); if (!targetColumn) return null; // detailSettings에서 hierarchyRole 추출 let hierarchyRole: ColumnHierarchyInfo["hierarchyRole"]; let hierarchyParentField: string | undefined; if (targetColumn.detailSettings) { try { const settings = typeof targetColumn.detailSettings === "string" ? JSON.parse(targetColumn.detailSettings) : targetColumn.detailSettings; hierarchyRole = settings.hierarchyRole; hierarchyParentField = settings.hierarchyParentField; } catch { // JSON 파싱 실패 시 무시 } } // hierarchyParentField가 없으면 같은 codeCategory를 가진 상위 역할 컬럼을 자동으로 찾음 if (hierarchyRole && !hierarchyParentField && targetColumn.codeCategory) { const roleOrder = { large: 0, medium: 1, small: 2 }; const currentOrder = roleOrder[hierarchyRole]; if (currentOrder > 0) { // 같은 codeCategory를 가진 컬럼들 중에서 상위 역할을 찾음 const parentRole = currentOrder === 1 ? "large" : "medium"; const parentColumn = columns.find((col) => { if (col.codeCategory !== targetColumn.codeCategory) return false; try { const colSettings = typeof col.detailSettings === "string" ? JSON.parse(col.detailSettings) : col.detailSettings; return colSettings?.hierarchyRole === parentRole; } catch { return false; } }); if (parentColumn) { hierarchyParentField = parentColumn.columnName; } } } return { hierarchyRole, hierarchyParentField, codeCategory: targetColumn.codeCategory, }; }, enabled: !!(tableName && columnName), staleTime: 10 * 60 * 1000, gcTime: 30 * 60 * 1000, }); } // 코드 옵션 조회 (select용) export function useCodeOptions(codeCategory?: string, enabled: boolean = true, menuObjid?: number) { const query = useQuery({ queryKey: menuObjid ? [...queryKeys.codes.options(codeCategory || ""), "menu", menuObjid] : queryKeys.codes.options(codeCategory || ""), queryFn: async () => { if (!codeCategory || codeCategory === "none") return []; const response = await commonCodeApi.codes.getList(codeCategory, { isActive: true, menuObjid, }); 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; // 계층구조 정보 포함 const depth = code.depth ?? 1; const parentCodeValue = code.parentCodeValue || code.parent_code_value || null; return { value: actualValue, label: actualLabel, depth, parentCodeValue, }; }); 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: any }) => commonCodeApi.codes.create(categoryCode, data), onSuccess: (_, variables) => { // 해당 카테고리의 모든 코드 관련 쿼리 무효화 queryClient.invalidateQueries({ queryKey: queryKeys.codes.all, }); // 무한 스크롤 쿼리도 명시적으로 무효화 queryClient.invalidateQueries({ queryKey: queryKeys.codes.infiniteList(variables.categoryCode), }); }, }); } // 코드 수정 export function useUpdateCode() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ categoryCode, codeValue, data }: { categoryCode: string; codeValue: string; 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, }); // 무한 스크롤 쿼리도 명시적으로 무효화 queryClient.invalidateQueries({ queryKey: queryKeys.codes.infiniteList(variables.categoryCode), }); }, }); } // 코드 삭제 export function useDeleteCode() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ categoryCode, codeValue }: { categoryCode: string; codeValue: string }) => commonCodeApi.codes.delete(categoryCode, codeValue), onSuccess: (_, variables) => { // 해당 코드 관련 쿼리 무효화 및 캐시 제거 queryClient.invalidateQueries({ queryKey: queryKeys.codes.all, }); // 무한 스크롤 쿼리도 명시적으로 무효화 queryClient.invalidateQueries({ queryKey: queryKeys.codes.infiniteList(variables.categoryCode), }); queryClient.removeQueries({ queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue), }); }, }); } // 코드 순서 변경 export function useReorderCodes() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ categoryCode, codes, }: { categoryCode: string; codes: Array<{ codeValue: string; sortOrder: number }>; }) => commonCodeApi.codes.reorder(categoryCode, codes), onMutate: async ({ categoryCode, codes }) => { // 진행 중인 쿼리들을 취소해서 optimistic update가 덮어쓰이지 않도록 함 await queryClient.cancelQueries({ queryKey: queryKeys.codes.list(categoryCode) }); // 이전 데이터를 백업 const previousCodes = queryClient.getQueryData(queryKeys.codes.list(categoryCode)); // Optimistic update: 새로운 순서로 즉시 업데이트 if (previousCodes && Array.isArray((previousCodes as any).data)) { const previousCodesArray = (previousCodes as any).data; // 기존 데이터를 복사하고 sort_order만 업데이트 const updatedCodes = [...previousCodesArray].map((code: any) => { const newCodeData = codes.find((c) => c.codeValue === code.code_value); return newCodeData ? { ...code, sort_order: newCodeData.sortOrder } : code; }); // 순서대로 정렬 updatedCodes.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)); // API 응답 형태로 캐시에 저장 (기존 구조 유지) queryClient.setQueryData(queryKeys.codes.list(categoryCode), { ...(previousCodes as any), data: updatedCodes, }); } return { previousCodes }; }, onError: (err, variables, context) => { // 에러 시 이전 데이터로 롤백 if (context?.previousCodes) { queryClient.setQueryData(queryKeys.codes.list(variables.categoryCode), context.previousCodes); } }, onSettled: (_, __, variables) => { // 성공/실패와 관계없이 최종적으로 서버 데이터로 동기화 queryClient.invalidateQueries({ queryKey: queryKeys.codes.all, }); // 무한 스크롤 쿼리도 명시적으로 무효화 queryClient.invalidateQueries({ queryKey: queryKeys.codes.infiniteList(variables.categoryCode), }); }, }); }