From da37cbe16a91b49cb7b7294050952dd9b1a530e6 Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Fri, 4 Jul 2025 14:08:40 +0530 Subject: [PATCH] Edit Signature --- .../setting/Signature/AddSignatureModal.tsx | 179 +++++++++++++---- .../setting/Signature/DrawSignatureCanvas.tsx | 182 +++++++++++++++--- .../setting/Signature/Signature.tsx | 29 ++- 3 files changed, 321 insertions(+), 69 deletions(-) diff --git a/src/components/setting/Signature/AddSignatureModal.tsx b/src/components/setting/Signature/AddSignatureModal.tsx index fd784e3..86c2fbb 100644 --- a/src/components/setting/Signature/AddSignatureModal.tsx +++ b/src/components/setting/Signature/AddSignatureModal.tsx @@ -1,15 +1,34 @@ import React, { useEffect, useState } from 'react'; -import { AiOutlineClose, AiOutlineUpload, AiOutlineEdit } from 'react-icons/ai'; -import { MdImage, MdCloudUpload } from 'react-icons/md'; +import { AiOutlineUpload, AiOutlineEdit } from 'react-icons/ai'; +import { MdCloudUpload } from 'react-icons/md'; import api from '../../../config/api'; import DrawSignatureCanvas from './DrawSignatureCanvas'; import { FaSpinner } from "react-icons/fa"; 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 { setShowAddSignature?: (value: boolean) => void; - getAllSignatures: () => Promise; + getAllSignatures: () => Promise; + editingSignature?: Signature | null; } + interface Account { id: number; isDefault: boolean; @@ -29,7 +48,12 @@ interface SelectStyle { id: number, style: string } -const AddSignatureModal: React.FC = ({ setShowAddSignature , getAllSignatures }) => { + +const AddSignatureModal: React.FC = ({ + setShowAddSignature, + getAllSignatures, + editingSignature +}) => { const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT'); const [fullName, setFullName] = useState(''); const [initials, setInitials] = useState(''); @@ -38,6 +62,7 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu fullSignature: string | null; initials: string | null; }>({ fullSignature: null, initials: null }); + const signatureOptions = [ { id: 1, name: 'print', initials: 'LA', style: 'print' }, { id: 2, name: 'cursive', initials: 'LA', style: 'cursive' }, @@ -46,6 +71,7 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu { id: 5, name: 'modern', initials: 'LA', style: 'modern' }, { id: 6, name: 'classic', initials: 'LA', style: 'classic' }, ]; + const [selectedSignature, setSelectedSignature] = useState( signatureOptions.length > 0 ? { id: signatureOptions[0].id, style: signatureOptions[0].style } : { id: 0, style: "" } ); @@ -58,7 +84,7 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu const [preview1, setPreview1] = useState(null); const [preview2, setPreview2] = useState(null); - //user + // User data const [user, setUser] = useState({ id: "", firstName: "", @@ -71,22 +97,48 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu name: "", }, }); + + const isEditMode = !!editingSignature; + // Navigation items const navigationItems = [ { id: 'SELECT', label: 'SELECT', icon: }, { id: 'DRAW', label: 'DRAW', icon: }, { id: 'UPLOAD', label: 'UPLOAD', icon: }, ]; - 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 }) => { 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 useEffect(() => { @@ -129,37 +181,85 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu }; const handleSubmit = async () => { - setIsSubmitting(true) + setIsSubmitting(true); + if (!signatures.fullSignature || !signatures.initials) { alert('Please provide both signature and initials'); + setIsSubmitting(false); return; } + try { const payload = { signatureType: activeTab, - // "signatureId": 0, this work on only the put req. signatureBase64: signatures.fullSignature, initialsBase64: signatures.initials, - firstName: user.firstName, - lastName: user.lastName, + firstName: fullName.split(' ')[0] || user.firstName, + lastName: fullName.split(' ')[1] || user.lastName, font: selectedSignature.style }; - console.log('Submitting Signature:', payload); - let res = await api.post(`/api/user/signatures`, payload) - console.log("handleSubmit", res) + let res; + if (isEditMode && editingSignature) { + // 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) { - getAllSignatures() - setShowAddSignature?.(false) + await getAllSignatures(); + setShowAddSignature?.(false); } } 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 { - setIsSubmitting(false) + setIsSubmitting(false); } }; - const renderContent = () => { switch (activeTab) { case 'SELECT': @@ -218,7 +318,10 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu case 'DRAW': return ( - + ); case 'UPLOAD': @@ -287,20 +390,24 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu const getUserDetails = async () => { try { - let res = await api.get(`/api/user/details`) - console.log("getuser", res) - let currUser = res?.data - setUser(currUser) - setFullName(currUser.firstName + " " + currUser.lastName) - setInitials(currUser.firstName.slice(0, 1) + currUser.lastName.slice(0, 1)) + let res = await api.get(`/api/user/details`); + console.log("getuser", res); + let currUser = res?.data; + setUser(currUser); + + // 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) { console.error("Error fetching user details:", error); } - } + }; useEffect(() => { - getUserDetails() - }, []) + getUserDetails(); + }, []); return (
@@ -405,10 +512,10 @@ const AddSignatureModal: React.FC = ({ setShowAddSignatu {isSubmitting ? ( <> - CREATING... + {isEditMode ? 'UPDATING...' : 'CREATING...'} ) : ( - "CREATE SIGNATURE" + isEditMode ? 'UPDATE SIGNATURE' : 'CREATE SIGNATURE' )}
diff --git a/src/components/setting/Signature/DrawSignatureCanvas.tsx b/src/components/setting/Signature/DrawSignatureCanvas.tsx index 1ba50d5..250218e 100644 --- a/src/components/setting/Signature/DrawSignatureCanvas.tsx +++ b/src/components/setting/Signature/DrawSignatureCanvas.tsx @@ -1,10 +1,11 @@ -import React, { useRef, useState } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; type Props = { onSignatureChange: (data: { fullSignature: string | null; initials: string | null }) => void; + initialSignatures?: { fullSignature: string | null; initials: string | null }; }; -const DrawSignatureCanvas: React.FC = ({ onSignatureChange }) => { +const DrawSignatureCanvas: React.FC = ({ onSignatureChange, initialSignatures }) => { const canvasRef1 = useRef(null); const canvasRef2 = useRef(null); const [isDrawing, setIsDrawing] = useState(false); @@ -12,6 +13,84 @@ const DrawSignatureCanvas: React.FC = ({ onSignatureChange }) => { const [paths2, setPaths2] = useState>>([]); const [currentPath, setCurrentPath] = useState>([]); const [activeCanvas, setActiveCanvas] = useState<'full' | 'initials' | null>(null); + const [initialLoaded, setInitialLoaded] = useState(false); + + // Store initial signature images to avoid reloading + const [initialImage1, setInitialImage1] = useState(null); + const [initialImage2, setInitialImage2] = useState(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) => { setIsDrawing(true); @@ -25,9 +104,31 @@ const DrawSignatureCanvas: React.FC = ({ onSignatureChange }) => { if (!isDrawing) return; const rect = e.currentTarget.getBoundingClientRect(); const point = { x: e.clientX - rect.left, y: e.clientY - rect.top }; - const newPath = [...currentPath, point]; // <- Build new path - setCurrentPath(newPath); // <- Save for next event - drawPath(e.currentTarget as HTMLCanvasElement, newPath); // <- Draw current + const newPath = [...currentPath, point]; + setCurrentPath(newPath); + + // 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 => { @@ -42,45 +143,48 @@ const DrawSignatureCanvas: React.FC = ({ onSignatureChange }) => { if (activeCanvas === 'full') { const newPaths = [...paths1, currentPath]; setPaths1(newPaths); - const fullSignature = exportCanvasAsImage(canvasRef1.current); - onSignatureChange({ fullSignature, initials: exportCanvasAsImage(canvasRef2.current) }); } else { const newPaths = [...paths2, currentPath]; setPaths2(newPaths); - const initials = exportCanvasAsImage(canvasRef2.current); - onSignatureChange({ fullSignature: exportCanvasAsImage(canvasRef1.current), initials }); } 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 }>) => { - if (!canvas) return; + const redrawCanvas = (canvas: HTMLCanvasElement, paths: Array>, initialImage: HTMLImageElement | null) => { const ctx = canvas.getContext('2d'); if (!ctx) return; - const allPaths = - canvas === canvasRef1.current ? [...paths1, path] : [...paths2, path]; - + // Clear canvas 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.lineWidth = 2; ctx.lineJoin = 'round'; ctx.lineCap = 'round'; - allPaths.forEach((p) => { - if (p.length < 2) return; + paths.forEach((path) => { + if (path.length < 2) return; ctx.beginPath(); - ctx.moveTo(p[0].x, p[0].y); - for (let i = 1; i < p.length; i++) { - ctx.lineTo(p[i].x, p[i].y); + ctx.moveTo(path[0].x, path[0].y); + for (let i = 1; i < path.length; i++) { + ctx.lineTo(path[i].x, path[i].y); } ctx.stroke(); }); }; - - const clearCanvas = () => { if (canvasRef1.current) { const ctx1 = canvasRef1.current.getContext('2d'); @@ -93,9 +197,30 @@ const DrawSignatureCanvas: React.FC = ({ onSignatureChange }) => { setPaths1([]); setPaths2([]); + setInitialImage1(null); + setInitialImage2(null); + setInitialLoaded(false); 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 (
@@ -113,8 +238,6 @@ const DrawSignatureCanvas: React.FC = ({ onSignatureChange }) => { = ({ onSignatureChange }) => {
= ({ onSignatureChange }) => { className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50 transition-colors" onClick={clearCanvas} > - Clear + Clear All + +
@@ -155,4 +283,4 @@ const DrawSignatureCanvas: React.FC = ({ onSignatureChange }) => { ); }; -export default DrawSignatureCanvas; +export default DrawSignatureCanvas; \ No newline at end of file diff --git a/src/components/setting/Signature/Signature.tsx b/src/components/setting/Signature/Signature.tsx index 9cb6809..58244b7 100644 --- a/src/components/setting/Signature/Signature.tsx +++ b/src/components/setting/Signature/Signature.tsx @@ -25,7 +25,8 @@ const SignatureManager: React.FC = () => { const [loading, setLoading] = useState(false) const [signatures, setSignatures] = useState([]); const [activeDropdown, setActiveDropdown] = useState(null); - const [showAddSignature, setShowAddSignature] = useState(false) + const [showAddSignature, setShowAddSignature] = useState(false); + const [editingSignature, setEditingSignature] = useState(null); const getAllSignatures = async () => { setLoading(true) @@ -47,6 +48,13 @@ const SignatureManager: React.FC = () => { const handleEdit = (id: string) => { 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); }; @@ -74,14 +82,21 @@ const SignatureManager: React.FC = () => { setActiveDropdown(null); setLoading(false) } - }; const toggleDropdown = (id: string) => { setActiveDropdown(activeDropdown === id ? null : id); }; + const handleAddNew = () => { + setEditingSignature(null); + setShowAddSignature(true); + }; + const handleModalClose = () => { + setShowAddSignature(false); + setEditingSignature(null); + }; return ( <> @@ -102,7 +117,7 @@ const SignatureManager: React.FC = () => {

Signatures

Add or update your name and signature styles.

- @@ -153,7 +168,6 @@ const SignatureManager: React.FC = () => { - {/* Initial Column */}
@@ -173,7 +187,6 @@ const SignatureManager: React.FC = () => {
- {/* Actions Column */}
@@ -233,7 +246,11 @@ const SignatureManager: React.FC = () => { )} {showAddSignature && ( - + )}
}