SME-Web/src/components/setting/Signature/Signature.tsx

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;