캔버스에 그리드 시스템 적용
This commit is contained in:
parent
1c00ee28e8
commit
ae23a4408e
|
|
@ -9,7 +9,8 @@ interface CanvasComponentProps {
|
|||
}
|
||||
|
||||
export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||
const { selectedComponentId, selectComponent, updateComponent, getQueryResult } = useReportDesigner();
|
||||
const { selectedComponentId, selectComponent, updateComponent, getQueryResult, snapValueToGrid } =
|
||||
useReportDesigner();
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
|
||||
|
|
@ -53,13 +54,21 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
|||
if (isDragging) {
|
||||
const newX = Math.max(0, e.clientX - dragStart.x);
|
||||
const newY = Math.max(0, e.clientY - dragStart.y);
|
||||
updateComponent(component.id, { x: newX, y: newY });
|
||||
// Grid Snap 적용
|
||||
updateComponent(component.id, {
|
||||
x: snapValueToGrid(newX),
|
||||
y: snapValueToGrid(newY),
|
||||
});
|
||||
} else if (isResizing) {
|
||||
const deltaX = e.clientX - resizeStart.x;
|
||||
const deltaY = e.clientY - resizeStart.y;
|
||||
const newWidth = Math.max(50, resizeStart.width + deltaX);
|
||||
const newHeight = Math.max(30, resizeStart.height + deltaY);
|
||||
updateComponent(component.id, { width: newWidth, height: newHeight });
|
||||
// Grid Snap 적용
|
||||
updateComponent(component.id, {
|
||||
width: snapValueToGrid(newWidth),
|
||||
height: snapValueToGrid(newHeight),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -86,6 +95,7 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
|||
resizeStart.height,
|
||||
component.id,
|
||||
updateComponent,
|
||||
snapValueToGrid,
|
||||
]);
|
||||
|
||||
// 표시할 값 결정
|
||||
|
|
|
|||
|
|
@ -9,8 +9,18 @@ import { v4 as uuidv4 } from "uuid";
|
|||
|
||||
export function ReportDesignerCanvas() {
|
||||
const canvasRef = useRef<HTMLDivElement>(null);
|
||||
const { components, addComponent, canvasWidth, canvasHeight, selectComponent, selectedComponentId, removeComponent } =
|
||||
useReportDesigner();
|
||||
const {
|
||||
components,
|
||||
addComponent,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
selectComponent,
|
||||
selectedComponentId,
|
||||
removeComponent,
|
||||
showGrid,
|
||||
gridSize,
|
||||
snapValueToGrid,
|
||||
} = useReportDesigner();
|
||||
|
||||
const [{ isOver }, drop] = useDrop(() => ({
|
||||
accept: "component",
|
||||
|
|
@ -25,14 +35,14 @@ export function ReportDesignerCanvas() {
|
|||
const x = offset.x - canvasRect.left;
|
||||
const y = offset.y - canvasRect.top;
|
||||
|
||||
// 새 컴포넌트 생성
|
||||
// 새 컴포넌트 생성 (Grid Snap 적용)
|
||||
const newComponent: ComponentConfig = {
|
||||
id: `comp_${uuidv4()}`,
|
||||
type: item.componentType,
|
||||
x: Math.max(0, x - 100),
|
||||
y: Math.max(0, y - 25),
|
||||
width: 200,
|
||||
height: item.componentType === "table" ? 200 : 100,
|
||||
x: snapValueToGrid(Math.max(0, x - 100)),
|
||||
y: snapValueToGrid(Math.max(0, y - 25)),
|
||||
width: snapValueToGrid(200),
|
||||
height: snapValueToGrid(item.componentType === "table" ? 200 : 100),
|
||||
zIndex: components.length,
|
||||
fontSize: 13,
|
||||
fontFamily: "Malgun Gothic",
|
||||
|
|
@ -89,6 +99,13 @@ export function ReportDesignerCanvas() {
|
|||
style={{
|
||||
width: `${canvasWidth}mm`,
|
||||
minHeight: `${canvasHeight}mm`,
|
||||
backgroundImage: showGrid
|
||||
? `
|
||||
linear-gradient(to right, #e5e7eb 1px, transparent 1px),
|
||||
linear-gradient(to bottom, #e5e7eb 1px, transparent 1px)
|
||||
`
|
||||
: undefined,
|
||||
backgroundSize: showGrid ? `${gridSize}px ${gridSize}px` : undefined,
|
||||
}}
|
||||
onClick={handleCanvasClick}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Save, Eye, RotateCcw, ArrowLeft, Loader2, BookTemplate } from "lucide-react";
|
||||
import { Save, Eye, RotateCcw, ArrowLeft, Loader2, BookTemplate, Grid3x3 } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
|
||||
import { useState } from "react";
|
||||
|
|
@ -13,8 +13,20 @@ import { ReportPreviewModal } from "./ReportPreviewModal";
|
|||
|
||||
export function ReportDesignerToolbar() {
|
||||
const router = useRouter();
|
||||
const { reportDetail, saveLayout, isSaving, loadLayout, components, canvasWidth, canvasHeight, queries } =
|
||||
useReportDesigner();
|
||||
const {
|
||||
reportDetail,
|
||||
saveLayout,
|
||||
isSaving,
|
||||
loadLayout,
|
||||
components,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
queries,
|
||||
snapToGrid,
|
||||
setSnapToGrid,
|
||||
showGrid,
|
||||
setShowGrid,
|
||||
} = useReportDesigner();
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const [showSaveAsTemplate, setShowSaveAsTemplate] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
|
@ -22,6 +34,13 @@ export function ReportDesignerToolbar() {
|
|||
// 템플릿 저장 가능 여부: 컴포넌트가 있어야 함
|
||||
const canSaveAsTemplate = components.length > 0;
|
||||
|
||||
// Grid 토글 (Snap과 Grid 표시 함께 제어)
|
||||
const handleToggleGrid = () => {
|
||||
const newValue = !snapToGrid;
|
||||
setSnapToGrid(newValue);
|
||||
setShowGrid(newValue);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
await saveLayout();
|
||||
};
|
||||
|
|
@ -120,6 +139,16 @@ export function ReportDesignerToolbar() {
|
|||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant={snapToGrid && showGrid ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={handleToggleGrid}
|
||||
className="gap-2"
|
||||
title="Grid Snap 및 표시 켜기/끄기"
|
||||
>
|
||||
<Grid3x3 className="h-4 w-4" />
|
||||
{snapToGrid && showGrid ? "Grid ON" : "Grid OFF"}
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={handleReset} className="gap-2">
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
초기화
|
||||
|
|
|
|||
|
|
@ -307,6 +307,15 @@ interface ReportDesignerContextType {
|
|||
left: number;
|
||||
right: number;
|
||||
};
|
||||
|
||||
// 레이아웃 도구
|
||||
gridSize: number;
|
||||
setGridSize: (size: number) => void;
|
||||
showGrid: boolean;
|
||||
setShowGrid: (show: boolean) => void;
|
||||
snapToGrid: boolean;
|
||||
setSnapToGrid: (snap: boolean) => void;
|
||||
snapValueToGrid: (value: number) => number;
|
||||
}
|
||||
|
||||
const ReportDesignerContext = createContext<ReportDesignerContextType | undefined>(undefined);
|
||||
|
|
@ -322,6 +331,20 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
|||
const [isSaving, setIsSaving] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
// 레이아웃 도구 설정
|
||||
const [gridSize, setGridSize] = useState(10); // Grid Snap 크기 (px)
|
||||
const [showGrid, setShowGrid] = useState(true); // Grid 표시 여부
|
||||
const [snapToGrid, setSnapToGrid] = useState(true); // Grid Snap 활성화
|
||||
|
||||
// Grid Snap 함수
|
||||
const snapValueToGrid = useCallback(
|
||||
(value: number): number => {
|
||||
if (!snapToGrid) return value;
|
||||
return Math.round(value / gridSize) * gridSize;
|
||||
},
|
||||
[snapToGrid, gridSize],
|
||||
);
|
||||
|
||||
// 캔버스 설정 (기본값)
|
||||
const [canvasWidth, setCanvasWidth] = useState(210);
|
||||
const [canvasHeight, setCanvasHeight] = useState(297);
|
||||
|
|
@ -562,7 +585,7 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
|||
|
||||
// 커스텀 템플릿 찾기
|
||||
const customTemplates = response.data.custom || [];
|
||||
const template = customTemplates.find((t: any) => t.template_id === templateId);
|
||||
const template = customTemplates.find((t: { template_id: string }) => t.template_id === templateId);
|
||||
|
||||
if (!template) {
|
||||
throw new Error("템플릿을 찾을 수 없습니다.");
|
||||
|
|
@ -578,13 +601,21 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
|||
: template.default_queries || [];
|
||||
|
||||
// 컴포넌트 적용 (ID 재생성)
|
||||
const newComponents = layoutConfig.components.map((comp: any) => ({
|
||||
const newComponents = (layoutConfig.components as ComponentConfig[]).map((comp) => ({
|
||||
...comp,
|
||||
id: `comp-${Date.now()}-${Math.random()}`,
|
||||
}));
|
||||
|
||||
// 쿼리 적용 (ID 재생성)
|
||||
const newQueries = defaultQueries.map((q: any) => ({
|
||||
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,
|
||||
|
|
@ -637,6 +668,14 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
|||
canvasHeight,
|
||||
pageOrientation,
|
||||
margins,
|
||||
// 레이아웃 도구
|
||||
gridSize,
|
||||
setGridSize,
|
||||
showGrid,
|
||||
setShowGrid,
|
||||
snapToGrid,
|
||||
setSnapToGrid,
|
||||
snapValueToGrid,
|
||||
};
|
||||
|
||||
return <ReportDesignerContext.Provider value={value}>{children}</ReportDesignerContext.Provider>;
|
||||
|
|
|
|||
Loading…
Reference in New Issue