111 lines
3.4 KiB
TypeScript
111 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { useRef, useEffect } from "react";
|
|
import { useDrop } from "react-dnd";
|
|
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
|
|
import { ComponentConfig } from "@/types/report";
|
|
import { CanvasComponent } from "./CanvasComponent";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
|
|
export function ReportDesignerCanvas() {
|
|
const canvasRef = useRef<HTMLDivElement>(null);
|
|
const { components, addComponent, canvasWidth, canvasHeight, selectComponent, selectedComponentId, removeComponent } =
|
|
useReportDesigner();
|
|
|
|
const [{ isOver }, drop] = useDrop(() => ({
|
|
accept: "component",
|
|
drop: (item: { componentType: string }, monitor) => {
|
|
if (!canvasRef.current) return;
|
|
|
|
const offset = monitor.getClientOffset();
|
|
const canvasRect = canvasRef.current.getBoundingClientRect();
|
|
|
|
if (!offset) return;
|
|
|
|
const x = offset.x - canvasRect.left;
|
|
const y = offset.y - canvasRect.top;
|
|
|
|
// 새 컴포넌트 생성
|
|
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,
|
|
zIndex: components.length,
|
|
fontSize: 13,
|
|
fontFamily: "Malgun Gothic",
|
|
fontWeight: "normal",
|
|
fontColor: "#000000",
|
|
backgroundColor: "#ffffff",
|
|
borderWidth: 1,
|
|
borderColor: "#666666",
|
|
borderRadius: 5,
|
|
textAlign: "left",
|
|
padding: 10,
|
|
visible: true,
|
|
printable: true,
|
|
};
|
|
|
|
addComponent(newComponent);
|
|
},
|
|
collect: (monitor) => ({
|
|
isOver: monitor.isOver(),
|
|
}),
|
|
}));
|
|
|
|
const handleCanvasClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
if (e.target === e.currentTarget) {
|
|
selectComponent(null);
|
|
}
|
|
};
|
|
|
|
// Delete 키 삭제 처리
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.key === "Delete" && selectedComponentId) {
|
|
removeComponent(selectedComponentId);
|
|
}
|
|
};
|
|
|
|
window.addEventListener("keydown", handleKeyDown);
|
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
}, [selectedComponentId, removeComponent]);
|
|
|
|
return (
|
|
<div className="flex flex-1 flex-col overflow-hidden bg-gray-100">
|
|
{/* 작업 영역 제목 */}
|
|
<div className="border-b bg-white px-4 py-2 text-center text-sm font-medium text-gray-700">작업 영역</div>
|
|
|
|
{/* 캔버스 스크롤 영역 */}
|
|
<div className="flex-1 overflow-auto p-8">
|
|
<div
|
|
ref={(node) => {
|
|
canvasRef.current = node;
|
|
drop(node);
|
|
}}
|
|
className={`relative mx-auto bg-white shadow-lg ${isOver ? "ring-2 ring-blue-500" : ""}`}
|
|
style={{
|
|
width: `${canvasWidth}mm`,
|
|
minHeight: `${canvasHeight}mm`,
|
|
}}
|
|
onClick={handleCanvasClick}
|
|
>
|
|
{/* 컴포넌트 렌더링 */}
|
|
{components.map((component) => (
|
|
<CanvasComponent key={component.id} component={component} />
|
|
))}
|
|
|
|
{/* 빈 캔버스 안내 */}
|
|
{components.length === 0 && (
|
|
<div className="absolute inset-0 flex items-center justify-center text-gray-400">
|
|
<p className="text-sm">왼쪽에서 컴포넌트를 드래그하여 추가하세요</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|