diff --git a/frontend/components/report/designer/ReportDesignerRightPanel.tsx b/frontend/components/report/designer/ReportDesignerRightPanel.tsx index 63304bca..b3fea2cb 100644 --- a/frontend/components/report/designer/ReportDesignerRightPanel.tsx +++ b/frontend/components/report/designer/ReportDesignerRightPanel.tsx @@ -11,6 +11,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Trash2, Settings, Database, Link2, Upload, Loader2 } from "lucide-react"; import { useReportDesigner } from "@/contexts/ReportDesignerContext"; import { QueryManager } from "./QueryManager"; +import { SignaturePad } from "./SignaturePad"; import { reportApi } from "@/lib/api/reportApi"; import { useToast } from "@/hooks/use-toast"; @@ -636,48 +637,110 @@ export function ReportDesignerRightPanel() { - {/* 파일 업로드 */} -
- -
- - +
+

JPG, PNG, GIF, WEBP (최대 10MB)

+ {selectedComponent.imageUrl && !selectedComponent.imageUrl.startsWith("data:") && ( +

현재: {selectedComponent.imageUrl}

)} - + + + ) : ( + // 도장란: 기존 방식 유지 +
+ +
+ + +
+

JPG, PNG, GIF, WEBP (최대 10MB)

+ {selectedComponent.imageUrl && !selectedComponent.imageUrl.startsWith("data:") && ( +

+ 현재: {selectedComponent.imageUrl} +

+ )}
-

JPG, PNG, GIF, WEBP (최대 10MB)

- {selectedComponent.imageUrl && ( -

- 현재: {selectedComponent.imageUrl} -

- )} -
+ )} {/* 맞춤 방식 */}
diff --git a/frontend/components/report/designer/SignaturePad.tsx b/frontend/components/report/designer/SignaturePad.tsx new file mode 100644 index 00000000..3ed7b01e --- /dev/null +++ b/frontend/components/report/designer/SignaturePad.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { useRef, useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Eraser, Pen } from "lucide-react"; + +interface SignaturePadProps { + onSignatureChange: (dataUrl: string) => void; + initialSignature?: string; +} + +export function SignaturePad({ onSignatureChange, initialSignature }: SignaturePadProps) { + const canvasRef = useRef(null); + const [isDrawing, setIsDrawing] = useState(false); + const [hasDrawn, setHasDrawn] = useState(false); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + // 초기 서명이 있으면 로드 + if (initialSignature) { + const img = new Image(); + img.onload = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(img, 0, 0); + setHasDrawn(true); + }; + img.src = initialSignature; + } else { + // 캔버스 초기화 (흰색 배경) + ctx.fillStyle = "#ffffff"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + }, [initialSignature]); + + const startDrawing = (e: React.MouseEvent) => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + setIsDrawing(true); + setHasDrawn(true); + + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + ctx.strokeStyle = "#000000"; + ctx.lineWidth = 2; + }; + + const draw = (e: React.MouseEvent) => { + if (!isDrawing) return; + + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + ctx.lineTo(x, y); + ctx.stroke(); + }; + + const stopDrawing = () => { + if (!isDrawing) return; + + setIsDrawing(false); + + const canvas = canvasRef.current; + if (!canvas) return; + + // 서명 이미지를 Base64 데이터로 변환하여 콜백 호출 + const dataUrl = canvas.toDataURL("image/png"); + onSignatureChange(dataUrl); + }; + + const clearSignature = () => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + // 캔버스 클리어 (흰색 배경) + ctx.fillStyle = "#ffffff"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + setHasDrawn(false); + onSignatureChange(""); + }; + + return ( +
+ + + +
+

+ + 마우스로 서명해주세요 +

+ +
+
+ ); +}