297 lines
15 KiB
TypeScript
297 lines
15 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { FiEdit3, FiTrash2, FiPlus, FiMoreVertical } from 'react-icons/fi';
|
|
import AddSignatureModal from './AddSignatureModal';
|
|
import api from '../../../config/api';
|
|
import { Bounce, toast } from "react-toastify";
|
|
import { FaSpinner } from 'react-icons/fa';
|
|
|
|
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;
|
|
signatureMimeType: string;
|
|
initialsMimeType: string
|
|
}
|
|
|
|
const SignatureManager: React.FC = () => {
|
|
const [loading, setLoading] = useState<boolean>(false)
|
|
const [signatures, setSignatures] = useState<Signature[]>([]);
|
|
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
|
|
const [showAddSignature, setShowAddSignature] = useState<boolean>(false);
|
|
const [editingSignature, setEditingSignature] = useState<Signature | null>(null);
|
|
|
|
const signatureOptions = [
|
|
{ id: 1, name: 'print', initials: 'LA', style: 'print' },
|
|
{ id: 2, name: 'cursive', initials: 'LA', style: 'cursive' },
|
|
{ id: 3, name: 'script', initials: 'LA', style: 'script' },
|
|
{ id: 4, name: 'elegant', initials: 'LA', style: 'elegant' },
|
|
{ id: 5, name: 'modern', initials: 'LA', style: 'modern' },
|
|
{ id: 6, name: 'classic', initials: 'LA', style: 'classic' },
|
|
];
|
|
|
|
const getAllSignatures = async () => {
|
|
setLoading(true)
|
|
try {
|
|
let res = await api.get("/api/v1/user/signatures")
|
|
let signatureData = res?.data
|
|
setSignatures(signatureData)
|
|
} catch (error) {
|
|
console.log("getAllSignatures", error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
getAllSignatures()
|
|
}, [])
|
|
|
|
const handleEdit = (id: string) => {
|
|
let editSignature = signatures.find((item) => item.id == id);
|
|
|
|
if (editSignature) {
|
|
setEditingSignature(editSignature);
|
|
setShowAddSignature(true);
|
|
}
|
|
setActiveDropdown(null);
|
|
};
|
|
|
|
const handleDelete = async (id: string) => {
|
|
if (!id) return;
|
|
setLoading(true)
|
|
try {
|
|
let res = await api.delete(`/api/v1/user/signatures/${id}`)
|
|
// toast.success("Signature Delete Successfull", {
|
|
// position: "top-center",
|
|
// autoClose: 500,
|
|
// hideProgressBar: false,
|
|
// closeOnClick: false,
|
|
// pauseOnHover: true,
|
|
// draggable: true,
|
|
// progress: undefined,
|
|
// theme: "light",
|
|
// transition: Bounce,
|
|
// });
|
|
await getAllSignatures();
|
|
} catch (error) {
|
|
console.log("delete signature", error)
|
|
} finally {
|
|
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);
|
|
};
|
|
|
|
|
|
const renderSignatureText = (type, font, firstName, lastName, mode = 'name') => {
|
|
if (type != 'SELECT') return null;
|
|
|
|
const selectedFont = signatureOptions.find(opt => opt.style == font);
|
|
const fontClass = `${selectedFont?.style || 'print'}`;
|
|
|
|
if (mode == 'initials') {
|
|
const initials = `${(firstName?.[0] ?? '')}${(lastName?.[0] ?? '')}`.toUpperCase();
|
|
return (
|
|
<div className={`text-center text-[24px]`} style={{ fontFamily: selectedFont?.style }}>
|
|
{initials}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const fullName = `${firstName ?? ''} ${lastName ?? ''}`.trim();
|
|
return (
|
|
<div className={`text-center text-[20px]`} style={{ fontFamily: selectedFont?.style }}>
|
|
<div>{fullName}</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
<>
|
|
{loading ? <div className=' flex items-center justify-center'>
|
|
<div className="p-12 text-center mt-8">
|
|
<div className="text-gray-400 mb-4">
|
|
<FaSpinner className="w-20 h-20 mx-auto animate-spin" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold text-gray-700 mb-2">Loading...</h3>
|
|
</div>
|
|
</div> :
|
|
<div className="min-h-screen bg-white p-3 rounded-md px-4 mb-6">
|
|
<div className="max-w-[84vw] mx-auto ">
|
|
<>
|
|
{/* Header */}
|
|
<div className="flex items-end justify-between mb-3">
|
|
<div className="flex flex-col">
|
|
<p className="text-lg font-semibold">Signatures</p>
|
|
<p className="text-xs text-slate-400 mt-1">Add or update your name and signature styles.</p>
|
|
</div>
|
|
<button className="bg-[var(--mainPrimaryBtn)] text-white px-3.5 gap-1 py-2 rounded-lg text-sm font-semibold flex items-center cursor-pointer transition-all duration-200 transform hover:-translate-y-0.5"
|
|
onClick={handleAddNew}>
|
|
<FiPlus className="text-lg font-semibold" />
|
|
Add Signature
|
|
</button>
|
|
</div>
|
|
|
|
{/* Table Card */}
|
|
|
|
<div className="overflow-x-auto mt-6">
|
|
<div className="bg-white min-w-[800px] md:min-w-full overflow-hidden">
|
|
{/* Table Header */}
|
|
<div className="bg-gray-50 rounded-lg">
|
|
<div className="grid grid-cols-12 gap-4 px-6 py-2">
|
|
<div className="col-span-6">
|
|
<h3 className="text-sm font-semibold">Signature</h3>
|
|
</div>
|
|
<div className="col-span-4">
|
|
<h3 className="text-sm font-semibold">Initial</h3>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<h3 className="text-sm font-semibold ml-4">Action</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Table Body */}
|
|
<div className="divide-y divide-gray-100">
|
|
{signatures.map((signature, index) => (
|
|
<div key={signature.id} className="grid grid-cols-12 gap-4 py-4 transition-colors duration-150">
|
|
{/* Signature Column */}
|
|
<div className="col-span-6">
|
|
<div className="flex flex-col space-y-2 relative ps-6 bg-slate-50 p-4 rounded-lg">
|
|
{/* Label */}
|
|
<div className="text-xs text-gray-500 absolute top-[8px] left-[15px]">
|
|
Signed by
|
|
</div>
|
|
|
|
{/* Signature Image or Name/Initials */}
|
|
<div className="rounded-lg p-1 my-4 flex items-center justify-center">
|
|
{signature?.signatureType != 'SELECT' ? (
|
|
<img
|
|
src={`data:${signature?.signatureMimeType};base64,${signature?.signatureBlob}`}
|
|
className="h-[50px] w-[250px] object-contain"
|
|
alt="Signature"
|
|
/>
|
|
) : (
|
|
renderSignatureText(signature?.signatureType, signature?.font, signature?.firstName, signature?.lastName, 'name')
|
|
)}
|
|
</div>
|
|
|
|
{/* Signature ID */}
|
|
<div className="text-xs text-gray-500 truncate absolute bottom-[8px] left-[15px] flex-nowrap max-w-[30ch] md:max-w-[30ch] lg:max-w-[50ch] overflow-hidden text-ellipsis">
|
|
{signature.signatureHash}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/* Initial Column */}
|
|
<div className="col-span-4">
|
|
<div className="flex flex-col space-y-2 relative ps-6 bg-slate-50 p-4 rounded-lg">
|
|
{/* Label */}
|
|
<div className="text-xs text-gray-500 absolute top-[8px] left-[15px]">
|
|
Initial
|
|
</div>
|
|
|
|
{/* Signature Initials: Image or Generated Text */}
|
|
<div className="rounded-lg p-1 my-4 flex items-center justify-center">
|
|
{signature?.signatureType != 'SELECT' ? (
|
|
<img
|
|
src={`data:${signature.initialsMimeType};base64,${signature.initialsBlob}`}
|
|
className="h-[50px] object-contain"
|
|
alt="Initials"
|
|
/>
|
|
) : (
|
|
renderSignatureText(signature?.signatureType, signature?.font, signature?.firstName, signature?.lastName, 'initials')
|
|
)}
|
|
</div>
|
|
|
|
{/* Initial ID */}
|
|
<div className="text-xs text-gray-500 truncate absolute bottom-[8px] left-[15px] flex-nowrap sm:max-w-[10ch] md:max-w-[20ch] lg:max-w-[30ch] overflow-hidden text-ellipsis">
|
|
{signature.signatureHash}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{/* Actions Column */}
|
|
<div className="col-span-2">
|
|
<div className="relative flex justify-start gap-4">
|
|
<button
|
|
onClick={() => handleEdit(signature.id)}
|
|
className="max-w-max text-gray-500 text-lg"
|
|
>
|
|
<FiEdit3 />
|
|
</button>
|
|
<button
|
|
onClick={() => handleDelete(signature.id)}
|
|
className="max-w-max text-red-500 text-lg"
|
|
>
|
|
<FiTrash2 />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Empty State (if no signatures) */}
|
|
{signatures.length == 0 && (
|
|
<div className=" rounded-xl p-12 text-center mt-8">
|
|
<div className="text-gray-400 mb-4">
|
|
<FiEdit3 className="w-16 h-16 mx-auto" />
|
|
</div>
|
|
<h3 className="text-xl font-semibold text-gray-700 mb-2">No signatures yet</h3>
|
|
<p className="text-gray-500 mb-6">Create your first signature to get started.</p>
|
|
</div>
|
|
)}
|
|
</>
|
|
</div>
|
|
|
|
{/* Click outside to close dropdown */}
|
|
{activeDropdown && (
|
|
<div
|
|
className="fixed inset-0 z-0"
|
|
onClick={() => setActiveDropdown(null)}
|
|
/>
|
|
)}
|
|
|
|
{showAddSignature && (
|
|
<AddSignatureModal
|
|
setShowAddSignature={handleModalClose}
|
|
getAllSignatures={getAllSignatures}
|
|
editingSignature={editingSignature}
|
|
/>
|
|
)}
|
|
</div>
|
|
}</>
|
|
);
|
|
};
|
|
|
|
export default SignatureManager; |