ERP-node/frontend/hooks/queries/useCodes.ts

250 lines
8.4 KiB
TypeScript

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분 가비지 컬렉션
});
}
// 코드 옵션 조회 (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 [];
console.log(`🔍 [useCodeOptions] 코드 옵션 조회 시작:`, {
codeCategory,
menuObjid,
hasMenuObjid: !!menuObjid,
});
const response = await commonCodeApi.codes.getList(codeCategory, {
isActive: true,
menuObjid
});
console.log(`📦 [useCodeOptions] API 응답:`, {
codeCategory,
menuObjid,
success: response.success,
dataCount: response.data?.length || 0,
rawData: response.data,
});
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(`✅ [useCodeOptions] 옵션 변환 완료:`, {
codeCategory,
menuObjid,
optionsCount: options.length,
options,
});
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),
});
},
});
}