Edit Signature
This commit is contained in:
parent
261dc665c6
commit
da37cbe16a
|
|
@ -1,15 +1,34 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { AiOutlineClose, AiOutlineUpload, AiOutlineEdit } from 'react-icons/ai';
|
import { AiOutlineUpload, AiOutlineEdit } from 'react-icons/ai';
|
||||||
import { MdImage, MdCloudUpload } from 'react-icons/md';
|
import { MdCloudUpload } from 'react-icons/md';
|
||||||
import api from '../../../config/api';
|
import api from '../../../config/api';
|
||||||
import DrawSignatureCanvas from './DrawSignatureCanvas';
|
import DrawSignatureCanvas from './DrawSignatureCanvas';
|
||||||
import { FaSpinner } from "react-icons/fa";
|
import { FaSpinner } from "react-icons/fa";
|
||||||
import { GrCheckboxSelected } from "react-icons/gr";
|
import { GrCheckboxSelected } from "react-icons/gr";
|
||||||
|
import { Bounce, toast } from "react-toastify";
|
||||||
|
|
||||||
|
interface Signature {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
signatureType: string;
|
||||||
|
lastName: string;
|
||||||
|
firstName: string
|
||||||
|
font: string;
|
||||||
|
initialsBlob: string;
|
||||||
|
initialsStampedBlob: string;
|
||||||
|
initialsHash: string;
|
||||||
|
signatureHash: string;
|
||||||
|
signatureBlob: string;
|
||||||
|
signatureStampedBlob: string;
|
||||||
|
default: boolean
|
||||||
|
}
|
||||||
|
|
||||||
interface AddSignatureModalProps {
|
interface AddSignatureModalProps {
|
||||||
setShowAddSignature?: (value: boolean) => void;
|
setShowAddSignature?: (value: boolean) => void;
|
||||||
getAllSignatures: () => Promise<void>;
|
getAllSignatures: () => Promise<void>;
|
||||||
|
editingSignature?: Signature | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Account {
|
interface Account {
|
||||||
id: number;
|
id: number;
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
|
|
@ -29,7 +48,12 @@ interface SelectStyle {
|
||||||
id: number,
|
id: number,
|
||||||
style: string
|
style: string
|
||||||
}
|
}
|
||||||
const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignature , getAllSignatures }) => {
|
|
||||||
|
const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
|
||||||
|
setShowAddSignature,
|
||||||
|
getAllSignatures,
|
||||||
|
editingSignature
|
||||||
|
}) => {
|
||||||
const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT');
|
const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT');
|
||||||
const [fullName, setFullName] = useState('');
|
const [fullName, setFullName] = useState('');
|
||||||
const [initials, setInitials] = useState('');
|
const [initials, setInitials] = useState('');
|
||||||
|
|
@ -38,6 +62,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
|
||||||
fullSignature: string | null;
|
fullSignature: string | null;
|
||||||
initials: string | null;
|
initials: string | null;
|
||||||
}>({ fullSignature: null, initials: null });
|
}>({ fullSignature: null, initials: null });
|
||||||
|
|
||||||
const signatureOptions = [
|
const signatureOptions = [
|
||||||
{ id: 1, name: 'print', initials: 'LA', style: 'print' },
|
{ id: 1, name: 'print', initials: 'LA', style: 'print' },
|
||||||
{ id: 2, name: 'cursive', initials: 'LA', style: 'cursive' },
|
{ id: 2, name: 'cursive', initials: 'LA', style: 'cursive' },
|
||||||
|
|
@ -46,6 +71,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
|
||||||
{ id: 5, name: 'modern', initials: 'LA', style: 'modern' },
|
{ id: 5, name: 'modern', initials: 'LA', style: 'modern' },
|
||||||
{ id: 6, name: 'classic', initials: 'LA', style: 'classic' },
|
{ id: 6, name: 'classic', initials: 'LA', style: 'classic' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const [selectedSignature, setSelectedSignature] = useState<SelectStyle>(
|
const [selectedSignature, setSelectedSignature] = useState<SelectStyle>(
|
||||||
signatureOptions.length > 0 ? { id: signatureOptions[0].id, style: signatureOptions[0].style } : { id: 0, style: "" }
|
signatureOptions.length > 0 ? { id: signatureOptions[0].id, style: signatureOptions[0].style } : { id: 0, style: "" }
|
||||||
);
|
);
|
||||||
|
|
@ -58,7 +84,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
|
||||||
const [preview1, setPreview1] = useState<string | null>(null);
|
const [preview1, setPreview1] = useState<string | null>(null);
|
||||||
const [preview2, setPreview2] = useState<string | null>(null);
|
const [preview2, setPreview2] = useState<string | null>(null);
|
||||||
|
|
||||||
//user
|
// User data
|
||||||
const [user, setUser] = useState<UserData>({
|
const [user, setUser] = useState<UserData>({
|
||||||
id: "",
|
id: "",
|
||||||
firstName: "",
|
firstName: "",
|
||||||
|
|
@ -71,22 +97,48 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
|
||||||
name: "",
|
name: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isEditMode = !!editingSignature;
|
||||||
|
|
||||||
// Navigation items
|
// Navigation items
|
||||||
const navigationItems = [
|
const navigationItems = [
|
||||||
{ id: 'SELECT', label: 'SELECT', icon: <GrCheckboxSelected className="w-4 h-4" /> },
|
{ id: 'SELECT', label: 'SELECT', icon: <GrCheckboxSelected className="w-4 h-4" /> },
|
||||||
{ id: 'DRAW', label: 'DRAW', icon: <AiOutlineEdit className="w-5 h-5" /> },
|
{ id: 'DRAW', label: 'DRAW', icon: <AiOutlineEdit className="w-5 h-5" /> },
|
||||||
{ id: 'UPLOAD', label: 'UPLOAD', icon: <AiOutlineUpload className="w-5 h-5" /> },
|
{ id: 'UPLOAD', label: 'UPLOAD', icon: <AiOutlineUpload className="w-5 h-5" /> },
|
||||||
];
|
];
|
||||||
console.log("signatures", signatures)
|
|
||||||
|
// Initialize form with editing signature data
|
||||||
|
useEffect(() => {
|
||||||
|
if (editingSignature) {
|
||||||
|
setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`);
|
||||||
|
setInitials(`${editingSignature.firstName.slice(0, 1)}${editingSignature.lastName.slice(0, 1)}`);
|
||||||
|
setActiveTab(editingSignature.signatureType as 'SELECT' | 'DRAW' | 'UPLOAD');
|
||||||
|
|
||||||
|
// Set font for SELECT tab
|
||||||
|
const fontOption = signatureOptions.find(option => option.style === editingSignature.font);
|
||||||
|
if (fontOption) {
|
||||||
|
setSelectedSignature({ id: fontOption.id, style: fontOption.style });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set signature blobs for all tabs
|
||||||
|
if (editingSignature.signatureBlob && editingSignature.initialsBlob) {
|
||||||
|
setSignatures({
|
||||||
|
fullSignature: `data:image/png;base64,${editingSignature.signatureBlob}`,
|
||||||
|
initials: `data:image/png;base64,${editingSignature.initialsBlob}`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set preview URLs for UPLOAD tab
|
||||||
|
if (editingSignature.signatureType === 'UPLOAD') {
|
||||||
|
setPreview1(`data:image/png;base64,${editingSignature.signatureBlob}`);
|
||||||
|
setPreview2(`data:image/png;base64,${editingSignature.initialsBlob}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [editingSignature]);
|
||||||
|
|
||||||
const handleSignatureChange = (data: { fullSignature: string | null; initials: string | null }) => {
|
const handleSignatureChange = (data: { fullSignature: string | null; initials: string | null }) => {
|
||||||
setSignatures(data);
|
setSignatures(data);
|
||||||
};
|
};
|
||||||
console.log("activeTab", activeTab)
|
|
||||||
console.log("selectedSignature", selectedSignature);
|
|
||||||
|
|
||||||
console.log('file1', file1)
|
|
||||||
console.log("file1", file2)
|
|
||||||
|
|
||||||
|
|
||||||
// Cleanup preview URLs when component unmounts or image changes
|
// Cleanup preview URLs when component unmounts or image changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -129,37 +181,85 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true);
|
||||||
|
|
||||||
if (!signatures.fullSignature || !signatures.initials) {
|
if (!signatures.fullSignature || !signatures.initials) {
|
||||||
alert('Please provide both signature and initials');
|
alert('Please provide both signature and initials');
|
||||||
|
setIsSubmitting(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
signatureType: activeTab,
|
signatureType: activeTab,
|
||||||
// "signatureId": 0, this work on only the put req.
|
|
||||||
signatureBase64: signatures.fullSignature,
|
signatureBase64: signatures.fullSignature,
|
||||||
initialsBase64: signatures.initials,
|
initialsBase64: signatures.initials,
|
||||||
firstName: user.firstName,
|
firstName: fullName.split(' ')[0] || user.firstName,
|
||||||
lastName: user.lastName,
|
lastName: fullName.split(' ')[1] || user.lastName,
|
||||||
font: selectedSignature.style
|
font: selectedSignature.style
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Submitting Signature:', payload);
|
let res;
|
||||||
let res = await api.post(`/api/user/signatures`, payload)
|
if (isEditMode && editingSignature) {
|
||||||
console.log("handleSubmit", res)
|
// Update existing signature
|
||||||
|
const updatePayload = {
|
||||||
|
...payload,
|
||||||
|
signatureId: editingSignature.id
|
||||||
|
};
|
||||||
|
res = await api.put(`/api/user/signatures`, updatePayload);
|
||||||
|
console.log("handleUpdate", res);
|
||||||
|
|
||||||
|
toast.success("Signature Updated Successfully", {
|
||||||
|
position: "top-center",
|
||||||
|
autoClose: 500,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: false,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
transition: Bounce,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Create new signature
|
||||||
|
res = await api.post(`/api/user/signatures`, payload);
|
||||||
|
console.log("handleSubmit", res);
|
||||||
|
|
||||||
|
toast.success("Signature Created Successfully", {
|
||||||
|
position: "top-center",
|
||||||
|
autoClose: 500,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: false,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
transition: Bounce,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (res?.data) {
|
if (res?.data) {
|
||||||
getAllSignatures()
|
await getAllSignatures();
|
||||||
setShowAddSignature?.(false)
|
setShowAddSignature?.(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching user details:", error);
|
console.error("Error submitting signature:", error);
|
||||||
|
toast.error(isEditMode ? "Failed to update signature" : "Failed to create signature", {
|
||||||
|
position: "top-center",
|
||||||
|
autoClose: 2000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: false,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
theme: "light",
|
||||||
|
transition: Bounce,
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case 'SELECT':
|
case 'SELECT':
|
||||||
|
|
@ -218,7 +318,10 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
|
||||||
|
|
||||||
case 'DRAW':
|
case 'DRAW':
|
||||||
return (
|
return (
|
||||||
<DrawSignatureCanvas onSignatureChange={handleSignatureChange} />
|
<DrawSignatureCanvas
|
||||||
|
onSignatureChange={handleSignatureChange}
|
||||||
|
initialSignatures={isEditMode ? signatures : undefined}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'UPLOAD':
|
case 'UPLOAD':
|
||||||
|
|
@ -287,20 +390,24 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
|
||||||
|
|
||||||
const getUserDetails = async () => {
|
const getUserDetails = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await api.get(`/api/user/details`)
|
let res = await api.get(`/api/user/details`);
|
||||||
console.log("getuser", res)
|
console.log("getuser", res);
|
||||||
let currUser = res?.data
|
let currUser = res?.data;
|
||||||
setUser(currUser)
|
setUser(currUser);
|
||||||
setFullName(currUser.firstName + " " + currUser.lastName)
|
|
||||||
setInitials(currUser.firstName.slice(0, 1) + currUser.lastName.slice(0, 1))
|
// Only set default values if not in edit mode
|
||||||
|
if (!isEditMode) {
|
||||||
|
setFullName(currUser.firstName + " " + currUser.lastName);
|
||||||
|
setInitials(currUser.firstName.slice(0, 1) + currUser.lastName.slice(0, 1));
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching user details:", error);
|
console.error("Error fetching user details:", error);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getUserDetails()
|
getUserDetails();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||||
|
|
@ -405,10 +512,10 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({ setShowAddSignatu
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
<>
|
||||||
<FaSpinner className="animate-spin" />
|
<FaSpinner className="animate-spin" />
|
||||||
CREATING...
|
{isEditMode ? 'UPDATING...' : 'CREATING...'}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"CREATE SIGNATURE"
|
isEditMode ? 'UPDATE SIGNATURE' : 'CREATE SIGNATURE'
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onSignatureChange: (data: { fullSignature: string | null; initials: string | null }) => void;
|
onSignatureChange: (data: { fullSignature: string | null; initials: string | null }) => void;
|
||||||
|
initialSignatures?: { fullSignature: string | null; initials: string | null };
|
||||||
};
|
};
|
||||||
|
|
||||||
const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
|
const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignatures }) => {
|
||||||
const canvasRef1 = useRef<HTMLCanvasElement>(null);
|
const canvasRef1 = useRef<HTMLCanvasElement>(null);
|
||||||
const canvasRef2 = useRef<HTMLCanvasElement>(null);
|
const canvasRef2 = useRef<HTMLCanvasElement>(null);
|
||||||
const [isDrawing, setIsDrawing] = useState(false);
|
const [isDrawing, setIsDrawing] = useState(false);
|
||||||
|
|
@ -12,6 +13,84 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
|
||||||
const [paths2, setPaths2] = useState<Array<Array<{ x: number; y: number }>>>([]);
|
const [paths2, setPaths2] = useState<Array<Array<{ x: number; y: number }>>>([]);
|
||||||
const [currentPath, setCurrentPath] = useState<Array<{ x: number; y: number }>>([]);
|
const [currentPath, setCurrentPath] = useState<Array<{ x: number; y: number }>>([]);
|
||||||
const [activeCanvas, setActiveCanvas] = useState<'full' | 'initials' | null>(null);
|
const [activeCanvas, setActiveCanvas] = useState<'full' | 'initials' | null>(null);
|
||||||
|
const [initialLoaded, setInitialLoaded] = useState(false);
|
||||||
|
|
||||||
|
// Store initial signature images to avoid reloading
|
||||||
|
const [initialImage1, setInitialImage1] = useState<HTMLImageElement | null>(null);
|
||||||
|
const [initialImage2, setInitialImage2] = useState<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
|
// Setup canvas dimensions when component mounts
|
||||||
|
useEffect(() => {
|
||||||
|
const setupCanvas = () => {
|
||||||
|
if (canvasRef1.current) {
|
||||||
|
const rect1 = canvasRef1.current.getBoundingClientRect();
|
||||||
|
canvasRef1.current.width = rect1.width;
|
||||||
|
canvasRef1.current.height = rect1.height;
|
||||||
|
}
|
||||||
|
if (canvasRef2.current) {
|
||||||
|
const rect2 = canvasRef2.current.getBoundingClientRect();
|
||||||
|
canvasRef2.current.width = rect2.width;
|
||||||
|
canvasRef2.current.height = rect2.height;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setupCanvas();
|
||||||
|
|
||||||
|
// Load initial signatures after canvas setup
|
||||||
|
if (initialSignatures && !initialLoaded) {
|
||||||
|
setTimeout(() => {
|
||||||
|
loadInitialSignatures();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadInitialSignatures = () => {
|
||||||
|
if (!initialSignatures) return;
|
||||||
|
|
||||||
|
let loadedCount = 0;
|
||||||
|
const totalToLoad = (initialSignatures.fullSignature ? 1 : 0) + (initialSignatures.initials ? 1 : 0);
|
||||||
|
|
||||||
|
const checkComplete = () => {
|
||||||
|
loadedCount++;
|
||||||
|
if (loadedCount === totalToLoad) {
|
||||||
|
setInitialLoaded(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load full signature
|
||||||
|
if (initialSignatures.fullSignature && canvasRef1.current) {
|
||||||
|
const img1 = new Image();
|
||||||
|
img1.onload = () => {
|
||||||
|
setInitialImage1(img1);
|
||||||
|
const ctx1 = canvasRef1.current?.getContext('2d');
|
||||||
|
if (ctx1) {
|
||||||
|
ctx1.clearRect(0, 0, canvasRef1.current!.width, canvasRef1.current!.height);
|
||||||
|
ctx1.drawImage(img1, 0, 0);
|
||||||
|
}
|
||||||
|
checkComplete();
|
||||||
|
};
|
||||||
|
img1.src = initialSignatures.fullSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load initials
|
||||||
|
if (initialSignatures.initials && canvasRef2.current) {
|
||||||
|
const img2 = new Image();
|
||||||
|
img2.onload = () => {
|
||||||
|
setInitialImage2(img2);
|
||||||
|
const ctx2 = canvasRef2.current?.getContext('2d');
|
||||||
|
if (ctx2) {
|
||||||
|
ctx2.clearRect(0, 0, canvasRef2.current!.width, canvasRef2.current!.height);
|
||||||
|
ctx2.drawImage(img2, 0, 0);
|
||||||
|
}
|
||||||
|
checkComplete();
|
||||||
|
};
|
||||||
|
img2.src = initialSignatures.initials;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalToLoad === 0) {
|
||||||
|
setInitialLoaded(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||||
setIsDrawing(true);
|
setIsDrawing(true);
|
||||||
|
|
@ -25,9 +104,31 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
|
||||||
if (!isDrawing) return;
|
if (!isDrawing) return;
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
||||||
const newPath = [...currentPath, point]; // <- Build new path
|
const newPath = [...currentPath, point];
|
||||||
setCurrentPath(newPath); // <- Save for next event
|
setCurrentPath(newPath);
|
||||||
drawPath(e.currentTarget as HTMLCanvasElement, newPath); // <- Draw current
|
|
||||||
|
// Draw immediately for smooth experience
|
||||||
|
drawCurrentPath(e.currentTarget as HTMLCanvasElement, newPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawCurrentPath = (canvas: HTMLCanvasElement, path: Array<{ x: number; y: number }>) => {
|
||||||
|
if (!canvas || path.length < 2) return;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
// Only draw the current path segment without clearing the canvas
|
||||||
|
ctx.strokeStyle = '#000';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.lineJoin = 'round';
|
||||||
|
ctx.lineCap = 'round';
|
||||||
|
|
||||||
|
const lastPoint = path[path.length - 2];
|
||||||
|
const currentPoint = path[path.length - 1];
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(lastPoint.x, lastPoint.y);
|
||||||
|
ctx.lineTo(currentPoint.x, currentPoint.y);
|
||||||
|
ctx.stroke();
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportCanvasAsImage = (canvas: HTMLCanvasElement | null): string | null => {
|
const exportCanvasAsImage = (canvas: HTMLCanvasElement | null): string | null => {
|
||||||
|
|
@ -42,45 +143,48 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
|
||||||
if (activeCanvas === 'full') {
|
if (activeCanvas === 'full') {
|
||||||
const newPaths = [...paths1, currentPath];
|
const newPaths = [...paths1, currentPath];
|
||||||
setPaths1(newPaths);
|
setPaths1(newPaths);
|
||||||
const fullSignature = exportCanvasAsImage(canvasRef1.current);
|
|
||||||
onSignatureChange({ fullSignature, initials: exportCanvasAsImage(canvasRef2.current) });
|
|
||||||
} else {
|
} else {
|
||||||
const newPaths = [...paths2, currentPath];
|
const newPaths = [...paths2, currentPath];
|
||||||
setPaths2(newPaths);
|
setPaths2(newPaths);
|
||||||
const initials = exportCanvasAsImage(canvasRef2.current);
|
|
||||||
onSignatureChange({ fullSignature: exportCanvasAsImage(canvasRef1.current), initials });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentPath([]);
|
setCurrentPath([]);
|
||||||
|
|
||||||
|
// Export both canvases and notify parent
|
||||||
|
const fullSignature = exportCanvasAsImage(canvasRef1.current);
|
||||||
|
const initials = exportCanvasAsImage(canvasRef2.current);
|
||||||
|
onSignatureChange({ fullSignature, initials });
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawPath = (canvas: HTMLCanvasElement | null, path: Array<{ x: number; y: number }>) => {
|
const redrawCanvas = (canvas: HTMLCanvasElement, paths: Array<Array<{ x: number; y: number }>>, initialImage: HTMLImageElement | null) => {
|
||||||
if (!canvas) return;
|
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
const allPaths =
|
// Clear canvas
|
||||||
canvas === canvasRef1.current ? [...paths1, path] : [...paths2, path];
|
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Draw initial signature if exists
|
||||||
|
if (initialImage) {
|
||||||
|
ctx.drawImage(initialImage, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw all paths
|
||||||
ctx.strokeStyle = '#000';
|
ctx.strokeStyle = '#000';
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.lineJoin = 'round';
|
ctx.lineJoin = 'round';
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
|
|
||||||
allPaths.forEach((p) => {
|
paths.forEach((path) => {
|
||||||
if (p.length < 2) return;
|
if (path.length < 2) return;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(p[0].x, p[0].y);
|
ctx.moveTo(path[0].x, path[0].y);
|
||||||
for (let i = 1; i < p.length; i++) {
|
for (let i = 1; i < path.length; i++) {
|
||||||
ctx.lineTo(p[i].x, p[i].y);
|
ctx.lineTo(path[i].x, path[i].y);
|
||||||
}
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const clearCanvas = () => {
|
const clearCanvas = () => {
|
||||||
if (canvasRef1.current) {
|
if (canvasRef1.current) {
|
||||||
const ctx1 = canvasRef1.current.getContext('2d');
|
const ctx1 = canvasRef1.current.getContext('2d');
|
||||||
|
|
@ -93,9 +197,30 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
|
||||||
|
|
||||||
setPaths1([]);
|
setPaths1([]);
|
||||||
setPaths2([]);
|
setPaths2([]);
|
||||||
|
setInitialImage1(null);
|
||||||
|
setInitialImage2(null);
|
||||||
|
setInitialLoaded(false);
|
||||||
onSignatureChange({ fullSignature: null, initials: null });
|
onSignatureChange({ fullSignature: null, initials: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const undoLastPath = () => {
|
||||||
|
if (activeCanvas === 'full' && paths1.length > 0) {
|
||||||
|
const newPaths = paths1.slice(0, -1);
|
||||||
|
setPaths1(newPaths);
|
||||||
|
redrawCanvas(canvasRef1.current!, newPaths, initialImage1);
|
||||||
|
} else if (activeCanvas === 'initials' && paths2.length > 0) {
|
||||||
|
const newPaths = paths2.slice(0, -1);
|
||||||
|
setPaths2(newPaths);
|
||||||
|
redrawCanvas(canvasRef2.current!, newPaths, initialImage2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update parent with new signature data
|
||||||
|
setTimeout(() => {
|
||||||
|
const fullSignature = exportCanvasAsImage(canvasRef1.current);
|
||||||
|
const initials = exportCanvasAsImage(canvasRef2.current);
|
||||||
|
onSignatureChange({ fullSignature, initials });
|
||||||
|
}, 10);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
|
|
@ -113,8 +238,6 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef1}
|
ref={canvasRef1}
|
||||||
className="w-full h-48 cursor-crosshair"
|
className="w-full h-48 cursor-crosshair"
|
||||||
width={canvasRef1.current?.clientWidth || 600} // use real width
|
|
||||||
height={canvasRef1.current?.clientHeight || 180}
|
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleMouseUp}
|
||||||
|
|
@ -129,8 +252,6 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
|
||||||
<div className="border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors">
|
<div className="border-2 border-dashed border-gray-300 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors">
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef2}
|
ref={canvasRef2}
|
||||||
width={canvasRef2.current?.clientWidth || 300}
|
|
||||||
height={canvasRef2.current?.clientHeight || 180}
|
|
||||||
className="w-full h-48 cursor-crosshair"
|
className="w-full h-48 cursor-crosshair"
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
|
|
@ -147,7 +268,14 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
|
||||||
className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
||||||
onClick={clearCanvas}
|
onClick={clearCanvas}
|
||||||
>
|
>
|
||||||
Clear
|
Clear All
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
|
||||||
|
onClick={undoLastPath}
|
||||||
|
disabled={!activeCanvas || (activeCanvas === 'full' ? paths1.length === 0 : paths2.length === 0)}
|
||||||
|
>
|
||||||
|
Undo Last
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -155,4 +283,4 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DrawSignatureCanvas;
|
export default DrawSignatureCanvas;
|
||||||
|
|
@ -25,7 +25,8 @@ const SignatureManager: React.FC = () => {
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [signatures, setSignatures] = useState<Signature[]>([]);
|
const [signatures, setSignatures] = useState<Signature[]>([]);
|
||||||
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
|
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
|
||||||
const [showAddSignature, setShowAddSignature] = useState<boolean>(false)
|
const [showAddSignature, setShowAddSignature] = useState<boolean>(false);
|
||||||
|
const [editingSignature, setEditingSignature] = useState<Signature | null>(null);
|
||||||
|
|
||||||
const getAllSignatures = async () => {
|
const getAllSignatures = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
@ -47,6 +48,13 @@ const SignatureManager: React.FC = () => {
|
||||||
|
|
||||||
const handleEdit = (id: string) => {
|
const handleEdit = (id: string) => {
|
||||||
console.log('Edit signature:', id);
|
console.log('Edit signature:', id);
|
||||||
|
let editSignature = signatures.find((item) => item.id === id);
|
||||||
|
console.log("editSignature", editSignature);
|
||||||
|
|
||||||
|
if (editSignature) {
|
||||||
|
setEditingSignature(editSignature);
|
||||||
|
setShowAddSignature(true);
|
||||||
|
}
|
||||||
setActiveDropdown(null);
|
setActiveDropdown(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -74,14 +82,21 @@ const SignatureManager: React.FC = () => {
|
||||||
setActiveDropdown(null);
|
setActiveDropdown(null);
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleDropdown = (id: string) => {
|
const toggleDropdown = (id: string) => {
|
||||||
setActiveDropdown(activeDropdown === id ? null : id);
|
setActiveDropdown(activeDropdown === id ? null : id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddNew = () => {
|
||||||
|
setEditingSignature(null);
|
||||||
|
setShowAddSignature(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setShowAddSignature(false);
|
||||||
|
setEditingSignature(null);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -102,7 +117,7 @@ const SignatureManager: React.FC = () => {
|
||||||
<h1 className="text-[17px] font-medium text-gray-500 mb-2">Signatures</h1>
|
<h1 className="text-[17px] font-medium text-gray-500 mb-2">Signatures</h1>
|
||||||
<p className="text-gray-300 text-sm">Add or update your name and signature styles.</p>
|
<p className="text-gray-300 text-sm">Add or update your name and signature styles.</p>
|
||||||
</div>
|
</div>
|
||||||
<button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-semibold text-sm text-[14px] flex items-center cursor-pointer transition-all duration-200 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5" onClick={() => setShowAddSignature((prev) => !prev)}>
|
<button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-semibold text-sm text-[14px] flex items-center cursor-pointer transition-all duration-200 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5" onClick={handleAddNew}>
|
||||||
<FiPlus className="" />
|
<FiPlus className="" />
|
||||||
ADD
|
ADD
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -153,7 +168,6 @@ const SignatureManager: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Initial Column */}
|
{/* Initial Column */}
|
||||||
<div className="col-span-5">
|
<div className="col-span-5">
|
||||||
<div className="flex flex-col space-y-2 relative ps-6 border-l-3 border-green-600 rounded-bl-2xl rounded-tl-2xl">
|
<div className="flex flex-col space-y-2 relative ps-6 border-l-3 border-green-600 rounded-bl-2xl rounded-tl-2xl">
|
||||||
|
|
@ -173,7 +187,6 @@ const SignatureManager: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Actions Column */}
|
{/* Actions Column */}
|
||||||
<div className="col-span-2 flex justify-between items-start pt-9 ps-8 ">
|
<div className="col-span-2 flex justify-between items-start pt-9 ps-8 ">
|
||||||
<div className="relative ">
|
<div className="relative ">
|
||||||
|
|
@ -233,7 +246,11 @@ const SignatureManager: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showAddSignature && (
|
{showAddSignature && (
|
||||||
<AddSignatureModal setShowAddSignature={setShowAddSignature} getAllSignatures={getAllSignatures} />
|
<AddSignatureModal
|
||||||
|
setShowAddSignature={handleModalClose}
|
||||||
|
getAllSignatures={getAllSignatures}
|
||||||
|
editingSignature={editingSignature}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}</>
|
}</>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue