ERP-node/frontend/components/report/designer/SignaturePad.tsx

136 lines
3.6 KiB
TypeScript
Raw Normal View History

2025-10-02 10:04:02 +09:00
"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<HTMLCanvasElement>(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<HTMLCanvasElement>) => {
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<HTMLCanvasElement>) => {
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 (
<div className="space-y-2">
<Card className="overflow-hidden border-2 border-dashed border-gray-300 bg-white p-2">
<canvas
ref={canvasRef}
width={300}
height={150}
onMouseDown={startDrawing}
onMouseMove={draw}
onMouseUp={stopDrawing}
onMouseLeave={stopDrawing}
className="cursor-crosshair touch-none"
style={{ width: "100%", height: "auto" }}
/>
</Card>
<div className="flex items-center justify-between">
<p className="text-xs text-gray-500">
<Pen className="mr-1 inline h-3 w-3" />
</p>
<Button type="button" variant="outline" size="sm" onClick={clearSignature} disabled={!hasDrawn}>
<Eraser className="mr-1 h-3 w-3" />
</Button>
</div>
</div>
);
}