27-10
This commit is contained in:
parent
950601171c
commit
ca1a4da954
|
|
@ -6,6 +6,7 @@ import { IoSettings, IoSettingsOutline } from 'react-icons/io5';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { RootState } from '../store/store';
|
import { RootState } from '../store/store';
|
||||||
import { GoBell } from 'react-icons/go';
|
import { GoBell } from 'react-icons/go';
|
||||||
|
import getInitialsUtils from '../utlity/getInitialUtils';
|
||||||
|
|
||||||
interface AccountNavProps {
|
interface AccountNavProps {
|
||||||
accountName?: string | null;
|
accountName?: string | null;
|
||||||
|
|
@ -65,7 +66,7 @@ const CommonNav: React.FC<AccountNavProps> = ({
|
||||||
const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false);
|
const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false);
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [initial, setInitial] = useState<string>("");
|
// const [initial, setInitial] = useState<string>("");
|
||||||
|
|
||||||
const [user, setUser] = useState<UserData>({
|
const [user, setUser] = useState<UserData>({
|
||||||
id: 0,
|
id: 0,
|
||||||
|
|
@ -86,13 +87,13 @@ const CommonNav: React.FC<AccountNavProps> = ({
|
||||||
navigate("/s/settings#profile-info");
|
navigate("/s/settings#profile-info");
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (initial != "") return
|
// if (initial != "") return
|
||||||
console.log(userDetailsAsync, 'line 89')
|
// console.log(userDetailsAsync, 'line 89')
|
||||||
let initialN = (userDetailsAsync.firstName && userDetailsAsync.firstName[0])
|
// let initialN = (userDetailsAsync.firstName && userDetailsAsync.firstName[0])
|
||||||
initialN = initialN + "" + (userDetailsAsync.lastName && userDetailsAsync.lastName[0])
|
// initialN = initialN + "" + (userDetailsAsync.lastName && userDetailsAsync.lastName[0])
|
||||||
setInitial(initialN)
|
// setInitial(initialN)
|
||||||
}, [userDetailsAsync])
|
// }, [userDetailsAsync])
|
||||||
|
|
||||||
const getAccountDetails = async () => {
|
const getAccountDetails = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -153,11 +154,11 @@ const CommonNav: React.FC<AccountNavProps> = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (account.firstName && account.lastName) {
|
// if (account.firstName && account.lastName) {
|
||||||
setInitial((account?.firstName?.slice(0, 1)).toUpperCase() + "" + (account?.lastName?.slice(0, 1)).toUpperCase())
|
// setInitial((account?.firstName?.slice(0, 1)).toUpperCase() + "" + (account?.lastName?.slice(0, 1)).toUpperCase())
|
||||||
} else {
|
// } else {
|
||||||
setInitial((account?.name?.slice(0, 1)).toUpperCase())
|
// setInitial((account?.name?.slice(0, 1)).toUpperCase())
|
||||||
}
|
// }
|
||||||
|
|
||||||
sessionStorage.setItem('userDetails', JSON.stringify({
|
sessionStorage.setItem('userDetails', JSON.stringify({
|
||||||
id: account.userId,
|
id: account.userId,
|
||||||
|
|
@ -198,11 +199,12 @@ const CommonNav: React.FC<AccountNavProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
setUser(userData);
|
setUser(userData);
|
||||||
if (userData.firstName && userData.lastName) {
|
|
||||||
setInitial((userData?.firstName?.slice(0, 1)).toUpperCase() + "" + (userData?.lastName?.slice(0, 1)).toUpperCase())
|
// if (userData.firstName && userData.lastName) {
|
||||||
} else {
|
// setInitial((userData?.firstName?.slice(0, 1)).toUpperCase() + "" + (userData?.lastName?.slice(0, 1)).toUpperCase())
|
||||||
setInitial((userData?.name?.slice(0, 1)).toUpperCase())
|
// } else {
|
||||||
}
|
// setInitial((userData?.name?.slice(0, 1)).toUpperCase())
|
||||||
|
// }
|
||||||
|
|
||||||
sessionStorage.setItem('userDetails', JSON.stringify(userData));
|
sessionStorage.setItem('userDetails', JSON.stringify(userData));
|
||||||
sessionStorage.setItem('activeAccountId', JSON.stringify(userData.account.id));
|
sessionStorage.setItem('activeAccountId', JSON.stringify(userData.account.id));
|
||||||
|
|
@ -405,8 +407,8 @@ const CommonNav: React.FC<AccountNavProps> = ({
|
||||||
>
|
>
|
||||||
<div className="w-8 h-8 rounded-md overflow-hidden bg-gradient-to-br from-[var(--primary)] to-blue-600 flex items-center justify-center text-white font-semibold text-sm shadow-md">
|
<div className="w-8 h-8 rounded-md overflow-hidden bg-gradient-to-br from-[var(--primary)] to-blue-600 flex items-center justify-center text-white font-semibold text-sm shadow-md">
|
||||||
{user ? (
|
{user ? (
|
||||||
<span className="w-full h-full object-cover p-1.5 font-bold">
|
<span className="w-full h-full object-cover p-1.5 font-bold capitalize">
|
||||||
{initial}
|
{getInitialsUtils((user.firstName ?? '') + (user.firstName && user.lastName ? ' ' : '') + (user.lastName ?? ''))}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<FaUser className="text-lg" />
|
<FaUser className="text-lg" />
|
||||||
|
|
@ -420,8 +422,8 @@ const CommonNav: React.FC<AccountNavProps> = ({
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="w-12 h-12 rounded-full overflow-hidden bg-gradient-to-br from-[var(--primary)] to-blue-600 flex items-center justify-center text-white font-semibold shadow-md">
|
<div className="w-12 h-12 rounded-full overflow-hidden bg-gradient-to-br from-[var(--primary)] to-blue-600 flex items-center justify-center text-white font-semibold shadow-md">
|
||||||
{user ? (
|
{user ? (
|
||||||
<span className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold">
|
<span className="w-full h-full object-cover px-3 text-xl py-2.5 font-bold capitalize">
|
||||||
{initial}
|
{getInitialsUtils((user.firstName ?? '') + (user.firstName && user.lastName ? ' ' : '') + (user.lastName ?? ''))}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<FaUser className="text-lg" />
|
<FaUser className="text-lg" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
type PropsType = {
|
||||||
|
setShowConfirmationModal: React.Dispatch<{ show: boolean, id: string }>;
|
||||||
|
onConfirm: () => void;
|
||||||
|
message: string;
|
||||||
|
title: string;
|
||||||
|
textColor: string;
|
||||||
|
hoverTextColor: string;
|
||||||
|
rejectBtnText: string;
|
||||||
|
confirmBtnText: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConfirmationModal = ({ setShowConfirmationModal, onConfirm, message, title, textColor, hoverTextColor, rejectBtnText, confirmBtnText }: PropsType) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 backdrop-blur-sm bg-black/30 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white rounded-lg shadow-xl p-6 w-100">
|
||||||
|
<h3 className="text-lg font-medium text-gray-800 mb-4">{title}</h3>
|
||||||
|
<p className="text-gray-600 mb-6">
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-end gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setShowConfirmationModal({ show: false, id: "" });
|
||||||
|
}}
|
||||||
|
|
||||||
|
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
{rejectBtnText}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
onConfirm();
|
||||||
|
}}
|
||||||
|
className={`px-4 py-2 ${textColor} text-white hover:${hoverTextColor} rounded-md transition-colors cursor-pointer`}
|
||||||
|
>
|
||||||
|
{confirmBtnText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmationModal;
|
||||||
|
|
@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } from "react-icons/fa";
|
import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } from "react-icons/fa";
|
||||||
import api from '../../config/api';
|
import api from '../../config/api';
|
||||||
|
import getInitialsUtils from '../../utlity/getInitialUtils';
|
||||||
|
|
||||||
interface AccountNavProps {
|
interface AccountNavProps {
|
||||||
accountName?: string | null;
|
accountName?: string | null;
|
||||||
|
|
@ -294,7 +295,7 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
|
||||||
<div className="w-8 h-8 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold text-sm shadow-md">
|
<div className="w-8 h-8 rounded-full overflow-hidden bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center text-white font-semibold text-sm shadow-md">
|
||||||
{user ? (
|
{user ? (
|
||||||
<span className="w-full h-full object-cover p-1.5 font-bold">
|
<span className="w-full h-full object-cover p-1.5 font-bold">
|
||||||
{(user?.name?.slice(0, 2)).toUpperCase()}
|
{(user?.name?.slice(0, 2)).toUpperCase()}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<FaUser className="text-lg" />
|
<FaUser className="text-lg" />
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ const DownloadDocument: React.FC<DownloadDocumentInt> = ({ showMenu, setShowMenu
|
||||||
const includeCertificate = formData.get('download-doc-type-include-cert');
|
const includeCertificate = formData.get('download-doc-type-include-cert');
|
||||||
|
|
||||||
downloadDocumentUtils(accountId, showMenu.transactionId as string, docType as string, includeCertificate as string | null)
|
downloadDocumentUtils(accountId, showMenu.transactionId as string, docType as string, includeCertificate as string | null)
|
||||||
|
setShowMenu({ open: false, transactionId: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ const TransactionDataTable = ({ activeAccountId }) => {
|
||||||
<td className="px-6 py-3 text-[var(--mainPrimaryBtn)] font-medium">
|
<td className="px-6 py-3 text-[var(--mainPrimaryBtn)] font-medium">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowMenu({ open: true, transactionId: tx.id })}
|
onClick={() => setShowMenu({ open: true, transactionId: tx.id })}
|
||||||
className="flex items-center gap-2 text-sm hover:underline"
|
className="flex items-center gap-2 text-sm hover:underline cursor-pointer"
|
||||||
>
|
>
|
||||||
<MdOutlineFileDownload className="text-lg" /> Download
|
<MdOutlineFileDownload className="text-lg" /> Download
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownToggl
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDropDownTogglePage(`${(dropDownTogglePage == 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`);
|
setDropDownTogglePage(`${(dropDownTogglePage == 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`);
|
||||||
|
setCurrentPage(0);
|
||||||
window.location.hash = document.documentId + "" + 0;
|
window.location.hash = document.documentId + "" + 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import { Bounce, toast } from "react-toastify";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
|
import { hideLoader, showLoader } from "../../store/slices/loaderSlice";
|
||||||
|
|
||||||
import { FaCheck, FaQuestionCircle, FaDownload } from 'react-icons/fa'
|
import { FaCheck, FaQuestionCircle, FaDownload, FaAngleUp } from 'react-icons/fa'
|
||||||
import { IoIosDocument } from 'react-icons/io'
|
import { IoIosDocument, IoMdClose } from 'react-icons/io'
|
||||||
import { MdKeyboardArrowDown } from 'react-icons/md'
|
import { MdKeyboardArrowDown } from 'react-icons/md'
|
||||||
import confetti from 'canvas-confetti'
|
import confetti from 'canvas-confetti'
|
||||||
|
|
||||||
|
|
@ -134,6 +134,10 @@ const fitObj = [
|
||||||
label: "50%",
|
label: "50%",
|
||||||
value: "50"
|
value: "50"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "130%",
|
||||||
|
value: "130"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "150%",
|
label: "150%",
|
||||||
value: "150"
|
value: "150"
|
||||||
|
|
@ -159,6 +163,7 @@ const SignDocument = () => {
|
||||||
// Signer data - START
|
// Signer data - START
|
||||||
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
|
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
|
||||||
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
|
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
|
||||||
|
const [isPages, setIsPages] = useState<boolean>(true);
|
||||||
|
|
||||||
const [recipient, setRecipient] = useState<RecipientType | null>(null);
|
const [recipient, setRecipient] = useState<RecipientType | null>(null);
|
||||||
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
|
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
|
||||||
|
|
@ -190,7 +195,7 @@ const SignDocument = () => {
|
||||||
const [isFields, setIsFields] = useState<boolean>(true);
|
const [isFields, setIsFields] = useState<boolean>(true);
|
||||||
|
|
||||||
const [isFitDd, setIsFitDd] = useState<boolean>(false)
|
const [isFitDd, setIsFitDd] = useState<boolean>(false)
|
||||||
const [selFitIndx, setSelFitIndx] = useState<number>(0);
|
const [selFitIndx, setSelFitIndx] = useState<number>(2);
|
||||||
|
|
||||||
const globalLoader = useSelector((state: RootState) => state.loader)
|
const globalLoader = useSelector((state: RootState) => state.loader)
|
||||||
|
|
||||||
|
|
@ -833,7 +838,7 @@ const SignDocument = () => {
|
||||||
} else {
|
} else {
|
||||||
setNavigationTagPos(NavigationTagPosEnum.VISIBLE)
|
setNavigationTagPos(NavigationTagPosEnum.VISIBLE)
|
||||||
}
|
}
|
||||||
}, 150);
|
}, 10);
|
||||||
|
|
||||||
// Initial check
|
// Initial check
|
||||||
checkPosition();
|
checkPosition();
|
||||||
|
|
@ -871,8 +876,42 @@ const SignDocument = () => {
|
||||||
{/* Nav Two - Options - START */}
|
{/* Nav Two - Options - START */}
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
|
|
||||||
<div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)]'>
|
<div className='flex justify-between items-center px-4 py-1 bg-[var(--primary)] mt-0'>
|
||||||
<img className="w-[150px] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
|
<div className="flex items-center">
|
||||||
|
<img className="w-[150px] max-w-max py-1 pr-3.5" src="/sme-white.png" alt="" />
|
||||||
|
|
||||||
|
{/* FIT - START */}
|
||||||
|
<div
|
||||||
|
className="hidden sm:block relative"
|
||||||
|
tabIndex={1}
|
||||||
|
onBlur={() => setIsFitDd(false)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onClick={() => setIsFitDd(!isFitDd)}
|
||||||
|
className='text-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ml-2'>
|
||||||
|
<p className='tracking-wide'>Fit</p>
|
||||||
|
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isFitDd && <div className='absolute top-[30px] left-[20px] bg-white rounded-sm z-50'>
|
||||||
|
{fitObj.map((val, indx) => {
|
||||||
|
return (<div
|
||||||
|
key={indx}
|
||||||
|
onClick={() => setSelFitIndx(indx)}
|
||||||
|
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
|
||||||
|
<div
|
||||||
|
|
||||||
|
className='flex items-center justify-between w-full'>
|
||||||
|
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{val.label}</p>
|
||||||
|
{selFitIndx == indx && <FaCheck className='text-sm text-slate-400' />}
|
||||||
|
</div>
|
||||||
|
</div>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
{/* FIT - END */}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Right Side - STARTS */}
|
{/* Right Side - STARTS */}
|
||||||
<div className='flex justify-center items-center'>
|
<div className='flex justify-center items-center'>
|
||||||
|
|
@ -950,7 +989,7 @@ const SignDocument = () => {
|
||||||
<div
|
<div
|
||||||
className={`absolute z-50 left-0 bottom-0 bg-[var(--primary)] w-full flex justify-around items-center
|
className={`absolute z-50 left-0 bottom-0 bg-[var(--primary)] w-full flex justify-around items-center
|
||||||
px-2.5 transition-all duration-500 ease-in-out
|
px-2.5 transition-all duration-500 ease-in-out
|
||||||
${requiredFilled ? "translate-y-0 opacity-100" : "translate-y-full opacity-0"}`}
|
${requiredFilled ? "translate-y-0" : "translate-y-full hidden"}`}
|
||||||
>
|
>
|
||||||
<div className="py-6 text-white">
|
<div className="py-6 text-white">
|
||||||
<p className="text-2xl font-semibold">All Set to Wrap This Up?</p>
|
<p className="text-2xl font-semibold">All Set to Wrap This Up?</p>
|
||||||
|
|
@ -995,16 +1034,26 @@ const SignDocument = () => {
|
||||||
|
|
||||||
|
|
||||||
{/* Document Container Start */}
|
{/* Document Container Start */}
|
||||||
<div className="grid grid-cols-12 gap-1 mt-1 h-[calc(100vh-86px)] overflow-hidden">
|
<div className={`grid grid-cols-12 gap-1 mt-1 ${requiredFilled ? "h-[calc(100vh-100px)]" : "h-[calc(100vh-86px)]"} overflow-hidden`}>
|
||||||
|
|
||||||
{/* Left Side pages start */}
|
{/* Left Side pages start */}
|
||||||
{/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */}
|
{/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */}
|
||||||
<div
|
<div
|
||||||
id="tags-list-client-side"
|
id="tags-list-client-side"
|
||||||
className={`absolute w-[220px] left-0 top-[42px] border-r-2 sm:border-none border-slate-200 z-10 sm:static sm:w-auto sm:col-span-2 h-full p-4 overflow-y-auto ${recipient?.type != "CC" ? "bg-white" : ""} ${!provideTags && isFields ? "hidden" : ""}`}>
|
className={`${isPages ? "block" : "hidden"} sm:block absolute w-[220px] left-0 top-[42px] border-r-2 sm:border-none border-slate-200 z-10 sm:static sm:w-auto sm:col-span-2 h-full p-4 overflow-y-auto ${recipient?.type != "CC" ? "bg-white" : ""} ${!provideTags && isFields ? "hidden" : ""}`}>
|
||||||
|
|
||||||
|
|
||||||
{!transparentTags && <div className='flex flex-col md:flex-row justify-between items-center bg-slate-100 p-1.5 rounded-2xl border-1 border-gray-200 mb-3'>
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setIsPages(false)
|
||||||
|
setIsFields(true)
|
||||||
|
}}
|
||||||
|
className='absolute sm:hidden top-[5px] right-[10px] bg-white rounded-full text-2xl z-50'>
|
||||||
|
<IoMdClose />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{provideTags && !transparentTags && <div className='flex flex-col md:flex-row justify-between items-center bg-slate-100 p-1.5 rounded-2xl border-1 border-gray-200 mb-3'>
|
||||||
{provideTags && recipient?.type != "CC" && <div
|
{provideTags && recipient?.type != "CC" && <div
|
||||||
onClick={() => handleSetIsFields(true)}
|
onClick={() => handleSetIsFields(true)}
|
||||||
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${isFields ? 'bg-[var(--primary)] text-white' : ''} transition-all duration-300'}`}
|
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${isFields ? 'bg-[var(--primary)] text-white' : ''} transition-all duration-300'}`}
|
||||||
|
|
@ -1026,10 +1075,10 @@ const SignDocument = () => {
|
||||||
|
|
||||||
{!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />}
|
{!transparentTags && isFields && recipientId && provideTags && recipient?.type != "CC" && <FieldList color={"#8f87f1"} recipientId={recipientId} recipientIndx={0} acntId={"1"} />}
|
||||||
|
|
||||||
{!isFields && <DocumentPages
|
<DocumentPages
|
||||||
selectedDocuments={selectedDocuments}
|
selectedDocuments={selectedDocuments}
|
||||||
setDropDownTogglePage={setDropDownTogglePage}
|
setDropDownTogglePage={setDropDownTogglePage}
|
||||||
dropDownTogglePage={dropDownTogglePage} />}
|
dropDownTogglePage={dropDownTogglePage} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/* Pages - END */}
|
{/* Pages - END */}
|
||||||
|
|
@ -1084,6 +1133,7 @@ const SignDocument = () => {
|
||||||
width={page.width}
|
width={page.width}
|
||||||
height={page.height}
|
height={page.height}
|
||||||
src={page.src}
|
src={page.src}
|
||||||
|
loading='lazy'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!hideTags && <>
|
{!hideTags && <>
|
||||||
|
|
@ -1127,7 +1177,7 @@ const SignDocument = () => {
|
||||||
{!transparentTags && focusElement != null && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index &&
|
{!transparentTags && focusElement != null && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index &&
|
||||||
<div
|
<div
|
||||||
id="tag-navigation-btn"
|
id="tag-navigation-btn"
|
||||||
className={`absolute right-0 translate-x-[100%] flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
|
className={`hidden absolute right-0 translate-x-[100%] sm:flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
|
||||||
style={{ top: (focusElement.dimensions.top) + "px" }}
|
style={{ top: (focusElement.dimensions.top) + "px" }}
|
||||||
onClick={() => handleTagNavClick("N")}
|
onClick={() => handleTagNavClick("N")}
|
||||||
>
|
>
|
||||||
|
|
@ -1159,7 +1209,7 @@ const SignDocument = () => {
|
||||||
{(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) &&
|
{(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) &&
|
||||||
<div
|
<div
|
||||||
onClick={() => scrollToElement(focusElement?.navigationOrder)}
|
onClick={() => scrollToElement(focusElement?.navigationOrder)}
|
||||||
className={`cursor-pointer fixed ${navigationTagPos == NavigationTagPosEnum.ABOVE ? "top-[45px]" : "bottom-[45px]"} right-[8%] flex flex-col items-center`}>
|
className={`hidden sm:block cursor-pointer fixed ${navigationTagPos == NavigationTagPosEnum.ABOVE ? "top-[45px]" : requiredFilled ? "bottom-[82px]" : "bottom-[35px]"} right-[8%] flex flex-col items-center`}>
|
||||||
{/* Arrow pointing up */}
|
{/* Arrow pointing up */}
|
||||||
{navigationTagPos == NavigationTagPosEnum.ABOVE && <div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--primary)]"></div>}
|
{navigationTagPos == NavigationTagPosEnum.ABOVE && <div className="w-0 h-0 border-l-6 border-r-6 border-b-6 border-l-transparent border-r-transparent border-b-[var(--primary)]"></div>}
|
||||||
|
|
||||||
|
|
@ -1214,6 +1264,25 @@ const SignDocument = () => {
|
||||||
|
|
||||||
{globalLoader.isLoading && <Loader />}
|
{globalLoader.isLoading && <Loader />}
|
||||||
|
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setIsPages(true)
|
||||||
|
setIsFields(false)
|
||||||
|
}}
|
||||||
|
className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] flex items-center text-white rounded-tr-md rounded-br-md cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'>
|
||||||
|
<div className='py-2 pl-2 pr-1'>
|
||||||
|
<FaAngleUp className='rotate-90' />
|
||||||
|
<p className='flex flex-col justify-center items-center text-xs tracking-widest mt-1 pt-1 border-t-1 border-white'>
|
||||||
|
<span>P</span>
|
||||||
|
<span>A</span>
|
||||||
|
<span>G</span>
|
||||||
|
<span>E</span>
|
||||||
|
<span>S</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
!transparentTags && selectedDocuments != null && !showCompletionScreen && !signerConsent.consent && recipient?.type != "CC" &&
|
!transparentTags && selectedDocuments != null && !showCompletionScreen && !signerConsent.consent && recipient?.type != "CC" &&
|
||||||
<ConsentScreen
|
<ConsentScreen
|
||||||
|
|
@ -1244,8 +1313,8 @@ const SignDocument = () => {
|
||||||
|
|
||||||
|
|
||||||
{!transparentTags && recipient?.type != "CC" && <>
|
{!transparentTags && recipient?.type != "CC" && <>
|
||||||
<div className={`fixed bottom-0 w-full shadow-[0_20px_35px_rgba(0,0,0,0.25)] bottom-0 left-0 bg-white px-4 py-2 flex justify-between items-center gap-4`}>
|
<div className={`fixed bottom-0 w-full shadow-[0_20px_35px_rgba(0,0,0,0.25)] left-0 bg-white px-4 py-2 flex justify-between items-center gap-4`}>
|
||||||
<p className='text-sm text-slate-600'>By Signing I agree Sign Made Easy <a href="" className="text-[var(--mainPrimaryBtn)]">Terms & Conditions.</a></p>
|
<p className='text-xs sm:text-sm text-slate-600'>By Signing I agree Sign Made Easy <a href="" className="block py-1 sm:py-0 sm:inline text-[var(--mainPrimaryBtn)]">Terms & Conditions.</a></p>
|
||||||
{/* <div className='flex items-center gap-8 cursor-pointer text-xs px-2 py-1 rounded-sm outline-none'>
|
{/* <div className='flex items-center gap-8 cursor-pointer text-xs px-2 py-1 rounded-sm outline-none'>
|
||||||
<FaAngleLeft
|
<FaAngleLeft
|
||||||
onClick={() => handleTagNavClick("P")}
|
onClick={() => handleTagNavClick("P")}
|
||||||
|
|
@ -1260,44 +1329,25 @@ const SignDocument = () => {
|
||||||
onClick={() => handleTagNavClick("N")}
|
onClick={() => handleTagNavClick("N")}
|
||||||
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
|
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
|
||||||
</div> */}
|
</div> */}
|
||||||
<div></div>
|
<div>
|
||||||
|
{!transparentTags && focusElement != null &&
|
||||||
|
<div
|
||||||
|
id="tag-navigation-btn-mb"
|
||||||
|
className={`sm:hidden flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
|
||||||
|
style={{ top: (focusElement.dimensions.top) + "px" }}
|
||||||
|
onClick={() => handleTagNavClick("N")}
|
||||||
|
>
|
||||||
|
|
||||||
|
<p className="bg-[var(--mainPrimaryBtn)] p-2 rounded-md text-white capitalize">{requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()}</p>
|
||||||
|
|
||||||
|
{/* NEXT Button - END */}
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
{/* <p className='cursor-pointer text-sm text-white bg-[var(--mainPrimaryBtn)] px-4 py-2 rounded'
|
{/* <p className='cursor-pointer text-sm text-white bg-[var(--mainPrimaryBtn)] px-4 py-2 rounded'
|
||||||
onClick={handleFormSubmit}
|
onClick={handleFormSubmit}
|
||||||
>Sign</p> */}
|
>Sign</p> */}
|
||||||
|
|
||||||
<div>
|
|
||||||
{/* FIT - START */}
|
|
||||||
<div
|
|
||||||
className="hidden sm:block"
|
|
||||||
tabIndex={1}
|
|
||||||
onBlur={() => setIsFitDd(false)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onClick={() => setIsFitDd(!isFitDd)}
|
|
||||||
className='bg-white flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ml-2'>
|
|
||||||
<p className='tracking-wide'>Fit</p>
|
|
||||||
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isFitDd && <div className='absolute top-[120px] bg-white rounded-sm z-50'>
|
|
||||||
{fitObj.map((val, indx) => {
|
|
||||||
return (<div
|
|
||||||
key={indx}
|
|
||||||
onClick={() => setSelFitIndx(indx)}
|
|
||||||
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
|
|
||||||
<div
|
|
||||||
|
|
||||||
className='flex items-center justify-between w-full'>
|
|
||||||
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{val.label}</p>
|
|
||||||
{selFitIndx == indx && <FaCheck className='text-sm text-slate-400' />}
|
|
||||||
</div>
|
|
||||||
</div>)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>}
|
|
||||||
</div>
|
|
||||||
{/* FIT - END */}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { IoMdAdd } from 'react-icons/io';
|
||||||
import LoaderUi from '../../helper/LoaderUi';
|
import LoaderUi from '../../helper/LoaderUi';
|
||||||
import logoRecord from '../../assets/record.svg'
|
import logoRecord from '../../assets/record.svg'
|
||||||
import Loader from '../dashboard/Loader';
|
import Loader from '../dashboard/Loader';
|
||||||
|
import ConfirmationModal from '../ConfirmationModal';
|
||||||
|
|
||||||
type ContactSchema = [{
|
type ContactSchema = [{
|
||||||
id: number,
|
id: number,
|
||||||
|
|
@ -27,6 +28,12 @@ const Contacts = () => {
|
||||||
const [editJsonData, setEditJsonData] = useState({})
|
const [editJsonData, setEditJsonData] = useState({})
|
||||||
const [actionStatus, setActionStatus] = useState("")
|
const [actionStatus, setActionStatus] = useState("")
|
||||||
|
|
||||||
|
// Delete modal
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState<{
|
||||||
|
show: boolean;
|
||||||
|
id: string;
|
||||||
|
}>({ show: false, id: "" });
|
||||||
|
|
||||||
const fetchContacts = async () => {
|
const fetchContacts = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
|
|
@ -65,6 +72,7 @@ const Contacts = () => {
|
||||||
// });
|
// });
|
||||||
fetchContacts()
|
fetchContacts()
|
||||||
setOpenMenuId(null);
|
setOpenMenuId(null);
|
||||||
|
setShowDeleteModal({ show: false, id: "" });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
|
@ -88,9 +96,9 @@ const Contacts = () => {
|
||||||
let res;
|
let res;
|
||||||
setCreateAndInviteLoader(true);
|
setCreateAndInviteLoader(true);
|
||||||
if (type == 'Edit') {
|
if (type == 'Edit') {
|
||||||
res = await api.put(`/api/v1/accounts/${activeAccountId}/contacts`, {contacts: [data], empty: true})
|
res = await api.put(`/api/v1/accounts/${activeAccountId}/contacts`, { contacts: [data], empty: true })
|
||||||
} else {
|
} else {
|
||||||
res = await api.post(`/api/v1/accounts/${activeAccountId}/contacts`, {contacts: [data], empty: true});
|
res = await api.post(`/api/v1/accounts/${activeAccountId}/contacts`, { contacts: [data], empty: true });
|
||||||
}
|
}
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
// toast.success('Contact created successfully!', {
|
// toast.success('Contact created successfully!', {
|
||||||
|
|
@ -177,7 +185,7 @@ const Contacts = () => {
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="text-gray-400 hover:text-gray-600 p-1 focus:outline-none"
|
className="text-gray-400 hover:text-gray-600 p-1 focus:outline-none cursor-pointer"
|
||||||
onClick={() => toggleMenu(contact.id)}
|
onClick={() => toggleMenu(contact.id)}
|
||||||
>
|
>
|
||||||
<FaEllipsisV />
|
<FaEllipsisV />
|
||||||
|
|
@ -186,19 +194,22 @@ const Contacts = () => {
|
||||||
{openMenuId == contact.id && (
|
{openMenuId == contact.id && (
|
||||||
<div
|
<div
|
||||||
className={`absolute mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-20 min-w-[100px] ${contacts.length - 1 == index
|
className={`absolute mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-20 min-w-[100px] ${contacts.length - 1 == index
|
||||||
? "bottom-1 right-4"
|
? "bottom-1 right-4"
|
||||||
: "bottom-0 right-5"
|
: "bottom-0 right-5"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onMouseDown={() => handleEdit(contact.id)}
|
onMouseDown={() => handleEdit(contact.id)}
|
||||||
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"
|
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600 cursor-pointer"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onMouseDown={() => handleDelete(contact.id)}
|
onMouseDown={() => setShowDeleteModal({
|
||||||
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600"
|
show: true,
|
||||||
|
id: contact.id + "",
|
||||||
|
})}
|
||||||
|
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600 cursor-pointer"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -224,6 +235,19 @@ const Contacts = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{showDeleteModal.show && <ConfirmationModal
|
||||||
|
setShowConfirmationModal={setShowDeleteModal}
|
||||||
|
onConfirm={() => handleDelete(showDeleteModal.id)}
|
||||||
|
message={"Are you sure you want to delete this contact? This action cannot be undone."}
|
||||||
|
title={"Delete Confirmation."}
|
||||||
|
textColor={"bg-red-600"}
|
||||||
|
hoverTextColor={"bg-red-700"}
|
||||||
|
rejectBtnText={"Cancel"}
|
||||||
|
confirmBtnText={"Yes, Delete it!"}
|
||||||
|
/>}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { FiUpload } from 'react-icons/fi'
|
|
||||||
import { handleMultipleTransactionThunk, resetDocuments, uploadDocuments } from '../../store/slices/documentSlice';
|
import { handleMultipleTransactionThunk, resetDocuments, uploadDocuments } from '../../store/slices/documentSlice';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { RootState } from '../../store/store';
|
import { RootState } from '../../store/store';
|
||||||
import api from '../../config/api';
|
import api from '../../config/api';
|
||||||
import TagsCon from '../client/TagsCon';
|
|
||||||
import TransactionChoiceModal from './TransactionChoiceModal';
|
import TransactionChoiceModal from './TransactionChoiceModal';
|
||||||
import { resetRecipients } from '../../store/slices/selectedRecipientsSlice';
|
import { resetRecipients } from '../../store/slices/selectedRecipientsSlice';
|
||||||
import { AiOutlineConsoleSql } from 'react-icons/ai';
|
|
||||||
|
|
||||||
|
|
||||||
const StartNow = () => {
|
const StartNow = () => {
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,15 @@ const ProcessStepOne = () => {
|
||||||
order: index,
|
order: index,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ---- Neeed to add documents check tha tif the processed completed only then allow for next step ----
|
||||||
|
// for (let i = 0; i < documentsData.length; i++) {
|
||||||
|
// const res = await api.get(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents/${documentsData[i].documentId}`);
|
||||||
|
// console.log(res)
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
await api.patch(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents`, { "documents": documentsData });
|
await api.patch(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents`, { "documents": documentsData });
|
||||||
|
|
||||||
await api.get(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents?include_pages=true`).then(res => {
|
await api.get(`/api/v1/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}/documents?include_pages=true`).then(res => {
|
||||||
|
|
|
||||||
|
|
@ -419,6 +419,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hotKeysObj.isActive) return
|
if (!hotKeysObj.isActive) return
|
||||||
|
if((!selectedTag?.key || `${selectedTag?.key}` in droppedItems && droppedItems[selectedTag?.key].length == 0)) return;
|
||||||
|
|
||||||
const docCon = document.getElementById("doc-con");
|
const docCon = document.getElementById("doc-con");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,11 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
||||||
// onBlur={() => setDropDownTogglePage('')}
|
// onBlur={() => setDropDownTogglePage('')}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={() => setDropDownTogglePage(`${(dropDownTogglePage == 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`)}
|
onClick={() => {
|
||||||
|
setDropDownTogglePage(`${(dropDownTogglePage == 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`);
|
||||||
|
setCurrentPage(0);
|
||||||
|
window.location.hash = document.id + "" + 0;
|
||||||
|
}}
|
||||||
className={`p-2 ${(dropDownTogglePage == 'pageDropDown' + indx) ? "bg-[var(--primary)]" : "bg-slate-200"} flex flex-col justify-center items-center gap-2 rounded`}>
|
className={`p-2 ${(dropDownTogglePage == 'pageDropDown' + indx) ? "bg-[var(--primary)]" : "bg-slate-200"} flex flex-col justify-center items-center gap-2 rounded`}>
|
||||||
{
|
{
|
||||||
document.pages != undefined && <img
|
document.pages != undefined && <img
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { RootState } from "../../store/store";
|
||||||
import { LuUserRound } from "react-icons/lu";
|
import { LuUserRound } from "react-icons/lu";
|
||||||
import { AiFillSignature, AiOutlineSignature } from "react-icons/ai";
|
import { AiFillSignature, AiOutlineSignature } from "react-icons/ai";
|
||||||
import { FaRegUser } from "react-icons/fa";
|
import { FaRegUser } from "react-icons/fa";
|
||||||
|
import getInitialsUtils from "../../utlity/getInitialUtils";
|
||||||
|
|
||||||
// Define the shape of state object
|
// Define the shape of state object
|
||||||
interface Account {
|
interface Account {
|
||||||
|
|
@ -247,7 +248,7 @@ const Setting = () => {
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: "userProfile", label: "User Profile", type: "settings" },
|
{ id: "userProfile", label: "User Profile", type: "settings" },
|
||||||
// { id: "create and invite", label: "Create and invite", type: "settings" },
|
// { id: "create and invite", label: "Create and invite", type: "settings" },
|
||||||
{ id: "contacts", label: "Contacts", type: "settings" },
|
// { id: "contacts", label: "Contacts", type: "settings" },
|
||||||
// { id: "language", label: "Language", type: "settings" },
|
// { id: "language", label: "Language", type: "settings" },
|
||||||
{ id: "contactAdd", label: "Add Contact", type: "contact" },
|
{ id: "contactAdd", label: "Add Contact", type: "contact" },
|
||||||
{ id: "contactRemove", label: "Remove Contact", type: "contact" },
|
{ id: "contactRemove", label: "Remove Contact", type: "contact" },
|
||||||
|
|
@ -443,7 +444,7 @@ const Setting = () => {
|
||||||
<div className="border-b-1 border-slate-200 py-4 px-6">
|
<div className="border-b-1 border-slate-200 py-4 px-6">
|
||||||
<div className="flex justify-start items-center gap-2">
|
<div className="flex justify-start items-center gap-2">
|
||||||
<div>
|
<div>
|
||||||
<p className="bg-[#FAF0FF] text-[#A915FF] font-semibold max-w-max px-3 py-2 rounded-full ">PK</p>
|
<p className="bg-[#FAF0FF] text-[#A915FF] font-semibold max-w-max px-3 py-2 rounded-full capitalize">{getInitialsUtils(user.name)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">{user.name}</p>
|
<p className="font-semibold">{user.name}</p>
|
||||||
|
|
@ -468,12 +469,12 @@ const Setting = () => {
|
||||||
{/* Sidebar as horizontal nav (only on mobile) */}
|
{/* Sidebar as horizontal nav (only on mobile) */}
|
||||||
{activeTab == 'userProfile' && (
|
{activeTab == 'userProfile' && (
|
||||||
<section className="lg:hidden mb-2">
|
<section className="lg:hidden mb-2">
|
||||||
<div className="flex overflow-x-auto no-scrollbar gap-4 border-b border-gray-200 pb-2">
|
<div className="flex overflow-x-auto no-scrollbar gap-2 border-b border-gray-200 p-2">
|
||||||
{getSubSections(activeTab).map((sub) => (
|
{getSubSections(activeTab).map((sub) => (
|
||||||
<button
|
<button
|
||||||
key={sub.id}
|
key={sub.id}
|
||||||
onClick={() => handleSubSectionClick(sub.id)}
|
onClick={() => handleSubSectionClick(sub.id)}
|
||||||
className={`whitespace-nowrap text-sm font-medium px-2 py-1 rounded-md
|
className={`whitespace-nowrap text-sm font-medium px-2 py-2 rounded-md cursor-pointer
|
||||||
${activeSubSection == sub.id ? "bg-blue-100 text-blue-600" : "text-gray-600"}
|
${activeSubSection == sub.id ? "bg-blue-100 text-blue-600" : "text-gray-600"}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { GrCheckboxSelected } from "react-icons/gr";
|
||||||
import { Bounce, toast } from "react-toastify";
|
import { Bounce, toast } from "react-toastify";
|
||||||
import { FiCheck } from 'react-icons/fi';
|
import { FiCheck } from 'react-icons/fi';
|
||||||
import { IoMdClose } from 'react-icons/io';
|
import { IoMdClose } from 'react-icons/io';
|
||||||
|
import getInitialsUtils from '../../../utlity/getInitialUtils';
|
||||||
|
|
||||||
interface Signature {
|
interface Signature {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -125,7 +126,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editingSignature) {
|
if (editingSignature) {
|
||||||
setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`);
|
setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`);
|
||||||
setInitials((`${editingSignature.firstName.slice(0, 1)}${editingSignature.lastName.slice(0, 1)}`).toUpperCase());
|
setInitials(getInitialsUtils((editingSignature.firstName ?? '') + (editingSignature.firstName && editingSignature.lastName ? ' ' : '') + (editingSignature.lastName ?? '')))
|
||||||
setActiveTab(editingSignature.signatureType as 'SELECT' | 'DRAW' | 'UPLOAD');
|
setActiveTab(editingSignature.signatureType as 'SELECT' | 'DRAW' | 'UPLOAD');
|
||||||
|
|
||||||
// Set font for SELECT tab
|
// Set font for SELECT tab
|
||||||
|
|
@ -222,11 +223,12 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Base payload
|
// Base payload
|
||||||
|
const nameParts = fullName.trim().split(/\s+/);
|
||||||
const payload = {
|
const payload = {
|
||||||
signatureType: activeTab,
|
signatureType: activeTab,
|
||||||
initials: activeTab == "SELECT" ? initials : "",
|
initials: activeTab == "SELECT" ? initials : "",
|
||||||
firstName: fullName.split(' ').length > 1 ? fullName.split(' ')[0] : "",
|
firstName: nameParts[0] || '',
|
||||||
lastName: fullName.split(' ').length > 1 ? fullName.split(' ')[1] : fullName.split(' ')[0],
|
lastName: nameParts.length > 1 ? nameParts.slice(1).join(' ') : nameParts[0] || '',
|
||||||
font: activeTab == "SELECT" ? selectedSignature.id : "",
|
font: activeTab == "SELECT" ? selectedSignature.id : "",
|
||||||
default: checked
|
default: checked
|
||||||
};
|
};
|
||||||
|
|
@ -317,7 +319,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
|
||||||
value={fullName}
|
value={fullName}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setFullName(e.target.value)
|
setFullName(e.target.value)
|
||||||
setInitials(e.target.value.trim().split(' ').length > 1 ? e.target.value.split(' ')[0][0] + "" + e.target.value.split(' ')[1][0] : e.target.value.split(' ')[0][0])
|
setInitials(getInitialsUtils(e.target.value))
|
||||||
}}
|
}}
|
||||||
placeholder="Enter full name"
|
placeholder="Enter full name"
|
||||||
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-md outline-none"
|
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-md outline-none"
|
||||||
|
|
@ -470,7 +472,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
|
||||||
// Only set default values if not in edit mode
|
// Only set default values if not in edit mode
|
||||||
if (!isEditMode) {
|
if (!isEditMode) {
|
||||||
setFullName(currUser.firstName + " " + currUser.lastName);
|
setFullName(currUser.firstName + " " + currUser.lastName);
|
||||||
setInitials(currUser.firstName.slice(0, 1) + currUser.lastName.slice(0, 1));
|
setInitials(getInitialsUtils(currUser.firstName + " " + currUser.lastName));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching user details:", error);
|
console.error("Error fetching user details:", error);
|
||||||
|
|
@ -519,7 +521,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
|
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
|
||||||
className={`flex items-center justify-start space-x-1 px-2 sm:space-x-2 sm:px-3 py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap ${activeTab == item.id
|
className={`flex items-center justify-start space-x-1 px-2 sm:space-x-2 sm:px-3 py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap cursor-pointer ${activeTab == item.id
|
||||||
? ' text-[var(--primary)]'
|
? ' text-[var(--primary)]'
|
||||||
: 'text-gray-400'
|
: 'text-gray-400'
|
||||||
}`}
|
}`}
|
||||||
|
|
@ -569,14 +571,14 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
|
||||||
|
|
||||||
<div className='flex sm:flex-row justify-end gap-2 md:gap-3 flex-shrink-0 px-2 mt-4 mb-2'>
|
<div className='flex sm:flex-row justify-end gap-2 md:gap-3 flex-shrink-0 px-2 mt-4 mb-2'>
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 hover:border-gray-400 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200"
|
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 hover:border-gray-400 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200 cursor-pointer"
|
||||||
onClick={() => setShowAddSignature?.(false)}
|
onClick={() => setShowAddSignature?.(false)}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
CANCEL
|
CANCEL
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg bg-[var(--mainPrimaryBtn)] text-white hover:opacity-90 hover:shadow-lg flex items-center justify-center gap-1 md:gap-2 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200"
|
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg bg-[var(--mainPrimaryBtn)] text-white hover:opacity-90 hover:shadow-lg flex items-center justify-center gap-1 md:gap-2 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200 cursor-pointer"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,318 @@
|
||||||
|
// import React, { useRef, useState, useEffect } from 'react';
|
||||||
|
// import { IoMdClose } from 'react-icons/io';
|
||||||
|
|
||||||
|
// type Props = {
|
||||||
|
// onSignatureChange: (data: { fullSignature: string | null; initials: string | null }) => void;
|
||||||
|
// initialSignatures?: { fullSignature: string | null; initials: string | null };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignatures }) => {
|
||||||
|
// const canvasRef1 = useRef<HTMLCanvasElement>(null);
|
||||||
|
// const canvasRef2 = useRef<HTMLCanvasElement>(null);
|
||||||
|
// const [isDrawing, setIsDrawing] = useState(false);
|
||||||
|
// const [isDrawingType, setIsDrawingType] = useState<{ signatureDrawn: boolean, initialDrawn: boolean }>({ signatureDrawn: false, initialDrawn: false });
|
||||||
|
// const [paths1, setPaths1] = 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 [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 resizeCanvas = (canvas: HTMLCanvasElement | null) => {
|
||||||
|
// if (canvas) {
|
||||||
|
// const rect = canvas.getBoundingClientRect();
|
||||||
|
// if (rect.width > 0 && rect.height > 0) {
|
||||||
|
// canvas.width = rect.width;
|
||||||
|
// canvas.height = rect.height;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const observer1 = new ResizeObserver(() => resizeCanvas(canvasRef1.current));
|
||||||
|
// const observer2 = new ResizeObserver(() => resizeCanvas(canvasRef2.current));
|
||||||
|
|
||||||
|
// if (canvasRef1.current) observer1.observe(canvasRef1.current);
|
||||||
|
// if (canvasRef2.current) observer2.observe(canvasRef2.current);
|
||||||
|
|
||||||
|
// // initial setup
|
||||||
|
// resizeCanvas(canvasRef1.current);
|
||||||
|
// resizeCanvas(canvasRef2.current);
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// observer1.disconnect();
|
||||||
|
// observer2.disconnect();
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
|
||||||
|
// 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>) => {
|
||||||
|
// setIsDrawing(true);
|
||||||
|
// const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
// const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
||||||
|
// setCurrentPath([point]);
|
||||||
|
// setActiveCanvas(e.currentTarget == canvasRef1.current ? 'full' : 'initials');
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>, type: "SIGNATURE" | "INITIAL") => {
|
||||||
|
// if (!isDrawing) return;
|
||||||
|
// setIsDrawingType((prev) => {
|
||||||
|
// return { ...prev, signatureDrawn: type == "SIGNATURE" ? true : prev.signatureDrawn, initialDrawn: type == "INITIAL" ? true : prev.initialDrawn }
|
||||||
|
// })
|
||||||
|
// const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
// const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
||||||
|
// 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 => {
|
||||||
|
// if (!canvas) return null;
|
||||||
|
// return canvas.toDataURL('image/png');
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const handleMouseUp = () => {
|
||||||
|
// setIsDrawing(false);
|
||||||
|
|
||||||
|
// if (currentPath.length == 0 || !activeCanvas) return;
|
||||||
|
|
||||||
|
// if (activeCanvas == 'full') {
|
||||||
|
// const newPaths = [...paths1, currentPath];
|
||||||
|
// setPaths1(newPaths);
|
||||||
|
// } else {
|
||||||
|
// const newPaths = [...paths2, currentPath];
|
||||||
|
// setPaths2(newPaths);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// setCurrentPath([]);
|
||||||
|
|
||||||
|
// // Export both canvases and notify parent
|
||||||
|
// const fullSignature = exportCanvasAsImage(canvasRef1.current);
|
||||||
|
// const initials = exportCanvasAsImage(canvasRef2.current);
|
||||||
|
// onSignatureChange({ fullSignature: (isDrawingType.signatureDrawn) ? fullSignature : null, initials: (isDrawingType.initialDrawn) ? initials : null });
|
||||||
|
// // onSignatureChange({ fullSignature, initials });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const redrawCanvas = (canvas: HTMLCanvasElement, paths: Array<Array<{ x: number; y: number }>>, initialImage: HTMLImageElement | null) => {
|
||||||
|
// const ctx = canvas.getContext('2d');
|
||||||
|
// if (!ctx) return;
|
||||||
|
|
||||||
|
// // 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';
|
||||||
|
|
||||||
|
// paths.forEach((path) => {
|
||||||
|
// if (path.length < 2) return;
|
||||||
|
// ctx.beginPath();
|
||||||
|
// 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');
|
||||||
|
// ctx1?.clearRect(0, 0, canvasRef1.current.width, canvasRef1.current.height);
|
||||||
|
// }
|
||||||
|
// if (canvasRef2.current) {
|
||||||
|
// const ctx2 = canvasRef2.current.getContext('2d');
|
||||||
|
// ctx2?.clearRect(0, 0, canvasRef2.current.width, canvasRef2.current.height);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// setIsDrawingType({ signatureDrawn: false, initialDrawn: false })
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
// <div className="px-6 py-2 sm:py-4 flex-shrink-0">
|
||||||
|
// <div className='flex justify-between'>
|
||||||
|
// {/* <h2 className="text-lg sm:text-xl lg:text-2xl font-semibold text-gray-900 mb-1 sm:mb-2 ">Draw Signature</h2> */}
|
||||||
|
// <h2 className="mt-4 mb-5 text-sm">Draw your signature. <span className='text-red-600'>*</span> Are required fields.</h2>
|
||||||
|
|
||||||
|
// {/* Controls */}
|
||||||
|
// <div className="flex gap-2 mt-3">
|
||||||
|
// <div
|
||||||
|
// onClick={clearCanvas}
|
||||||
|
// className='flex justify-center items-center gap-1 font-medium'>
|
||||||
|
// <IoMdClose className='text-red-500' />
|
||||||
|
// <button
|
||||||
|
// className="text-xs text-red-500 text-nowrap"
|
||||||
|
// >
|
||||||
|
// Clear Signature
|
||||||
|
// </button>
|
||||||
|
// </div>
|
||||||
|
// {/* <button
|
||||||
|
// className="text-xs"
|
||||||
|
// onClick={undoLastPath}
|
||||||
|
// disabled={!activeCanvas || (activeCanvas == 'full' ? paths1.length == 0 : paths2.length == 0)}
|
||||||
|
// >
|
||||||
|
// Undo Last
|
||||||
|
// </button> */}
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div className="flex-1 overflow-y-auto">
|
||||||
|
// <div className="sm:grid sm:grid-cols-12 gap-4 lg:gap-6 overflow-x-auto">
|
||||||
|
// {/* Full Signature Canvas */}
|
||||||
|
// <div className="sm:col-span-8">
|
||||||
|
// <label className="block text-sm font-medium mb-2">Full Name
|
||||||
|
// <span className='text-red-600'> *</span>
|
||||||
|
// </label>
|
||||||
|
// <div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
|
||||||
|
// <canvas
|
||||||
|
// ref={canvasRef1}
|
||||||
|
// className="w-full h-36 sm:h-51 cursor-crosshair"
|
||||||
|
// onPointerDown={handleMouseDown}
|
||||||
|
// onPointerMove={(e) => handleMouseMove(e, "SIGNATURE")}
|
||||||
|
// onPointerUp={() => handleMouseUp()}
|
||||||
|
// onPointerLeave={() => handleMouseUp()}
|
||||||
|
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// {/* Initials Canvas */}
|
||||||
|
// <div className="mt-2 sm:mt-0 sm:col-span-4">
|
||||||
|
// <label className="block text-sm font-medium mb-2">Initials
|
||||||
|
// <span className='text-red-600'> *</span>
|
||||||
|
// </label>
|
||||||
|
// <div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
|
||||||
|
// <canvas
|
||||||
|
// ref={canvasRef2}
|
||||||
|
// className="w-full h-36 sm:h-51 cursor-crosshair"
|
||||||
|
// onPointerDown={handleMouseDown}
|
||||||
|
// onPointerMove={(e) => handleMouseMove(e, "INITIAL")}
|
||||||
|
// onPointerUp={() => handleMouseUp()}
|
||||||
|
// onPointerLeave={() => handleMouseUp()}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default DrawSignatureCanvas;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import React, { useRef, useState, useEffect } from 'react';
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
import { IoMdClose } from 'react-icons/io';
|
import { IoMdClose } from 'react-icons/io';
|
||||||
|
|
||||||
|
|
@ -9,26 +324,36 @@ type Props = {
|
||||||
const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignatures }) => {
|
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 [isDrawingType, setIsDrawingType] = useState<{ signatureDrawn: boolean, initialDrawn: boolean }>({ signatureDrawn: false, initialDrawn: false });
|
const [isDrawingType, setIsDrawingType] = useState({
|
||||||
|
signatureDrawn: false,
|
||||||
|
initialDrawn: false,
|
||||||
|
});
|
||||||
|
const [activeCanvas, setActiveCanvas] = useState<'full' | 'initials' | null>(null);
|
||||||
|
|
||||||
const [paths1, setPaths1] = useState<Array<Array<{ x: number; y: number }>>>([]);
|
const [paths1, setPaths1] = useState<Array<Array<{ x: number; y: number }>>>([]);
|
||||||
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 [pointerDown, setPointerDown] = useState(false);
|
||||||
const [initialLoaded, setInitialLoaded] = useState(false);
|
const [pointerId, setPointerId] = useState<number | null>(null);
|
||||||
|
|
||||||
// Store initial signature images to avoid reloading
|
// 📏 Resize observer to match canvas with container size
|
||||||
const [initialImage1, setInitialImage1] = useState<HTMLImageElement | null>(null);
|
|
||||||
const [initialImage2, setInitialImage2] = useState<HTMLImageElement | null>(null);
|
|
||||||
|
|
||||||
// Setup canvas dimensions when component mounts
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const resizeCanvas = (canvas: HTMLCanvasElement | null) => {
|
const resizeCanvas = (canvas: HTMLCanvasElement | null) => {
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
if (rect.width > 0 && rect.height > 0) {
|
if (rect.width > 0 && rect.height > 0) {
|
||||||
|
const imgData = canvas.toDataURL(); // save before resize
|
||||||
canvas.width = rect.width;
|
canvas.width = rect.width;
|
||||||
canvas.height = rect.height;
|
canvas.height = rect.height;
|
||||||
|
if (imgData) {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
canvas.getContext('2d')?.drawImage(img, 0, 0);
|
||||||
|
};
|
||||||
|
img.src = imgData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -39,7 +364,6 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
||||||
if (canvasRef1.current) observer1.observe(canvasRef1.current);
|
if (canvasRef1.current) observer1.observe(canvasRef1.current);
|
||||||
if (canvasRef2.current) observer2.observe(canvasRef2.current);
|
if (canvasRef2.current) observer2.observe(canvasRef2.current);
|
||||||
|
|
||||||
// initial setup
|
|
||||||
resizeCanvas(canvasRef1.current);
|
resizeCanvas(canvasRef1.current);
|
||||||
resizeCanvas(canvasRef2.current);
|
resizeCanvas(canvasRef2.current);
|
||||||
|
|
||||||
|
|
@ -49,94 +373,31 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const getPoint = (e: React.PointerEvent<HTMLCanvasElement>) => {
|
||||||
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>) => {
|
|
||||||
setIsDrawing(true);
|
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
||||||
setCurrentPath([point]);
|
|
||||||
setActiveCanvas(e.currentTarget == canvasRef1.current ? 'full' : 'initials');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = (e: React.MouseEvent<HTMLCanvasElement>, type: "SIGNATURE" | "INITIAL") => {
|
const drawSmoothLine = (
|
||||||
if (!isDrawing) return;
|
canvas: HTMLCanvasElement,
|
||||||
setIsDrawingType((prev) => {
|
path: Array<{ x: number; y: number }>
|
||||||
return { ...prev, signatureDrawn: type == "SIGNATURE" ? true : prev.signatureDrawn, initialDrawn: type == "INITIAL" ? true : prev.initialDrawn }
|
) => {
|
||||||
})
|
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
|
||||||
const point = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
||||||
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');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return;
|
if (!ctx || path.length < 2) return;
|
||||||
|
|
||||||
// Only draw the current path segment without clearing the canvas
|
|
||||||
ctx.strokeStyle = '#000';
|
ctx.strokeStyle = '#000';
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.lineJoin = 'round';
|
ctx.lineJoin = 'round';
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
|
|
||||||
const lastPoint = path[path.length - 2];
|
|
||||||
const currentPoint = path[path.length - 1];
|
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(lastPoint.x, lastPoint.y);
|
ctx.moveTo(path[0].x, path[0].y);
|
||||||
ctx.lineTo(currentPoint.x, currentPoint.y);
|
for (let i = 1; i < path.length - 1; i++) {
|
||||||
|
const midX = (path[i].x + path[i + 1].x) / 2;
|
||||||
|
const midY = (path[i].y + path[i + 1].y) / 2;
|
||||||
|
ctx.quadraticCurveTo(path[i].x, path[i].y, midX, midY);
|
||||||
|
}
|
||||||
|
ctx.lineTo(path[path.length - 1].x, path[path.length - 1].y);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -145,158 +406,105 @@ const DrawSignatureCanvas: React.FC<Props> = ({ onSignatureChange, initialSignat
|
||||||
return canvas.toDataURL('image/png');
|
return canvas.toDataURL('image/png');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
const handlePointerDown = (e: React.PointerEvent<HTMLCanvasElement>) => {
|
||||||
setIsDrawing(false);
|
e.currentTarget.setPointerCapture(e.pointerId);
|
||||||
|
setPointerDown(true);
|
||||||
if (currentPath.length == 0 || !activeCanvas) return;
|
setPointerId(e.pointerId);
|
||||||
|
setCurrentPath([getPoint(e)]);
|
||||||
if (activeCanvas == 'full') {
|
setActiveCanvas(e.currentTarget === canvasRef1.current ? 'full' : 'initials');
|
||||||
const newPaths = [...paths1, currentPath];
|
|
||||||
setPaths1(newPaths);
|
|
||||||
} else {
|
|
||||||
const newPaths = [...paths2, currentPath];
|
|
||||||
setPaths2(newPaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentPath([]);
|
|
||||||
|
|
||||||
// Export both canvases and notify parent
|
|
||||||
const fullSignature = exportCanvasAsImage(canvasRef1.current);
|
|
||||||
const initials = exportCanvasAsImage(canvasRef2.current);
|
|
||||||
onSignatureChange({ fullSignature: (isDrawingType.signatureDrawn) ? fullSignature : null, initials: (isDrawingType.initialDrawn) ? initials : null });
|
|
||||||
// onSignatureChange({ fullSignature, initials });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const redrawCanvas = (canvas: HTMLCanvasElement, paths: Array<Array<{ x: number; y: number }>>, initialImage: HTMLImageElement | null) => {
|
const handlePointerMove = (e: React.PointerEvent<HTMLCanvasElement>, type: 'SIGNATURE' | 'INITIAL') => {
|
||||||
const ctx = canvas.getContext('2d');
|
if (!pointerDown || pointerId !== e.pointerId) return;
|
||||||
if (!ctx) return;
|
|
||||||
|
|
||||||
// Clear canvas
|
const newPath = [...currentPath, getPoint(e)];
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
setCurrentPath(newPath);
|
||||||
|
|
||||||
// Draw initial signature if exists
|
setIsDrawingType(prev => ({
|
||||||
if (initialImage) {
|
...prev,
|
||||||
ctx.drawImage(initialImage, 0, 0);
|
signatureDrawn: type === 'SIGNATURE' ? true : prev.signatureDrawn,
|
||||||
}
|
initialDrawn: type === 'INITIAL' ? true : prev.initialDrawn,
|
||||||
|
}));
|
||||||
|
|
||||||
// Draw all paths
|
drawSmoothLine(e.currentTarget, newPath);
|
||||||
ctx.strokeStyle = '#000';
|
};
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.lineJoin = 'round';
|
|
||||||
ctx.lineCap = 'round';
|
|
||||||
|
|
||||||
paths.forEach((path) => {
|
const handlePointerUp = (e: React.PointerEvent<HTMLCanvasElement>) => {
|
||||||
if (path.length < 2) return;
|
if (!pointerDown || pointerId !== e.pointerId) return;
|
||||||
ctx.beginPath();
|
setPointerDown(false);
|
||||||
ctx.moveTo(path[0].x, path[0].y);
|
setPointerId(null);
|
||||||
for (let i = 1; i < path.length; i++) {
|
|
||||||
ctx.lineTo(path[i].x, path[i].y);
|
if (currentPath.length > 0 && activeCanvas) {
|
||||||
|
if (activeCanvas === 'full') {
|
||||||
|
setPaths1(prev => [...prev, currentPath]);
|
||||||
|
} else {
|
||||||
|
setPaths2(prev => [...prev, currentPath]);
|
||||||
}
|
}
|
||||||
ctx.stroke();
|
}
|
||||||
|
setCurrentPath([]);
|
||||||
|
|
||||||
|
const fullSignature = exportCanvasAsImage(canvasRef1.current);
|
||||||
|
const initials = exportCanvasAsImage(canvasRef2.current);
|
||||||
|
onSignatureChange({
|
||||||
|
fullSignature: isDrawingType.signatureDrawn ? fullSignature : null,
|
||||||
|
initials: isDrawingType.initialDrawn ? initials : null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearCanvas = () => {
|
const clearCanvas = () => {
|
||||||
if (canvasRef1.current) {
|
const ctx1 = canvasRef1.current?.getContext('2d');
|
||||||
const ctx1 = canvasRef1.current.getContext('2d');
|
const ctx2 = canvasRef2.current?.getContext('2d');
|
||||||
ctx1?.clearRect(0, 0, canvasRef1.current.width, canvasRef1.current.height);
|
if (ctx1 && canvasRef1.current) ctx1.clearRect(0, 0, canvasRef1.current.width, canvasRef1.current.height);
|
||||||
}
|
if (ctx2 && canvasRef2.current) ctx2.clearRect(0, 0, canvasRef2.current.width, canvasRef2.current.height);
|
||||||
if (canvasRef2.current) {
|
|
||||||
const ctx2 = canvasRef2.current.getContext('2d');
|
|
||||||
ctx2?.clearRect(0, 0, canvasRef2.current.width, canvasRef2.current.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsDrawingType({ signatureDrawn: false, initialDrawn: false })
|
|
||||||
|
|
||||||
setPaths1([]);
|
setPaths1([]);
|
||||||
setPaths2([]);
|
setPaths2([]);
|
||||||
setInitialImage1(null);
|
setIsDrawingType({ signatureDrawn: false, initialDrawn: false });
|
||||||
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="lg:px-6 py-4 flex-shrink-0">
|
<div className="px-6 py-2 sm:py-4 flex-shrink-0">
|
||||||
<div className='flex justify-between'>
|
<div className="flex justify-between">
|
||||||
{/* <h2 className="text-lg sm:text-xl lg:text-2xl font-semibold text-gray-900 mb-1 sm:mb-2 ">Draw Signature</h2> */}
|
<h2 className="mt-4 mb-5 text-sm">
|
||||||
<h2 className="mt-4 mb-5 text-sm">Draw your signature. <span className='text-red-600'>*</span> Are required fields.</h2>
|
Draw your signature. <span className="text-red-600">*</span> Required fields.
|
||||||
|
</h2>
|
||||||
{/* Controls */}
|
<div className="flex gap-2 mt-3 items-center" onClick={clearCanvas}>
|
||||||
<div className="flex gap-2 mt-3">
|
<IoMdClose className="text-red-500 cursor-pointer" />
|
||||||
<div
|
<button className="text-xs text-red-500 text-nowrap cursor-pointer">Clear Signature</button>
|
||||||
onClick={clearCanvas}
|
|
||||||
className='flex justify-center items-center gap-1 font-medium'>
|
|
||||||
<IoMdClose className='text-red-500' />
|
|
||||||
<button
|
|
||||||
className="text-xs text-red-500"
|
|
||||||
>
|
|
||||||
Clear Signature
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/* <button
|
|
||||||
className="text-xs"
|
|
||||||
onClick={undoLastPath}
|
|
||||||
disabled={!activeCanvas || (activeCanvas == 'full' ? paths1.length == 0 : paths2.length == 0)}
|
|
||||||
>
|
|
||||||
Undo Last
|
|
||||||
</button> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<div className="grid grid-cols-12 gap-4 lg:gap-6 overflow-x-auto">
|
<div className="sm:grid sm:grid-cols-12 gap-4 lg:gap-6 overflow-x-auto">
|
||||||
{/* Full Signature Canvas */}
|
{/* Full Signature */}
|
||||||
<div className="col-span-8">
|
<div className="sm:col-span-8">
|
||||||
<label className="block text-sm font-medium mb-2">Full Name
|
<label className="block text-sm font-medium mb-2">
|
||||||
<span className='text-red-600'> *</span>
|
Full Name <span className="text-red-600">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
|
<div className="border border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef1}
|
ref={canvasRef1}
|
||||||
style={{ width: "100%", height: "200px" }}
|
className="w-full h-36 sm:h-51 cursor-crosshair"
|
||||||
className="w-full h-51 cursor-crosshair"
|
onPointerDown={handlePointerDown}
|
||||||
onPointerDown={handleMouseDown}
|
onPointerMove={e => handlePointerMove(e, 'SIGNATURE')}
|
||||||
onPointerMove={(e) => handleMouseMove(e, "SIGNATURE")}
|
onPointerUp={handlePointerUp}
|
||||||
onPointerUp={() => handleMouseUp()}
|
|
||||||
onPointerLeave={() => handleMouseUp()}
|
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Initials Canvas */}
|
{/* Initials */}
|
||||||
<div className="col-span-4">
|
<div className="mt-2 sm:mt-0 sm:col-span-4">
|
||||||
<label className="block text-sm font-medium mb-2">Initials
|
<label className="block text-sm font-medium mb-2">
|
||||||
<span className='text-red-600'> *</span>
|
Initials <span className="text-red-600">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="border-1 border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
|
<div className="border border-gray-300 rounded-lg bg-white hover:bg-gray-50 transition-colors">
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef2}
|
ref={canvasRef2}
|
||||||
className="w-full h-51 cursor-crosshair"
|
className="w-full h-36 sm:h-51 cursor-crosshair"
|
||||||
onPointerDown={handleMouseDown}
|
onPointerDown={handlePointerDown}
|
||||||
onPointerMove={(e) => handleMouseMove(e, "INITIAL")}
|
onPointerMove={e => handlePointerMove(e, 'INITIAL')}
|
||||||
onPointerUp={() => handleMouseUp()}
|
onPointerUp={handlePointerUp}
|
||||||
onPointerLeave={() => handleMouseUp()}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import AddSignatureModal from './AddSignatureModal';
|
||||||
import api from '../../../config/api';
|
import api from '../../../config/api';
|
||||||
import { Bounce, toast } from "react-toastify";
|
import { Bounce, toast } from "react-toastify";
|
||||||
import { FaSpinner } from 'react-icons/fa';
|
import { FaSpinner } from 'react-icons/fa';
|
||||||
|
import getInitialsUtils from '../../../utlity/getInitialUtils';
|
||||||
|
import DeleteModal from '../../ConfirmationModal';
|
||||||
|
import ConfirmationModal from '../../ConfirmationModal';
|
||||||
|
|
||||||
interface Signature {
|
interface Signature {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -30,6 +33,12 @@ const SignatureManager: React.FC = () => {
|
||||||
const [showAddSignature, setShowAddSignature] = useState<boolean>(false);
|
const [showAddSignature, setShowAddSignature] = useState<boolean>(false);
|
||||||
const [editingSignature, setEditingSignature] = useState<Signature | null>(null);
|
const [editingSignature, setEditingSignature] = useState<Signature | null>(null);
|
||||||
|
|
||||||
|
// Delete modal
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState<{
|
||||||
|
show: boolean;
|
||||||
|
id: string;
|
||||||
|
}>({ show: false, id: "" });
|
||||||
|
|
||||||
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' },
|
||||||
|
|
@ -71,17 +80,8 @@ const SignatureManager: React.FC = () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
let res = await api.delete(`/api/v1/user/signatures/${id}`)
|
let res = await api.delete(`/api/v1/user/signatures/${id}`)
|
||||||
// toast.success("Signature Delete Successfull", {
|
|
||||||
// position: "top-center",
|
setShowDeleteModal({ show: false, id: "" });
|
||||||
// autoClose: 500,
|
|
||||||
// hideProgressBar: false,
|
|
||||||
// closeOnClick: false,
|
|
||||||
// pauseOnHover: true,
|
|
||||||
// draggable: true,
|
|
||||||
// progress: undefined,
|
|
||||||
// theme: "light",
|
|
||||||
// transition: Bounce,
|
|
||||||
// });
|
|
||||||
await getAllSignatures();
|
await getAllSignatures();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("delete signature", error)
|
console.log("delete signature", error)
|
||||||
|
|
@ -113,7 +113,7 @@ const SignatureManager: React.FC = () => {
|
||||||
const fontClass = `${selectedFont?.style || 'print'}`;
|
const fontClass = `${selectedFont?.style || 'print'}`;
|
||||||
|
|
||||||
if (mode == 'initials') {
|
if (mode == 'initials') {
|
||||||
const initials = `${(firstName?.[0] ?? '')}${(lastName?.[0] ?? '')}`.toUpperCase();
|
const initials = getInitialsUtils((firstName ?? '') + (firstName && lastName ? ' ' : '') + (lastName ?? ''));
|
||||||
return (
|
return (
|
||||||
<div className={`text-center text-[24px]`} style={{ fontFamily: selectedFont?.style }}>
|
<div className={`text-center text-[24px]`} style={{ fontFamily: selectedFont?.style }}>
|
||||||
{initials}
|
{initials}
|
||||||
|
|
@ -243,13 +243,18 @@ const SignatureManager: React.FC = () => {
|
||||||
<div className="relative flex justify-start gap-4">
|
<div className="relative flex justify-start gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEdit(signature.id)}
|
onClick={() => handleEdit(signature.id)}
|
||||||
className="max-w-max text-gray-500 text-lg"
|
className="max-w-max text-gray-500 text-lg cursor-pointer"
|
||||||
>
|
>
|
||||||
<FiEdit3 />
|
<FiEdit3 />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDelete(signature.id)}
|
onClick={() =>
|
||||||
className="max-w-max text-red-500 text-lg"
|
setShowDeleteModal({
|
||||||
|
show: true,
|
||||||
|
id: signature.id + "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="max-w-max text-red-500 text-lg cursor-pointer"
|
||||||
>
|
>
|
||||||
<FiTrash2 />
|
<FiTrash2 />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -290,7 +295,21 @@ const SignatureManager: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}</>
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{showDeleteModal.show && <ConfirmationModal
|
||||||
|
setShowConfirmationModal={setShowDeleteModal}
|
||||||
|
onConfirm={() => handleDelete(showDeleteModal.id)}
|
||||||
|
message={"Are you sure you want to delete this signature? This action cannot be undone."}
|
||||||
|
title={"Delete Confirmation."}
|
||||||
|
textColor={"bg-red-600"}
|
||||||
|
hoverTextColor={"bg-red-700"}
|
||||||
|
rejectBtnText={"Cancel"}
|
||||||
|
confirmBtnText={"Yes, Delete it!"}
|
||||||
|
/>}
|
||||||
|
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import { SaveType, SaveTypeEnum } from '../client/SignDocument';
|
||||||
import { CiSaveDown1 } from 'react-icons/ci';
|
import { CiSaveDown1 } from 'react-icons/ci';
|
||||||
import api from '../../config/api';
|
import api from '../../config/api';
|
||||||
import { IoMdClose } from 'react-icons/io';
|
import { IoMdClose } from 'react-icons/io';
|
||||||
|
import getInitialsUtils from '../../utlity/getInitialUtils';
|
||||||
|
|
||||||
interface Signature {
|
interface Signature {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -244,12 +245,13 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const nameParts = fullName.trim().split(/\s+/);
|
||||||
const payload = {
|
const payload = {
|
||||||
tagId: signTagId,
|
tagId: signTagId,
|
||||||
// signatureId: signatureId,
|
// signatureId: signatureId,
|
||||||
type: activeTab,
|
type: activeTab,
|
||||||
firstName: fullName.split(' ').length > 1 ? fullName.split(' ')[0] : "",
|
firstName: nameParts[0] || '',
|
||||||
lastName: fullName.split(' ').length > 1 ? fullName.split(' ')[1] : fullName.split(' ')[0],
|
lastName: nameParts.length > 1 ? nameParts.slice(1).join(' ') : nameParts[0] || '',
|
||||||
initials: initials,
|
initials: initials,
|
||||||
initialsBase64: signatures.initials,
|
initialsBase64: signatures.initials,
|
||||||
signatureBase64: signatures.fullSignature,
|
signatureBase64: signatures.fullSignature,
|
||||||
|
|
@ -356,7 +358,7 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
value={fullName}
|
value={fullName}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setFullName(e.target.value)
|
setFullName(e.target.value)
|
||||||
setInitials(e.target.value.trim().split(' ').length > 1 ? e.target.value.split(' ')[0][0]+""+e.target.value.split(' ')[1][0] : e.target.value.split(' ')[0][0])
|
setInitials(getInitialsUtils(e.target.value))
|
||||||
}}
|
}}
|
||||||
placeholder="Enter full name"
|
placeholder="Enter full name"
|
||||||
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-md outline-none"
|
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-md outline-none"
|
||||||
|
|
@ -417,9 +419,9 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
|
|
||||||
case 'UPLOAD':
|
case 'UPLOAD':
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col px-6">
|
||||||
<div className="mt-4 mb-5 text-sm">
|
<div className="mt-4 mb-5 text-sm">
|
||||||
<h2 className="text-sm font-semibold text-gray-900 mb-1">Upload your Signature and confirm your name * Are required fields.</h2>
|
<h2 className="text-sm text-gray-900 mb-1">Upload your Signature and confirm your name * Are required fields.</h2>
|
||||||
<p className="text-xs md:text-sm text-gray-400 hidden md:block mt-2">For best results, use an image that is 400 x 150 pixels for signature and 150 x 150 for initials</p>
|
<p className="text-xs md:text-sm text-gray-400 hidden md:block mt-2">For best results, use an image that is 400 x 150 pixels for signature and 150 x 150 for initials</p>
|
||||||
<p className="text-xs md:text-sm text-gray-400 hidden md:block">The maximum file size for the image is 300kb. File types supported include .png, .jpg, .jpeg</p>
|
<p className="text-xs md:text-sm text-gray-400 hidden md:block">The maximum file size for the image is 300kb. File types supported include .png, .jpg, .jpeg</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -586,7 +588,7 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
// let currUser = res?.data;
|
// let currUser = res?.data;
|
||||||
setUser(recipient);
|
setUser(recipient);
|
||||||
setFullName(recipient.firstName + " " + recipient.lastName);
|
setFullName(recipient.firstName + " " + recipient.lastName);
|
||||||
setInitials((recipient.firstName.slice(0, 1) + recipient.lastName.slice(0, 1)).toUpperCase());
|
setInitials(getInitialsUtils((recipient.firstName ?? '') + (recipient.firstName && recipient.lastName ? ' ' : '') + (recipient.lastName ?? '')));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching user details:", error);
|
console.error("Error fetching user details:", error);
|
||||||
}
|
}
|
||||||
|
|
@ -597,8 +599,8 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 bg-black/30 flex items-center justify-center z-50 p-2 md:p-4">
|
<div className="fixed inset-0 backdrop-blur-sm bg-opacity-50 bg-black/30 flex items-center justify-center z-50 p-0 sm:p-2 md:p-4">
|
||||||
<div className="bg-white max-w-[80%] rounded-2xl shadow-xl w-full relative overflow-hidden ">
|
<div className="bg-white sm:max-w-[80%] max-w-[100%] h-[100dvh] sm:h-auto sm:rounded-2xl shadow-xl w-full relative overflow-hidden ">
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* Main Content Area */}
|
||||||
<div className="flex-1 md:flex flex-col min-h-0">
|
<div className="flex-1 md:flex flex-col min-h-0">
|
||||||
|
|
@ -608,9 +610,9 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
|
|
||||||
|
|
||||||
{/* Navigation Menu - START */}
|
{/* Navigation Menu - START */}
|
||||||
<div className="px-6 flex-shrink-0 border-b-2 border-gray-200">
|
<div className="px-1 sm:px-6 flex-shrink-0 border-b-2 border-gray-200">
|
||||||
|
|
||||||
<div className='pt-6 pb-4 relative '>
|
<div className='px-6 sm:px-0 pt-6 pb-4 relative '>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<p className='text-2xl font-semibold'>Signature Style Selection</p>
|
<p className='text-2xl font-semibold'>Signature Style Selection</p>
|
||||||
<IoMdClose
|
<IoMdClose
|
||||||
|
|
@ -620,27 +622,35 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
<p className='text-gray-500'>Create your digital signatures</p>
|
<p className='text-gray-500'>Create your digital signatures</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="flex gap-4">
|
<nav className="flex gap-0 sm:gap-4">
|
||||||
{navigationItems.map((item) => (
|
{navigationItems.map((item) => {
|
||||||
<>
|
if (item.label === 'SAVED' && preSaveSignatures.length === 0) return null;
|
||||||
<div className='relative'>
|
|
||||||
<button
|
return (
|
||||||
key={item.id}
|
<>
|
||||||
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
|
<div className='relative'>
|
||||||
className={`flex items-center justify-start space-x-2 px-3 py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap ${activeTab == item.id
|
<button
|
||||||
? ' text-[var(--primary)]'
|
key={item.id}
|
||||||
: 'text-gray-400'
|
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
|
||||||
}`}
|
className={`flex items-center justify-start space-x-2 px-2.5 sm:px-3 py-2 sm:py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap cursor-pointer ${activeTab == item.id
|
||||||
>
|
? ' text-[var(--primary)]'
|
||||||
<span className={activeTab == item.id ? 'text-[var(--mainPrimaryBtn)]' : 'text-gray-400'}>
|
: 'text-gray-400'
|
||||||
{item.icon}
|
}`}
|
||||||
</span>
|
>
|
||||||
<span>{item.label}</span>
|
<span className={activeTab == item.id ? 'text-[var(--mainPrimaryBtn)]' : 'text-gray-400'}>
|
||||||
</button>
|
{item.icon}
|
||||||
{activeTab == item.id && <div className='absolute bottom-[-1px] w-full h-[3px] bg-[var(--mainPrimaryBtn)]'></div>}
|
</span>
|
||||||
</div>
|
<span>{item.label}</span>
|
||||||
</>
|
</button>
|
||||||
))}
|
{activeTab == item.id && <div className='absolute bottom-[-1px] w-full h-[3px] bg-[var(--mainPrimaryBtn)]'></div>}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -652,18 +662,18 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer Buttons - Fixed */}
|
{/* Footer Buttons - Fixed */}
|
||||||
<div className="px-4 py-3 flex sm:flex-row justify-between items-center gap-2 md:gap-3 flex-shrink-0">
|
<div className="absolute bottom-0 w-full sm:relative px-6 py-3 flex flex-col sm:flex-row justify-center sm:justify-between items-start sm:items-center gap-2 md:gap-3 flex-shrink-0">
|
||||||
<p className='text-sm'>* I understand that I am signing this document.</p>
|
<p className='text-sm'>* I understand that I am signing this document.</p>
|
||||||
<div className='flex sm:flex-row justify-end gap-2 md:gap-3 flex-shrink-0 px-2 mt-4 mb-2'>
|
<div className='flex w-full sm:max-w-max sm:flex-row justify-end gap-2 md:gap-3 flex-shrink-0 sm:px-2 sm:mt-4 mb-2'>
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 hover:border-gray-400 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200"
|
className="px-4 py-3 sm:py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg border border-gray-300 text-gray-700 hover:bg-gray-50 hover:border-gray-400 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200 cursor-pointer"
|
||||||
onClick={() => setOpenSign?.(false)}
|
onClick={() => setOpenSign?.(false)}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
CANCEL
|
CANCEL
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="px-4 py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg bg-[var(--mainPrimaryBtn)] text-white hover:opacity-90 hover:shadow-lg flex items-center justify-center gap-1 md:gap-2 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200"
|
className="cursor-pointer px-4 py-3 sm:py-2 w-full sm:w-auto text-xs md:text-sm font-medium rounded-lg bg-[var(--mainPrimaryBtn)] text-white hover:opacity-90 hover:shadow-lg flex items-center justify-center gap-1 md:gap-2 disabled:opacity-60 disabled:cursor-not-allowed transition-all duration-200"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
|
|
@ -673,7 +683,7 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||||
<span>CREATING...</span>
|
<span>CREATING...</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span>CREATE SIGNATURE</span>
|
<span>Sign</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ const TemplateDeleteWarnModal = ({ setShowTemplateDeleteWarnModal, onConfirm }:
|
||||||
setShowTemplateDeleteWarnModal({show: false, templateId: ""});
|
setShowTemplateDeleteWarnModal({show: false, templateId: ""});
|
||||||
}}
|
}}
|
||||||
|
|
||||||
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
|
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
No
|
No
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -30,7 +30,7 @@ const TemplateDeleteWarnModal = ({ setShowTemplateDeleteWarnModal, onConfirm }:
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onConfirm();
|
onConfirm();
|
||||||
}}
|
}}
|
||||||
className="px-4 py-2 bg-red-600 text-white hover:bg-red-700 rounded-md transition-colors"
|
className="px-4 py-2 bg-red-600 text-white hover:bg-red-700 rounded-md transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
Yes
|
Yes
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { resetRecipients } from "../../store/slices/selectedRecipientsSlice";
|
||||||
import { resetDocuments } from "../../store/slices/documentSlice";
|
import { resetDocuments } from "../../store/slices/documentSlice";
|
||||||
import { AppDispatch } from "../../store/store";
|
import { AppDispatch } from "../../store/store";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
|
import { showLoader } from "../../store/slices/loaderSlice";
|
||||||
|
|
||||||
interface Template {
|
interface Template {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -80,6 +81,7 @@ const TemplateTable = () => {
|
||||||
|
|
||||||
const handleUseClick = async (item: Template) => {
|
const handleUseClick = async (item: Template) => {
|
||||||
try {
|
try {
|
||||||
|
await dispatch(showLoader("Creating Transaction..."))
|
||||||
await dispatch(resetRecipients())
|
await dispatch(resetRecipients())
|
||||||
await dispatch(resetDocuments())
|
await dispatch(resetDocuments())
|
||||||
const payload = {
|
const payload = {
|
||||||
|
|
@ -97,6 +99,7 @@ const TemplateTable = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditClick = (templateId: string) => {
|
const handleEditClick = (templateId: string) => {
|
||||||
|
dispatch(showLoader("Loading..."))
|
||||||
setShowMenu({ open: false, templateId: null });
|
setShowMenu({ open: false, templateId: null });
|
||||||
navigate(`/setup-transaction/${activeAccountId}/template/${templateId}`);
|
navigate(`/setup-transaction/${activeAccountId}/template/${templateId}`);
|
||||||
};
|
};
|
||||||
|
|
@ -181,7 +184,7 @@ const TemplateTable = () => {
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="text-gray-500 hover:text-gray-700 p-1 rounded-md hover:bg-gray-100"
|
className="text-gray-500 hover:text-gray-700 p-1 rounded-md hover:bg-gray-100 cursor-pointer"
|
||||||
>
|
>
|
||||||
<PiDotsThreeOutlineVerticalFill className="text-lg" />
|
<PiDotsThreeOutlineVerticalFill className="text-lg" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -190,13 +193,13 @@ const TemplateTable = () => {
|
||||||
<div className="absolute right-0 mt-2 w-40 bg-white border border-gray-200 rounded-lg shadow-lg z-20 animate-fade-in">
|
<div className="absolute right-0 mt-2 w-40 bg-white border border-gray-200 rounded-lg shadow-lg z-20 animate-fade-in">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleUseClick(template)}
|
onClick={() => handleUseClick(template)}
|
||||||
className="w-full text-left px-4 py-2 text-sm hover:bg-blue-50 hover:text-blue-600"
|
className="w-full text-left px-4 py-2 text-sm hover:bg-blue-50 hover:text-blue-600 cursor-pointer"
|
||||||
>
|
>
|
||||||
Use
|
Use
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEditClick(template.id + "")}
|
onClick={() => handleEditClick(template.id + "")}
|
||||||
className="w-full text-left px-4 py-2 text-sm hover:bg-yellow-50 hover:text-yellow-600"
|
className="w-full text-left px-4 py-2 text-sm hover:bg-yellow-50 hover:text-yellow-600 cursor-pointer"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -207,7 +210,7 @@ const TemplateTable = () => {
|
||||||
templateId: template.id + "",
|
templateId: template.id + "",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="w-full text-left px-4 py-2 text-sm hover:bg-red-50 hover:text-red-600"
|
className="w-full text-left px-4 py-2 text-sm hover:bg-red-50 hover:text-red-600 cursor-pointer"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ const Templates = () => {
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-3 px-4">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-3 px-4">
|
||||||
{/* Filter as pill buttons */}
|
{/* Filter as pill buttons */}
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{filters.map((filter) => (
|
{/* {filters.map((filter) => (
|
||||||
<button
|
<button
|
||||||
key={filter}
|
key={filter}
|
||||||
onClick={() => setActiveFilter(filter)}
|
onClick={() => setActiveFilter(filter)}
|
||||||
|
|
@ -66,7 +66,7 @@ const Templates = () => {
|
||||||
>
|
>
|
||||||
{filter}
|
{filter}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))} */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add Template Button */}
|
{/* Add Template Button */}
|
||||||
|
|
@ -75,7 +75,7 @@ const Templates = () => {
|
||||||
className="flex items-center justify-center gap-1 bg-gradient-to-br from-[var(--primary)] to-blue-600 px-3 py-2 rounded-sm cursor-pointer hover:opacity-90"
|
className="flex items-center justify-center gap-1 bg-gradient-to-br from-[var(--primary)] to-blue-600 px-3 py-2 rounded-sm cursor-pointer hover:opacity-90"
|
||||||
>
|
>
|
||||||
<IoMdAdd className="text-white text-lg" />
|
<IoMdAdd className="text-white text-lg" />
|
||||||
<button className="text-white text-sm font-medium">
|
<button className="text-white text-sm font-medium cursor-pointer">
|
||||||
Add Template
|
Add Template
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
function getInitialsUtils(name) {
|
||||||
|
if (!name || typeof name !== 'string') return '';
|
||||||
|
|
||||||
|
return name
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/) // split by spaces (handles multiple spaces)
|
||||||
|
.filter(word => word.length) // remove empty strings
|
||||||
|
.map(word => word[0]) // take first letter of each word
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getInitialsUtils;
|
||||||
Loading…
Reference in New Issue