템플릿관리, 컴포넌트 관리
This commit is contained in:
395
backend-node/src/services/templateStandardService.ts
Normal file
395
backend-node/src/services/templateStandardService.ts
Normal file
@@ -0,0 +1,395 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* 템플릿 표준 관리 서비스
|
||||
*/
|
||||
export class TemplateStandardService {
|
||||
/**
|
||||
* 템플릿 목록 조회
|
||||
*/
|
||||
async getTemplates(params: {
|
||||
active?: string;
|
||||
category?: string;
|
||||
search?: string;
|
||||
company_code?: string;
|
||||
is_public?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}) {
|
||||
const {
|
||||
active = "Y",
|
||||
category,
|
||||
search,
|
||||
company_code,
|
||||
is_public = "Y",
|
||||
page = 1,
|
||||
limit = 50,
|
||||
} = params;
|
||||
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// 기본 필터 조건
|
||||
const where: any = {};
|
||||
|
||||
if (active && active !== "all") {
|
||||
where.is_active = active;
|
||||
}
|
||||
|
||||
if (category && category !== "all") {
|
||||
where.category = category;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ template_name: { contains: search, mode: "insensitive" } },
|
||||
{ template_name_eng: { contains: search, mode: "insensitive" } },
|
||||
{ description: { contains: search, mode: "insensitive" } },
|
||||
];
|
||||
}
|
||||
|
||||
// 회사별 필터링 (공개 템플릿 + 해당 회사 템플릿)
|
||||
if (company_code) {
|
||||
where.OR = [{ is_public: "Y" }, { company_code: company_code }];
|
||||
} else if (is_public === "Y") {
|
||||
where.is_public = "Y";
|
||||
}
|
||||
|
||||
const [templates, total] = await Promise.all([
|
||||
prisma.template_standards.findMany({
|
||||
where,
|
||||
orderBy: [{ sort_order: "asc" }, { template_name: "asc" }],
|
||||
skip,
|
||||
take: limit,
|
||||
}),
|
||||
prisma.template_standards.count({ where }),
|
||||
]);
|
||||
|
||||
return { templates, total };
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 상세 조회
|
||||
*/
|
||||
async getTemplate(templateCode: string) {
|
||||
return await prisma.template_standards.findUnique({
|
||||
where: { template_code: templateCode },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 생성
|
||||
*/
|
||||
async createTemplate(templateData: any) {
|
||||
// 템플릿 코드 중복 확인
|
||||
const existing = await prisma.template_standards.findUnique({
|
||||
where: { template_code: templateData.template_code },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new Error(
|
||||
`템플릿 코드 '${templateData.template_code}'는 이미 존재합니다.`
|
||||
);
|
||||
}
|
||||
|
||||
return await prisma.template_standards.create({
|
||||
data: {
|
||||
template_code: templateData.template_code,
|
||||
template_name: templateData.template_name,
|
||||
template_name_eng: templateData.template_name_eng,
|
||||
description: templateData.description,
|
||||
category: templateData.category,
|
||||
icon_name: templateData.icon_name,
|
||||
default_size: templateData.default_size,
|
||||
layout_config: templateData.layout_config,
|
||||
preview_image: templateData.preview_image,
|
||||
sort_order: templateData.sort_order || 0,
|
||||
is_active: templateData.is_active || "Y",
|
||||
is_public: templateData.is_public || "N",
|
||||
company_code: templateData.company_code,
|
||||
created_by: templateData.created_by,
|
||||
updated_by: templateData.updated_by,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 수정
|
||||
*/
|
||||
async updateTemplate(templateCode: string, templateData: any) {
|
||||
const updateData: any = {};
|
||||
|
||||
// 수정 가능한 필드들만 업데이트
|
||||
if (templateData.template_name !== undefined) {
|
||||
updateData.template_name = templateData.template_name;
|
||||
}
|
||||
if (templateData.template_name_eng !== undefined) {
|
||||
updateData.template_name_eng = templateData.template_name_eng;
|
||||
}
|
||||
if (templateData.description !== undefined) {
|
||||
updateData.description = templateData.description;
|
||||
}
|
||||
if (templateData.category !== undefined) {
|
||||
updateData.category = templateData.category;
|
||||
}
|
||||
if (templateData.icon_name !== undefined) {
|
||||
updateData.icon_name = templateData.icon_name;
|
||||
}
|
||||
if (templateData.default_size !== undefined) {
|
||||
updateData.default_size = templateData.default_size;
|
||||
}
|
||||
if (templateData.layout_config !== undefined) {
|
||||
updateData.layout_config = templateData.layout_config;
|
||||
}
|
||||
if (templateData.preview_image !== undefined) {
|
||||
updateData.preview_image = templateData.preview_image;
|
||||
}
|
||||
if (templateData.sort_order !== undefined) {
|
||||
updateData.sort_order = templateData.sort_order;
|
||||
}
|
||||
if (templateData.is_active !== undefined) {
|
||||
updateData.is_active = templateData.is_active;
|
||||
}
|
||||
if (templateData.is_public !== undefined) {
|
||||
updateData.is_public = templateData.is_public;
|
||||
}
|
||||
if (templateData.updated_by !== undefined) {
|
||||
updateData.updated_by = templateData.updated_by;
|
||||
}
|
||||
|
||||
updateData.updated_date = new Date();
|
||||
|
||||
try {
|
||||
return await prisma.template_standards.update({
|
||||
where: { template_code: templateCode },
|
||||
data: updateData,
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2025") {
|
||||
return null; // 템플릿을 찾을 수 없음
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 삭제
|
||||
*/
|
||||
async deleteTemplate(templateCode: string) {
|
||||
try {
|
||||
await prisma.template_standards.delete({
|
||||
where: { template_code: templateCode },
|
||||
});
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
if (error.code === "P2025") {
|
||||
return false; // 템플릿을 찾을 수 없음
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 정렬 순서 일괄 업데이트
|
||||
*/
|
||||
async updateSortOrder(
|
||||
templates: { template_code: string; sort_order: number }[]
|
||||
) {
|
||||
const updatePromises = templates.map((template) =>
|
||||
prisma.template_standards.update({
|
||||
where: { template_code: template.template_code },
|
||||
data: {
|
||||
sort_order: template.sort_order,
|
||||
updated_date: new Date(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(updatePromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 복제
|
||||
*/
|
||||
async duplicateTemplate(params: {
|
||||
originalCode: string;
|
||||
newCode: string;
|
||||
newName: string;
|
||||
company_code: string;
|
||||
created_by: string;
|
||||
}) {
|
||||
const { originalCode, newCode, newName, company_code, created_by } = params;
|
||||
|
||||
// 원본 템플릿 조회
|
||||
const originalTemplate = await this.getTemplate(originalCode);
|
||||
if (!originalTemplate) {
|
||||
throw new Error("원본 템플릿을 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
// 새 템플릿 코드 중복 확인
|
||||
const existing = await this.getTemplate(newCode);
|
||||
if (existing) {
|
||||
throw new Error(`템플릿 코드 '${newCode}'는 이미 존재합니다.`);
|
||||
}
|
||||
|
||||
// 템플릿 복제
|
||||
return await this.createTemplate({
|
||||
template_code: newCode,
|
||||
template_name: newName,
|
||||
template_name_eng: originalTemplate.template_name_eng
|
||||
? `${originalTemplate.template_name_eng} (Copy)`
|
||||
: undefined,
|
||||
description: originalTemplate.description,
|
||||
category: originalTemplate.category,
|
||||
icon_name: originalTemplate.icon_name,
|
||||
default_size: originalTemplate.default_size,
|
||||
layout_config: originalTemplate.layout_config,
|
||||
preview_image: originalTemplate.preview_image,
|
||||
sort_order: 0,
|
||||
is_active: "Y",
|
||||
is_public: "N", // 복제된 템플릿은 기본적으로 비공개
|
||||
company_code,
|
||||
created_by,
|
||||
updated_by: created_by,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 카테고리 목록 조회
|
||||
*/
|
||||
async getCategories(companyCode: string) {
|
||||
const categories = await prisma.template_standards.findMany({
|
||||
where: {
|
||||
OR: [{ is_public: "Y" }, { company_code: companyCode }],
|
||||
is_active: "Y",
|
||||
},
|
||||
select: { category: true },
|
||||
distinct: ["category"],
|
||||
orderBy: { category: "asc" },
|
||||
});
|
||||
|
||||
return categories.map((item) => item.category).filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 템플릿 데이터 삽입 (초기 설정용)
|
||||
*/
|
||||
async seedDefaultTemplates() {
|
||||
const defaultTemplates = [
|
||||
{
|
||||
template_code: "advanced-data-table",
|
||||
template_name: "고급 데이터 테이블",
|
||||
template_name_eng: "Advanced Data Table",
|
||||
description:
|
||||
"컬럼 설정, 필터링, 페이지네이션이 포함된 완전한 데이터 테이블",
|
||||
category: "table",
|
||||
icon_name: "table",
|
||||
default_size: { width: 1000, height: 680 },
|
||||
layout_config: {
|
||||
components: [
|
||||
{
|
||||
type: "datatable",
|
||||
label: "데이터 테이블",
|
||||
position: { x: 0, y: 0 },
|
||||
size: { width: 1000, height: 680 },
|
||||
style: {
|
||||
border: "1px solid #e5e7eb",
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#ffffff",
|
||||
padding: "16px",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
sort_order: 1,
|
||||
is_active: "Y",
|
||||
is_public: "Y",
|
||||
company_code: "*",
|
||||
created_by: "system",
|
||||
updated_by: "system",
|
||||
},
|
||||
{
|
||||
template_code: "universal-button",
|
||||
template_name: "버튼",
|
||||
template_name_eng: "Universal Button",
|
||||
description:
|
||||
"다양한 기능을 설정할 수 있는 범용 버튼. 상세설정에서 기능을 선택하세요.",
|
||||
category: "button",
|
||||
icon_name: "mouse-pointer",
|
||||
default_size: { width: 80, height: 36 },
|
||||
layout_config: {
|
||||
components: [
|
||||
{
|
||||
type: "widget",
|
||||
widgetType: "button",
|
||||
label: "버튼",
|
||||
position: { x: 0, y: 0 },
|
||||
size: { width: 80, height: 36 },
|
||||
style: {
|
||||
backgroundColor: "#3b82f6",
|
||||
color: "#ffffff",
|
||||
border: "none",
|
||||
borderRadius: "6px",
|
||||
fontSize: "14px",
|
||||
fontWeight: "500",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
sort_order: 2,
|
||||
is_active: "Y",
|
||||
is_public: "Y",
|
||||
company_code: "*",
|
||||
created_by: "system",
|
||||
updated_by: "system",
|
||||
},
|
||||
{
|
||||
template_code: "file-upload",
|
||||
template_name: "파일 첨부",
|
||||
template_name_eng: "File Upload",
|
||||
description: "드래그앤드롭 파일 업로드 영역",
|
||||
category: "file",
|
||||
icon_name: "upload",
|
||||
default_size: { width: 300, height: 120 },
|
||||
layout_config: {
|
||||
components: [
|
||||
{
|
||||
type: "widget",
|
||||
widgetType: "file",
|
||||
label: "파일 첨부",
|
||||
position: { x: 0, y: 0 },
|
||||
size: { width: 300, height: 120 },
|
||||
style: {
|
||||
border: "2px dashed #d1d5db",
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "#f9fafb",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: "14px",
|
||||
color: "#6b7280",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
sort_order: 3,
|
||||
is_active: "Y",
|
||||
is_public: "Y",
|
||||
company_code: "*",
|
||||
created_by: "system",
|
||||
updated_by: "system",
|
||||
},
|
||||
];
|
||||
|
||||
// 기존 데이터가 있는지 확인 후 삽입
|
||||
for (const template of defaultTemplates) {
|
||||
const existing = await this.getTemplate(template.template_code);
|
||||
if (!existing) {
|
||||
await this.createTemplate(template);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const templateStandardService = new TemplateStandardService();
|
||||
Reference in New Issue
Block a user