From ae23cfdc2b4de63413eab930d830190a47faf2e9 Mon Sep 17 00:00:00 2001 From: dohyeons Date: Thu, 2 Oct 2025 09:49:21 +0900 Subject: [PATCH] =?UTF-8?q?=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=EB=90=9C?= =?UTF-8?q?=20=EB=B6=80=EB=B6=84=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../report/designer/TemplatePalette.tsx | 63 ++-- frontend/contexts/ReportDesignerContext.tsx | 316 ++++-------------- 2 files changed, 98 insertions(+), 281 deletions(-) diff --git a/frontend/components/report/designer/TemplatePalette.tsx b/frontend/components/report/designer/TemplatePalette.tsx index eff6bdc6..276ccff4 100644 --- a/frontend/components/report/designer/TemplatePalette.tsx +++ b/frontend/components/report/designer/TemplatePalette.tsx @@ -8,13 +8,7 @@ import { reportApi } from "@/lib/api/reportApi"; import { useToast } from "@/hooks/use-toast"; import { Badge } from "@/components/ui/badge"; -const SYSTEM_TEMPLATES = [ - { id: "order", name: "λ°œμ£Όμ„œ", icon: "πŸ“‹" }, - { id: "invoice", name: "μ²­κ΅¬μ„œ", icon: "πŸ’°" }, - { id: "basic", name: "κΈ°λ³Έ", icon: "πŸ“„" }, -]; - -interface CustomTemplate { +interface Template { template_id: string; template_name_kor: string; template_name_eng: string | null; @@ -23,27 +17,35 @@ interface CustomTemplate { export function TemplatePalette() { const { applyTemplate } = useReportDesigner(); - const [customTemplates, setCustomTemplates] = useState([]); + const [systemTemplates, setSystemTemplates] = useState([]); + const [customTemplates, setCustomTemplates] = useState([]); const [isLoading, setIsLoading] = useState(false); const [deletingId, setDeletingId] = useState(null); const { toast } = useToast(); - const fetchCustomTemplates = async () => { + const fetchTemplates = async () => { setIsLoading(true); try { const response = await reportApi.getTemplates(); if (response.success && response.data) { - setCustomTemplates(response.data.custom || []); + setSystemTemplates(Array.isArray(response.data.system) ? response.data.system : []); + setCustomTemplates(Array.isArray(response.data.custom) ? response.data.custom : []); } } catch (error) { console.error("ν…œν”Œλ¦Ώ 쑰회 μ‹€νŒ¨:", error); + toast({ + title: "였λ₯˜", + description: "ν…œν”Œλ¦Ώ λͺ©λ‘μ„ 뢈러올 수 μ—†μŠ΅λ‹ˆλ‹€.", + variant: "destructive", + }); } finally { setIsLoading(false); } }; useEffect(() => { - fetchCustomTemplates(); + fetchTemplates(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleApplyTemplate = async (templateId: string) => { @@ -63,7 +65,7 @@ export function TemplatePalette() { title: "성곡", description: "ν…œν”Œλ¦Ώμ΄ μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.", }); - fetchCustomTemplates(); + fetchTemplates(); } } catch (error: any) { toast({ @@ -78,30 +80,31 @@ export function TemplatePalette() { return (
- {/* μ‹œμŠ€ν…œ ν…œν”Œλ¦Ώ */} -
-
-

μ‹œμŠ€ν…œ ν…œν”Œλ¦Ώ

+ {/* μ‹œμŠ€ν…œ ν…œν”Œλ¦Ώ (DBμ—μ„œ 쑰회) */} + {systemTemplates.length > 0 && ( +
+
+

μ‹œμŠ€ν…œ ν…œν”Œλ¦Ώ

+
+ {systemTemplates.map((template) => ( + + ))}
- {SYSTEM_TEMPLATES.map((template) => ( - - ))} -
+ )} {/* μ‚¬μš©μž μ •μ˜ ν…œν”Œλ¦Ώ */}

μ‚¬μš©μž μ •μ˜ ν…œν”Œλ¦Ώ

-
diff --git a/frontend/contexts/ReportDesignerContext.tsx b/frontend/contexts/ReportDesignerContext.tsx index 95eb564a..62725ec3 100644 --- a/frontend/contexts/ReportDesignerContext.tsx +++ b/frontend/contexts/ReportDesignerContext.tsx @@ -14,212 +14,7 @@ export interface ReportQuery { externalConnectionId?: number | null; // μ™ΈλΆ€ DB μ—°κ²° ID } -// ν…œν”Œλ¦Ώ λ ˆμ΄μ•„μ›ƒ μ •μ˜ -interface TemplateLayout { - components: ComponentConfig[]; - queries: ReportQuery[]; -} - -function getTemplateLayout(templateId: string): TemplateLayout | null { - switch (templateId) { - case "order": - return { - components: [ - { - id: `comp-${Date.now()}-1`, - type: "label", - x: 50, - y: 30, - width: 200, - height: 40, - fontSize: 24, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - defaultValue: "λ°œμ£Όμ„œ", - }, - { - id: `comp-${Date.now()}-2`, - type: "text", - x: 50, - y: 80, - width: 150, - height: 30, - fontSize: 14, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - }, - { - id: `comp-${Date.now()}-3`, - type: "text", - x: 220, - y: 80, - width: 150, - height: 30, - fontSize: 14, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - }, - { - id: `comp-${Date.now()}-4`, - type: "text", - x: 390, - y: 80, - width: 150, - height: 30, - fontSize: 14, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - }, - { - id: `comp-${Date.now()}-5`, - type: "table", - x: 50, - y: 130, - width: 500, - height: 200, - fontSize: 12, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - }, - ], - queries: [], - }; - - case "invoice": - return { - components: [ - { - id: `comp-${Date.now()}-1`, - type: "label", - x: 50, - y: 30, - width: 200, - height: 40, - fontSize: 24, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - defaultValue: "μ²­κ΅¬μ„œ", - }, - { - id: `comp-${Date.now()}-2`, - type: "text", - x: 50, - y: 80, - width: 150, - height: 30, - fontSize: 14, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - }, - { - id: `comp-${Date.now()}-3`, - type: "text", - x: 220, - y: 80, - width: 150, - height: 30, - fontSize: 14, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - }, - { - id: `comp-${Date.now()}-4`, - type: "table", - x: 50, - y: 130, - width: 500, - height: 200, - fontSize: 12, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - }, - { - id: `comp-${Date.now()}-5`, - type: "label", - x: 400, - y: 350, - width: 150, - height: 30, - fontSize: 16, - fontColor: "#000000", - backgroundColor: "#ffffcc", - borderColor: "#000000", - borderWidth: 1, - zIndex: 1, - defaultValue: "합계: 0원", - }, - ], - queries: [], - }; - - case "basic": - return { - components: [ - { - id: `comp-${Date.now()}-1`, - type: "label", - x: 50, - y: 30, - width: 300, - height: 40, - fontSize: 20, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - defaultValue: "리포트 제λͺ©", - }, - { - id: `comp-${Date.now()}-2`, - type: "text", - x: 50, - y: 80, - width: 500, - height: 100, - fontSize: 14, - fontColor: "#000000", - backgroundColor: "transparent", - borderColor: "#cccccc", - borderWidth: 0, - zIndex: 1, - defaultValue: "λ‚΄μš©μ„ μž…λ ₯ν•˜μ„Έμš”", - }, - ], - queries: [], - }; - - default: - return null; - } -} +// ν•˜λ“œμ½”λ”©λœ ν…œν”Œλ¦Ώ 제거 - λͺ¨λ“  ν…œν”Œλ¦Ώμ€ DBμ—μ„œ 관리 export interface QueryResult { queryId: string; @@ -1243,75 +1038,93 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin } } - // 1. λ¨Όμ € ν•˜λ“œμ½”λ”©λœ μ‹œμŠ€ν…œ ν…œν”Œλ¦Ώ 확인 (order, invoice, basic) - const systemTemplate = getTemplateLayout(templateId); - - if (systemTemplate) { - // μ‹œμŠ€ν…œ ν…œν”Œλ¦Ώ 적용 - setComponents(systemTemplate.components); - setQueries(systemTemplate.queries); - - toast({ - title: "성곡", - description: "ν…œν”Œλ¦Ώμ΄ μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.", - }); - return; - } - - // 2. μ‚¬μš©μž μ •μ˜ ν…œν”Œλ¦Ώμ€ λ°±μ—”λ“œμ—μ„œ 쑰회 + // DBμ—μ„œ ν…œν”Œλ¦Ώ 쑰회 (μ‹œμŠ€ν…œ ν…œν”Œλ¦Ώ λ˜λŠ” μ‚¬μš©μž μ •μ˜ ν…œν”Œλ¦Ώ) const response = await reportApi.getTemplates(); if (!response.success || !response.data) { throw new Error("ν…œν”Œλ¦Ώ λͺ©λ‘μ„ 뢈러올 수 μ—†μŠ΅λ‹ˆλ‹€."); } - // μ»€μŠ€ν…€ ν…œν”Œλ¦Ώ μ°ΎκΈ° - const customTemplates = response.data.custom || []; - const template = customTemplates.find((t: { template_id: string }) => t.template_id === templateId); + // μ‹œμŠ€ν…œ ν…œν”Œλ¦Ώκ³Ό μ»€μŠ€ν…€ ν…œν”Œλ¦Ώ λͺ¨λ‘μ—μ„œ μ°ΎκΈ° + const allTemplates = [...(response.data.system || []), ...(response.data.custom || [])]; + const template = allTemplates.find((t: { template_id: string }) => t.template_id === templateId); if (!template) { throw new Error("ν…œν”Œλ¦Ώμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."); } - // 3. ν…œν”Œλ¦Ώ 데이터 νŒŒμ‹± 및 적용 - const layoutConfig = - typeof template.layout_config === "string" ? JSON.parse(template.layout_config) : template.layout_config; + // ν…œν”Œλ¦Ώ 데이터 ν™•μΈμš© 둜그 + console.log("===== μ„ νƒλœ ν…œν”Œλ¦Ώ ====="); + console.log("Template ID:", template.template_id); + console.log("Template Name:", template.template_name_kor); + console.log("Layout Config:", template.layout_config); + console.log("Default Queries:", template.default_queries); + console.log("========================"); - const defaultQueries = - typeof template.default_queries === "string" - ? JSON.parse(template.default_queries) - : template.default_queries || []; + // ν…œν”Œλ¦Ώ 데이터 νŒŒμ‹± 및 적용 + let layoutConfig: { components?: ComponentConfig[] } | null = null; + let defaultQueries: unknown[] = []; + + // layout_config νŒŒμ‹± (μ•ˆμ „ν•˜κ²Œ) + try { + if (template.layout_config) { + layoutConfig = + typeof template.layout_config === "string" ? JSON.parse(template.layout_config) : template.layout_config; + } + } catch (e) { + console.error("layout_config νŒŒμ‹± 였λ₯˜:", e); + layoutConfig = { components: [] }; + } + + // default_queries νŒŒμ‹± (μ•ˆμ „ν•˜κ²Œ) + try { + if (template.default_queries) { + defaultQueries = + typeof template.default_queries === "string" + ? JSON.parse(template.default_queries) + : template.default_queries; + } + } catch (e) { + console.error("default_queries νŒŒμ‹± 였λ₯˜:", e); + defaultQueries = []; + } + + // layoutConfigκ°€ μ—†μœΌλ©΄ 빈 ꡬ쑰둜 μ΄ˆκΈ°ν™” + if (!layoutConfig || typeof layoutConfig !== "object") { + layoutConfig = { components: [] }; + } // μ»΄ν¬λ„ŒνŠΈ 적용 (ID μž¬μƒμ„±) - const newComponents = (layoutConfig.components as ComponentConfig[]).map((comp) => ({ + const templateComponents = Array.isArray(layoutConfig.components) ? layoutConfig.components : []; + const newComponents = templateComponents.map((comp: ComponentConfig) => ({ ...comp, id: `comp-${Date.now()}-${Math.random()}`, })); // 쿼리 적용 (ID μž¬μƒμ„±) - const newQueries = ( - defaultQueries as Array<{ - name: string; - type: "MASTER" | "DETAIL"; - sqlQuery: string; - parameters: string[]; - externalConnectionId?: number | null; - }> - ).map((q) => ({ - id: `query-${Date.now()}-${Math.random()}`, - name: q.name, - type: q.type, - sqlQuery: q.sqlQuery, - parameters: q.parameters || [], - externalConnectionId: q.externalConnectionId || null, - })); + const templateQueries = Array.isArray(defaultQueries) ? defaultQueries : []; + const newQueries = templateQueries + .filter((q): q is Record => typeof q === "object" && q !== null) + .map((q) => ({ + id: `query-${Date.now()}-${Math.random()}`, + name: (q.name as string) || "", + type: (q.type as "MASTER" | "DETAIL") || "MASTER", + sqlQuery: (q.sqlQuery as string) || "", + parameters: Array.isArray(q.parameters) ? (q.parameters as string[]) : [], + externalConnectionId: (q.externalConnectionId as number | null) || null, + })); setComponents(newComponents); setQueries(newQueries); + const message = + newComponents.length === 0 + ? "ν…œν”Œλ¦Ώμ΄ μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€. (빈 ν…œν”Œλ¦Ώ)" + : `ν…œν”Œλ¦Ώμ΄ μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€. (μ»΄ν¬λ„ŒνŠΈ ${newComponents.length}개, 쿼리 ${newQueries.length}개)`; + toast({ title: "성곡", - description: "μ‚¬μš©μž μ •μ˜ ν…œν”Œλ¦Ώμ΄ μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.", + description: message, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : "ν…œν”Œλ¦Ώ μ μš©μ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."; @@ -1322,7 +1135,8 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin }); } }, - [components.length, toast], + // eslint-disable-next-line react-hooks/exhaustive-deps + [toast], ); const value: ReportDesignerContextType = {