"use client"; import { useRef, useState, useEffect } from "react"; import { useDrop } from "react-dnd"; import { useBarcodeDesigner, MM_TO_PX } from "@/contexts/BarcodeDesignerContext"; import { BarcodeLabelCanvasComponent } from "./BarcodeLabelCanvasComponent"; import { BarcodeLabelComponent } from "@/types/barcode"; import { v4 as uuidv4 } from "uuid"; /** 작업 영역에 라벨이 들어가도록 스케일 (최소 0.5=작게 맞춤, 최대 3) */ const MIN_SCALE = 0.5; const MAX_SCALE = 3; export function BarcodeDesignerCanvas() { const containerRef = useRef(null); const canvasRef = useRef(null); const [scale, setScale] = useState(1); const { widthMm, heightMm, components, addComponent, selectComponent, showGrid, snapValueToGrid, } = useBarcodeDesigner(); const widthPx = widthMm * MM_TO_PX; const heightPx = heightMm * MM_TO_PX; // 컨테이너 크기에 맞춰 캔버스 스케일 계산 (라벨이 너무 작게 보이지 않도록) useEffect(() => { const el = containerRef.current; if (!el || widthPx <= 0 || heightPx <= 0) return; const observer = new ResizeObserver(() => { const w = el.clientWidth - 48; const h = el.clientHeight - 48; if (w <= 0 || h <= 0) return; const scaleX = w / widthPx; const scaleY = h / heightPx; const fitScale = Math.min(scaleX, scaleY); const s = Math.max(MIN_SCALE, Math.min(MAX_SCALE, fitScale)); setScale(s); }); observer.observe(el); const w = el.clientWidth - 48; const h = el.clientHeight - 48; if (w > 0 && h > 0) { const scaleX = w / widthPx; const scaleY = h / heightPx; const fitScale = Math.min(scaleX, scaleY); const s = Math.max(MIN_SCALE, Math.min(MAX_SCALE, fitScale)); setScale(s); } return () => observer.disconnect(); }, [widthPx, heightPx]); const [{ isOver }, drop] = useDrop(() => ({ accept: "barcode-component", drop: (item: { component: BarcodeLabelComponent }, monitor) => { const canvasEl = canvasRef.current; if (!canvasEl) return; const offset = monitor.getClientOffset(); const rect = canvasEl.getBoundingClientRect(); if (!offset) return; // 스케일 적용된 좌표 → 실제 캔버스 좌표 const s = scale; let x = (offset.x - rect.left) / s; let y = (offset.y - rect.top) / s; x -= item.component.width / 2; y -= item.component.height / 2; x = Math.max(0, Math.min(x, widthPx - item.component.width)); y = Math.max(0, Math.min(y, heightPx - item.component.height)); const newComp: BarcodeLabelComponent = { ...item.component, id: `comp_${uuidv4()}`, x: snapValueToGrid(x), y: snapValueToGrid(y), zIndex: components.length, }; addComponent(newComp); }, collect: (m) => ({ isOver: m.isOver() }), }), [widthPx, heightPx, scale, components.length, addComponent, snapValueToGrid]); // 스케일된 캔버스가 컨테이너 안에 들어가도록 fit (하단 잘림 방지) const scaledW = widthPx * scale; const scaledH = heightPx * scale; return (
{/* 래퍼: 스케일된 크기만큼 차지해서 flex로 정확히 가운데 + 하단 잘림 방지 */}
{ (canvasRef as { current: HTMLDivElement | null }).current = r; drop(r); }} className="relative bg-white shadow-lg" style={{ width: widthPx, height: heightPx, minWidth: widthPx, minHeight: heightPx, backgroundImage: showGrid ? `linear-gradient(to right, #e5e7eb 1px, transparent 1px), linear-gradient(to bottom, #e5e7eb 1px, transparent 1px)` : undefined, backgroundSize: showGrid ? `${MM_TO_PX * 5}px ${MM_TO_PX * 5}px` : undefined, outline: isOver ? "2px dashed #2563eb" : "1px solid #d1d5db", }} onClick={(e) => { if (e.target === e.currentTarget) selectComponent(null); }} > {components.map((c) => ( ))}
); }