레포트에 페이지번호 컴포넌트 추가

This commit is contained in:
dohyeons
2025-12-18 09:45:07 +09:00
parent 0abe87ae1a
commit 0ed8e686c0
7 changed files with 271 additions and 9 deletions

View File

@@ -23,6 +23,8 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
canvasWidth,
canvasHeight,
margins,
layoutConfig,
currentPageId,
} = useReportDesigner();
const [isDragging, setIsDragging] = useState(false);
const [isResizing, setIsResizing] = useState(false);
@@ -563,6 +565,43 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
</div>
);
case "pageNumber":
// 페이지 번호 포맷
const format = component.pageNumberFormat || "number";
const sortedPages = layoutConfig.pages.sort((a, b) => a.page_order - b.page_order);
const currentPageIndex = sortedPages.findIndex((p) => p.page_id === currentPageId);
const totalPages = sortedPages.length;
const currentPageNum = currentPageIndex + 1;
let pageNumberText = "";
switch (format) {
case "number":
pageNumberText = `${currentPageNum}`;
break;
case "numberTotal":
pageNumberText = `${currentPageNum} / ${totalPages}`;
break;
case "koreanNumber":
pageNumberText = `${currentPageNum} 페이지`;
break;
default:
pageNumberText = `${currentPageNum}`;
}
return (
<div
className="flex h-full w-full items-center justify-center"
style={{
fontSize: `${component.fontSize}px`,
color: component.fontColor,
fontWeight: component.fontWeight,
textAlign: component.textAlign as "left" | "center" | "right",
}}
>
{pageNumberText}
</div>
);
default:
return <div> </div>;
}

View File

@@ -1,7 +1,7 @@
"use client";
import { useDrag } from "react-dnd";
import { Type, Table, Image, Minus, PenLine, Stamp as StampIcon } from "lucide-react";
import { Type, Table, Image, Minus, PenLine, Stamp as StampIcon, Hash } from "lucide-react";
interface ComponentItem {
type: string;
@@ -16,6 +16,7 @@ const COMPONENTS: ComponentItem[] = [
{ type: "divider", label: "구분선", icon: <Minus className="h-4 w-4" /> },
{ type: "signature", label: "서명란", icon: <PenLine className="h-4 w-4" /> },
{ type: "stamp", label: "도장란", icon: <StampIcon className="h-4 w-4" /> },
{ type: "pageNumber", label: "페이지번호", icon: <Hash className="h-4 w-4" /> },
];
function DraggableComponentItem({ type, label, icon }: ComponentItem) {

View File

@@ -65,6 +65,9 @@ export function ReportDesignerCanvas() {
} else if (item.componentType === "stamp") {
width = 70;
height = 70;
} else if (item.componentType === "pageNumber") {
width = 100;
height = 30;
}
// 여백을 px로 변환 (1mm ≈ 3.7795px)
@@ -143,6 +146,11 @@ export function ReportDesignerCanvas() {
borderWidth: 0,
borderColor: "#cccccc",
}),
// 페이지 번호 전용
...(item.componentType === "pageNumber" && {
pageNumberFormat: "number" as const, // number, numberTotal, koreanNumber
textAlign: "center" as const,
}),
// 테이블 전용
...(item.componentType === "table" && {
queryId: undefined,

View File

@@ -919,6 +919,37 @@ export function ReportDesignerRightPanel() {
</Card>
)}
{/* 페이지 번호 설정 */}
{selectedComponent.type === "pageNumber" && (
<Card className="mt-4 border-purple-200 bg-purple-50">
<CardHeader className="pb-3">
<CardTitle className="text-sm text-purple-900"> </CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div>
<Label className="text-xs"> </Label>
<Select
value={selectedComponent.pageNumberFormat || "number"}
onValueChange={(value) =>
updateComponent(selectedComponent.id, {
pageNumberFormat: value as "number" | "numberTotal" | "koreanNumber",
})
}
>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="number"> (1, 2, 3...)</SelectItem>
<SelectItem value="numberTotal">/ (1 / 3)</SelectItem>
<SelectItem value="koreanNumber"> (1 )</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
)}
{/* 데이터 바인딩 (텍스트/라벨/테이블 컴포넌트) */}
{(selectedComponent.type === "text" ||
selectedComponent.type === "label" ||

View File

@@ -58,6 +58,8 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
pageWidth: number,
pageHeight: number,
backgroundColor: string,
pageIndex: number = 0,
totalPages: number = 1,
): string => {
const componentsHTML = pageComponents
.map((component) => {
@@ -139,6 +141,26 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
</div>`;
}
// PageNumber 컴포넌트
else if (component.type === "pageNumber") {
const format = component.pageNumberFormat || "number";
let pageNumberText = "";
switch (format) {
case "number":
pageNumberText = `${pageIndex + 1}`;
break;
case "numberTotal":
pageNumberText = `${pageIndex + 1} / ${totalPages}`;
break;
case "koreanNumber":
pageNumberText = `${pageIndex + 1} 페이지`;
break;
default:
pageNumberText = `${pageIndex + 1}`;
}
content = `<div style="display: flex; align-items: center; justify-content: center; height: 100%; font-size: ${component.fontSize}px; color: ${component.fontColor}; font-weight: ${component.fontWeight}; text-align: ${component.textAlign};">${pageNumberText}</div>`;
}
// Table 컴포넌트
else if (component.type === "table" && queryResult && queryResult.rows.length > 0) {
const columns =
@@ -189,14 +211,18 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
// 모든 페이지 HTML 생성 (인쇄/PDF용)
const generatePrintHTML = (): string => {
const pagesHTML = layoutConfig.pages
.sort((a, b) => a.page_order - b.page_order)
.map((page) =>
const sortedPages = layoutConfig.pages.sort((a, b) => a.page_order - b.page_order);
const totalPages = sortedPages.length;
const pagesHTML = sortedPages
.map((page, pageIndex) =>
generatePageHTML(
Array.isArray(page.components) ? page.components : [],
page.width,
page.height,
page.background_color,
pageIndex,
totalPages,
),
)
.join('<div style="page-break-after: always;"></div>');
@@ -700,6 +726,44 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
</div>
</div>
)}
{component.type === "pageNumber" && (() => {
const format = component.pageNumberFormat || "number";
const pageIndex = layoutConfig.pages
.sort((a, b) => a.page_order - b.page_order)
.findIndex((p) => p.page_id === page.page_id);
const totalPages = layoutConfig.pages.length;
let pageNumberText = "";
switch (format) {
case "number":
pageNumberText = `${pageIndex + 1}`;
break;
case "numberTotal":
pageNumberText = `${pageIndex + 1} / ${totalPages}`;
break;
case "koreanNumber":
pageNumberText = `${pageIndex + 1} 페이지`;
break;
default:
pageNumberText = `${pageIndex + 1}`;
}
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
height: "100%",
fontSize: `${component.fontSize}px`,
color: component.fontColor,
fontWeight: component.fontWeight,
}}
>
{pageNumberText}
</div>
);
})()}
</div>
);
})}