diff --git a/backend-node/src/controllers/reportController.ts b/backend-node/src/controllers/reportController.ts index 438e02e1..4c6845fa 100644 --- a/backend-node/src/controllers/reportController.ts +++ b/backend-node/src/controllers/reportController.ts @@ -1364,6 +1364,45 @@ export class ReportController { } } + // Checkbox 컴포넌트 + else if (component.type === "checkbox") { + // 체크 상태 결정 (쿼리 바인딩 또는 고정값) + let isChecked = component.checkboxChecked === true; + if (component.checkboxFieldName && component.queryId && queryResultsMapRef[component.queryId]) { + const qResult = queryResultsMapRef[component.queryId]; + if (qResult.rows && qResult.rows.length > 0) { + const row = qResult.rows[0]; + const val = row[component.checkboxFieldName]; + // truthy/falsy 값 판정 + if (val === true || val === "true" || val === "Y" || val === 1 || val === "1") { + isChecked = true; + } else { + isChecked = false; + } + } + } + + const checkboxSymbol = isChecked ? "☑" : "☐"; + const checkboxLabel = component.checkboxLabel || ""; + const labelPosition = component.checkboxLabelPosition || "right"; + const displayText = labelPosition === "left" + ? `${checkboxLabel} ${checkboxSymbol}` + : `${checkboxSymbol} ${checkboxLabel}`; + + result.push( + new ParagraphRef({ + children: [ + new TextRunRef({ + text: displayText.trim(), + size: pxToHalfPtFn(component.fontSize || 14), + font: "맑은 고딕", + color: (component.fontColor || "#374151").replace("#", ""), + }), + ], + }) + ); + } + // Divider - 테이블 셀로 감싸서 정확한 너비 적용 else if ( component.type === "divider" && @@ -2809,6 +2848,58 @@ export class ReportController { lastBottomY = adjustedY + component.height; } + // Checkbox 컴포넌트 + else if (component.type === "checkbox") { + // 체크 상태 결정 (쿼리 바인딩 또는 고정값) + let isChecked = component.checkboxChecked === true; + if (component.checkboxFieldName && component.queryId && queryResultsMap[component.queryId]) { + const qResult = queryResultsMap[component.queryId]; + if (qResult.rows && qResult.rows.length > 0) { + const row = qResult.rows[0]; + const val = row[component.checkboxFieldName]; + // truthy/falsy 값 판정 + if (val === true || val === "true" || val === "Y" || val === 1 || val === "1") { + isChecked = true; + } else { + isChecked = false; + } + } + } + + const checkboxSymbol = isChecked ? "☑" : "☐"; + const checkboxLabel = component.checkboxLabel || ""; + const labelPosition = component.checkboxLabelPosition || "right"; + const displayText = labelPosition === "left" + ? `${checkboxLabel} ${checkboxSymbol}` + : `${checkboxSymbol} ${checkboxLabel}`; + + // spacing을 위한 빈 paragraph + if (spacingBefore > 0) { + children.push( + new Paragraph({ + spacing: { before: spacingBefore, after: 0 }, + children: [], + }) + ); + } + + children.push( + new Paragraph({ + indent: { left: indentLeft }, + children: [ + new TextRun({ + text: displayText.trim(), + size: pxToHalfPt(component.fontSize || 14), + font: "맑은 고딕", + color: (component.fontColor || "#374151").replace("#", ""), + }), + ], + }) + ); + + lastBottomY = adjustedY + component.height; + } + // Table 컴포넌트 else if (component.type === "table" && component.queryId) { const queryResult = queryResultsMap[component.queryId]; diff --git a/backend-node/src/types/report.ts b/backend-node/src/types/report.ts index e622a65c..f82b2db4 100644 --- a/backend-node/src/types/report.ts +++ b/backend-node/src/types/report.ts @@ -260,4 +260,12 @@ export interface ComponentConfig { barcodeBackground?: string; barcodeMargin?: number; qrErrorCorrectionLevel?: "L" | "M" | "Q" | "H"; + // 체크박스 컴포넌트 전용 + checkboxChecked?: boolean; // 체크 상태 (고정값) + checkboxFieldName?: string; // 쿼리 필드 바인딩 (truthy/falsy 값) + checkboxLabel?: string; // 체크박스 옆 레이블 텍스트 + checkboxSize?: number; // 체크박스 크기 (px) + checkboxColor?: string; // 체크 색상 + checkboxBorderColor?: string; // 테두리 색상 + checkboxLabelPosition?: "left" | "right"; // 레이블 위치 } diff --git a/frontend/components/report/designer/CanvasComponent.tsx b/frontend/components/report/designer/CanvasComponent.tsx index e3f06e9f..12011813 100644 --- a/frontend/components/report/designer/CanvasComponent.tsx +++ b/frontend/components/report/designer/CanvasComponent.tsx @@ -1002,6 +1002,89 @@ export function CanvasComponent({ component }: CanvasComponentProps) { ); + case "checkbox": + // 체크박스 컴포넌트 렌더링 + const checkboxSize = component.checkboxSize || 18; + const checkboxColor = component.checkboxColor || "#2563eb"; + const checkboxBorderColor = component.checkboxBorderColor || "#6b7280"; + const checkboxLabelPosition = component.checkboxLabelPosition || "right"; + const checkboxLabel = component.checkboxLabel || ""; + + // 체크 상태 결정 (쿼리 바인딩 또는 고정값) + const getCheckboxValue = (): boolean => { + if (component.checkboxFieldName && component.queryId) { + const queryResult = getQueryResult(component.queryId); + if (queryResult && queryResult.rows && queryResult.rows.length > 0) { + const row = queryResult.rows[0]; + const val = row[component.checkboxFieldName]; + // truthy/falsy 값 판정 + if (val === true || val === "true" || val === "Y" || val === 1 || val === "1") { + return true; + } + return false; + } + return false; + } + return component.checkboxChecked === true; + }; + + const isChecked = getCheckboxValue(); + + return ( +
+ true, "Y", 1 등 truthy 값이면 체크됨 +
+