ERP-node/frontend/hooks/admin/useTemplates.ts

377 lines
10 KiB
TypeScript

"use client";
import { useState, useEffect, useCallback } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { apiClient } from "@/lib/api/client";
// 템플릿 데이터 인터페이스
export interface TemplateStandard {
template_code: string;
template_name: string;
template_name_eng?: string;
description?: string;
category: string;
icon_name?: string;
default_size?: {
width: number;
height: number;
};
layout_config: any; // 템플릿의 컴포넌트 구조
preview_image?: string;
sort_order?: number;
is_active: string;
is_public?: string;
company_code: string;
created_date?: string;
created_by?: string;
updated_date?: string;
updated_by?: string;
}
// 템플릿 생성/수정 데이터
export interface TemplateFormData {
template_code: string;
template_name: string;
template_name_eng?: string;
description?: string;
category: string;
icon_name?: string;
default_size?: {
width: number;
height: number;
};
layout_config: any;
preview_image?: string;
sort_order?: number;
is_active?: string;
is_public?: string;
}
// API 응답 인터페이스
interface ApiResponse<T> {
success: boolean;
data?: T;
message?: string;
error?: string;
pagination?: {
total: number;
page: number;
limit: number;
totalPages: number;
};
}
// 쿼리 파라미터 인터페이스
interface TemplateQueryParams {
active?: string;
category?: string;
search?: string;
company_code?: string;
is_public?: string;
page?: number;
limit?: number;
}
/**
* 템플릿 관리 훅
*/
export const useTemplates = (params?: TemplateQueryParams) => {
const queryClient = useQueryClient();
// 템플릿 목록 조회
const {
data: templatesData,
isLoading,
error,
refetch,
} = useQuery({
queryKey: ["templates", params],
queryFn: async (): Promise<{ templates: TemplateStandard[]; pagination: any }> => {
const searchParams = new URLSearchParams();
if (params?.active) searchParams.append("active", params.active);
if (params?.category) searchParams.append("category", params.category);
if (params?.search) searchParams.append("search", params.search);
if (params?.company_code) searchParams.append("company_code", params.company_code);
if (params?.is_public) searchParams.append("is_public", params.is_public);
if (params?.page) searchParams.append("page", params.page.toString());
if (params?.limit) searchParams.append("limit", params.limit.toString());
const response = await apiClient.get(`/admin/template-standards?${searchParams.toString()}`);
const result: ApiResponse<TemplateStandard[]> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to fetch templates");
}
return {
templates: result.data || [],
pagination: result.pagination,
};
},
staleTime: 5 * 60 * 1000, // 5분간 캐시 유지
cacheTime: 10 * 60 * 1000, // 10분간 메모리 보관
});
const templates = templatesData?.templates || [];
const pagination = templatesData?.pagination;
// 템플릿 상세 조회
const getTemplate = useCallback(async (templateCode: string) => {
const response = await apiClient.get(`/admin/template-standards/${templateCode}`);
const result: ApiResponse<TemplateStandard> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to fetch template");
}
return result.data!;
}, []);
// 템플릿 생성
const createTemplateMutation = useMutation({
mutationFn: async (data: TemplateFormData): Promise<TemplateStandard> => {
const response = await apiClient.post("/admin/template-standards", data);
const result: ApiResponse<TemplateStandard> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to create template");
}
return result.data!;
},
onSuccess: () => {
// 목록 새로고침
queryClient.invalidateQueries({ queryKey: ["templates"] });
},
});
// 템플릿 수정
const updateTemplateMutation = useMutation({
mutationFn: async ({
templateCode,
data,
}: {
templateCode: string;
data: Partial<TemplateFormData>;
}): Promise<TemplateStandard> => {
const response = await apiClient.put(`/admin/template-standards/${templateCode}`, data);
const result: ApiResponse<TemplateStandard> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to update template");
}
return result.data!;
},
onSuccess: () => {
// 목록 새로고침
queryClient.invalidateQueries({ queryKey: ["templates"] });
},
});
// 템플릿 삭제
const deleteTemplateMutation = useMutation({
mutationFn: async (templateCode: string): Promise<void> => {
const response = await apiClient.delete(`/admin/template-standards/${templateCode}`);
const result: ApiResponse<void> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to delete template");
}
},
onSuccess: () => {
// 목록 새로고침
queryClient.invalidateQueries({ queryKey: ["templates"] });
},
});
// 정렬 순서 업데이트
const updateSortOrderMutation = useMutation({
mutationFn: async (templates: { template_code: string; sort_order: number }[]): Promise<void> => {
const response = await apiClient.put("/admin/template-standards/sort-order/bulk", { templates });
const result: ApiResponse<void> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to update sort order");
}
},
onSuccess: () => {
// 목록 새로고침
queryClient.invalidateQueries({ queryKey: ["templates"] });
},
});
// 템플릿 복제
const duplicateTemplateMutation = useMutation({
mutationFn: async ({
templateCode,
newTemplateCode,
newTemplateName,
}: {
templateCode: string;
newTemplateCode: string;
newTemplateName: string;
}): Promise<TemplateStandard> => {
const response = await apiClient.post(`/admin/template-standards/${templateCode}/duplicate`, {
new_template_code: newTemplateCode,
new_template_name: newTemplateName,
});
const result: ApiResponse<TemplateStandard> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to duplicate template");
}
return result.data!;
},
onSuccess: () => {
// 목록 새로고침
queryClient.invalidateQueries({ queryKey: ["templates"] });
},
});
// 템플릿 가져오기
const importTemplateMutation = useMutation({
mutationFn: async (templateData: any): Promise<TemplateStandard> => {
const response = await apiClient.post("/admin/template-standards/import", templateData);
const result: ApiResponse<TemplateStandard> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to import template");
}
return result.data!;
},
onSuccess: () => {
// 목록 새로고침
queryClient.invalidateQueries({ queryKey: ["templates"] });
},
});
// 템플릿 내보내기
const exportTemplate = useCallback(async (templateCode: string) => {
const response = await apiClient.get(`/admin/template-standards/${templateCode}/export`);
const result: ApiResponse<any> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to export template");
}
return result.data;
}, []);
// 카테고리 목록 조회
const {
data: categories,
isLoading: categoriesLoading,
error: categoriesError,
} = useQuery({
queryKey: ["template-categories"],
queryFn: async (): Promise<string[]> => {
const response = await apiClient.get("/admin/template-standards/categories");
const result: ApiResponse<string[]> = response.data;
if (!result.success) {
throw new Error(result.error || "Failed to fetch categories");
}
return result.data || [];
},
staleTime: 10 * 60 * 1000, // 10분간 캐시 유지
});
// 편의 메서드들
const createTemplate = useCallback(
(data: TemplateFormData) => {
return createTemplateMutation.mutateAsync(data);
},
[createTemplateMutation],
);
const updateTemplate = useCallback(
(templateCode: string, data: Partial<TemplateFormData>) => {
return updateTemplateMutation.mutateAsync({ templateCode, data });
},
[updateTemplateMutation],
);
const deleteTemplate = useCallback(
(templateCode: string) => {
return deleteTemplateMutation.mutateAsync(templateCode);
},
[deleteTemplateMutation],
);
const updateSortOrder = useCallback(
(templates: { template_code: string; sort_order: number }[]) => {
return updateSortOrderMutation.mutateAsync(templates);
},
[updateSortOrderMutation],
);
const duplicateTemplate = useCallback(
(templateCode: string, newTemplateCode: string, newTemplateName: string) => {
return duplicateTemplateMutation.mutateAsync({
templateCode,
newTemplateCode,
newTemplateName,
});
},
[duplicateTemplateMutation],
);
const importTemplate = useCallback(
(templateData: any) => {
return importTemplateMutation.mutateAsync(templateData);
},
[importTemplateMutation],
);
return {
// 데이터
templates,
pagination,
categories: categories || [],
// 로딩 상태
isLoading,
categoriesLoading,
isCreating: createTemplateMutation.isPending,
isUpdating: updateTemplateMutation.isPending,
isDeleting: deleteTemplateMutation.isPending,
isDuplicating: duplicateTemplateMutation.isPending,
isImporting: importTemplateMutation.isPending,
isSortOrderUpdating: updateSortOrderMutation.isPending,
// 에러 상태
error,
categoriesError,
createError: createTemplateMutation.error,
updateError: updateTemplateMutation.error,
deleteError: deleteTemplateMutation.error,
duplicateError: duplicateTemplateMutation.error,
importError: importTemplateMutation.error,
sortOrderError: updateSortOrderMutation.error,
// 메서드
getTemplate,
createTemplate,
updateTemplate,
deleteTemplate,
updateSortOrder,
duplicateTemplate,
importTemplate,
exportTemplate,
refetch,
};
};