Compare commits
10 Commits
aed2aee60b
...
2290eca277
| Author | SHA1 | Date |
|---|---|---|
|
|
2290eca277 | |
|
|
0c360233c1 | |
|
|
f7352c04aa | |
|
|
68c45678a3 | |
|
|
6efe737e50 | |
|
|
f6e0b3b8af | |
|
|
dcb1934cfa | |
|
|
7612f6a3d3 | |
|
|
42b4c1dd5b | |
|
|
f527539b96 |
2
.env
2
.env
|
|
@ -6,5 +6,7 @@
|
|||
# VITE_BASE_URI="https://smashing-albacore-engaging.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://basically-polished-yeti.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://sacha-countryfied-evolutionistically.ngrok-free.dev"
|
||||
# VITE_BASE_URI="https://web.signmadeasy.app"
|
||||
# VITE_SSE_URI="https://web.signmadeasy.app"
|
||||
VITE_BASE_URI="https://web.signmadeasy.app"
|
||||
VITE_SSE_URI="https://web.signmadeasy.app"
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
"build:skip-check": "tsc --noEmit && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"deploy": "npm run build:skip-check && scp -i private\\SME-Web.pem -r dist root@52.10.89.255:/var/www/sme"
|
||||
"deploy": "npm run build:skip-check && pscp -i private\\SME-Web.ppk -r dist root@100.22.185.166:/var/www/sme"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ const App = () => {
|
|||
<Route path="/view" element={<DocView />} />
|
||||
<Route path="/session_expired" element={<SessionExpired />} />
|
||||
|
||||
<Route path="/transaction-complete" element={<CompletionScreen />} />
|
||||
<Route path="/transaction-complete/:recipientId/" element={<CompletionScreen />} />
|
||||
<Route path="/transaction-declined" element={<CompletionScreen hit="Action Canceled! " title="Maybe Next Time!" message={" Your request has been declined successfully. You can revisit anytime if you change your mind."} textColorSuccess={false} />} />
|
||||
<Route path="/transaction-reassigned" element={<CompletionScreen hit="Reasssigned Successfully" title=" The transaction has been reassigned!" message={"The transaction has been reassigned to another user"} textColorSuccess={false} />} />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { RiFileList3Line } from "react-icons/ri";
|
||||
import confetti from 'canvas-confetti'
|
||||
import handleDownloadDocument from "../../helper/handleDownloadDocumentRecSide";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
interface CompletionScreenProps {
|
||||
hit?: string;
|
||||
|
|
@ -15,7 +17,7 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
message = "Your document is now complete and securely stored. We’ve emailed you a copy for your reference.",
|
||||
textColorSuccess = true
|
||||
}) => {
|
||||
|
||||
const { recipientId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
|
|
@ -60,10 +62,11 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
{/* Header */}
|
||||
<header className="px-4 py-3 flex justify-center sm:justify-start items-center bg-white shadow-md z-10">
|
||||
<div className="flex items-center gap-2">
|
||||
<RiFileList3Line className="text-[var(--mainPrimaryBtn)] text-2xl" />
|
||||
{/* <RiFileList3Line className="text-[var(--mainPrimaryBtn)] text-2xl" />
|
||||
<span className="text-md font-semibold text-[var(--mainPrimaryBtn)]">
|
||||
Sign made easy
|
||||
</span>
|
||||
</span> */}
|
||||
<img class="py-2 w-[140px]" alt="" src="/sme-colored.png" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -76,6 +79,13 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
{message}
|
||||
</p>
|
||||
|
||||
<p
|
||||
onClick={() => { handleDownloadDocument(recipientId || "") }}
|
||||
className="text-white bg-[var(--mainPrimaryBtn)] px-3 py-2 rounded-sm mt-4 cursor-pointer">
|
||||
Download Signed Documents
|
||||
</p>
|
||||
|
||||
|
||||
{/* <button className="flex items-center gap-2 mt-6 bg-blue-600 text-white px-6 py-2 rounded-md hover:bg-blue-700 transition">
|
||||
<FiDownload />
|
||||
Download
|
||||
|
|
@ -84,11 +94,10 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
{/* <button className="mt-6 flex items-center gap-2 bg-orange-500 text-white px-6 py-2 rounded-md hover:bg-orange-600 transition">
|
||||
<FiZap /> Login
|
||||
</button> */}
|
||||
<p className="relative z-50 font-semibold mt-4 2xl:mt-6 text-md 2xl:text-xl">
|
||||
Access your documents anytime — just <a href="/login" className="text-white bg-[var(--mainPrimaryBtn)] px-3 py-1 rounded-sm">Login</a>
|
||||
<p className="relative z-50 font-semibold mt-2 2xl:mt-4 text-md 2xl:text-xl">
|
||||
Access your documents anytime — just <a href="/login" className="text-[var(--mainPrimaryBtn)] py-1 border-b-1 border-dashed">Login</a>
|
||||
</p>
|
||||
|
||||
|
||||
{/* Illustration placeholder */}
|
||||
<div className="mt-6 2xl:mt-10">
|
||||
{textColorSuccess ? <img
|
||||
|
|
@ -104,7 +113,8 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
</div>
|
||||
|
||||
{/* Apps Section */}
|
||||
<section className="relative z-10 top-6 2xl:top-10 w-full">
|
||||
{/* <section className="relative z-10 top-6 2xl:top-10 w-full"> */}
|
||||
<section className="relative z-10 w-full mt-6 2xl:mt-10 pb-6">
|
||||
<h2 className="text-xl font-semibold text-gray-800 mb-4 2xl:text-2xl">
|
||||
Boost Efficiency with Our Apps
|
||||
</h2>
|
||||
|
|
@ -119,7 +129,9 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
<div className="w-full">
|
||||
<div className="w-full flex flex-col justify-start items-start sm:flex-row sm:justify-between sm:items-center">
|
||||
<img src="/forms-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
|
||||
<button className="text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
|
||||
<button
|
||||
onClick={() => window.open("https://formsmadeasy.com/", "_blank")}
|
||||
className="cursor-pointer text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
|
||||
Try Now
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -137,7 +149,9 @@ const CompletionScreen: React.FC<CompletionScreenProps> = ({
|
|||
<div className="w-full">
|
||||
<div className="w-full flex flex-col justify-start items-start sm:flex-row sm:justify-between sm:items-center">
|
||||
<img src="/docs-made-easy.png" className="w-[40%] 2xl:w-[60%]" alt="Docs Made Easy" />
|
||||
<button className="text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
|
||||
<button
|
||||
onClick={() => window.open("https://docsmadeasy.com/", "_blank")}
|
||||
className="cursor-pointer text-xs sm:text-sm md:text-md sm:mt-0 bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 transition">
|
||||
Try Now
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,103 +1,150 @@
|
|||
import React, { useState } from 'react'
|
||||
import { MdKeyboardArrowDown, MdOutlineNavigateNext } from 'react-icons/md';
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { MdKeyboardArrowUp, MdMoreVert } from 'react-icons/md';
|
||||
import { DocumentType } from './SignDocument';
|
||||
import { GrFormPrevious } from 'react-icons/gr';
|
||||
import { DroppedItem } from '../../utlity/types';
|
||||
import { PiTagSimpleFill } from 'react-icons/pi';
|
||||
|
||||
interface DocumentPagesInterface {
|
||||
selectedDocuments: DocumentType[] | null;
|
||||
setDropDownTogglePage: React.Dispatch<React.SetStateAction<string>>;
|
||||
dropDownTogglePage: string;
|
||||
droppedItems: Record<string, DroppedItem[]> | null;
|
||||
}
|
||||
|
||||
const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownTogglePage }: DocumentPagesInterface) => {
|
||||
const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownTogglePage, droppedItems }: DocumentPagesInterface) => {
|
||||
|
||||
const [activePage, setActivePage] = useState<string | null>(
|
||||
selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null
|
||||
)
|
||||
|
||||
const [documentPagesTagsMapping, setDocumentPagesTagsMapping] = useState<Record<string, Record<number, [number, string]>>>({})
|
||||
|
||||
useEffect(() => {
|
||||
const documentId = selectedDocuments?.[0] ? `${selectedDocuments[0].documentId}_0` : null
|
||||
if (documentId === null) return;
|
||||
|
||||
const key = 'pageDropDown' + 0;
|
||||
setDropDownTogglePage(dropDownTogglePage === key ? '' : key);
|
||||
setActivePage(`${documentId}_0`);
|
||||
window.location.hash = documentId + "0";
|
||||
}, [selectedDocuments])
|
||||
|
||||
const toggleDoc = (indx: number, documentId: string) => {
|
||||
const key = 'pageDropDown' + indx;
|
||||
setDropDownTogglePage(dropDownTogglePage === key ? '' : key);
|
||||
setActivePage(`${documentId}_0`);
|
||||
window.location.hash = documentId + "0";
|
||||
}
|
||||
|
||||
const goToPage = (documentId: string, pageIndex: number) => {
|
||||
const key = `${documentId}_${pageIndex}`;
|
||||
setActivePage(key);
|
||||
window.location.hash = documentId + "" + pageIndex;
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
let documentPagesTagsMappingLocal: Record<string, Record<number, [number, string]>> = {}
|
||||
|
||||
droppedItems && Object.keys(droppedItems).length > 0 && droppedItems.forEach(item => {
|
||||
|
||||
const documentId = item.documentId;
|
||||
const pageIndex = item.pageIndex;
|
||||
|
||||
if (!documentPagesTagsMappingLocal[documentId]) {
|
||||
documentPagesTagsMappingLocal[documentId] = {};
|
||||
}
|
||||
|
||||
documentPagesTagsMappingLocal[documentId][pageIndex] = documentPagesTagsMappingLocal[documentId][pageIndex] ? [documentPagesTagsMappingLocal[documentId][pageIndex][0] + 1, "#005AB5"] : [1, "#005AB5"]
|
||||
})
|
||||
|
||||
setDocumentPagesTagsMapping(documentPagesTagsMappingLocal)
|
||||
}, 300)
|
||||
}, [droppedItems])
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 pb-[6rem]">
|
||||
{selectedDocuments && selectedDocuments.length && selectedDocuments.map((document, indx) => {
|
||||
<div className="flex flex-col w-full px-2 gap-2 pb-[6rem] bg-slate-100">
|
||||
{selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((document, indx) => {
|
||||
const isOpen = dropDownTogglePage === 'pageDropDown' + indx;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={indx}
|
||||
<div key={indx} className="border-b border-slate-200">
|
||||
|
||||
// tabIndex={1}
|
||||
// onBlur={() => setDropDownTogglePage('')}
|
||||
>
|
||||
{/* Document Header */}
|
||||
<div
|
||||
onClick={() => {
|
||||
setDropDownTogglePage(`${(dropDownTogglePage == 'pageDropDown' + indx) ? '' : 'pageDropDown' + indx}`);
|
||||
setCurrentPage(0);
|
||||
window.location.hash = document.documentId + "" + 0;
|
||||
}
|
||||
}
|
||||
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
|
||||
key={indx}
|
||||
className='rounded-sm shadow'
|
||||
src={document.pages[0].src}
|
||||
alt="" />
|
||||
}
|
||||
|
||||
<div
|
||||
className={`flex items-center justify-between w-full text-xs rounded-sm cursor-pointer ${(dropDownTogglePage == 'pageDropDown' + indx) ? "text-slate-100" : "text-slate-600"}`}>
|
||||
<p className={`whitespace-nowrap overflow-hidden text-ellipsis max-w-[18ch]`}>{document.name}</p>
|
||||
<MdKeyboardArrowDown className={`${(dropDownTogglePage == 'pageDropDown' + indx) ? 'rotate-180' : ''}`} />
|
||||
onClick={() => toggleDoc(indx, document.documentId)}
|
||||
className="flex items-start justify-between px-3 py-2.5 cursor-pointer hover:bg-slate-50 transition-colors"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[13px] font-medium text-slate-800 leading-tight truncate max-w-[16ch]">
|
||||
{document.name}
|
||||
</p>
|
||||
<p className="text-[11px] text-slate-400 mt-0.5">
|
||||
{document.pages?.length} {document.pages?.length === 1 ? 'page' : 'pages'}
|
||||
</p>
|
||||
</div>
|
||||
<MdKeyboardArrowUp
|
||||
size={20}
|
||||
className={`text-slate-400 mt-0.5 flex-shrink-0 transition-transform duration-200 ${isOpen ? '' : 'rotate-180'}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Page Thumbnails - DocuSign style */}
|
||||
{isOpen && (
|
||||
<div className="flex flex-col px-2">
|
||||
{document.pages?.map((page, pageIndex) => {
|
||||
const key = `${document.documentId}_${pageIndex}`;
|
||||
const isActive = activePage === key;
|
||||
|
||||
{
|
||||
dropDownTogglePage == 'pageDropDown' + indx &&
|
||||
<div className="my-1 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Prev Button */}
|
||||
<a
|
||||
href={"#" + document.documentId + (currentPage - 1)}
|
||||
className={`cursor-pointer px-2 py-1 shadow rounded-sm text-sm bg-slate-200 hover:border-[var(--primary)] transition-all duration-300 ${currentPage == 0 ? "pointer-events-none opacity-50" : ""
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
if (currentPage == 0) e.preventDefault();
|
||||
else setCurrentPage(currentPage - 1);
|
||||
}}
|
||||
>
|
||||
<GrFormPrevious />
|
||||
</a>
|
||||
return (
|
||||
<div
|
||||
key={pageIndex}
|
||||
onClick={() => goToPage(document.documentId, pageIndex)}
|
||||
className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors"
|
||||
>
|
||||
{/* Big Thumbnail */}
|
||||
<div className={`relative sm:p-2 lg:p-4 bg-slate-100 rounded border flex items-center justify-center overflow-hidden
|
||||
${isActive ? 'border-blue-500 border-2' : 'border-slate-200'}`}
|
||||
>
|
||||
<img
|
||||
src={page.src}
|
||||
alt={`Page ${pageIndex + 1}`}
|
||||
className="w-[140px] h-[180px] h-full object-cover"
|
||||
draggable="false"
|
||||
/>
|
||||
|
||||
{/* Page Selector */}
|
||||
<select
|
||||
className="w-[58px] py-1 text-xs text-slate-400 outline-none"
|
||||
value={currentPage}
|
||||
onChange={(e) => {
|
||||
const newPage = Number(e.target.value);
|
||||
setCurrentPage(newPage);
|
||||
window.location.hash = document.documentId + "" + newPage;
|
||||
}}
|
||||
>
|
||||
{document.pages != undefined && document.pages.map((_, pageIndex) => (
|
||||
<option key={pageIndex} value={pageIndex}>
|
||||
{pageIndex + 1}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* Page number + dots */}
|
||||
<div className="absolute bottom-0 bg-slate-800 flex items-center justify-between w-full pl-4 pr-2 py-3 bg-white">
|
||||
<span className="text-[13px] text-slate-500">{pageIndex + 1}</span>
|
||||
{
|
||||
// <>
|
||||
// <button
|
||||
// onClick={(e) => { e.stopPropagation(); /* open context menu */ }}
|
||||
// className="text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded p-0.5"
|
||||
// >
|
||||
// <MdMoreVert size={18} />
|
||||
// </button>
|
||||
// </>
|
||||
}
|
||||
</div>
|
||||
|
||||
<span className="text-xs text-slate-600">
|
||||
/ {document.pages?.length}
|
||||
</span>
|
||||
{documentPagesTagsMapping[document.documentId]?.[pageIndex] && (
|
||||
<div className='absolute top-4 right-2 rotate-180'>
|
||||
{/* <span className='absolute text-white text-[10px] font-medium text-slate-500 top-[50%] left-[50%] transform -translate-x-1/2 -translate-y-1/2 pointer-events-none select-none rotate-180'>
|
||||
{documentPagesTagsMapping[document.documentId][pageIndex][0]}
|
||||
</span> */}
|
||||
<PiTagSimpleFill style={{ fontSize: '1.8rem', color: documentPagesTagsMapping[document.documentId][pageIndex][1] }} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Next Button */}
|
||||
<a
|
||||
href={"#" + document.documentId + (currentPage + 1)}
|
||||
className={`cursor-pointer px-2 py-1 shadow rounded-sm text-sm bg-slate-200 hover:border-[var(--primary)] transition-all duration-300 ${currentPage == document.pages?.length - 1 ? "pointer-events-none opacity-50" : ""
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
if (currentPage == document.pages?.length - 1) e.preventDefault();
|
||||
else setCurrentPage(currentPage + 1);
|
||||
}}
|
||||
>
|
||||
<MdOutlineNavigateNext />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { FaSignature, FaDownload, FaUserEdit, FaTimesCircle, FaAngleLeft, FaAngl
|
|||
import { BsThreeDotsVertical } from "react-icons/bs";
|
||||
import ClientDownloadDocument from "./ClientDownloadDocument";
|
||||
import { AiOutlineAudit } from "react-icons/ai";
|
||||
import { useParams } from "react-router-dom";
|
||||
import handleDownloadDocument from "../../helper/handleDownloadDocumentRecSide";
|
||||
|
||||
interface Props {
|
||||
transparentTags?: boolean;
|
||||
|
|
@ -30,6 +32,8 @@ export default function SignButton({
|
|||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { recipientId } = useParams();
|
||||
|
||||
const [showMenuDownload, setShowMenuDownload] = useState<{ open: boolean, transactionId: string | null }>({
|
||||
open: false,
|
||||
transactionId: null
|
||||
|
|
@ -91,6 +95,18 @@ export default function SignButton({
|
|||
<span>Download Document</span>
|
||||
</button>
|
||||
</li> */}
|
||||
<li>
|
||||
<button
|
||||
onClick={() => {
|
||||
handleDownloadDocument(recipientId || "")
|
||||
setMenuOpen(false);
|
||||
}}
|
||||
className="flex items-center gap-3 w-full cursor-pointer text-left px-4 py-2 hover:bg-gray-100"
|
||||
>
|
||||
<AiOutlineAudit />
|
||||
<span>Download Documents</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import DocumentPages from "./DocumentPages";
|
|||
import FieldList from "../process/processStepTwo/FieldList";
|
||||
import typeToIconMap from "../../helper/iconMap";
|
||||
|
||||
import RightSideBar from "../process/processStepTwo/rightSideBar/RightSideBar";
|
||||
import TagProperties from "../process/processStepTwo/tagProperties/TagProperties";
|
||||
import TagsCon from "./TagsCon";
|
||||
import ConsentScreen from "./ConsentScreen";
|
||||
import SignButton from "./SignButton";
|
||||
|
|
@ -142,6 +142,22 @@ const fitObj = [
|
|||
label: "200%",
|
||||
value: "200"
|
||||
},
|
||||
{
|
||||
label: "250%",
|
||||
value: "250"
|
||||
},
|
||||
{
|
||||
label: "300%",
|
||||
value: "300"
|
||||
},
|
||||
{
|
||||
label: "350%",
|
||||
value: "350"
|
||||
},
|
||||
{
|
||||
label: "400%",
|
||||
value: "400"
|
||||
},
|
||||
]
|
||||
|
||||
enum RightSideNav {
|
||||
|
|
@ -150,6 +166,7 @@ enum RightSideNav {
|
|||
}
|
||||
|
||||
const SignDocument = () => {
|
||||
|
||||
const windowWidth = window.innerWidth;
|
||||
const { recipientId } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -158,6 +175,9 @@ const SignDocument = () => {
|
|||
|
||||
// Signer data - START
|
||||
const [selectedDocuments, setSelectedDocuments] = useState<DocumentType[] | null>(null);
|
||||
|
||||
const [imageSizes, setImageSizes] = useState<Record<string, { width: number; height: number }>>({});
|
||||
|
||||
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
|
||||
const [isPages, setIsPages] = useState<boolean>(true);
|
||||
|
||||
|
|
@ -165,13 +185,14 @@ const SignDocument = () => {
|
|||
const [tagJson, setTagJson] = useState<DroppedItem[] | []>([]);
|
||||
const [transaction, setTransaction] = useState<TransactionType | null>(null);
|
||||
const [recSignStatus, setRecSignStatus] = useState<string | null>(null);
|
||||
|
||||
const [droppedItems, setDroppedItems] = useState<DroppedItem[]>([]);
|
||||
|
||||
const [signTagId, setSignTagId] = useState<number>(0)
|
||||
const [showConfetti, setShowConfetti] = useState<boolean>(false)
|
||||
|
||||
const [pageScales, setPageScales] = useState<Record<string, { x: number; y: number }>>({});
|
||||
|
||||
|
||||
const [requiredFilled, setRequiredFilled] = useState<boolean>(false);
|
||||
const [horizontalFormLoader, setHorizontalFormLoader] = useState<{ requiredFilled: number, totalRequired: number | null }>({ requiredFilled: 0, totalRequired: null });
|
||||
|
||||
|
|
@ -207,6 +228,55 @@ const SignDocument = () => {
|
|||
const [provideTags, setProvideTags] = useState(false)
|
||||
const [showCompletionScreen, setShowCompletionScreen] = useState(false)
|
||||
|
||||
const [zoomScale, setZoomScale] = useState<number>(100)
|
||||
const scaleFactor: number = (zoomScale ?? 100) / 100;
|
||||
|
||||
// To center the container on every zoom scale change - START
|
||||
useEffect(() => {
|
||||
const container = document.getElementById("doc-con");
|
||||
if (!container) return;
|
||||
|
||||
const prevScrollRatio = container.scrollTop / (container.scrollHeight || 1);
|
||||
|
||||
const newScrollTop = prevScrollRatio * container.scrollHeight;
|
||||
const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
|
||||
|
||||
container.scrollTo({
|
||||
top: newScrollTop,
|
||||
left: newScrollLeft,
|
||||
behavior: "instant" as ScrollBehavior
|
||||
});
|
||||
}, [zoomScale]);
|
||||
// To center the container on every zoom scale change - END
|
||||
|
||||
// Handle Tags and Image Size - START
|
||||
useEffect(() => {
|
||||
const observers: ResizeObserver[] = [];
|
||||
|
||||
Object.keys(imageRefs.current).forEach((key) => {
|
||||
const el = imageRefs.current[key];
|
||||
if (!el) return;
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
setImageSizes((prev) => ({
|
||||
...prev,
|
||||
[key]: { width, height },
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(el);
|
||||
observers.push(observer);
|
||||
});
|
||||
|
||||
return () => observers.forEach((o) => o.disconnect());
|
||||
|
||||
}, [selectedDocuments]); // re-run when documents load
|
||||
// Handle Tags anf Image Size = END
|
||||
|
||||
|
||||
const [saved, setSaved] = useState<SaveType>({
|
||||
signatureId: null,
|
||||
saveType: null,
|
||||
|
|
@ -228,25 +298,31 @@ const SignDocument = () => {
|
|||
|
||||
const [signConfObj, setSignConfObj] = useState<SignConfTypeObj | {}>({});
|
||||
|
||||
|
||||
const zoom = (percentage: number, contentClass: string) => {
|
||||
const content = document.querySelector(`.${contentClass}`);
|
||||
if (content) {
|
||||
(content as HTMLElement).style.zoom = `${percentage}%`;
|
||||
}
|
||||
const handleFitSelect = (indx: number) => {
|
||||
setSelFitIndx(indx);
|
||||
let value = fitObj[indx].value;
|
||||
if (value === "FIT") value = "100";
|
||||
setZoomScale?.(Number(value));
|
||||
}
|
||||
|
||||
// const zoom = (percentage: number, contentClass: string) => {
|
||||
// const content = document.querySelector(`.${contentClass}`);
|
||||
// if (content) {
|
||||
// (content as HTMLElement).style.zoom = `${percentage}%`;
|
||||
// }
|
||||
// }
|
||||
// useEffect(() => {
|
||||
// let indx = fitObj[selFitIndx].value
|
||||
// if (indx == "FIT") {
|
||||
// indx = "100";
|
||||
// }
|
||||
// zoom(Number(indx), 'zoom-con')
|
||||
// }, [selFitIndx])
|
||||
|
||||
const handleTagClick = (key: string, index: number) => {
|
||||
setSelectedTag({ key, index })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let indx = fitObj[selFitIndx].value
|
||||
if (indx == "FIT") {
|
||||
indx = "100";
|
||||
}
|
||||
zoom(Number(indx), 'zoom-con')
|
||||
}, [selFitIndx])
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(showLoader("Loading ..."));
|
||||
|
|
@ -529,7 +605,7 @@ const SignDocument = () => {
|
|||
// setShowCompletionScreen(true)
|
||||
setSelectedTag(null)
|
||||
// navigate("/transaction-complete")
|
||||
window.location.href = `/transaction-complete`
|
||||
window.location.href = `/transaction-complete/${recipientId}`
|
||||
}
|
||||
} catch (e) {
|
||||
dispatch(hideLoader());
|
||||
|
|
@ -893,7 +969,8 @@ const SignDocument = () => {
|
|||
{fitObj.map((val, indx) => {
|
||||
return (<div
|
||||
key={indx}
|
||||
onClick={() => setSelFitIndx(indx)}
|
||||
// onClick={() => setSelFitIndx(indx)}
|
||||
onClick={() => handleFitSelect(indx)}
|
||||
className='flex z-50 items-center justify-between gap-2 w-[70px] p-2 text-xs cursor-default hover:bg-slate-100'>
|
||||
<div
|
||||
|
||||
|
|
@ -1036,7 +1113,7 @@ const SignDocument = () => {
|
|||
{/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */}
|
||||
<div
|
||||
id="tags-list-client-side"
|
||||
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" : ""}`}>
|
||||
className={`${isPages ? "block" : "hidden"} bg-slate-100 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 overflow-y-auto ${recipient?.type != "CC" ? "bg-slate-100" : ""} ${!provideTags && isFields ? "hidden" : ""}`}>
|
||||
|
||||
|
||||
<div
|
||||
|
|
@ -1074,7 +1151,9 @@ const SignDocument = () => {
|
|||
<DocumentPages
|
||||
selectedDocuments={selectedDocuments}
|
||||
setDropDownTogglePage={setDropDownTogglePage}
|
||||
dropDownTogglePage={dropDownTogglePage} />
|
||||
dropDownTogglePage={dropDownTogglePage}
|
||||
droppedItems={droppedItems}
|
||||
/>
|
||||
|
||||
</div>
|
||||
{/* Pages - END */}
|
||||
|
|
@ -1084,145 +1163,174 @@ const SignDocument = () => {
|
|||
id="doc-con"
|
||||
className={`zoom-con relative ${provideTags ? (isFields && selectedTag != null ? "col-span-8" : "col-span-10") : (isFields ? "col-span-12" : "col-span-10")} sm:px-4 bg-slate-100 h-full w-full overflow-auto scroll-smooth transition-all duration-200 pb-[4rem]`}
|
||||
>
|
||||
{
|
||||
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
|
||||
return (
|
||||
<div key={doc.documentId} className='flex flex-col'>
|
||||
<div className='py-2 text-sm'>
|
||||
<p className='text-center'>{doc.name}</p>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'>
|
||||
{
|
||||
doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => {
|
||||
const key = `${doc.documentId}_${page.index}`;
|
||||
// const tagJsonData = [...droppedItems];
|
||||
const currDocTags = droppedItems
|
||||
.map((val, index) => ({ ...val, originalIndex: index })) // add original index
|
||||
.filter(val => val.pageIndex === page.index);
|
||||
let scale: { x: number | null, y: number | null } = { x: null, y: null }
|
||||
return (
|
||||
<div
|
||||
key={indx}
|
||||
id={doc.documentId + indx}
|
||||
onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
className='drop-page-in shadow-md relative'
|
||||
>
|
||||
|
||||
<img
|
||||
// style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
|
||||
id={`${doc.documentId}-${page.index}`}
|
||||
// style={{ "width": page.width, "height": page.height, "maxWidth": "100%", "maxHeight": "100%" }}
|
||||
className={`w-[${page.width / 2}px] h-[${page.height / 2}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`}
|
||||
ref={(el) => (imageRefs.current[page.index] = el)}
|
||||
draggable="false"
|
||||
// onLoad={(imgElem) => {
|
||||
// setPageScales((prev) => ({
|
||||
// ...prev,
|
||||
// [page.index]: {
|
||||
// x: imgElem.clientWidth / page.width,
|
||||
// y: imgElem.clientHeight / page.height,
|
||||
// },
|
||||
// }));
|
||||
// }}
|
||||
width={page.width}
|
||||
height={page.height}
|
||||
src={page.src}
|
||||
loading='lazy'
|
||||
/>
|
||||
|
||||
{!hideTags && <>
|
||||
<TagsCon
|
||||
pageWidth={page.width}
|
||||
pageHeight={page.height}
|
||||
renderedWidth={imageRefs.current[page.index]?.offsetWidth || 0}
|
||||
renderedHeight={imageRefs.current[page.index]?.offsetHeight || 0}
|
||||
currDocTags={currDocTags}
|
||||
pageIdkey={key}
|
||||
provideTags={provideTags}
|
||||
documentId={doc.documentId}
|
||||
pageIndex={page.index}
|
||||
hideTags={hideTags}
|
||||
transparentTags={transparentTags}
|
||||
focusElement={focusElement}
|
||||
selectedTag={selectedTag}
|
||||
droppedItems={droppedItems}
|
||||
saved={saved}
|
||||
signConfObj={signConfObj}
|
||||
recipientId={recipientId as string}
|
||||
setSaved={setSaved}
|
||||
setStamp={setStamp}
|
||||
setSignConfObj={setSignConfObj}
|
||||
setOpenSign={setOpenSign}
|
||||
setSignTagId={setSignTagId}
|
||||
setDroppedItems={setDroppedItems}
|
||||
setSelectedTag={setSelectedTag}
|
||||
setFocusElement={setFocusElement}
|
||||
handleTagNavClick={handleTagNavClick}
|
||||
/>
|
||||
</>}
|
||||
|
||||
{/* Side details of documents Pages and document Name - START */}
|
||||
< p className='hidden sm:block absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-50px] text-slate-600' >
|
||||
page {indx + 1} of {doc.pages && doc.pages.length}
|
||||
</p>
|
||||
{/* Side details of documents Pages and document Name - END */}
|
||||
|
||||
|
||||
{!transparentTags && focusElement != null && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index &&
|
||||
<div style={{
|
||||
width: `${100 * scaleFactor}%`, // ← outer wrapper grows to make room
|
||||
height: `${100 * scaleFactor}%`,
|
||||
}}>
|
||||
<div className="zoom-con"
|
||||
style={{
|
||||
transform: `scale(${scaleFactor})`,
|
||||
transformOrigin: 'top left', // ← change to top left
|
||||
width: `${100 / scaleFactor}%`,
|
||||
height: scaleFactor < 1
|
||||
? `${100 / scaleFactor}%` // zoomed out — expand to fill
|
||||
: 'auto', // zoomed in — let content grow
|
||||
}}>
|
||||
|
||||
{
|
||||
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
|
||||
return (
|
||||
<div key={doc.documentId} className='flex flex-col'>
|
||||
<div className='py-2 text-sm'>
|
||||
<p className='text-center'>{doc.name}</p>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'>
|
||||
{
|
||||
doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => {
|
||||
const key = `${doc.documentId}_${page.index}`;
|
||||
// const tagJsonData = [...droppedItems];
|
||||
const currDocTags = droppedItems
|
||||
.map((val, index) => ({ ...val, originalIndex: index })) // add original index
|
||||
.filter(val => val.pageIndex === page.index);
|
||||
let scale: { x: number | null, y: number | null } = { x: null, y: null }
|
||||
return (
|
||||
<div
|
||||
id="tag-navigation-btn"
|
||||
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" }}
|
||||
onClick={() => handleTagNavClick("N")}
|
||||
key={indx}
|
||||
id={doc.documentId + indx}
|
||||
onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)}
|
||||
onDragOver={(e) => handleDragOver(e)}
|
||||
className='drop-page-in shadow-md relative'
|
||||
>
|
||||
|
||||
<svg width="100" height="35" viewBox="0 0 112 45" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.87927 20.8542L15.0554 2.53357C15.7668 1.54438 16.9108 0.958008 18.1292 0.958008H108.049C110.14 0.958008 111.835 2.65317 111.835 4.74426V41.0235C111.835 43.1146 110.14 44.8098 108.049 44.8098H18.1084C16.9016 44.8098 15.7671 44.2345 15.054 43.261L1.89871 25.3024C0.930133 23.9802 0.92229 22.1849 1.87927 20.8542Z" fill="#005AB5" />
|
||||
{/* <path d="M53.2649 16.7474V28.3838H51.3899L45.907 20.4577H45.8104V28.3838H43.7024V16.7474H45.5888L51.0661 24.6792H51.1683V16.7474H53.2649ZM58.8545 28.5542C57.9795 28.5542 57.2238 28.3724 56.5875 28.0088C55.9549 27.6414 55.4682 27.1224 55.1272 26.452C54.7863 25.7777 54.6159 24.9842 54.6159 24.0713C54.6159 23.1736 54.7863 22.3857 55.1272 21.7077C55.4719 21.0258 55.953 20.4955 56.5704 20.1167C57.1879 19.7342 57.9132 19.5429 58.7466 19.5429C59.2844 19.5429 59.792 19.63 60.2693 19.8042C60.7504 19.9747 61.1746 20.2398 61.542 20.5997C61.9132 20.9595 62.2049 21.4179 62.417 21.9747C62.6291 22.5277 62.7352 23.1868 62.7352 23.952V24.5827H55.5818V23.1963H60.7636C60.7598 22.8023 60.6746 22.452 60.5079 22.1452C60.3413 21.8345 60.1083 21.5902 59.8091 21.4122C59.5136 21.2342 59.1689 21.1452 58.775 21.1452C58.3545 21.1452 57.9852 21.2474 57.667 21.452C57.3488 21.6527 57.1007 21.9179 56.9227 22.2474C56.7485 22.5732 56.6594 22.9311 56.6557 23.3213V24.5315C56.6557 25.0391 56.7485 25.4747 56.9341 25.8383C57.1197 26.1982 57.3791 26.4747 57.7125 26.6679C58.0458 26.8573 58.436 26.952 58.8829 26.952C59.1822 26.952 59.453 26.9103 59.6954 26.827C59.9379 26.7398 60.1481 26.613 60.3261 26.4463C60.5041 26.2796 60.6386 26.0732 60.7295 25.827L62.65 26.0429C62.5288 26.5505 62.2977 26.9936 61.9568 27.3724C61.6197 27.7474 61.1879 28.0391 60.6613 28.2474C60.1348 28.452 59.5325 28.5542 58.8545 28.5542ZM65.4896 19.6565L67.2509 22.8781L69.0407 19.6565H71.2168L68.5862 24.0202L71.2623 28.3838H69.0975L67.2509 25.2417L65.4214 28.3838H63.2396L65.8987 24.0202L63.3078 19.6565H65.4896ZM76.6857 19.6565V21.2474H71.6687V19.6565H76.6857ZM72.9073 17.5656H74.9641V25.7588C74.9641 26.0353 75.0058 26.2474 75.0891 26.3952C75.1762 26.5391 75.2899 26.6376 75.43 26.6906C75.5702 26.7436 75.7255 26.7702 75.8959 26.7702C76.0247 26.7702 76.1421 26.7607 76.2482 26.7417C76.3581 26.7228 76.4414 26.7058 76.4982 26.6906L76.8448 28.2986C76.735 28.3364 76.5778 28.3781 76.3732 28.4236C76.1725 28.469 75.9262 28.4955 75.6346 28.5031C75.1194 28.5183 74.6554 28.4406 74.2425 28.2702C73.8296 28.0959 73.502 27.827 73.2596 27.4633C73.0209 27.0997 72.9035 26.6452 72.9073 26.0997V17.5656Z" fill="white" /> */}
|
||||
<img
|
||||
// style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
|
||||
id={`${doc.documentId}-${page.index}`}
|
||||
// style={{ "width": page.width, "height": page.height, "maxWidth": "100%", "maxHeight": "100%" }}
|
||||
className={`w-[${page.width / 2}px] h-[${page.height / 2}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`}
|
||||
ref={(el) => (imageRefs.current[page.index] = el)}
|
||||
onLoad={(e) => {
|
||||
const el = e.currentTarget;
|
||||
setImageSizes((prev) => ({
|
||||
...prev,
|
||||
[page.index]: {
|
||||
width: el.offsetWidth,
|
||||
height: el.offsetHeight,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
draggable="false"
|
||||
// onLoad={(imgElem) => {
|
||||
// setPageScales((prev) => ({
|
||||
// ...prev,
|
||||
// [page.index]: {
|
||||
// x: imgElem.clientWidth / page.width,
|
||||
// y: imgElem.clientHeight / page.height,
|
||||
// },
|
||||
// }));
|
||||
// }}
|
||||
width={page.width}
|
||||
height={page.height}
|
||||
src={page.src}
|
||||
loading='lazy'
|
||||
/>
|
||||
|
||||
<text className="font-semibold capitalize" x="55%" y="54%" fill="white" font-size="14" font-family="Arial, sans-serif" dominant-baseline="middle" text-anchor="middle">
|
||||
{requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()}
|
||||
</text>
|
||||
</svg>
|
||||
{!hideTags && <>
|
||||
<TagsCon
|
||||
zoomScale={zoomScale}
|
||||
pageWidth={page.width}
|
||||
pageHeight={page.height}
|
||||
renderedWidth={imageSizes[page.index]?.width || imageRefs.current[page.index]?.offsetWidth || 0}
|
||||
renderedHeight={imageSizes[page.index]?.height || imageRefs.current[page.index]?.offsetHeight || 0}
|
||||
currDocTags={currDocTags}
|
||||
pageIdkey={key}
|
||||
provideTags={provideTags}
|
||||
documentId={doc.documentId}
|
||||
pageIndex={page.index}
|
||||
hideTags={hideTags}
|
||||
transparentTags={transparentTags}
|
||||
focusElement={focusElement}
|
||||
selectedTag={selectedTag}
|
||||
droppedItems={droppedItems}
|
||||
saved={saved}
|
||||
signConfObj={signConfObj}
|
||||
recipientId={recipientId as string}
|
||||
setSaved={setSaved}
|
||||
setStamp={setStamp}
|
||||
setSignConfObj={setSignConfObj}
|
||||
setOpenSign={setOpenSign}
|
||||
setSignTagId={setSignTagId}
|
||||
setDroppedItems={setDroppedItems}
|
||||
setSelectedTag={setSelectedTag}
|
||||
setFocusElement={setFocusElement}
|
||||
handleTagNavClick={handleTagNavClick}
|
||||
/>
|
||||
</>}
|
||||
|
||||
{/* NEXT Button - END */}
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
{/* Side details of documents Pages and document Name - START */}
|
||||
< p className='hidden sm:block absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-50px] text-slate-600' >
|
||||
page {indx + 1} of {doc.pages && doc.pages.length}
|
||||
</p>
|
||||
{/* Side details of documents Pages and document Name - END */}
|
||||
|
||||
|
||||
{!transparentTags && focusElement != null && focusElement && Object.keys(focusElement).length > 1 && focusElement.documentId == doc.documentId && focusElement.pageIndex == page.index &&
|
||||
<div
|
||||
id="tag-navigation-btn"
|
||||
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" }}
|
||||
onClick={() => handleTagNavClick("N")}
|
||||
>
|
||||
|
||||
<svg width="100" height="35" viewBox="0 0 112 45" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.87927 20.8542L15.0554 2.53357C15.7668 1.54438 16.9108 0.958008 18.1292 0.958008H108.049C110.14 0.958008 111.835 2.65317 111.835 4.74426V41.0235C111.835 43.1146 110.14 44.8098 108.049 44.8098H18.1084C16.9016 44.8098 15.7671 44.2345 15.054 43.261L1.89871 25.3024C0.930133 23.9802 0.92229 22.1849 1.87927 20.8542Z" fill="#005AB5" />
|
||||
{/* <path d="M53.2649 16.7474V28.3838H51.3899L45.907 20.4577H45.8104V28.3838H43.7024V16.7474H45.5888L51.0661 24.6792H51.1683V16.7474H53.2649ZM58.8545 28.5542C57.9795 28.5542 57.2238 28.3724 56.5875 28.0088C55.9549 27.6414 55.4682 27.1224 55.1272 26.452C54.7863 25.7777 54.6159 24.9842 54.6159 24.0713C54.6159 23.1736 54.7863 22.3857 55.1272 21.7077C55.4719 21.0258 55.953 20.4955 56.5704 20.1167C57.1879 19.7342 57.9132 19.5429 58.7466 19.5429C59.2844 19.5429 59.792 19.63 60.2693 19.8042C60.7504 19.9747 61.1746 20.2398 61.542 20.5997C61.9132 20.9595 62.2049 21.4179 62.417 21.9747C62.6291 22.5277 62.7352 23.1868 62.7352 23.952V24.5827H55.5818V23.1963H60.7636C60.7598 22.8023 60.6746 22.452 60.5079 22.1452C60.3413 21.8345 60.1083 21.5902 59.8091 21.4122C59.5136 21.2342 59.1689 21.1452 58.775 21.1452C58.3545 21.1452 57.9852 21.2474 57.667 21.452C57.3488 21.6527 57.1007 21.9179 56.9227 22.2474C56.7485 22.5732 56.6594 22.9311 56.6557 23.3213V24.5315C56.6557 25.0391 56.7485 25.4747 56.9341 25.8383C57.1197 26.1982 57.3791 26.4747 57.7125 26.6679C58.0458 26.8573 58.436 26.952 58.8829 26.952C59.1822 26.952 59.453 26.9103 59.6954 26.827C59.9379 26.7398 60.1481 26.613 60.3261 26.4463C60.5041 26.2796 60.6386 26.0732 60.7295 25.827L62.65 26.0429C62.5288 26.5505 62.2977 26.9936 61.9568 27.3724C61.6197 27.7474 61.1879 28.0391 60.6613 28.2474C60.1348 28.452 59.5325 28.5542 58.8545 28.5542ZM65.4896 19.6565L67.2509 22.8781L69.0407 19.6565H71.2168L68.5862 24.0202L71.2623 28.3838H69.0975L67.2509 25.2417L65.4214 28.3838H63.2396L65.8987 24.0202L63.3078 19.6565H65.4896ZM76.6857 19.6565V21.2474H71.6687V19.6565H76.6857ZM72.9073 17.5656H74.9641V25.7588C74.9641 26.0353 75.0058 26.2474 75.0891 26.3952C75.1762 26.5391 75.2899 26.6376 75.43 26.6906C75.5702 26.7436 75.7255 26.7702 75.8959 26.7702C76.0247 26.7702 76.1421 26.7607 76.2482 26.7417C76.3581 26.7228 76.4414 26.7058 76.4982 26.6906L76.8448 28.2986C76.735 28.3364 76.5778 28.3781 76.3732 28.4236C76.1725 28.469 75.9262 28.4955 75.6346 28.5031C75.1194 28.5183 74.6554 28.4406 74.2425 28.2702C73.8296 28.0959 73.502 27.827 73.2596 27.4633C73.0209 27.0997 72.9035 26.6452 72.9073 26.0997V17.5656Z" fill="white" /> */}
|
||||
|
||||
<text className="font-semibold capitalize" x="55%" y="54%" fill="white" font-size="14" font-family="Arial, sans-serif" dominant-baseline="middle" text-anchor="middle">
|
||||
{requiredFilled && focusElement.required == true ? "Submit" : focusElement.type.toLowerCase()}
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
{/* NEXT Button - END */}
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
{(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) &&
|
||||
<div
|
||||
onClick={() => scrollToElement(focusElement?.navigationOrder)}
|
||||
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 */}
|
||||
{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>}
|
||||
|
||||
{/* Label box */}
|
||||
<div
|
||||
className="bg-[var(--primary)] text-white text-sm px-3 py-1 rounded-md shadow-md flex items-center gap-1">
|
||||
<span className="capitalize">{focusElement?.type.toLowerCase()}</span>
|
||||
</div>
|
||||
{navigationTagPos == NavigationTagPosEnum.BELOW && <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)] rotate-[180deg]"></div>}
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
{(navigationTagPos == NavigationTagPosEnum.ABOVE || navigationTagPos == NavigationTagPosEnum.BELOW) &&
|
||||
<div
|
||||
onClick={() => scrollToElement(focusElement?.navigationOrder)}
|
||||
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 */}
|
||||
{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>}
|
||||
|
||||
{/* Label box */}
|
||||
<div
|
||||
className="bg-[var(--primary)] text-white text-sm px-3 py-1 rounded-md shadow-md flex items-center gap-1">
|
||||
<span className="capitalize">{focusElement?.type.toLowerCase()}</span>
|
||||
</div>
|
||||
{navigationTagPos == NavigationTagPosEnum.BELOW && <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)] rotate-[180deg]"></div>}
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{provideTags && selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag &&
|
||||
<div className={`col-span-2 ${provideTags ? "" : "order-first"} h-full ${!isFields ? "bg-white" : isFields && provideTags ? "bg-white" : ""} overflow-y-auto`}>
|
||||
{/* Right Side Attributes Section if providedTags - START */}
|
||||
<RightSideBar
|
||||
<TagProperties
|
||||
droppedItems={droppedItems}
|
||||
selectedTag={droppedItems[selectedTag.index]}
|
||||
setDroppedItems={setDroppedItems}
|
||||
|
|
@ -1244,6 +1352,7 @@ const SignDocument = () => {
|
|||
<div style={{ display: `${openSign ? 'block' : 'none'}` }}>
|
||||
{recipient &&
|
||||
<ClientSignCon
|
||||
zoomScale={zoomScale}
|
||||
recipient={recipient}
|
||||
recipientId={recipientId as string}
|
||||
setOpenSign={setOpenSign} saved={saved}
|
||||
|
|
@ -1326,7 +1435,7 @@ const SignDocument = () => {
|
|||
className="bg-[var(--mainPrimaryBtn)] p-1 text-2xl text-white rounded-md" />
|
||||
</div> */}
|
||||
<div>
|
||||
{!transparentTags && focusElement != null &&
|
||||
{!transparentTags && focusElement != null && Object.keys(focusElement).length > 1 &&
|
||||
<div
|
||||
id="tag-navigation-btn-mb"
|
||||
className={`sm:hidden flex justify-center gap-0 items-center z-50 cursor-pointer transition-all duration-300`}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import axios from 'axios';
|
|||
|
||||
|
||||
interface TagsConProps {
|
||||
zoomScale: number,
|
||||
pageWidth: number,
|
||||
pageHeight: number,
|
||||
renderedWidth: number,
|
||||
|
|
@ -43,6 +44,7 @@ interface TagsConProps {
|
|||
}
|
||||
|
||||
const TagsCon: React.FC<TagsConProps> = ({
|
||||
zoomScale,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
renderedWidth,
|
||||
|
|
@ -71,60 +73,76 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
handleTagNavClick
|
||||
}) => {
|
||||
|
||||
const scaleFactor: number = (zoomScale ?? 100) / 100;
|
||||
|
||||
const [dateValues, setDateValues] = useState<{ [key: string]: string }>({});
|
||||
const [isDateOpen, setIsDateOpen] = useState<string | null>(null);
|
||||
|
||||
const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
|
||||
setSelectedTag({ key, index })
|
||||
|
||||
setSelectedTag({ key, index });
|
||||
event.preventDefault();
|
||||
|
||||
const scale = scaleFactor ?? 1; // zoom factor
|
||||
|
||||
let targetContainer = event?.currentTarget?.parentElement?.parentElement;
|
||||
let targetElement = event?.currentTarget;
|
||||
let targetElement = event?.currentTarget?.parentElement;
|
||||
|
||||
if (!targetContainer || !targetElement) return;
|
||||
|
||||
const containerRect = targetContainer.getBoundingClientRect();
|
||||
const elementRect = targetElement.getBoundingClientRect();
|
||||
|
||||
// Calculate the initial offset (where the mouse was clicked inside the element)
|
||||
const offsetX = event.clientX - elementRect.left;
|
||||
const offsetY = event.clientY - elementRect.top;
|
||||
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
|
||||
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
|
||||
|
||||
const conScrollWidth = targetContainer?.scrollWidth;
|
||||
const conScrollHeight = targetContainer?.scrollHeight;
|
||||
// Click offset in screen pixels
|
||||
const clickOffsetX = event.clientX - elementRect.left;
|
||||
const clickOffsetY = event.clientY - elementRect.top;
|
||||
|
||||
let isGroupAttr = event.currentTarget.hasAttribute("group");
|
||||
let groupId = null;
|
||||
if (isGroupAttr) {
|
||||
groupId = event.currentTarget.getAttribute("group");
|
||||
}
|
||||
if (isGroupAttr) groupId = event.currentTarget.getAttribute("group");
|
||||
|
||||
let groupElements: NodeListOf<HTMLElement> | null = null
|
||||
let groupElements: NodeListOf<HTMLElement> | null = null;
|
||||
if (isGroupAttr && groupId) {
|
||||
groupElements = document.querySelectorAll(`div[group="${groupId}"]`);
|
||||
}
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
// Get new X and Y positions relative to the container
|
||||
let left = event.clientX - containerRect.left - offsetX;
|
||||
let top = event.clientY - containerRect.top - offsetY;
|
||||
const currentContainerRect = targetContainer!.getBoundingClientRect();
|
||||
|
||||
// Mouse position relative to container in screen pixels
|
||||
const mouseX = event.clientX - currentContainerRect.left;
|
||||
const mouseY = event.clientY - currentContainerRect.top;
|
||||
|
||||
// **Restrict movement within container bounds**
|
||||
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width));
|
||||
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height));
|
||||
// Convert to rendered pixel space (undo zoom)
|
||||
const renderedLeft = (mouseX - clickOffsetX) / scale;
|
||||
const renderedTop = (mouseY - clickOffsetY) / scale;
|
||||
|
||||
// Convert to original coord space for storage
|
||||
const originalLeft = renderedLeft / scaleX;
|
||||
const originalTop = renderedTop / scaleY;
|
||||
|
||||
if (groupElements != null && groupElements.length) handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
|
||||
if (groupElements != null && groupElements.length) {
|
||||
handleGroupBorderPosition(groupElements, currentContainerRect, clickOffsetX, clickOffsetY);
|
||||
}
|
||||
|
||||
// Apply the movement to the element
|
||||
// Update state in original coord space
|
||||
setDroppedItems(
|
||||
droppedItems.map((item, i) =>
|
||||
i == index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
|
||||
i === index
|
||||
? {
|
||||
...item,
|
||||
dimensions: {
|
||||
...item.dimensions,
|
||||
left: Math.max(0, originalLeft),
|
||||
top: Math.max(0, originalTop),
|
||||
},
|
||||
update: true
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
|
|
@ -132,7 +150,6 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
document.removeEventListener("pointerup", handleMouseUp);
|
||||
};
|
||||
|
||||
// Add event listeners
|
||||
document.addEventListener("pointermove", handleMouseMove);
|
||||
document.addEventListener("pointerup", handleMouseUp);
|
||||
};
|
||||
|
|
@ -308,9 +325,6 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(signConfObj, saved, "Line 305")
|
||||
|
||||
return (
|
||||
<>
|
||||
{currDocTags.map((item, index) => {
|
||||
|
|
@ -681,6 +695,49 @@ const TagsCon: React.FC<TagsConProps> = ({
|
|||
{/* CHECKBOX FIELD - END */}
|
||||
|
||||
|
||||
{/* RADIO FIELD - START */}
|
||||
{
|
||||
item.type == "RADIO" &&
|
||||
<div
|
||||
id={item.navigationOrder}
|
||||
tabIndex={1}
|
||||
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name={item.group ?? `radio-group-${item.recipientId}`} // group by recipient or group id
|
||||
value={item.id}
|
||||
checked={item.checked ?? false}
|
||||
onChange={(e) => {
|
||||
if (!e.target.checked) return;
|
||||
|
||||
// Uncheck all other radio tags in same group, check this one
|
||||
setDroppedItems(
|
||||
droppedItems.map((tag, i) => {
|
||||
if (tag.type !== "RADIO") return tag;
|
||||
|
||||
// Same group = same name attribute
|
||||
const sameGroup = (item.group && tag.group)
|
||||
? item.group === tag.group
|
||||
: tag.recipientId === item.recipientId; // fallback
|
||||
|
||||
if (!sameGroup) return tag;
|
||||
|
||||
return {
|
||||
...tag,
|
||||
checked: tag.id === item.id,
|
||||
update: true
|
||||
};
|
||||
})
|
||||
);
|
||||
}}
|
||||
className="w-4 h-4 cursor-pointer accent-[var(--primary)]"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{/* RADIO FIELD - END */}
|
||||
|
||||
|
||||
{/* SIGNATURE FIELD - START */}
|
||||
{
|
||||
item.type == "SIGNATURE" &&
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ function Dashboard() {
|
|||
{/* Close Icon */}
|
||||
<button
|
||||
onClick={() => setShowConfetti(false)} // or whatever logic hides the modal
|
||||
className="absolute top-2 right-2 text-gray-500 hover:text-gray-700"
|
||||
className="absolute top-2 right-2 text-gray-500 hover:text-gray-100"
|
||||
aria-label="Close"
|
||||
>
|
||||
<IoClose className="text-2xl" />
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ import { RootState } from '../../store/store';
|
|||
import api from '../../config/api';
|
||||
import TransactionChoiceModal from './TransactionChoiceModal';
|
||||
import { resetRecipients } from '../../store/slices/selectedRecipientsSlice';
|
||||
import { IoMdArrowDropdown } from 'react-icons/io';
|
||||
import { MdArrowRight } from 'react-icons/md';
|
||||
import { FaDesktop } from 'react-icons/fa';
|
||||
import { GrTemplate } from 'react-icons/gr';
|
||||
import { HiOutlineTemplate, HiTemplate } from 'react-icons/hi';
|
||||
import { LuLayoutTemplate } from 'react-icons/lu';
|
||||
|
||||
|
||||
const StartNow = () => {
|
||||
|
|
@ -141,11 +147,11 @@ const StartNow = () => {
|
|||
}}
|
||||
/>}
|
||||
<div
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
// onClick={() => fileInputRef.current?.click()}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
id='start-now-upload-con'
|
||||
className="group col-span-1 cursor-pointer border-1 border-dashed border-[#D3D3D3] rounded-lg bg-[var(--documentUploadBg)] transition-all duration-200 p-4 flex flex-col items-center justify-center gap-2 text-center shadow-sm hover:shadow-md">
|
||||
className="group col-span-1 cursor-pointer border-1 border-dashed border-[#D3D3D3] rounded-lg bg-[var(--documentUploadBg)] transition-all duration-200 p-4 flex flex-col items-center justify-center gap-2 text-center shadow-sm">
|
||||
|
||||
{/* Upload icon */}
|
||||
<svg className="mt-4" width="44" height="46" viewBox="0 0 62 46" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
|
@ -163,11 +169,70 @@ const StartNow = () => {
|
|||
PDF, DOCX (max 10MB)
|
||||
</p>
|
||||
|
||||
<button
|
||||
{/* <button
|
||||
className="text-xs mt-4 mb-6 text-white bg-[var(--dashBoardStartNowBtn)] px-8 py-2 rounded shadow-xs transition-colors duration-150 focus:outline-none focus:ring-1 focus:ring-blue-400 cursor-pointer"
|
||||
>
|
||||
Upload File
|
||||
</button>
|
||||
</button> */}
|
||||
|
||||
|
||||
<div className="relative inline-block group mt-4 mb-6">
|
||||
|
||||
<button
|
||||
className="flex text-lg font-medium items-center gap-1 text-xs text-white bg-[var(--dashBoardStartNowBtn)] pl-3 pr-2 py-2 rounded shadow-xs transition-colors duration-150 focus:outline-none focus:ring-1 focus:ring-blue-400 cursor-pointer"
|
||||
>
|
||||
<p>Upload File</p>
|
||||
<IoMdArrowDropdown className='text-lg' />
|
||||
</button>
|
||||
|
||||
{/* Dropdown */}
|
||||
<div
|
||||
className="
|
||||
absolute
|
||||
left-0
|
||||
top-full
|
||||
mt-1
|
||||
min-w-[180px]
|
||||
bg-white
|
||||
border
|
||||
border-slate-200
|
||||
rounded-md
|
||||
shadow-lg
|
||||
overflow-hidden
|
||||
z-50
|
||||
|
||||
opacity-0
|
||||
invisible
|
||||
translate-y-[-5px]
|
||||
scale-[0.98]
|
||||
|
||||
transition-all
|
||||
duration-200
|
||||
ease-out
|
||||
|
||||
group-hover:opacity-100
|
||||
group-hover:visible
|
||||
group-hover:translate-y-0
|
||||
group-hover:scale-100
|
||||
"
|
||||
>
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 cursor-pointer flex gap-2 items-center"
|
||||
>
|
||||
<FaDesktop className='text-[var(--primary)]' />
|
||||
<span>Desktop</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => navigate("/templates")}
|
||||
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 border-t border-slate-100 cursor-pointer flex gap-2 items-center"
|
||||
>
|
||||
<HiTemplate className='text-[var(--primary)]' />
|
||||
<span>Use a Template</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{filesError && <span className="text-red-500 text-xs">{filesError}</span>}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,83 +91,380 @@ const TransactionSentSuccess = () => {
|
|||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col p-4 justify-center items-center w-full">
|
||||
<div className="mx-auto w-full max-w-6xl">
|
||||
{/* Success banner */}
|
||||
<div className="flex flex-col justify-center items-center gap-3 mb-4 p-4 rounded-md">
|
||||
{/* <div className="w-12 h-12 bg-gradient-to-br from-teal-500 to-green-400 rounded-full flex items-center justify-center shadow-sm">
|
||||
<FaCheck className="text-white text-2xl" />
|
||||
</div> */}
|
||||
<div className="relative z-10 w-full px-4 py-6 sm:px-6">
|
||||
|
||||
<div className="max-w-7xl mx-auto">
|
||||
|
||||
<svg width="70" height="70" viewBox="0 0 114 109" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M114 54.3837L101.095 40.1811L103.115 21.1673L84.2441 17.2075L74.6181 0.631836L57 8.43682L54.6221 54.3837L57 100.33L74.6183 108.135L84.2441 91.56L103.115 87.5999L101.095 68.5861L114 54.3837Z" fill="#0ED678" />
|
||||
<path d="M39.3816 0.631836L29.7558 17.2075L10.8847 21.1675L12.9053 40.1811L0 54.3837L12.9053 68.5861L10.8847 87.5999L29.7558 91.56L39.3819 108.135L57.0001 100.33V8.43682L39.3816 0.631836Z" fill="#04EB84" />
|
||||
<path d="M80.8957 42.013L75.9064 37.5986L57 58.5946L54.6221 63.8124L57 68.5499L80.8957 42.013Z" fill="#F7F0EB" />
|
||||
<path d="M37.1311 49.3916L32.4023 54.0791L52.2842 73.7868L57.0003 68.5495V58.5942L52.0082 64.138L37.1311 49.3916Z" fill="#FFFBF5" />
|
||||
</svg>
|
||||
{/* Success Hero */}
|
||||
{/* Success Hero */}
|
||||
<div
|
||||
className="
|
||||
relative
|
||||
overflow-hidden
|
||||
bg-white
|
||||
border
|
||||
border-slate-200
|
||||
rounded-md
|
||||
px-6
|
||||
py-8
|
||||
lg:px-10
|
||||
lg:py-10
|
||||
"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<p className="text-lg sm:text-4xl font-bold text-[var(--mainPrimaryBtn)]">
|
||||
Your Document is sent for signature
|
||||
</p>
|
||||
<p className="text-lg text-slate-600 text-center font-medium">All signers will be notified via email</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main two-column layout */}
|
||||
<div className="flex flex-col lg:flex-row gap-4 mt-6 lg:justify-center lg:items-stretch">
|
||||
{/* Left: Document Preview */}
|
||||
<div className="flex-1 bg-slate-100 rounded-md overflow-hidden">
|
||||
<div className="w-full h-[50vh] overflow-auto">
|
||||
<div className="py-2 bg-white px-4 shadow-sm rounded-sm sticky top-0 z-10 border-1 border-gray-200">
|
||||
<p className="text-sm font-medium">Quick Preview</p>
|
||||
</div>
|
||||
<div className="px-4">
|
||||
<DocumentContainer isPreview={true} aId={accountId} tId={transactionId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Signers list + actions */}
|
||||
{/* subtle background */}
|
||||
<div
|
||||
className={`flex-1 transition-all rounded-lg duration-700 ${isVisible ? 'translate-y-0 opacity-100' : 'translate-y-8 opacity-0'
|
||||
}`}
|
||||
>
|
||||
<div className="p-4 h-full bg-white rounded-md shadow-2xl border-1 border-gray-100">
|
||||
className="
|
||||
absolute
|
||||
right-[-80px]
|
||||
top-[-80px]
|
||||
w-[220px]
|
||||
h-[220px]
|
||||
bg-emerald-50
|
||||
rounded-full
|
||||
blur-3xl
|
||||
opacity-70
|
||||
"
|
||||
/>
|
||||
|
||||
<div className='overflow-auto'>
|
||||
<SignersList signers={selectedRecipients} />
|
||||
<div
|
||||
className="
|
||||
relative
|
||||
z-10
|
||||
flex
|
||||
flex-col
|
||||
lg:flex-row
|
||||
items-start
|
||||
lg:items-center
|
||||
justify-between
|
||||
gap-8
|
||||
"
|
||||
>
|
||||
|
||||
{/* Left */}
|
||||
<div className="flex-1">
|
||||
|
||||
{/* success badge */}
|
||||
<div
|
||||
className="
|
||||
inline-flex
|
||||
items-center
|
||||
gap-2
|
||||
px-4
|
||||
py-2
|
||||
rounded-full
|
||||
bg-emerald-50
|
||||
border
|
||||
border-emerald-100
|
||||
mb-5
|
||||
"
|
||||
>
|
||||
|
||||
<div
|
||||
className="
|
||||
w-5
|
||||
h-5
|
||||
rounded-full
|
||||
bg-emerald-500
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
"
|
||||
>
|
||||
<FaCheck className="text-white text-[11px]" />
|
||||
</div>
|
||||
|
||||
<span
|
||||
className="
|
||||
text-sm
|
||||
font-medium
|
||||
text-emerald-700
|
||||
"
|
||||
>
|
||||
Transaction Sent Successfully
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2 max-h-max relative top-auto bottom-0">
|
||||
<Link
|
||||
to="/"
|
||||
className="flex-1 p-2 text-center rounded-md bg-slate-200 hover:shadow-sm transition-all duration-150"
|
||||
{/* title */}
|
||||
<h1
|
||||
className="
|
||||
text-3xl
|
||||
sm:text-4xl
|
||||
font-bold
|
||||
text-slate-900
|
||||
leading-tight
|
||||
tracking-[-0.02em]
|
||||
max-w-3xl
|
||||
"
|
||||
>
|
||||
Your document has been sent for signature
|
||||
</h1>
|
||||
|
||||
{/* description */}
|
||||
<p
|
||||
className="
|
||||
mt-4
|
||||
text-base
|
||||
leading-relaxed
|
||||
text-slate-600
|
||||
max-w-2xl
|
||||
"
|
||||
>
|
||||
All recipients will receive an email notification with
|
||||
secure access to review and sign the document.
|
||||
</p>
|
||||
|
||||
{/* actions */}
|
||||
<div
|
||||
className="
|
||||
mt-7
|
||||
flex
|
||||
flex-col
|
||||
sm:flex-row
|
||||
gap-3
|
||||
"
|
||||
>
|
||||
|
||||
{/* <button
|
||||
onClick={() => {
|
||||
handleDownloadDocument(recipientId || "")
|
||||
}}
|
||||
className="
|
||||
h-11
|
||||
px-5
|
||||
rounded-sm
|
||||
bg-[var(--mainPrimaryBtn)]
|
||||
text-white
|
||||
font-medium
|
||||
hover:opacity-90
|
||||
transition
|
||||
shadow-sm
|
||||
cursor-pointer
|
||||
"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
Download Documents
|
||||
</button> */}
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
window.location.href = "/"
|
||||
}}
|
||||
className="
|
||||
h-11
|
||||
px-5
|
||||
rounded-sm
|
||||
bg-white
|
||||
text-slate-700
|
||||
font-medium
|
||||
hover:opacity-90
|
||||
transition
|
||||
shadow-sm
|
||||
cursor-pointer
|
||||
"
|
||||
>
|
||||
Back To Home
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
{selfUserSign.sign && (
|
||||
<p
|
||||
onClick={() => signSessionRedirect(accountId, transactionId, selfUserSign.recipientId + "")}
|
||||
className="flex-1 p-2 text-center rounded-md bg-[var(--mainPrimaryBtn)] text-white hover:shadow-sm transition-all duration-150 cursor-pointer"
|
||||
<button
|
||||
onClick={() =>
|
||||
signSessionRedirect(
|
||||
accountId,
|
||||
transactionId,
|
||||
selfUserSign.recipientId + ""
|
||||
)
|
||||
}
|
||||
className="
|
||||
h-11
|
||||
px-5
|
||||
rounded-sm
|
||||
border
|
||||
border-slate-300
|
||||
bg-[var(--primary)]
|
||||
text-white
|
||||
font-medium
|
||||
opacity-80
|
||||
hover:opacity-100
|
||||
transition
|
||||
cursor-pointer
|
||||
"
|
||||
>
|
||||
Sign Now
|
||||
</p>
|
||||
</button>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Right */}
|
||||
<div
|
||||
className="
|
||||
hidden
|
||||
lg:flex
|
||||
items-center
|
||||
justify-center
|
||||
shrink-0
|
||||
"
|
||||
>
|
||||
|
||||
{/* Optional: Signing Order */}
|
||||
{/* <div className="mt-4">
|
||||
<SigningOrderDiagram signers={selectedRecipients} />
|
||||
</div> */}
|
||||
<div
|
||||
className="
|
||||
w-[260px]
|
||||
h-[220px]
|
||||
rounded-md
|
||||
bg-gradient-to-br
|
||||
from-emerald-50
|
||||
to-blue-50
|
||||
border
|
||||
border-slate-200
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
relative
|
||||
overflow-hidden
|
||||
"
|
||||
>
|
||||
|
||||
<img
|
||||
src="/completion-screen-1.png"
|
||||
alt=""
|
||||
className="
|
||||
w-44
|
||||
object-contain
|
||||
relative
|
||||
z-10
|
||||
"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Section */}
|
||||
<div
|
||||
className="
|
||||
grid
|
||||
grid-cols-1
|
||||
xl:grid-cols-[1.3fr_0.7fr]
|
||||
gap-6
|
||||
mt-6
|
||||
items-start
|
||||
"
|
||||
>
|
||||
|
||||
{/* Preview */}
|
||||
<div
|
||||
className="
|
||||
bg-white
|
||||
rounded-md
|
||||
border
|
||||
border-slate-200
|
||||
shadow-sm
|
||||
overflow-hidden
|
||||
"
|
||||
>
|
||||
|
||||
{/* Header */}
|
||||
<div
|
||||
className="
|
||||
flex
|
||||
items-center
|
||||
justify-between
|
||||
px-5
|
||||
py-4
|
||||
border-b
|
||||
border-slate-200
|
||||
bg-slate-50
|
||||
"
|
||||
>
|
||||
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">
|
||||
Document Preview
|
||||
</p>
|
||||
|
||||
<p className="text-sm text-slate-500">
|
||||
Review the final document before signing
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preview Body */}
|
||||
<div className="h-[70vh] overflow-auto bg-slate-100">
|
||||
|
||||
<div className="px-4 py-4">
|
||||
|
||||
{
|
||||
selectedRecipients.length > 0 &&
|
||||
selectedDocuments.length > 0 && (
|
||||
<DocumentContainer
|
||||
isPreview={true}
|
||||
aId={accountId}
|
||||
tId={transactionId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Sidebar */}
|
||||
<div
|
||||
className={`
|
||||
transition-all
|
||||
duration-700
|
||||
${isVisible
|
||||
? 'translate-y-0 opacity-100'
|
||||
: 'translate-y-8 opacity-0'
|
||||
}
|
||||
`}
|
||||
>
|
||||
|
||||
<div
|
||||
className="
|
||||
sticky
|
||||
top-5
|
||||
bg-white
|
||||
rounded-md
|
||||
border
|
||||
border-slate-200
|
||||
shadow-sm
|
||||
overflow-hidden
|
||||
"
|
||||
>
|
||||
|
||||
{/* Header */}
|
||||
<div
|
||||
className="
|
||||
px-5
|
||||
py-4
|
||||
border-b
|
||||
border-slate-200
|
||||
bg-slate-50
|
||||
"
|
||||
>
|
||||
|
||||
<p className="font-semibold text-slate-800">
|
||||
Signers
|
||||
</p>
|
||||
|
||||
<p className="text-sm text-slate-500 mt-1">
|
||||
Recipients who will receive this document
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="max-h-[70vh] overflow-auto p-4">
|
||||
|
||||
<SignersList signers={selectedRecipients} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { RootState } from "../../../store/store";
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
|
||||
import { addRecipientAsync, fetchRecipients, recipientType, removeAllRecipients, removeRecipientAsync, resetRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from "../../../store/slices/selectedRecipientsSlice";
|
||||
import { addRecipientAsync, fetchRecipients, recipientType, removeAllRecipients, removeRecipientAsync, resetRecipients, setSigningOrder, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE, updateRecipientOrder } from "../../../store/slices/selectedRecipientsSlice";
|
||||
import { TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from "../../../store/slices/transactionSlice";
|
||||
import { hideLoader, showLoader } from "../../../store/slices/loaderSlice";
|
||||
import Loader from "../../dashboard/Loader";
|
||||
|
|
@ -78,6 +78,8 @@ const ProcessStepOne = () => {
|
|||
|
||||
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
|
||||
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients)
|
||||
const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder)
|
||||
|
||||
const [showSelfSignWarnModal, setShowSelfSignWarnModal] = useState(false)
|
||||
const [editRecipientCurrentEmail, setEditRecipientCurrentEmail] = useState('')
|
||||
|
||||
|
|
@ -141,6 +143,12 @@ const ProcessStepOne = () => {
|
|||
if (submitError.duplicateEmailSameRoutingOrder) setSubmitError(prev => ({ ...prev, duplicateEmailSameRoutingOrder: false }))
|
||||
if (submitError.documents) setSubmitError(prev => ({ ...prev, documents: false }))
|
||||
if (submitError.recipients) setSubmitError(prev => ({ ...prev, recipients: false }))
|
||||
|
||||
// Handle Signing Order for template and for transction - fill on refresh based on recipients routing order or prefill - START
|
||||
let isSigningOrder = signingOrder;
|
||||
selectedRecipients && selectedRecipients.length > 1 && selectedRecipients.some(recipient => recipient.routingOrder && recipient.routingOrder > 1) ? isSigningOrder = true : "";
|
||||
isSigningOrder && handleSigningOrderChange(isSigningOrder);
|
||||
// Handle Signing Order for template and for transction - fill on refresh based on recipients routing order or prefill - END
|
||||
}, [selectedRecipients])
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
|
@ -391,6 +399,9 @@ const ProcessStepOne = () => {
|
|||
|
||||
useEffect(() => {
|
||||
validationCheck()
|
||||
selectedRecipients.forEach((recipient, index) => {
|
||||
|
||||
})
|
||||
}, [selectedDocuments, selectedRecipients, emailDtls])
|
||||
|
||||
const handleSelfSignCb = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
|
@ -414,13 +425,28 @@ const ProcessStepOne = () => {
|
|||
)
|
||||
}
|
||||
|
||||
function handleSigningOrderChange(checked?: boolean) {
|
||||
dispatch(setSigningOrder(checked ?? !signingOrder))
|
||||
|
||||
if (checked ?? !signingOrder) {
|
||||
selectedRecipients.forEach((recipient, index) => {
|
||||
const id = recipient.id || recipient.uuid || "1"
|
||||
dispatch(updateRecipientOrder({ id, routingOrder: index + 1 }));
|
||||
})
|
||||
} else {
|
||||
selectedRecipients.forEach((recipient, index) => {
|
||||
const id = recipient.id || recipient.uuid || "1"
|
||||
dispatch(updateRecipientOrder({ id, routingOrder: 1 }));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`relative w-full h-screen bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none overflow-hidden' : ''}`}>
|
||||
|
||||
{/* Top Navigation - Fixed */}
|
||||
<div className='fixed top-0 left-0 right-0 w-full bg-white shadow z-50 px-4 py-1.5 lg:py-3'>
|
||||
<div className='fixed top-0 left-0 right-0 w-full bg-white shadow z-50 px-4 py-1.5 lg:py-3 z-50'>
|
||||
<div className='flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 flex-wrap'>
|
||||
|
||||
{/* Left Section */}
|
||||
|
|
@ -489,7 +515,7 @@ const ProcessStepOne = () => {
|
|||
|
||||
{/* Documents */}
|
||||
<section>
|
||||
<h2 className="text-lg font-medium mb-4 pt-3 text-[var(--processStepOneTextHead)]">Documents</h2>
|
||||
<h2 className="text-lg font-medium mb-4 pt-3 text-[var(--processStepOneTextHead)]">Add documents</h2>
|
||||
<div id="documents_con" className={`${submitError && submitError.documents && submitError.showError ? "border border-red-500 rounded-sm p-2 ring-2 ring-red-300" : ""}`}>
|
||||
<SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} />
|
||||
<p className="text-xs text-right px-2 pt-2 text-red-500">{submitError && submitError.documents && submitError.showError && "At least one document is required."}</p>
|
||||
|
|
@ -499,21 +525,40 @@ const ProcessStepOne = () => {
|
|||
{/* Recipients */}
|
||||
<section className="mt-8">
|
||||
<p className="border-t border-gray-200"></p>
|
||||
{!isTemplate &&
|
||||
transactionTemplateToggle != TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES &&
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className=" text-lg font-medium mb-4 pt-6 text-[var(--processStepOneTextHead)]">Signers</h2>
|
||||
{/* <div className="flex items-center mr-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className=" text-lg font-medium mb-4 pt-6 text-[var(--processStepOneTextHead)]">Add recipients</h2>
|
||||
|
||||
{!isTemplate &&
|
||||
transactionTemplateToggle != TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES &&
|
||||
<>
|
||||
{/* <div className="flex items-center mr-3">
|
||||
<input
|
||||
checked={signType == SignTypeEnum.selfSign}
|
||||
onChange={(e) => handleSelfSignCb(e)}
|
||||
id="default-checkbox" type="checkbox" value="" className="w-4 h-4 checked:bg-[var(--mainPrimaryBtn)] bg-gray-100 border-gray-300 rounded-sm outline-none"
|
||||
checked={signType == SignTypeEnum.selfSign}
|
||||
onChange={(e) => handleSelfSignCb(e)}
|
||||
id="default-checkbox" type="checkbox" value="" className="w-4 h-4 checked:bg-[var(--mainPrimaryBtn)] bg-gray-100 border-gray-300 rounded-sm outline-none"
|
||||
/>
|
||||
<label htmlFor="default-checkbox" className="ms-2 text-sm flex gap-1 text-slate-600">
|
||||
I AM THE ONLY SIGNER</label>
|
||||
</div> */}
|
||||
</div>
|
||||
}
|
||||
I AM THE ONLY SIGNER</label>
|
||||
</div> */}
|
||||
</>
|
||||
}
|
||||
|
||||
{/* HERE WE AREEEEEEEE */}
|
||||
{selectedRecipients.length > 1 && <div className="flex items-center">
|
||||
<input
|
||||
// checked={signType == SignTypeEnum.selfSign}
|
||||
onChange={(e) => handleSigningOrderChange(e.target.checked)}
|
||||
checked={signingOrder}
|
||||
id="default-checkbox" type="checkbox" value="" className="w-4 h-4 checked:bg-[var(--mainPrimaryBtn)] bg-gray-100 border-gray-300 rounded-sm outline-none"
|
||||
/>
|
||||
<label htmlFor="default-checkbox" className="ms-2 text-sm flex gap-1 text-slate-600">
|
||||
Set signing order
|
||||
</label>
|
||||
</div>}
|
||||
{/* HERE WE AREEEEEEEE */}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{
|
||||
(signType != SignTypeEnum.selfSign || showSelfSignWarnModal) &&
|
||||
|
|
@ -533,7 +578,7 @@ const ProcessStepOne = () => {
|
|||
|
||||
{/* Email */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-lg font-medium mb-4 pt-6 border-t border-gray-200 text-[var(--processStepOneTextHead)]">Email</h2>
|
||||
<h2 className="text-lg font-medium mb-4 pt-6 border-t border-gray-200 text-[var(--processStepOneTextHead)]">Add Message</h2>
|
||||
|
||||
{/* Subject */}
|
||||
<div className="border-1 rounded-xl border-gray-300 px-6 py-8">
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ const RecipientCards = ({
|
|||
let { accountId, transactionId } = useParams<{ accountId: string; transactionId: string }>();
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder)
|
||||
|
||||
if (!transactionId) {
|
||||
transactionId = templateId;
|
||||
}
|
||||
|
|
@ -355,10 +357,10 @@ const RecipientCards = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="w-full rounded-md bg-slate-50">
|
||||
<div className="w-full rounded-md">
|
||||
{recipients.length > 0 ? (
|
||||
<>
|
||||
{recipients.length > 1 && (
|
||||
{/* {recipients.length > 1 && (
|
||||
<div className="flex sm:flex-row sm:items-center justify-between px-3 sm:px-4 py-2 border-b border-gray-300 gap-2">
|
||||
<h3 className="text-sm font-semibold text-gray-700">
|
||||
Recipients ({recipients.length})
|
||||
|
|
@ -371,7 +373,7 @@ const RecipientCards = ({
|
|||
<FaUsersLine className="text-[var(--primary)] mt-0.5 text-lg" />
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
|
||||
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
|
|
@ -379,7 +381,7 @@ const RecipientCards = ({
|
|||
items={recipients.map((r) => r.id || r.uuid)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<div className="flex flex-col divide-y divide-gray-200 max-h-[47vh] overflow-y-auto custom-scrollbar">
|
||||
<div className="flex flex-col max-h-[47vh] overflow-y-auto custom-scrollbar">
|
||||
{recipients.map((recipient, idx) => (
|
||||
<SortableRecipient
|
||||
key={recipient.id || recipient.uuid}
|
||||
|
|
@ -389,230 +391,249 @@ const RecipientCards = ({
|
|||
>
|
||||
<div
|
||||
key={recipient.email + idx}
|
||||
className={`relative flex flex-col lg:flex-row lg:items-center justify-between px-2 sm:px-3 lg:px-2 py-3 transition-colors gap-3 lg:gap-0 ${(recipient.uuid || recipient.id) == editRecipientCurrentEmail
|
||||
className={`relative flex flex-col lg:flex-row lg:items-center justify-between transition-colors gap-3 lg:gap-0 ${(recipient.uuid || recipient.id) == editRecipientCurrentEmail
|
||||
? "bg-neutral-100 opacity-40"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{/* Main Content Section */}
|
||||
<div className="flex items-start sm:items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
||||
{/* Order Number */}
|
||||
{recipients.length > 1 ? (
|
||||
<div className="relative flex-shrink-0">
|
||||
<input
|
||||
type="text"
|
||||
value={recipient.routingOrder == 0 ? "" : recipient.routingOrder}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (/^\d*$/.test(value)) {
|
||||
const num = Number(value);
|
||||
handleOrderChange(recipient.id || recipient.uuid, num);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (!/[0-9]/.test(e.key) && e.key != "Backspace" && e.key != "Tab") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
className="w-8 sm:w-10 h-6 text-center rounded text-sm text-gray-600 focus:outline-none focus:ring-[var(--primary)] border border-dashed border-gray-400"
|
||||
/>
|
||||
{idx == 0 && recipients.length > 1 && <p className="group">
|
||||
<BiSolidHelpCircle className="absolute top-[-12px] right-[-10px] text-gray-400 cursor-pointer" />
|
||||
|
||||
{/* Tooltip */}
|
||||
<span className="absolute top-[2px] left-[48px] hidden group-hover:block w-56 p-2 rounded-lg text-sm bg-white text-slate-600 shadow-lg z-10">
|
||||
Signing order: Recipients receive emails one after another.
|
||||
Same order means they all receive at once.
|
||||
</span>
|
||||
</p>}
|
||||
<>
|
||||
{/* Main Content Section */}
|
||||
<div className="flex items-start sm:items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
||||
{/* Order Number */}
|
||||
|
||||
<div className="">
|
||||
{recipients.length > 1 && signingOrder ? (
|
||||
<div className="relative flex-shrink-0">
|
||||
<input
|
||||
type="text"
|
||||
value={recipient.routingOrder == 0 ? "" : recipient.routingOrder}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (/^\d*$/.test(value)) {
|
||||
const num = Number(value);
|
||||
handleOrderChange(recipient.id || recipient.uuid, num);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (!/[0-9]/.test(e.key) && e.key != "Backspace" && e.key != "Tab") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
className="w-8 sm:w-10 h-6 text-center rounded text-sm text-gray-600 focus:outline-none focus:ring-[var(--primary)] border border-dashed border-gray-400"
|
||||
/>
|
||||
|
||||
<>
|
||||
{idx == 0 && recipients.length > 1 && <p className="group">
|
||||
<>
|
||||
{/* <BiSolidHelpCircle className="absolute top-[-12px] right-[-10px] text-gray-400 cursor-pointer" />
|
||||
|
||||
<span className="absolute top-[2px] left-[48px] hidden group-hover:block w-56 p-2 rounded-lg text-sm bg-white text-slate-600 shadow-lg z-10">
|
||||
Signing order: Recipients receive emails one after another.
|
||||
Same order means they all receive at once.
|
||||
</span> */}
|
||||
</>
|
||||
</p>}
|
||||
</>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
) : (
|
||||
|
||||
<>{signingOrder &&
|
||||
<input
|
||||
type="number"
|
||||
disabled
|
||||
value={recipient.routingOrder}
|
||||
min={1}
|
||||
onChange={(e) =>
|
||||
handleOrderChange(
|
||||
recipient.id || recipient.uuid,
|
||||
Number(e.target.value)
|
||||
)
|
||||
}
|
||||
className="w-6 sm:w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)] flex-shrink-0 border-2 border-slate-200"
|
||||
/>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<input
|
||||
type="number"
|
||||
disabled
|
||||
value={recipient.routingOrder}
|
||||
min={1}
|
||||
onChange={(e) =>
|
||||
handleOrderChange(
|
||||
recipient.id || recipient.uuid,
|
||||
Number(e.target.value)
|
||||
)
|
||||
}
|
||||
className="w-6 sm:w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)] flex-shrink-0"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Avatar */}
|
||||
{/* <div className="flex-shrink-0 w-7 h-7 sm:w-8 sm:h-8 rounded-full bg-[var(--primary)] text-white flex items-center justify-center font-semibold text-xs select-none">
|
||||
{/* Avatar */}
|
||||
{/* <div className="flex-shrink-0 w-7 h-7 sm:w-8 sm:h-8 rounded-full bg-[var(--primary)] text-white flex items-center justify-center font-semibold text-xs select-none">
|
||||
{getInitials(`${recipient.firstName} ${recipient.lastName || ""}`)}
|
||||
</div> */}
|
||||
|
||||
{/* Form Fields Section */}
|
||||
<div className="min-w-0 flex-1">
|
||||
{/* Role Name and Permissions Row */}
|
||||
<div className="flex flex-col lg:flex-row lg:items-center gap-2 mb-2">
|
||||
{<div className="flex lg:w-1/2">
|
||||
{transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES ?
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "roleName", "Role name")}
|
||||
</div>
|
||||
:
|
||||
<div className="font-semibold text-gray-800 text-sm px-2">
|
||||
{recipient.roleName}
|
||||
</div>
|
||||
}
|
||||
</div>}
|
||||
{/* Form Fields Section */}
|
||||
<div className={`min-w-0 py-4 ${signingOrder ? "pl-8" : "pl-2"} flex-1 border-l-8 ${idx % 2 == 0 ? 'border-[#A5EDFC]' : 'border-[#84EFC3]'}`}>
|
||||
|
||||
{/* Permissions - Mobile: Below role, Desktop: Right side */}
|
||||
<div className="lg:w-1/2 lg:flex lg:items-center lg:justify-end">
|
||||
{transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && (
|
||||
<div className="flex ps-2.5 sm:flex-row items-start sm:items-center gap-2 sm:gap-4">
|
||||
<label className="flex items-center gap-1 text-xs sm:text-sm text-gray-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={recipient.allowSenderToEdit}
|
||||
onChange={() =>
|
||||
dispatch(
|
||||
updateRecipient({
|
||||
...recipient,
|
||||
allowSenderToEdit: !recipient.allowSenderToEdit,
|
||||
uuid: getRecipientIdentifier(recipient),
|
||||
})
|
||||
)
|
||||
{/* Role Name and Permissions Row */}
|
||||
<div className="flex flex-col lg:flex-row lg:items-center gap-2 mb-2">
|
||||
{<div className="flex lg:w-1/2">
|
||||
{transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES ?
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "roleName", "Role name")}
|
||||
</div>
|
||||
:
|
||||
<div className="font-semibold text-gray-800 text-sm px-2">
|
||||
{recipient.roleName}
|
||||
</div>
|
||||
}
|
||||
</div>}
|
||||
|
||||
{/* Permissions - Mobile: Below role, Desktop: Right side */}
|
||||
<div className="lg:w-1/2 lg:flex lg:items-center lg:justify-end">
|
||||
{transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && (
|
||||
<div className="flex ps-2.5 sm:flex-row items-start sm:items-center gap-2 sm:gap-4">
|
||||
<label className="flex items-center gap-1 text-xs sm:text-sm text-gray-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={recipient.allowSenderToEdit}
|
||||
onChange={() =>
|
||||
dispatch(
|
||||
updateRecipient({
|
||||
...recipient,
|
||||
allowSenderToEdit: !recipient.allowSenderToEdit,
|
||||
uuid: getRecipientIdentifier(recipient),
|
||||
})
|
||||
)
|
||||
}
|
||||
className="accent-blue-500 w-3 h-3 sm:w-4 sm:h-4"
|
||||
/>
|
||||
<span className="whitespace-nowrap">Allow Edit</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-1 text-xs sm:text-sm text-gray-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={recipient.allowSenderToDelete}
|
||||
onChange={() =>
|
||||
dispatch(
|
||||
updateRecipient({
|
||||
...recipient,
|
||||
allowSenderToDelete: !recipient.allowSenderToDelete,
|
||||
uuid: getRecipientIdentifier(recipient),
|
||||
})
|
||||
)
|
||||
}
|
||||
className="accent-red-500 w-3 h-3 sm:w-4 sm:h-4"
|
||||
/>
|
||||
<span className="whitespace-nowrap">Allow Delete</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Name and Email Fields */}
|
||||
<div className="mb-2 w-[100%]">
|
||||
{/* Mobile: Stack vertically, Tablet+: Side by side */}
|
||||
<div className="flex flex-col justify-center items-center sm:flex-row gap-1 sm:gap-2">
|
||||
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2 sm:flex-1">
|
||||
<div className="w-full sm:w-1/2">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "firstName", "First Name")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full sm:w-1/2">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "lastName", "Last Name")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full sm:flex-1">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "email", "Recipient Email", "", "email")}
|
||||
</div>
|
||||
</div>
|
||||
{/* Action Buttons - START*/}
|
||||
{signType != "self-sign" && (
|
||||
<div className="ml-4">
|
||||
<button
|
||||
type="button"
|
||||
title="Remove recipient"
|
||||
aria-label={`Remove recipient ${recipient.firstName}`}
|
||||
onClick={() => handleRemoveRecipient(recipient)}
|
||||
disabled={
|
||||
transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES
|
||||
? false
|
||||
: !recipient.newOne && !recipient.allowSenderToDelete
|
||||
}
|
||||
className="accent-blue-500 w-3 h-3 sm:w-4 sm:h-4"
|
||||
/>
|
||||
<span className="whitespace-nowrap">Allow Edit</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-1 text-xs sm:text-sm text-gray-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={recipient.allowSenderToDelete}
|
||||
onChange={() =>
|
||||
dispatch(
|
||||
updateRecipient({
|
||||
...recipient,
|
||||
allowSenderToDelete: !recipient.allowSenderToDelete,
|
||||
uuid: getRecipientIdentifier(recipient),
|
||||
})
|
||||
)
|
||||
}
|
||||
className="accent-red-500 w-3 h-3 sm:w-4 sm:h-4"
|
||||
/>
|
||||
<span className="whitespace-nowrap">Allow Delete</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Name and Email Fields */}
|
||||
<div className="mb-2 w-[92%]">
|
||||
{/* Mobile: Stack vertically, Tablet+: Side by side */}
|
||||
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2">
|
||||
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2 sm:flex-1">
|
||||
<div className="w-full sm:w-1/2">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "firstName", "First Name")}
|
||||
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50 p-1"
|
||||
>
|
||||
<ImBin size={14} className="sm:w-4 sm:h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full sm:w-1/2">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "lastName", "Last Name")}
|
||||
)}
|
||||
{/* Action Buttons - END*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Host Fields (for In-Person Signers) */}
|
||||
{recipient.type == recipientType.INPERSONSIGNER && (
|
||||
<div className="mb-2">
|
||||
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2">
|
||||
<div className="w-full sm:w-1/2">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "hostName", "Host Name")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full sm:w-1/2">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "hostEmail", "Host Email", "", "email")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full sm:flex-1">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "email", "Recipient Email", "", "email")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Host Fields (for In-Person Signers) */}
|
||||
{recipient.type == recipientType.INPERSONSIGNER && (
|
||||
<div className="mb-2">
|
||||
<div className="flex flex-col sm:flex-row gap-1 sm:gap-2">
|
||||
<div className="w-full sm:w-1/2">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "hostName", "Host Name")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full sm:w-1/2">
|
||||
<div className="font-semibold text-gray-800 text-xs">
|
||||
{renderEditableField(recipient, "hostEmail", "Host Email", "", "email")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Recipient Type Radio Buttons */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 my-1.5 mx-1 sm:mx-2">
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
type="radio"
|
||||
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
||||
value={recipientType.SIGNER}
|
||||
checked={recipient.type == recipientType.SIGNER}
|
||||
onChange={() => handleTypeChange(recipient, recipientType.SIGNER)}
|
||||
onBlur={() => setEditingField(null)}
|
||||
className="accent-[var(--primary)] w-3 h-3"
|
||||
disabled={!isEditable(recipient)}
|
||||
/>
|
||||
<span className="text-xs sm:text-sm">Signer</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
type="radio"
|
||||
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
||||
value={recipientType.INPERSONSIGNER}
|
||||
checked={recipient.type == recipientType.INPERSONSIGNER}
|
||||
onChange={() => handleTypeChange(recipient, recipientType.INPERSONSIGNER)}
|
||||
onBlur={() => setEditingField(null)}
|
||||
className="accent-[var(--primary)] w-3 h-3"
|
||||
disabled={!isEditable(recipient)}
|
||||
/>
|
||||
<span className="text-xs sm:text-sm whitespace-nowrap">In Person Signer</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
type="radio"
|
||||
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
||||
value={recipientType.CC}
|
||||
checked={recipient.type == recipientType.CC}
|
||||
onChange={() => handleTypeChange(recipient, recipientType.CC)}
|
||||
onBlur={() => setEditingField(null)}
|
||||
className="accent-[var(--primary)] w-3 h-3"
|
||||
disabled={!isEditable(recipient)}
|
||||
/>
|
||||
<span className="text-xs sm:text-sm">CC</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Recipient Type Radio Buttons */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 my-1.5 mx-1 sm:mx-2">
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
type="radio"
|
||||
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
||||
value={recipientType.SIGNER}
|
||||
checked={recipient.type == recipientType.SIGNER}
|
||||
onChange={() => handleTypeChange(recipient, recipientType.SIGNER)}
|
||||
onBlur={() => setEditingField(null)}
|
||||
className="accent-[var(--primary)] w-3 h-3"
|
||||
disabled={!isEditable(recipient)}
|
||||
/>
|
||||
<span className="text-xs sm:text-sm">Signer</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
type="radio"
|
||||
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
||||
value={recipientType.INPERSONSIGNER}
|
||||
checked={recipient.type == recipientType.INPERSONSIGNER}
|
||||
onChange={() => handleTypeChange(recipient, recipientType.INPERSONSIGNER)}
|
||||
onBlur={() => setEditingField(null)}
|
||||
className="accent-[var(--primary)] w-3 h-3"
|
||||
disabled={!isEditable(recipient)}
|
||||
/>
|
||||
<span className="text-xs sm:text-sm whitespace-nowrap">In Person Signer</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-1">
|
||||
<input
|
||||
type="radio"
|
||||
name={`recipient-type-${getRecipientIdentifier(recipient)}`}
|
||||
value={recipientType.CC}
|
||||
checked={recipient.type == recipientType.CC}
|
||||
onChange={() => handleTypeChange(recipient, recipientType.CC)}
|
||||
onBlur={() => setEditingField(null)}
|
||||
className="accent-[var(--primary)] w-3 h-3"
|
||||
disabled={!isEditable(recipient)}
|
||||
/>
|
||||
<span className="text-xs sm:text-sm">CC</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
|
||||
|
||||
{/* Action Buttons */}
|
||||
{signType != "self-sign" && (
|
||||
<div className="lg:ml-2 absolute top-[-8px] right-[-5px] px-1.5 py-1.5">
|
||||
<button
|
||||
type="button"
|
||||
title="Remove recipient"
|
||||
aria-label={`Remove recipient ${recipient.firstName}`}
|
||||
onClick={() => handleRemoveRecipient(recipient)}
|
||||
disabled={
|
||||
transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES
|
||||
? false
|
||||
: !recipient.newOne && !recipient.allowSenderToDelete
|
||||
}
|
||||
className="text-gray-400 hover:text-red-600 transition-colors cursor-pointer disabled:opacity-50 p-1"
|
||||
>
|
||||
<ImBin size={14} className="sm:w-4 sm:h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</SortableRecipient>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ interface RecipientsConIntProps {
|
|||
userData: UserState;
|
||||
isModal: boolean;
|
||||
submitError?: FormSubmitInt;
|
||||
setEditRecipientsModal?: React.Dispatch<boolean>
|
||||
setEditRecipientsModal?: React.Dispatch<boolean>;
|
||||
}
|
||||
|
||||
const RecipientsCon: React.FC<RecipientsConIntProps> = ({ transactionTemplateToggle, accountId, transactionId, userData, submitError, isModal = true, setEditRecipientsModal }) => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { RootState } from "../../../store/store";
|
|||
const SortableRecipient = ({ recipient, idx, editRecipientCurrentEmail, children }: any) => {
|
||||
|
||||
const selectedRecipientsLen = useSelector((state: RootState) => state.selectedRecipients.recipients.length);
|
||||
const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder)
|
||||
|
||||
const {
|
||||
attributes,
|
||||
|
|
@ -22,12 +23,12 @@ const SortableRecipient = ({ recipient, idx, editRecipientCurrentEmail, children
|
|||
transition,
|
||||
opacity: isDragging ? 0.6 : 1,
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
className={`relative flex flex-col lg:flex-row lg:items-center justify-between px-2 sm:px-3 lg:px-2 py-3 transition-colors gap-3 lg:gap-0 ${(recipient.uuid || recipient.id) == editRecipientCurrentEmail ? "bg-neutral-100 opacity-40" : ""}
|
||||
className={`relative flex flex-col lg:flex-row lg:items-center justify-between gap-3 lg:gap-0 my-3 ${(recipient.uuid || recipient.id) == editRecipientCurrentEmail ? "bg-neutral-100 opacity-40" : ""}
|
||||
${isDragging ? "bg-blue-50 shadow-lg" : ""}
|
||||
`}
|
||||
>
|
||||
|
|
@ -35,9 +36,11 @@ const SortableRecipient = ({ recipient, idx, editRecipientCurrentEmail, children
|
|||
{selectedRecipientsLen && <div
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="cursor-grab text-gray-500 flex items-center pr-2 absolute top-0 left-0 z-50"
|
||||
className="cursor-grab text-gray-500 flex items-center pr-2 absolute top-0 left-0 z-10 h-full"
|
||||
>
|
||||
<MdDragIndicator className="absolute top-[10px] left-[10px] text-xl text-gray-300" />
|
||||
|
||||
{signingOrder && <MdDragIndicator className="absolute top-[50%] left-[70px] transform -translate-y-1/2 text-gray-300" />}
|
||||
|
||||
</div>}
|
||||
|
||||
{/* Your card content */}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
const CloseConfirmationModal: React.FC<{ open: boolean, onClose: () => void, onConfirm: () => void }> = ({
|
||||
open,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}) => {
|
||||
|
||||
if (!open) return <></>;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-100 flex items-center justify-center">
|
||||
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/40 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative z-10 w-full max-w-sm rounded-sm bg-white p-6 shadow-2xl">
|
||||
|
||||
<h2 className="text-lg font-semibold text-slate-800">
|
||||
Leave this page?
|
||||
</h2>
|
||||
|
||||
<p className="mt-2 text-sm text-slate-600">
|
||||
Your progress may not be saved.
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-sm border border-slate-300 px-4 py-2 hover:bg-slate-100 cursor-pointer"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
className="rounded-sm bg-[var(--primary)] px-4 py-2 text-white cursor-pointer"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloseConfirmationModal;
|
||||
|
|
@ -4,7 +4,7 @@ import { RootState } from '../../../store/store'
|
|||
|
||||
import api from '../../../config/api';
|
||||
|
||||
import RightSideBar, { MASKTYPE, TEXT } from './rightSideBar/RightSideBar';
|
||||
import TagProperties, { MASKTYPE, TEXT } from './tagProperties/TagProperties';
|
||||
import { DroppedItem, TagsTypes } from '../../../utlity/types';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
|
|
@ -16,6 +16,13 @@ import typeToIconMap from '../../../helper/iconMap';
|
|||
import Loader from '../../dashboard/Loader';
|
||||
import { hideLoader, showLoader } from '../../../store/slices/loaderSlice';
|
||||
import DroppedItemsTagsCon from './DroppedItemsTagsCon';
|
||||
import DocumentPages from './documentPages/DocumentPages';
|
||||
import {
|
||||
initializeDroppedItems,
|
||||
setDroppedItems,
|
||||
clearTransactionTags,
|
||||
incrementCounter,
|
||||
} from '../../../store/slices/documentTagsSlice';
|
||||
|
||||
export interface DroppedItemsObj {
|
||||
[key: string]: DroppedItem[]
|
||||
|
|
@ -35,6 +42,9 @@ export const droppedTagsDefaultField: DroppedTagsDefaultFieldType = {
|
|||
}
|
||||
|
||||
interface DocumentContainerProps {
|
||||
isPages: boolean;
|
||||
setIsPages: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
zoomScale: number;
|
||||
transactionSaving?: boolean;
|
||||
setTransactionSaving?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isSend?: boolean | null;
|
||||
|
|
@ -42,6 +52,7 @@ interface DocumentContainerProps {
|
|||
isPreview?: boolean;
|
||||
aId?: string;
|
||||
tId?: string;
|
||||
selectedRecipientIndx?: number;
|
||||
}
|
||||
|
||||
let copiedTags: DroppedItem[] = []
|
||||
|
|
@ -54,10 +65,11 @@ export interface DocumentContainerRef {
|
|||
handleTransactionProcess: (isSave?: boolean, isNavigate?: boolean, navigateTo?: string) => void;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProps>(
|
||||
({ transactionSaving, setTransactionSaving, isSend, isPreview = false, aId = null, tId = null }, ref) => {
|
||||
({ isPages, zoomScale, transactionSaving, setTransactionSaving, isSend, isPreview = false, aId = null, tId = null, selectedRecipientIndx = 0, setIsPages }, ref) => {
|
||||
|
||||
const scaleFactor: number = (zoomScale ?? 100) / 100;
|
||||
|
||||
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||
const dispatch = useDispatch();
|
||||
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
|
||||
|
|
@ -86,11 +98,45 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
|
||||
const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({});
|
||||
|
||||
|
||||
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
|
||||
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
|
||||
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
|
||||
|
||||
const [droppedItems, setDroppedItems] = useState<DroppedItemsObj>({});
|
||||
|
||||
// handle Tag - Document Pos and size for Responsiveness - START
|
||||
const [imageSizes, setImageSizes] = useState<Record<string, { width: number; height: number }>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const observers: ResizeObserver[] = [];
|
||||
|
||||
Object.keys(imageRefs.current).forEach((key) => {
|
||||
const el = imageRefs.current[key];
|
||||
if (!el) return;
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
setImageSizes((prev) => ({
|
||||
...prev,
|
||||
[key]: { width, height },
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(el);
|
||||
observers.push(observer);
|
||||
});
|
||||
|
||||
return () => observers.forEach((o) => o.disconnect());
|
||||
|
||||
}, [selectedDocuments]);
|
||||
// handle Tag - Document Pos and size for Responsiveness - START
|
||||
|
||||
// Redux state for dropped items
|
||||
const transactionKey = `${accountId}_${transactionId}`;
|
||||
const droppedItems = useSelector((state: RootState) => state.documentTags.items[transactionKey] || {});
|
||||
const droppedItemsCounter = useSelector((state: RootState) => state.documentTags.counter[transactionKey] || 0);
|
||||
|
||||
interface HotKeysObj {
|
||||
isActive: boolean;
|
||||
|
|
@ -141,7 +187,11 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
}
|
||||
}
|
||||
|
||||
setDroppedItems(recTags)
|
||||
// Dispatch to Redux instead of local state
|
||||
dispatch(initializeDroppedItems({
|
||||
transactionKey,
|
||||
items: recTags
|
||||
}));
|
||||
// Handle your response here
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
|
@ -155,7 +205,6 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
}, [selectedRecipients, isPreview]);
|
||||
// FETCHING TAG - END
|
||||
|
||||
const [droppedItemsCounter, setDroppedItemsCounter] = useState<number>(1);
|
||||
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
|
||||
|
||||
// NAVIGATING to Dashboard if Transaction is not created - START
|
||||
|
|
@ -195,13 +244,17 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
if ("update" in tag && tag.update) {
|
||||
updateTags.push(tag)
|
||||
}
|
||||
if (updateTags.length && "update" in updateTags[updateTags.length - 1]) {
|
||||
delete updateTags[updateTags.length - 1].update
|
||||
delete updateTags[updateTags.length - 1].icon
|
||||
if (updateTags.length) {
|
||||
const lastTag = updateTags[updateTags.length - 1];
|
||||
if ("update" in lastTag || "icon" in lastTag) {
|
||||
// Create new object without update and icon properties
|
||||
const { update, icon, ...cleanTag } = lastTag as any;
|
||||
updateTags[updateTags.length - 1] = cleanTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatedData[recipientId] = updateTags.filter((tag, indx) => !tag.isDeleted);
|
||||
updatedData[recipientId] = updateTags.filter((tag) => !tag.isDeleted);
|
||||
}
|
||||
|
||||
const requests = Object.entries(updatedData)
|
||||
|
|
@ -222,13 +275,18 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
try {
|
||||
const responses = await Promise.all(requests);
|
||||
|
||||
// Dispatch Redux action instead of local state
|
||||
const droppedItemsUpdateFalse = Object.fromEntries(
|
||||
Object.entries(droppedItems).map(([recipientId, tags]) => {
|
||||
return [recipientId, tags.map(tag => ({ ...tag, update: false }))];
|
||||
})
|
||||
)
|
||||
|
||||
setDroppedItems(droppedItemsUpdateFalse);
|
||||
dispatch(setDroppedItems({
|
||||
transactionKey,
|
||||
items: droppedItemsUpdateFalse
|
||||
}));
|
||||
|
||||
return responses;
|
||||
} catch (error) {
|
||||
console.error("Error submitting recipient tags:", error);
|
||||
|
|
@ -256,7 +314,7 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
|
||||
let tagsData = groupByRecipientId(updatedTags);
|
||||
const responses = await sendTagsToRecipients(tagsData, accountId, transactionId);
|
||||
if(responses && "status" in responses && responses?.status !== 200) return
|
||||
if (responses && "status" in responses && responses?.status !== 200) return
|
||||
// return
|
||||
// SENDING Transaction - START
|
||||
if (!save) {
|
||||
|
|
@ -300,10 +358,11 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
// HANDLE DROP - START
|
||||
const handleDrop = (event: React.DragEvent<HTMLDivElement>, docId: string, documentId: number, pageIndex: number, originalWidth: number, originalHeight: number) => {
|
||||
event.preventDefault();
|
||||
const imageElement = document.getElementById(`${docId}-${pageIndex}`) as HTMLImageElement;
|
||||
const pageRefKey = `${docId}_${pageIndex}`;
|
||||
const imageElement = imageRefs.current[pageRefKey] || document.getElementById(`${docId}-${pageIndex}`) as HTMLImageElement;
|
||||
|
||||
const currentWidth = imageElement.offsetWidth;
|
||||
const currentHeight = imageElement.offsetHeight;
|
||||
const currentWidth = imageElement?.offsetWidth || originalWidth;
|
||||
const currentHeight = imageElement?.offsetHeight || originalHeight;
|
||||
|
||||
const scaleX = currentWidth / originalWidth; // 350 / 700 = 0.5
|
||||
const scaleY = currentHeight / originalHeight; // 225 / 450 = 0.5
|
||||
|
|
@ -333,8 +392,11 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
clientY = event.clientY;
|
||||
}
|
||||
|
||||
let x = clientX - dropZone.left - (dragElemData.width / 2);
|
||||
let y = clientY - dropZone.top - (dragElemData.height / 2);
|
||||
// let x = (clientX - dropZone.left) - (dragElemData.width / 2);
|
||||
// let y = (clientY - dropZone.top) - (dragElemData.height / 2);
|
||||
|
||||
let x = (clientX - dropZone.left) / scaleFactor - (dragElemData.width / 2);
|
||||
let y = (clientY - dropZone.top) / scaleFactor - (dragElemData.height / 2);
|
||||
|
||||
// Handling Drop Over Right
|
||||
// if (dropZone.width < x + dragElemData.width) {
|
||||
|
|
@ -353,8 +415,11 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
// y = 0;
|
||||
// }
|
||||
|
||||
x = Math.max(0, Math.min(x, dropZone.width - dragElemData.width));
|
||||
y = Math.max(0, Math.min(y, dropZone.height - dragElemData.height));
|
||||
// x = Math.max(0, Math.min(x, dropZone.width - dragElemData.width));
|
||||
// y = Math.max(0, Math.min(y, dropZone.height - dragElemData.height));
|
||||
|
||||
x = Math.max(0, Math.min(x, originalWidth - dragElemData.width));
|
||||
y = Math.max(0, Math.min(y, originalHeight - dragElemData.height));
|
||||
|
||||
const key = `${docId}_${pageIndex}`;
|
||||
|
||||
|
|
@ -369,10 +434,10 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
label: dragElemData.label,
|
||||
// subType: (dragElemData.label in droppedTagsDefaultField) ? droppedTagsDefaultField[dragElemData.label] : '',
|
||||
dimensions: {
|
||||
left: x,
|
||||
top: y,
|
||||
width: dragElemData.width,
|
||||
height: dragElemData.height,
|
||||
left: x / scaleX,
|
||||
top: y / scaleY,
|
||||
width: dragElemData.width / scaleX,
|
||||
height: dragElemData.height / scaleY,
|
||||
},
|
||||
styles: null,
|
||||
documentId: docId,
|
||||
|
|
@ -407,19 +472,22 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
prepData],
|
||||
}
|
||||
|
||||
setDroppedItems(dataToSet);
|
||||
setDroppedItemsCounter((prev) => prev + 1);
|
||||
dispatch(setDroppedItems({
|
||||
transactionKey,
|
||||
items: dataToSet
|
||||
}));
|
||||
dispatch(incrementCounter(transactionKey));
|
||||
|
||||
setTimeout(() => {
|
||||
const tagIndex = key in droppedItems ? droppedItems[key].length : 0;
|
||||
setSelectedTag({ key, index: tagIndex })
|
||||
}, 1000)
|
||||
// setTimeout(() => {
|
||||
// const tagIndex = key in droppedItems ? droppedItems[key].length : 0;
|
||||
// setSelectedTag({ key, index: tagIndex })
|
||||
// }, 1000)
|
||||
};
|
||||
// HANDLE DROP - END
|
||||
|
||||
useEffect(() => {
|
||||
if (!hotKeysObj.isActive) return
|
||||
if((!selectedTag?.key || `${selectedTag?.key}` in droppedItems && droppedItems[selectedTag?.key].length == 0)) return;
|
||||
if ((!selectedTag?.key || `${selectedTag?.key}` in droppedItems && droppedItems[selectedTag?.key].length == 0)) return;
|
||||
|
||||
const docCon = document.getElementById("doc-con");
|
||||
|
||||
|
|
@ -438,9 +506,12 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
console.log("copiedTags", copiedTags)
|
||||
console.log("updatedKey", updatedKey)
|
||||
|
||||
setDroppedItems(prevState => ({
|
||||
...prevState,
|
||||
[selectedDoc.key]: updatedKey
|
||||
dispatch(setDroppedItems({
|
||||
transactionKey,
|
||||
items: {
|
||||
...droppedItems,
|
||||
[selectedDoc.key]: updatedKey
|
||||
}
|
||||
}));
|
||||
|
||||
// Handle Ctrl+V
|
||||
|
|
@ -530,103 +601,159 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
|
|||
return (
|
||||
<>
|
||||
{/* Center Doc Container - START */}
|
||||
<div id="doc-con" className={`zoom-con col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth`}>
|
||||
<div id="doc-con"
|
||||
className={`sm:col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth`}>
|
||||
<div style={{
|
||||
width: `${100 * scaleFactor}%`, // ← outer wrapper grows to make room
|
||||
height: `${100 * scaleFactor}%`,
|
||||
}}>
|
||||
<div className="zoom-con"
|
||||
style={{
|
||||
transform: `scale(${scaleFactor})`,
|
||||
transformOrigin: 'top left', // ← change to top left
|
||||
width: `${100 / scaleFactor}%`,
|
||||
height: scaleFactor < 1
|
||||
? `${100 / scaleFactor}%` // zoomed out — expand to fill
|
||||
: 'auto', // zoomed in — let content grow
|
||||
}}>
|
||||
{
|
||||
selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => {
|
||||
return (
|
||||
<div key={indxDoc} className='flex flex-col overflow-x-auto'>
|
||||
<div
|
||||
className='py-2 text-sm'>
|
||||
<p className='text-center'>{doc.name}</p>
|
||||
</div>
|
||||
|
||||
{
|
||||
selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => {
|
||||
return (
|
||||
<div key={indxDoc} className='flex flex-col overflow-x-auto'>
|
||||
<div
|
||||
className='py-2 text-sm'>
|
||||
<p className='text-center'>{doc.name}</p>
|
||||
</div>
|
||||
<div className={`flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%] ${isPreview ? "pointer-events-none" : ""}`}>
|
||||
{
|
||||
doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => {
|
||||
const key = `${doc.id}_${page.index}`;
|
||||
return (
|
||||
<div
|
||||
tabIndex={-1}
|
||||
onFocus={() => handleDocumentFocus(key)}
|
||||
key={indx}
|
||||
id={`${doc.id}${indx}`}
|
||||
onDrop={(e) => handleDrop(e, doc.id, indxDoc, page.index, page.width, page.height)}
|
||||
onDragOver={handleDragOver}
|
||||
className='drop-page-in shadow-md relative'
|
||||
style={{ aspectRatio: `${page.width}/${page.height}` }}
|
||||
>
|
||||
|
||||
<div className={`flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%] ${isPreview ? "pointer-events-none" : ""}`}>
|
||||
{
|
||||
doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => {
|
||||
const key = `${doc.id}_${page.index}`;
|
||||
return (
|
||||
<div
|
||||
tabIndex={-1}
|
||||
onFocus={() => handleDocumentFocus(key)}
|
||||
key={indx}
|
||||
id={`${doc.id}${indx}`}
|
||||
onDrop={(e) => handleDrop(e, doc.id, indxDoc, page.index, page.width, page.height)}
|
||||
onDragOver={handleDragOver}
|
||||
className='drop-page-in shadow-md relative'
|
||||
style={{ aspectRatio: `${page.width}/${page.height}` }}
|
||||
>
|
||||
<img
|
||||
// style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
|
||||
// style={{ "width": page.width, "height": page.height, "maxWidth": "100%", "maxHeight": "100%" }}
|
||||
// style={{ "width": "100%", "height": "100%", "maxWidth": "100%", "maxHeight": "100%" }}
|
||||
// className={`w-[${(page.width / 2)}px] h-[${(page.height / 2)}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`}
|
||||
id={`${doc.id}-${page.index}`}
|
||||
className={`w-[${page.width / 2}px] h-[${page.height / 2}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`}
|
||||
ref={(el) => {
|
||||
const pageRefKey = `${doc.id}_${page.index}`;
|
||||
imageRefs.current[pageRefKey] = el;
|
||||
}}
|
||||
onLoad={(event) => {
|
||||
const pageRefKey = `${doc.id}_${page.index}`;
|
||||
const el = event.currentTarget;
|
||||
setImageSizes((prev) => ({
|
||||
...prev,
|
||||
[pageRefKey]: {
|
||||
width: el.offsetWidth,
|
||||
height: el.offsetHeight,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
draggable="false"
|
||||
width={page.width}
|
||||
height={page.height}
|
||||
// src={`http://10.10.1.99:8083/api/v1/accounts/${accountId}/transactions/${transactionId}/documents/${doc.id}/pages/${page.id}`}
|
||||
src={page.src}
|
||||
loading='lazy'
|
||||
data-doc-img="true"
|
||||
/>
|
||||
|
||||
<img
|
||||
// style={{ "minWidth": page.width, "minHeight": page.height, "maxWidth": page.width, "maxHeight": page.height }}
|
||||
// style={{ "width": page.width, "height": page.height, "maxWidth": "100%", "maxHeight": "100%" }}
|
||||
// style={{ "width": "100%", "height": "100%", "maxWidth": "100%", "maxHeight": "100%" }}
|
||||
// className={`w-[${(page.width / 2)}px] h-[${(page.height / 2)}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`}
|
||||
id={`${doc.id}-${page.index}`}
|
||||
className={`w-[${page.width / 2}px] h-[${page.height / 2}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`}
|
||||
ref={(el) => (imageRefs.current[page.index] = el)}
|
||||
draggable="false"
|
||||
width={page.width}
|
||||
height={page.height}
|
||||
// src={`http://10.10.1.99:8083/api/v1/accounts/${accountId}/transactions/${transactionId}/documents/${doc.id}/pages/${page.id}`}
|
||||
src={page.src}
|
||||
loading='lazy'
|
||||
/>
|
||||
<DroppedItemsTagsCon
|
||||
scaleFactor={scaleFactor}
|
||||
pageWidth={page.width}
|
||||
pageHeight={page.height}
|
||||
renderedWidth={imageSizes[key]?.width || imageRefs.current[key]?.offsetWidth || 0}
|
||||
renderedHeight={imageSizes[key]?.height || imageRefs.current[key]?.offsetHeight || 0}
|
||||
ref={childRef}
|
||||
droppedItems={droppedItems}
|
||||
pageIdkey={key}
|
||||
selectedTag={selectedTag}
|
||||
selectedRecipients={selectedRecipients}
|
||||
setSelectedTag={setSelectedTag}
|
||||
setDroppedItems={(updateFn: any) => {
|
||||
const newItems = typeof updateFn === 'function'
|
||||
? updateFn(droppedItems)
|
||||
: updateFn;
|
||||
dispatch(setDroppedItems({
|
||||
transactionKey,
|
||||
items: newItems
|
||||
}));
|
||||
}}
|
||||
accountId={accountId}
|
||||
transactionId={transactionId}
|
||||
transactionTemplateToggle={transactionTemplateToggle}
|
||||
tagUndoRef={tagUndoRef}
|
||||
transactionKey={transactionKey}
|
||||
/>
|
||||
|
||||
<DroppedItemsTagsCon
|
||||
pageWidth={page.width}
|
||||
pageHeight={page.height}
|
||||
renderedWidth={imageRefs.current[page.index]?.offsetWidth || 0}
|
||||
renderedHeight={imageRefs.current[page.index]?.offsetHeight || 0}
|
||||
ref={childRef}
|
||||
droppedItems={droppedItems}
|
||||
pageIdkey={key}
|
||||
selectedTag={selectedTag}
|
||||
selectedRecipients={selectedRecipients}
|
||||
setSelectedTag={setSelectedTag}
|
||||
setDroppedItems={setDroppedItems}
|
||||
accountId={accountId}
|
||||
transactionId={transactionId}
|
||||
transactionTemplateToggle={transactionTemplateToggle}
|
||||
tagUndoRef={tagUndoRef}
|
||||
/>
|
||||
|
||||
{/* Side details of documents Pages and document Name - START */}
|
||||
<p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-60px] text-slate-600'>
|
||||
page {indx + 1} of {doc.pages && doc.pages.length}
|
||||
</p>
|
||||
{/* <p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs right-[-140px] text-slate-600'>
|
||||
{/* Side details of documents Pages and document Name - START */}
|
||||
<p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-60px] text-slate-600'>
|
||||
page {indx + 1} of {doc.pages && doc.pages.length}
|
||||
</p>
|
||||
{/* <p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs right-[-140px] text-slate-600'>
|
||||
Document {indxDoc + 1} {doc.name}
|
||||
</p> */}
|
||||
{/* Side details of documents Pages and document Name - END */}
|
||||
{/* Side details of documents Pages and document Name - END */}
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}) : <Loader />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}) : <Loader />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag && <RightSideBar
|
||||
droppedItems={droppedItems}
|
||||
selectedTag={droppedItems[selectedTag.key][selectedTag.index]}
|
||||
setDroppedItems={setDroppedItems}
|
||||
selTagKey={selectedTag.key}
|
||||
selTagIndx={selectedTag.index}
|
||||
signClient={false}
|
||||
emptySelectedTag={() => setSelectedTag(null)}
|
||||
/>
|
||||
|
||||
}
|
||||
{/* Note Check Here - Work on Tag Propertieres Apppearrrrrrrrrrrrrrrr ------------------------- */}
|
||||
<>
|
||||
{selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag && droppedItems[selectedTag.key] && droppedItems[selectedTag.key][selectedTag.index] && <TagProperties
|
||||
droppedItems={droppedItems}
|
||||
selectedTag={droppedItems[selectedTag.key][selectedTag.index]}
|
||||
setDroppedItems={(updateFn: any) => {
|
||||
const newItems = typeof updateFn === 'function'
|
||||
? updateFn(droppedItems)
|
||||
: updateFn;
|
||||
dispatch(setDroppedItems({
|
||||
transactionKey,
|
||||
items: newItems
|
||||
}));
|
||||
}}
|
||||
selTagKey={selectedTag.key}
|
||||
selTagIndx={selectedTag.index}
|
||||
signClient={false}
|
||||
emptySelectedTag={() => setSelectedTag(null)}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
||||
{selectedDocuments && selectedDocuments.length > 0 && <DocumentPages
|
||||
isPages={isPages}
|
||||
setIsPages={setIsPages}
|
||||
droppedItems={droppedItems}
|
||||
/>}
|
||||
|
||||
{/* Center Doc Container - END */}
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
// -- handleAddGroupItemClicked
|
||||
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import type { AppDispatch } from '../../../store/store'
|
||||
import { DroppedItemsObj } from './DocumentContainer';
|
||||
import typeToIconMap from '../../../helper/iconMap';
|
||||
import { IoMdClose } from 'react-icons/io';
|
||||
|
|
@ -17,8 +19,10 @@ import { FaQuestionCircle } from 'react-icons/fa';
|
|||
import api from '../../../config/api';
|
||||
import { Recipient } from '../../../store/slices/selectedRecipientsSlice';
|
||||
import { DroppedItem } from '../../../utlity/types';
|
||||
import { setDroppedItems as setDroppedItemsAction } from '../../../store/slices/documentTagsSlice';
|
||||
|
||||
interface DroppedItemsTagsConProps {
|
||||
scaleFactor: number,
|
||||
pageWidth: number,
|
||||
pageHeight: number,
|
||||
renderedWidth: number,
|
||||
|
|
@ -28,15 +32,17 @@ interface DroppedItemsTagsConProps {
|
|||
selectedTag: { key: string; index: number } | null;
|
||||
selectedRecipients: Recipient[];
|
||||
setSelectedTag: React.Dispatch<React.SetStateAction<{ key: string; index: number } | null>>;
|
||||
setDroppedItems: React.Dispatch<React.SetStateAction<DroppedItemsObj>>;
|
||||
setDroppedItems: (updateFn: ((prevState: DroppedItemsObj) => DroppedItemsObj) | DroppedItemsObj) => void;
|
||||
accountId: string;
|
||||
transactionId: string;
|
||||
transactionTemplateToggle: string;
|
||||
tagUndoRef: React.MutableRefObject<DroppedItem[]>;
|
||||
transactionKey: string;
|
||||
}
|
||||
|
||||
// 👶 Child component with forwardRef
|
||||
const DroppedItemsTagsCon = forwardRef(({
|
||||
scaleFactor,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
renderedWidth,
|
||||
|
|
@ -50,9 +56,12 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
accountId,
|
||||
transactionId,
|
||||
transactionTemplateToggle,
|
||||
tagUndoRef
|
||||
tagUndoRef,
|
||||
transactionKey
|
||||
}: DroppedItemsTagsConProps, ref) => {
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const positionRef = useRef<{ left: number; top: number }>({ left: 0, top: 0 });
|
||||
|
||||
const handleTagClick = (key: string, index: number) => {
|
||||
|
|
@ -156,146 +165,217 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
handleRemoveTag
|
||||
}));
|
||||
|
||||
// HANDLE TAG MOUSE DOWN - START
|
||||
// Updated handleTagMouseDown function with proper group border positioning
|
||||
const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
|
||||
event.preventDefault();
|
||||
|
||||
const scale = scaleFactor ?? 1;
|
||||
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
|
||||
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
|
||||
|
||||
let targetContainer = event?.currentTarget?.parentElement?.parentElement;
|
||||
let targetElement = event?.currentTarget?.parentElement;
|
||||
|
||||
if (!targetContainer || !targetElement) return;
|
||||
|
||||
const containerRect = targetContainer.getBoundingClientRect();
|
||||
const elementRect = targetElement.getBoundingClientRect();
|
||||
|
||||
let clientX = event.clientX
|
||||
let clientY = event.clientY
|
||||
const currentItem = droppedItems[key][index];
|
||||
const tagOriginalLeft = currentItem.dimensions.left;
|
||||
const tagOriginalTop = currentItem.dimensions.top;
|
||||
|
||||
if ((event as any).changedTouches?.[0]) {
|
||||
clientX = (event as any).changedTouches[0].pageX;
|
||||
clientY = (event as any).changedTouches[0].pageY;
|
||||
}
|
||||
const tagScreenLeft = containerRect.left + tagOriginalLeft * scaleX * scale;
|
||||
const tagScreenTop = containerRect.top + tagOriginalTop * scaleY * scale;
|
||||
|
||||
// Calculate the initial offset (where the mouse was clicked inside the element)
|
||||
const offsetY = event.clientY - elementRect.top;
|
||||
const offsetX = event.clientX - elementRect.left;
|
||||
const clickOffsetX = event.clientX - tagScreenLeft;
|
||||
const clickOffsetY = event.clientY - tagScreenTop;
|
||||
|
||||
positionRef.current = { left: event.clientX - containerRect.left - offsetX, top: event.clientY - containerRect.top - offsetY };
|
||||
// Track whether mouse actually moved
|
||||
let hasMoved = false;
|
||||
|
||||
const conScrollWidth = targetContainer?.scrollWidth;
|
||||
const conScrollHeight = targetContainer?.scrollHeight;
|
||||
// Initialize positionRef with CURRENT position, not 0,0
|
||||
positionRef.current = { left: tagOriginalLeft, top: tagOriginalTop };
|
||||
|
||||
let isGroupAttr = event.currentTarget.hasAttribute("group");
|
||||
let groupId = null;
|
||||
if (isGroupAttr) {
|
||||
groupId = event.currentTarget.getAttribute("group");
|
||||
}
|
||||
if (isGroupAttr) groupId = event.currentTarget.getAttribute("group");
|
||||
|
||||
let groupElements: NodeListOf<HTMLElement> | null = null;
|
||||
if (isGroupAttr && groupId) {
|
||||
groupElements = document.querySelectorAll(`div[group="${groupId}"]`);
|
||||
// Show initial border position during drag start
|
||||
handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
|
||||
handleGroupBorderPosition(groupElements, containerRect, clickOffsetX, clickOffsetY);
|
||||
}
|
||||
|
||||
// let timeOut = null
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
// Get new X and Y positions relative to the container
|
||||
let clientX = event.clientX
|
||||
let clientY = event.clientY
|
||||
hasMoved = true; // Marking that it ge tmoved
|
||||
|
||||
if ((event as any).changedTouches?.[0]) {
|
||||
clientX = (event as any).changedTouches[0].pageX;
|
||||
clientY = (event as any).changedTouches[0].pageY;
|
||||
}
|
||||
const currentContainerRect = targetContainer!.getBoundingClientRect();
|
||||
|
||||
let left = clientX - containerRect.left - offsetX;
|
||||
let top = clientY - containerRect.top - offsetY;
|
||||
const mouseScreenX = event.clientX - currentContainerRect.left;
|
||||
const mouseScreenY = event.clientY - currentContainerRect.top;
|
||||
|
||||
// Restrict movement within container bounds
|
||||
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width));
|
||||
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height));
|
||||
const tagScreenX = mouseScreenX - clickOffsetX;
|
||||
const tagScreenY = mouseScreenY - clickOffsetY;
|
||||
|
||||
// Save in ref (fast, no re-render)
|
||||
positionRef.current = { left, top };
|
||||
const originalLeft = tagScreenX / scale / scaleX;
|
||||
const originalTop = tagScreenY / scale / scaleY;
|
||||
|
||||
// Update DOM directly
|
||||
targetElement!.style.top = `${top}px`;
|
||||
targetElement!.style.left = `${left}px`;
|
||||
positionRef.current = { left: originalLeft, top: originalTop };
|
||||
|
||||
targetElement!.style.left = `${originalLeft * scaleX}px`;
|
||||
targetElement!.style.top = `${originalTop * scaleY}px`;
|
||||
|
||||
|
||||
console.log(top, left, "top, left Line 283")
|
||||
|
||||
// Apply the movement to the element first
|
||||
// setDroppedItems(prevState => ({
|
||||
// ...prevState,
|
||||
// [key]: Array.isArray(prevState[key])
|
||||
// ? prevState[key].map((item, i) =>
|
||||
// i == index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
|
||||
// )
|
||||
// : [],
|
||||
// }));
|
||||
|
||||
// Update group border position during drag if it's a group element
|
||||
if (groupElements != null && groupElements.length) {
|
||||
// Use a small timeout to ensure DOM is updated before recalculating borders
|
||||
requestAnimationFrame(() => {
|
||||
handleGroupBorderPosition(groupElements!, containerRect, offsetX, offsetY);
|
||||
handleGroupBorderPosition(groupElements!, currentContainerRect, clickOffsetX, clickOffsetY);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
// CRITICAL: Ensure group borders are properly positioned after drag ends
|
||||
if (groupElements != null && groupElements.length && groupId) {
|
||||
// Multiple approaches to ensure the positioning works:
|
||||
|
||||
// 1. Immediate update
|
||||
setTimeout(() => {
|
||||
handleIGroupBorderPosition(groupId);
|
||||
}, 10);
|
||||
|
||||
// 2. Secondary update to ensure it's properly positioned
|
||||
setTimeout(() => {
|
||||
handleIGroupBorderPosition(groupId);
|
||||
}, 100);
|
||||
|
||||
// 3. Final update using requestAnimationFrame for DOM sync
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
handleIGroupBorderPosition(groupId);
|
||||
}, 50);
|
||||
});
|
||||
const handleMouseUp = (e: MouseEvent) => {
|
||||
// Just clean not update - NOTE
|
||||
if (!hasMoved) {
|
||||
document.removeEventListener("pointermove", handleMouseMove);
|
||||
document.removeEventListener("pointerup", handleMouseUp);
|
||||
return;
|
||||
}
|
||||
|
||||
const { left, top } = positionRef.current;
|
||||
console.log(top, left, "HandleTag Mouse Up top, left Line 283")
|
||||
let hoveredDocId: any = ""
|
||||
let hoveredPageIndex: string = ""
|
||||
let { left, top } = positionRef.current;
|
||||
const [currentDocumentId, currentPageIndex] = key.split("_");
|
||||
|
||||
setDroppedItems(prevState => ({
|
||||
...prevState,
|
||||
[key]: Array.isArray(prevState[key])
|
||||
? prevState[key].map((item, i) =>
|
||||
i == index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
|
||||
)
|
||||
: [],
|
||||
}));
|
||||
let tagDocumentUpdate = {
|
||||
update: false,
|
||||
updatingPage: false,
|
||||
updatingDocument: false,
|
||||
pageIndex: 0,
|
||||
documentId: ""
|
||||
}
|
||||
|
||||
targetElement.style.pointerEvents = "none";
|
||||
const el = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;
|
||||
const hoveredContainer = el?.closest(".drop-page-in");
|
||||
targetElement.style.pointerEvents = "auto";
|
||||
|
||||
if (hoveredContainer) {
|
||||
const imgElem: HTMLElement | null = hoveredContainer.querySelector("[data-doc-img]");
|
||||
hoveredDocId = imgElem ? imgElem.getAttribute("id") || "" : "";
|
||||
hoveredDocId = hoveredDocId.split("-");
|
||||
hoveredPageIndex = hoveredDocId[hoveredDocId.length - 1];
|
||||
hoveredDocId.pop();
|
||||
hoveredDocId = hoveredDocId.join("-");
|
||||
}
|
||||
|
||||
if (hoveredContainer === null || hoveredDocId != currentDocumentId) {
|
||||
targetElement!.style.left = `${tagOriginalLeft * scaleX}px`;
|
||||
targetElement!.style.top = `${tagOriginalTop * scaleY}px`;
|
||||
|
||||
document.removeEventListener("pointermove", handleMouseMove);
|
||||
document.removeEventListener("pointerup", handleMouseUp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key in droppedItems && index in droppedItems[key]) {
|
||||
const documentId = droppedItems[key][index]["documentId"];
|
||||
const pageIndex = droppedItems[key][index]["pageIndex"];
|
||||
|
||||
tagDocumentUpdate.pageIndex = pageIndex;
|
||||
tagDocumentUpdate.documentId = documentId;
|
||||
|
||||
if (hoveredDocId == documentId) {
|
||||
if (hoveredPageIndex != pageIndex) {
|
||||
tagDocumentUpdate.updatingPage = true;
|
||||
tagDocumentUpdate.pageIndex = hoveredPageIndex;
|
||||
tagDocumentUpdate.documentId = documentId;
|
||||
}
|
||||
} else {
|
||||
tagDocumentUpdate.updatingDocument = true;
|
||||
tagDocumentUpdate.updatingPage = true;
|
||||
tagDocumentUpdate.pageIndex = hoveredPageIndex;
|
||||
tagDocumentUpdate.documentId = hoveredDocId;
|
||||
}
|
||||
}
|
||||
|
||||
if (groupElements != null && groupElements.length && groupId) {
|
||||
setTimeout(() => { handleIGroupBorderPosition(groupId); }, 10);
|
||||
setTimeout(() => { handleIGroupBorderPosition(groupId); }, 100);
|
||||
requestAnimationFrame(() => { setTimeout(() => { handleIGroupBorderPosition(groupId); }, 50); });
|
||||
}
|
||||
|
||||
setDroppedItems(prevState => {
|
||||
const isSameDocument = currentDocumentId == tagDocumentUpdate.documentId;
|
||||
if (!isSameDocument) return prevState;
|
||||
|
||||
const isSamePage = currentPageIndex == tagDocumentUpdate.pageIndex;
|
||||
|
||||
if (isSameDocument && isSamePage) {
|
||||
const clampedLeft = Math.max(0, Math.min(left, pageWidth - currentItem.dimensions.width));
|
||||
const clampedTop = Math.max(0, Math.min(top, pageHeight - currentItem.dimensions.height));
|
||||
|
||||
return {
|
||||
...prevState,
|
||||
[key]: Array.isArray(prevState[key])
|
||||
? prevState[key].map((item, i) =>
|
||||
i === index ? {
|
||||
...item,
|
||||
documentId: tagDocumentUpdate.documentId,
|
||||
pageIndex: tagDocumentUpdate.pageIndex,
|
||||
dimensions: { ...item.dimensions, left: clampedLeft, top: clampedTop },
|
||||
update: true,
|
||||
} : item
|
||||
) : [],
|
||||
};
|
||||
}
|
||||
|
||||
const newContainerRect = hoveredContainer?.getBoundingClientRect() as DOMRect;
|
||||
const newMouseScreenX = e.clientX - newContainerRect.left;
|
||||
const newMouseScreenY = e.clientY - newContainerRect.top;
|
||||
left = Math.max(0, Math.min(
|
||||
(newMouseScreenX - clickOffsetX) / scale / scaleX,
|
||||
pageWidth - currentItem.dimensions.width
|
||||
));
|
||||
top = Math.max(0, Math.min(
|
||||
(newMouseScreenY - clickOffsetY) / scale / scaleY,
|
||||
pageHeight - currentItem.dimensions.height
|
||||
));
|
||||
|
||||
const targetKey = `${tagDocumentUpdate.documentId}_${tagDocumentUpdate.pageIndex}`;
|
||||
const currentItems = Array.isArray(prevState[key]) ? [...prevState[key]] : [];
|
||||
const movedItem = currentItems[index];
|
||||
if (!movedItem) return prevState;
|
||||
currentItems.splice(index, 1);
|
||||
|
||||
return {
|
||||
...prevState,
|
||||
[key]: currentItems,
|
||||
[targetKey]: [
|
||||
...(Array.isArray(prevState[targetKey]) ? prevState[targetKey] : []),
|
||||
{ ...movedItem, documentId: tagDocumentUpdate.documentId, pageIndex: tagDocumentUpdate.pageIndex, dimensions: { ...movedItem.dimensions, left, top }, update: true },
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
document.removeEventListener("pointermove", handleMouseMove);
|
||||
document.removeEventListener("pointerup", handleMouseUp);
|
||||
};
|
||||
|
||||
// Add event listeners
|
||||
document.addEventListener("pointermove", handleMouseMove);
|
||||
document.addEventListener("pointerup", handleMouseUp);
|
||||
};
|
||||
|
||||
// HANDLE TAG MOUSE DOWN - END
|
||||
|
||||
// HANDLE EXPAND TAG - START
|
||||
const handleExpandTag = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
|
||||
event.preventDefault(); // Prevent text selection issues
|
||||
event.preventDefault();
|
||||
|
||||
const scale = scaleFactor ?? 1;
|
||||
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
|
||||
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
|
||||
|
||||
// Combined scale: zoom * render ratio
|
||||
const combinedScaleX = scaleX * scale;
|
||||
const combinedScaleY = scaleY * scale;
|
||||
|
||||
const expandingElemIcon = event.currentTarget;
|
||||
const targetElement = expandingElemIcon.nextElementSibling?.parentElement;
|
||||
|
|
@ -308,49 +388,41 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
const startX = event.clientX;
|
||||
const startY = event.clientY;
|
||||
|
||||
const startWidth = elementRect.width;
|
||||
const startHeight = elementRect.height;
|
||||
const startWidth = elementRect.width / combinedScaleX;
|
||||
const startHeight = elementRect.height / combinedScaleY;
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
const dropZone = targetContainer.getBoundingClientRect();
|
||||
|
||||
const deltaX = (event.clientX - startX) / combinedScaleX;
|
||||
const deltaY = (event.clientY - startY) / combinedScaleY;
|
||||
|
||||
const dropZone: DOMRect = targetContainer?.getBoundingClientRect() as DOMRect;
|
||||
const droppedElemRect: DOMRect = targetElement?.getBoundingClientRect() as DOMRect;
|
||||
let newWidth = startWidth + deltaX;
|
||||
let newHeight = startHeight + deltaY;
|
||||
|
||||
console.log(dropZone, "dropZone Line 309")
|
||||
console.log(droppedElemRect, "rect Line 309")
|
||||
if (newWidth > 400) newWidth = 400;
|
||||
if (newHeight > 200) newHeight = 200;
|
||||
if (newWidth < 20) newWidth = 20;
|
||||
if (newHeight < 10) newHeight = 10;
|
||||
|
||||
let newWidth = startWidth + (event.clientX - startX);
|
||||
let newHeight = startHeight + (event.clientY - startY);
|
||||
const maxWidth = dropZone.width / combinedScaleX;
|
||||
const maxHeight = dropZone.height / combinedScaleY;
|
||||
const currentLeft = droppedItems[key][index].dimensions.left;
|
||||
const currentTop = droppedItems[key][index].dimensions.top;
|
||||
|
||||
if (currentLeft + newWidth > maxWidth) newWidth = maxWidth - currentLeft;
|
||||
if (currentTop + newHeight > maxHeight) newHeight = maxHeight - currentTop;
|
||||
|
||||
if (newHeight > 200 || newWidth > 400) {
|
||||
if (newHeight > 200) {
|
||||
newHeight = 200
|
||||
}
|
||||
|
||||
if (newWidth > 400) {
|
||||
newWidth = 400
|
||||
}
|
||||
}
|
||||
|
||||
if ((dropZone.right - (droppedElemRect.left + newWidth)) < 0) {
|
||||
newWidth = startWidth
|
||||
}
|
||||
if ((dropZone.bottom - (droppedElemRect.bottom + newHeight)) < 0) {
|
||||
newHeight = startHeight
|
||||
}
|
||||
|
||||
// Apply the new width & height
|
||||
setDroppedItems({
|
||||
...droppedItems,
|
||||
[key]: Array.isArray(droppedItems[key]) // Ensure it's an array
|
||||
[key]: Array.isArray(droppedItems[key])
|
||||
? droppedItems[key].map((item, i) =>
|
||||
i == index ? { ...item, dimensions: { ...item.dimensions, width: newWidth, height: newHeight }, update: true } : item
|
||||
i === index
|
||||
? { ...item, dimensions: { ...item.dimensions, width: newWidth, height: newHeight }, update: true }
|
||||
: item
|
||||
)
|
||||
: [], // Provide a fallback in case it's undefined or not an array
|
||||
: [],
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
|
|
@ -364,14 +436,15 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
// HANDLE EXPAND TAG - END
|
||||
|
||||
const removeGroupBorders = (groupId: string) => {
|
||||
console.log(`Removing borders for group: ${groupId}`);
|
||||
|
||||
const borderPositions = ['top', 'right', 'bottom', 'left'];
|
||||
|
||||
borderPositions.forEach(position => {
|
||||
const borderElement = document.querySelector(`[border-group='${groupId}'][border-pos='${position}']`);
|
||||
if (borderElement && borderElement.parentNode) {
|
||||
borderElement.parentNode.removeChild(borderElement);
|
||||
if (borderElement) {
|
||||
borderElement.remove?.();
|
||||
if (!borderElement.remove && borderElement.parentNode && borderElement.parentNode.contains(borderElement)) {
|
||||
borderElement.parentNode.removeChild(borderElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -384,34 +457,46 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
return;
|
||||
}
|
||||
|
||||
let targetContainer = groupElements[0].parentElement?.parentElement;
|
||||
|
||||
if (!targetContainer) {
|
||||
console.log("No target container found");
|
||||
return;
|
||||
}
|
||||
|
||||
const containerRect = targetContainer.getBoundingClientRect();
|
||||
|
||||
// Use consistent offset values
|
||||
const offsetX = 10;
|
||||
const offsetY = 10;
|
||||
|
||||
// Ensure DOM is fully updated before positioning
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
|
||||
let targetContainer = groupElements[0].parentElement?.parentElement;
|
||||
|
||||
if (!targetContainer) return;
|
||||
let containerRect = targetContainer.getBoundingClientRect();
|
||||
|
||||
handleGroupBorderPosition(groupElements, containerRect, 10, 10);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupBorderPosition = (groupElements: NodeListOf<HTMLElement>, containerRect: DOMRect, offsetX: number, offsetY: number) => {
|
||||
useEffect(() => {
|
||||
if (!selectedTag) {
|
||||
const borderElems = document.querySelectorAll("[border-group]")
|
||||
if (borderElems && borderElems.length) {
|
||||
borderElems.forEach(elem => {
|
||||
elem.style.display = "none";
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [selectedTag])
|
||||
|
||||
const handleGroupBorderPosition = (
|
||||
groupElements: NodeListOf<HTMLElement>,
|
||||
containerRect: DOMRect,
|
||||
offsetX: number,
|
||||
offsetY: number
|
||||
) => {
|
||||
if (!groupElements || groupElements.length == 0) return;
|
||||
|
||||
const groupId = groupElements[0].getAttribute('group');
|
||||
if (!groupId) return;
|
||||
|
||||
// Calculate the bounding box of all group elements
|
||||
const scale = scaleFactor ?? 1;
|
||||
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
|
||||
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
|
||||
const combinedScaleX = scaleX * scale;
|
||||
const combinedScaleY = scaleY * scale;
|
||||
|
||||
let minLeft = Infinity, minTop = Infinity, maxRight = -Infinity, maxBottom = -Infinity;
|
||||
|
||||
groupElements.forEach(element => {
|
||||
|
|
@ -419,10 +504,11 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
if (!parentElement) return;
|
||||
|
||||
const rect = parentElement.getBoundingClientRect();
|
||||
const relativeLeft = rect.left - containerRect.left;
|
||||
const relativeTop = rect.top - containerRect.top;
|
||||
const relativeRight = relativeLeft + rect.width;
|
||||
const relativeBottom = relativeTop + rect.height;
|
||||
|
||||
const relativeLeft = (rect.left - containerRect.left) / combinedScaleX;
|
||||
const relativeTop = (rect.top - containerRect.top) / combinedScaleY;
|
||||
const relativeRight = relativeLeft + rect.width / combinedScaleX;
|
||||
const relativeBottom = relativeTop + rect.height / combinedScaleY;
|
||||
|
||||
minLeft = Math.min(minLeft, relativeLeft);
|
||||
minTop = Math.min(minTop, relativeTop);
|
||||
|
|
@ -430,7 +516,6 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
maxBottom = Math.max(maxBottom, relativeBottom);
|
||||
});
|
||||
|
||||
// Add some padding around the group
|
||||
const padding = 10;
|
||||
minLeft -= padding;
|
||||
minTop -= padding;
|
||||
|
|
@ -440,39 +525,36 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
const width = maxRight - minLeft;
|
||||
const height = maxBottom - minTop;
|
||||
|
||||
// Update or create border elements
|
||||
const borderPositions = ['top', 'right', 'bottom', 'left'];
|
||||
|
||||
borderPositions.forEach(position => {
|
||||
let borderElement = document.querySelector(`[border-group='${groupId}'][border-pos='${position}']`) as HTMLElement;
|
||||
let borderElement = document.querySelector(
|
||||
`[border-group='${groupId}'][border-pos='${position}']`
|
||||
) as HTMLElement;
|
||||
|
||||
if (!borderElement) {
|
||||
// Create border element if it doesn't exist
|
||||
borderElement = document.createElement('div');
|
||||
borderElement.className = `group-border ${position == 'bottom' ? 'bottom-border' : ''}`;
|
||||
borderElement.setAttribute('border-group', groupId);
|
||||
borderElement.setAttribute('border-pos', position);
|
||||
|
||||
// Add the + icon for bottom border
|
||||
if (position == 'bottom') {
|
||||
const addIcon = document.createElement('div');
|
||||
addIcon.innerHTML = '<IoAdd />'; // You might need to adjust this based on your icon system
|
||||
addIcon.innerHTML = '<IoAdd />';
|
||||
addIcon.onclick = () => {
|
||||
// Find the original item to pass to handleAddGroupItemClicked
|
||||
const originalItem = droppedItems[key]?.find(item => 'group' in item && item.group == groupId);
|
||||
if (originalItem) {
|
||||
handleAddGroupItemClicked(key, originalItem);
|
||||
}
|
||||
const originalItem = droppedItems[pageIdkey]?.find(
|
||||
item => 'group' in item && item.group == groupId
|
||||
);
|
||||
if (originalItem) handleAddGroupItemClicked(pageIdkey, originalItem);
|
||||
};
|
||||
borderElement.appendChild(addIcon);
|
||||
}
|
||||
|
||||
// Append to container
|
||||
groupElements[0].parentElement?.parentElement?.appendChild(borderElement);
|
||||
}
|
||||
|
||||
// Position the border element
|
||||
borderElement.style.position = 'absolute';
|
||||
borderElement.style.display = 'block';
|
||||
borderElement.style.pointerEvents = position == 'bottom' ? 'auto' : 'none';
|
||||
|
||||
switch (position) {
|
||||
|
|
@ -587,24 +669,35 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
const IconComponent = typeToIconMap[type] || <FaQuestionCircle />;
|
||||
const selectedRecipient = selectedRecipients.filter((obj, indx) => obj.id == item.recipientId);
|
||||
|
||||
const scaleX = renderedWidth / Math.round(pageWidth);
|
||||
const scaleY = renderedHeight / Math.round(pageHeight);
|
||||
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
|
||||
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
|
||||
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<div
|
||||
// key={index}
|
||||
tabIndex={0}
|
||||
draggable="false"
|
||||
data-tag-id={index}
|
||||
data-drop-key={pageIdkey}
|
||||
data-tag-selected={index == selectedTag?.index}
|
||||
className="absolute rounded dropped-item"
|
||||
onClick={() => handleTagClick(pageIdkey, index)}
|
||||
className="absolute rounded dropped-item z-50"
|
||||
onClick={(e) => {
|
||||
e.currentTarget.focus();
|
||||
handleTagClick(pageIdkey, index)
|
||||
}}
|
||||
onBlur={(e: React.FocusEvent) => {
|
||||
const nextElement = e.relatedTarget as HTMLElement | null;
|
||||
if (nextElement?.hasAttribute("data-type-group") || nextElement?.closest("[data-tag-properties]")) return;
|
||||
setSelectedTag(null);
|
||||
}}
|
||||
style={{
|
||||
left: `${item.dimensions.left * scaleX}px`,
|
||||
top: `${item.dimensions.top * scaleY}px`,
|
||||
width: item.type == "radio" ? `${item.dimensions.width + 20}px` : `${item.dimensions.width * scaleX}px`,
|
||||
height: item.type == "radio" ? `max-content` : `${item.dimensions.height * scaleY}px`
|
||||
// width: item.type == "radio" ? `${item.dimensions.width + 20}px` : `${item.dimensions.width * scaleX}px`,
|
||||
// height: item.type == "radio" ? `max-content` : `${item.dimensions.height * scaleY}px`
|
||||
width: `${item.dimensions.width * scaleX}px`,
|
||||
height: `${item.dimensions.height * scaleY}px`
|
||||
}}
|
||||
|
||||
// style={{
|
||||
|
|
@ -640,10 +733,14 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
|
||||
|
||||
{/* Expand Circle - STARTS */}
|
||||
{item.type != "radio" && item.type != "checkbox" && <div
|
||||
{/* {item.type != "radio" && item.type != "checkbox" && <div
|
||||
onPointerDown={(e) => handleExpandTag(e, pageIdkey, index)}
|
||||
className='w-2.5 h-2.5 absolute bg-red-400 rounded-full bottom-[-8px] right-[-8px] cursor-nw-resize hover:bg-blue-400'>
|
||||
</div>}
|
||||
</div>} */}
|
||||
<div
|
||||
onPointerDown={(e) => handleExpandTag(e, pageIdkey, index)}
|
||||
className='w-2.5 h-2.5 absolute bg-red-400 rounded-full bottom-[-8px] right-[-8px] cursor-nw-resize hover:bg-blue-400'>
|
||||
</div>
|
||||
{/* Expand Circle - ENDS */}
|
||||
|
||||
{/* Tag - START */}
|
||||
|
|
@ -671,15 +768,22 @@ const DroppedItemsTagsCon = forwardRef(({
|
|||
{
|
||||
"group" in item && item.group && "isGroupExist" in item && !item.isGroupExist &&
|
||||
<>
|
||||
<div className="group-border" border-group={item.group} border-pos="top"></div>
|
||||
<div className="group-border" border-group={item.group} border-pos="right"></div>
|
||||
<div className="group-border bottom-border" border-group={item.group} border-pos="bottom">
|
||||
<div
|
||||
onClick={() => handleAddGroupItemClicked(pageIdkey, item)}>
|
||||
<div className="group-border z-50" border-group={item.group} border-pos="top"></div>
|
||||
<div className="group-border z-50" border-group={item.group} border-pos="right"></div>
|
||||
<div className="group-border z-50 bottom-border" border-group={item.group} border-pos="bottom">
|
||||
<button
|
||||
data-type-group="true"
|
||||
onClick={() => handleAddGroupItemClicked(pageIdkey, item)}
|
||||
onBlur={(e: React.FocusEvent) => {
|
||||
const nextElement = e.relatedTarget as HTMLElement | null;
|
||||
if (nextElement?.hasAttribute("groupp")) return;
|
||||
setSelectedTag(null);
|
||||
}}
|
||||
>
|
||||
<IoAdd />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className="group-border" border-group={item.group} border-pos="left"></div>
|
||||
<div className="group-border z-50" border-group={item.group} border-pos="left"></div>
|
||||
</>
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx
|
|||
const priorityOrder = ['SIGNATURE', 'INITIALS', 'STAMP'];
|
||||
|
||||
const data = res.data.filter((obj, indx) => {
|
||||
if (obj.type == "STAMP" || obj.type == "RADIO" || obj.type == "CHECKBOX" || obj.type == "ATTACHMENT" || obj.type == "SELECT") return false
|
||||
if (obj.type == "STAMP" || obj.type == "RADIO" || obj.type == "CHECKBOX" || obj.type == "ATTACHMENT" || obj.type == "SELECT") return true
|
||||
return true
|
||||
});
|
||||
|
||||
|
|
@ -308,7 +308,7 @@ const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx
|
|||
// );
|
||||
|
||||
return (
|
||||
<div className='flex flex-row sm:flex-wrap gap-2 justify-start items-start w-full overflow-x-auto'>
|
||||
<div className='flex flex-row sm:flex-wrap gap-2 justify-start items-start overflow-x-auto w-[90vw] sm:w-full px-2'>
|
||||
{tagsData.map((field, index) => {
|
||||
|
||||
const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />;
|
||||
|
|
@ -316,26 +316,29 @@ const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx
|
|||
if (!IconComponent) return null;
|
||||
|
||||
return (
|
||||
// <div key={index} draggable={true} onDragStart={(e) => handleDragStart(e, field.label, recipientId)} className="p-1 flex justify-start items-center gap-2 rounded-sm cursor-grab hover:bg-slate-200 transition-all duration-300">
|
||||
<div
|
||||
draggable={true}
|
||||
key={index}
|
||||
onPointerDown={(e) => handlePointerDown(e, field.type.toLowerCase(), recipientId, recipientIndx, field)}
|
||||
className={`border-[1px] flex flex-col justify-center bg-white p-2 w-[75px] h-[75px] items-center gap-2 rounded-sm cursor-grab hover:bg-slate-200 transition-all duration-300`}
|
||||
style={{ borderColor: color }}
|
||||
>
|
||||
<>
|
||||
{/* <div key={index} draggable={true} onDragStart={(e) => handleDragStart(e, field.label, recipientId)} className="p-1 flex justify-start items-center gap-2 rounded-sm cursor-grab hover:bg-slate-200 transition-all duration-300"> */}
|
||||
<div
|
||||
draggable={true}
|
||||
key={index}
|
||||
onPointerDown={(e) => handlePointerDown(e, field.type.toLowerCase(), recipientId, recipientIndx, field)}
|
||||
className={`border-[1px] flex w-full justify-left bg-white items-center gap-1 rounded-sm cursor-grab hover:bg-slate-200 transition-all duration-300`}
|
||||
style={{ borderColor: color }}
|
||||
>
|
||||
|
||||
<div
|
||||
className={`p-1 rounded-sm text-white`}
|
||||
// style={{ background: color }}
|
||||
>
|
||||
{IconComponent}
|
||||
</div>
|
||||
<p
|
||||
className="capitalize text-xs">
|
||||
{field.type.toLowerCase()}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className={`p-1 rounded-sm text-white`}
|
||||
// style={{ background: color }}
|
||||
>
|
||||
{IconComponent}
|
||||
</div>
|
||||
<p
|
||||
className="capitalize text-xs">
|
||||
{field.type.toLowerCase()}
|
||||
</p>
|
||||
</div>
|
||||
{/* </div> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@ import FieldList from './FieldList';
|
|||
import { addColorsToRecipient, fetchRecipients, recipientType, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice';
|
||||
|
||||
import { IoCheckmark } from 'react-icons/io5'
|
||||
import { MdKeyboardArrowDown, MdOutlineNavigateNext } from 'react-icons/md'
|
||||
import { MdKeyboardArrowDown } from 'react-icons/md'
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { fetchDocuments } from '../../../store/slices/documentSlice';
|
||||
import { GrFormPrevious } from 'react-icons/gr';
|
||||
import { LuTableOfContents } from 'react-icons/lu';
|
||||
import { IoIosDocument, IoMdClose } from 'react-icons/io';
|
||||
import { FaAngleUp, FaChevronRight } from 'react-icons/fa';
|
||||
import { FaAngleUp } from 'react-icons/fa';
|
||||
import { SlDocs } from "react-icons/sl";
|
||||
|
||||
interface LeftSideBarProps {
|
||||
isFields: boolean;
|
||||
|
|
@ -23,7 +21,7 @@ interface LeftSideBarProps {
|
|||
handleSetIsPages: (setTo: boolean) => void;
|
||||
}
|
||||
|
||||
const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRecipientIndx, setSelectedRecipient, handleSetIsFields, handleSetIsPages }) => {
|
||||
const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIndx, setSelectedRecipient, handleSetIsFields, handleSetIsPages }) => {
|
||||
|
||||
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -44,8 +42,6 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
|||
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients)
|
||||
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRecipients.length == 0) {
|
||||
dispatch(fetchRecipients({ accountId, transactionId }));
|
||||
|
|
@ -60,7 +56,6 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
|||
|
||||
// For Recipient
|
||||
const [dropDownToggle, setDropDownToggle] = useState(false);
|
||||
const [dropDownTogglePage, setDropDownTogglePage] = useState<string>("");
|
||||
|
||||
const selectedRecipient = selectedRecipients[selectedRecipientIndx];
|
||||
|
||||
|
|
@ -84,26 +79,26 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
|||
}, [selectedRecipient])
|
||||
|
||||
if (selectedRecipient && selectedRecipient.type == recipientType.CC) {
|
||||
return <div className='col-span-2 bg-white h-full p-4 overflow-y-auto'></div>;
|
||||
return <div className='sm:col-span-2 bg-white h-full p-4 overflow-y-auto'></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Left Side Bar - START */}
|
||||
<div className='col-span-2'>
|
||||
<div className='sm:col-span-2'>
|
||||
|
||||
{
|
||||
(isPages || isFields) && <div className={`sm:block p-4 absolute sm:top-[0] rounded-tr-md z-50 sm:relative sm:col-span-2 bg-white border-t-1 border-r-1 border-slate-300 sm:h-[calc(100vh-40px)] overflow-y-auto sm:w-auto ${isFields ? "h-[150px] bottom-0 overflow-x-hidden w-full" : "h-[calc(100vh-40px)]"} `}>
|
||||
<div
|
||||
(isFields || window.innerWidth > 638) && <div className={`sm:block p-4 absolute sm:top-[0] rounded-tr-md z-50 sm:relative sm:col-span-2 bg-white border-t-1 border-r-1 border-slate-300 sm:h-[calc(100vh-40px)] overflow-y-auto sm:w-auto ${(isFields || window.innerWidth > 638) ? "h-[100px] bottom-0 overflow-x-hidden w-full" : "h-[calc(100vh-40px)]"} `}>
|
||||
{/* <div
|
||||
onClick={() => {
|
||||
handleSetIsPages(false)
|
||||
// handleSetIsPages(false)
|
||||
handleSetIsFields(false)
|
||||
}}
|
||||
className='absolute sm:hidden top-[5px] right-[10px] bg-white rounded-full text-2xl z-50'>
|
||||
<IoMdClose />
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className='hidden sm: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 className='hidden sm: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={() => handleSetIsFields(!isFields)}
|
||||
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`}>
|
||||
|
|
@ -112,15 +107,15 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
|||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => handleSetIsPages(!isPages)}
|
||||
// onClick={() => handleSetIsPages(!isPages)}
|
||||
className={`flex items-center gap-1 cursor-pointer text-xs w-full justify-center py-1.5 rounded-2xl ${isPages ? 'bg-[var(--primary)] text-white' : ''} transition-all duration-300`}>
|
||||
<IoIosDocument />
|
||||
<p className='tracking-wide'>Pages</p>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
|
||||
{selectedRecipients.length > 0 && isFields && <>
|
||||
{selectedRecipients.length > 0 && (isFields || window.innerWidth > 638) && <>
|
||||
{/* Fields List and Recipeint - Start */}
|
||||
{/* Side Bar showing Recipients */}
|
||||
<div className='sm:relative sm:top-0 sm:left-0 fixed w-full'>
|
||||
|
|
@ -183,7 +178,10 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
|||
<div
|
||||
id='tags-con-dash-left'
|
||||
className='mt-2 w-full h-[400px] sm:h-auto'>
|
||||
<FieldList color={selectedRecipient?.color || '#2d2d2d'} recipientId={selectedRecipient.id} recipientIndx={selectedRecipientIndx} />
|
||||
<FieldList
|
||||
color={selectedRecipient?.color || '#2d2d2d'}
|
||||
recipientId={selectedRecipient.id}
|
||||
recipientIndx={selectedRecipientIndx} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -191,99 +189,6 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
|||
|
||||
{/* Fields List and Recipeint - End */}
|
||||
</>}
|
||||
|
||||
{isPages && <>
|
||||
{/* Pages - START */}
|
||||
<div className='flex flex-col gap-2'>
|
||||
{selectedDocuments.length && selectedDocuments.map((document, indx) => {
|
||||
return (
|
||||
<div
|
||||
key={indx}
|
||||
// tabIndex={1}
|
||||
// onBlur={() => setDropDownTogglePage('')}
|
||||
>
|
||||
<div
|
||||
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`}>
|
||||
{
|
||||
document.pages != undefined && <img
|
||||
key={indx}
|
||||
className='rounded-sm shadow'
|
||||
src={"data:image/png;base64," + document.thumbnail}
|
||||
alt="" />
|
||||
}
|
||||
|
||||
<div
|
||||
className='flex items-center justify-between w-full text-xs rounded-sm cursor-pointer text-slate-100'>
|
||||
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[18ch]'>{document.name}</p>
|
||||
<MdKeyboardArrowDown className={`${(dropDownTogglePage == 'pageDropDown' + indx) ? 'rotate-180' : ''}`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{
|
||||
dropDownTogglePage == 'pageDropDown' + indx &&
|
||||
<div className="my-1 flex justify-center items-center gap-2 flex-wrap">
|
||||
{/* Prev Button */}
|
||||
<a
|
||||
href={"#" + document.id + (currentPage - 1)}
|
||||
className={`cursor-pointer px-2 py-1 shadow rounded-sm text-sm bg-slate-200 hover:border-[var(--primary)] transition-all duration-300 ${currentPage == 0 ? "pointer-events-none opacity-50" : ""
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
if (currentPage == 0) e.preventDefault();
|
||||
else setCurrentPage(currentPage - 1);
|
||||
}}
|
||||
>
|
||||
<GrFormPrevious />
|
||||
</a>
|
||||
|
||||
{/* Page Selector */}
|
||||
<select
|
||||
className="w-[58px] py-1 text-xs text-slate-400 outline-none"
|
||||
value={currentPage}
|
||||
onChange={(e) => {
|
||||
const newPage = Number(e.target.value);
|
||||
setCurrentPage(newPage);
|
||||
window.location.hash = document.id + "" + newPage;
|
||||
}}
|
||||
>
|
||||
{document.pages != undefined && document.pages.map((_, pageIndex) => (
|
||||
<option key={pageIndex} value={pageIndex}>
|
||||
{pageIndex + 1}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<span className="text-xs text-slate-600">
|
||||
/ {document.pages?.length}
|
||||
</span>
|
||||
|
||||
{/* Next Button */}
|
||||
<a
|
||||
href={"#" + document.id + (currentPage + 1)}
|
||||
className={`cursor-pointer px-2 py-1 shadow rounded-sm text-sm bg-slate-200 hover:border-[var(--primary)] transition-all duration-300 ${currentPage == document.pages?.length - 1 ? "pointer-events-none opacity-50" : ""
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
if (currentPage == document.pages?.length - 1) e.preventDefault();
|
||||
else setCurrentPage(currentPage + 1);
|
||||
}}
|
||||
>
|
||||
<MdOutlineNavigateNext />
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{/* Pages - END */}
|
||||
</>}
|
||||
</div>
|
||||
}
|
||||
{/* Left Side Bar - END */}
|
||||
|
|
@ -292,29 +197,30 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, isPages, selectedRe
|
|||
<div>
|
||||
|
||||
{/* Left Side Pages */}
|
||||
{!isPages && <div
|
||||
{<div
|
||||
onClick={() => {
|
||||
handleSetIsPages(true)
|
||||
handleSetIsFields(false)
|
||||
// handleSetIsFields(false)
|
||||
}}
|
||||
className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] z-50 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'>
|
||||
className='sm:hidden fixed top-[8%] left-0 bg-[var(--primary)] z-50 flex items-center text-white rounded-tr-sm rounded-br-sm cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'>
|
||||
<div className='py-2 pl-2 pr-2'>
|
||||
{/* <FaAngleUp className='rotate-90' /> */}
|
||||
<SlDocs />
|
||||
{/* <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>
|
||||
</p> */}
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
|
||||
{!isFields && <div
|
||||
className='sm:hidden fixed bottom-0 left-0 rounded-tr-2xl rounded-tl-2xl bg-[var(--primary)] z-50 w-full flex justify-center text-white cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'
|
||||
{(!isFields && window.innerWidth <= 638) && <div
|
||||
className='sm:hidden fixed bottom-0 left-0 rounded-tr-sm rounded-tl-sm bg-[var(--primary)] z-50 w-full flex justify-center text-white cursor-pointer opacity-50 hover:opacity-100 transition-all duration-300'
|
||||
onClick={() => {
|
||||
handleSetIsPages(false)
|
||||
// handleSetIsPages(false)
|
||||
handleSetIsFields(true)
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,18 +3,19 @@ import React, { useEffect, useState } from 'react'
|
|||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from '../../../store/store'
|
||||
|
||||
import { FaCheck, FaSignature } from 'react-icons/fa'
|
||||
import { IoIosDocument } from 'react-icons/io'
|
||||
import { LuTableOfContents } from 'react-icons/lu'
|
||||
import { FaAngleRight, FaCheck, FaRegCopy, FaSignature } from 'react-icons/fa'
|
||||
import { IoIosDocument, IoMdHome } from 'react-icons/io'
|
||||
import { LuRedo2, LuUndo2 } from 'react-icons/lu'
|
||||
import { MdKeyboardArrowDown } from 'react-icons/md'
|
||||
import { RiResetRightLine } from 'react-icons/ri'
|
||||
import { fetchRecipients, recipientType, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice'
|
||||
import { fetchDocuments } from '../../../store/slices/documentSlice'
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||
import { Bounce, toast } from 'react-toastify'
|
||||
import { FiChevronDown } from 'react-icons/fi'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import RecipientsCon from '../processStepOne/RecipientsCon'
|
||||
import MessageCon from '../processStepOne/MessageCon'
|
||||
import { IoArrowBackSharp } from 'react-icons/io5'
|
||||
import { FaRegPaste } from 'react-icons/fa6'
|
||||
import ConfirmSendModal from './TransactionSendConfirmationModal'
|
||||
import CloseConfirmationModal from './CloseCofirmationModal'
|
||||
|
||||
interface NavTwoProps {
|
||||
transactionSaving?: boolean;
|
||||
|
|
@ -29,6 +30,7 @@ interface NavTwoProps {
|
|||
aId?: string | null
|
||||
tId?: string | null
|
||||
onSendClick?: (isSave?: boolean, navigate?: boolean, navigateTo?: string) => void;
|
||||
onZoomChange?: React.Dispatch<React.SetStateAction<number>>
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -54,20 +56,37 @@ const fitObj = [
|
|||
label: "200%",
|
||||
value: "200"
|
||||
},
|
||||
{
|
||||
label: "250%",
|
||||
value: "250"
|
||||
},
|
||||
{
|
||||
label: "300%",
|
||||
value: "300"
|
||||
},
|
||||
{
|
||||
label: "350%",
|
||||
value: "350"
|
||||
},
|
||||
{
|
||||
label: "400%",
|
||||
value: "400"
|
||||
},
|
||||
]
|
||||
|
||||
const NavTwo: React.FC<NavTwoProps> = ({
|
||||
transactionSaving = false,
|
||||
isFields,
|
||||
setIsFields,
|
||||
isPages,
|
||||
setIsPages,
|
||||
isSend,
|
||||
setIsSend,
|
||||
// isFields,
|
||||
// setIsFields,
|
||||
// isPages,
|
||||
// setIsPages,
|
||||
// isSend,
|
||||
// setIsSend,
|
||||
isPreview = false,
|
||||
aId = null,
|
||||
tId = null,
|
||||
onSendClick,
|
||||
onZoomChange
|
||||
}) => {
|
||||
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -75,6 +94,11 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
const [showActionsDropDown, setShowActionsDropDown] = useState<boolean>(false);
|
||||
const [editRecipientsModal, setEditRecipientsModal] = useState<boolean>(false);
|
||||
const [messageModal, setMessageModal] = useState<boolean>(false);
|
||||
const [confirmSendModalOpen, setConfirmSendModalOpen] = useState({
|
||||
"open": false,
|
||||
"confirm": false,
|
||||
"loading": false
|
||||
});
|
||||
|
||||
console.log(editRecipientsModal)
|
||||
|
||||
|
|
@ -100,6 +124,8 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
// const [transactionSaving, setTransactionSaving] = useState<boolean>(false);
|
||||
const [dotCount, setDotCount] = useState(0);
|
||||
|
||||
const [closePage, setClosePage] = useState(false);
|
||||
|
||||
const [isFitDd, setIsFitDd] = useState<boolean>(false)
|
||||
const [selFitIndx, setSelFitIndx] = useState<number>(0);
|
||||
const [selfUserSign, setSelfUserSign] = useState({ sign: false, recipientId: 0 });
|
||||
|
|
@ -169,6 +195,17 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
return transactionSendValidationObj;
|
||||
}
|
||||
|
||||
// Remove the zoom() function and the useEffect that calls it
|
||||
// Instead:
|
||||
const handleFitSelect = (indx: number) => {
|
||||
setSelFitIndx(indx);
|
||||
let value = fitObj[indx].value;
|
||||
if (value === "FIT") value = "100";
|
||||
onZoomChange?.(Number(value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
function handleSend(isSignSend: boolean = false) {
|
||||
// dispatch(showLoader("Loading ..."));
|
||||
if (onSendClick === undefined) return;
|
||||
|
|
@ -184,6 +221,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
// setIsSend(!isSend);
|
||||
|
||||
// setTimeout(() => {
|
||||
setConfirmSendModalOpen({ open: false, confirm: false, loading: false })
|
||||
if (transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
|
||||
if (isSignSend) {
|
||||
// SELF SIGN CHECK HERE
|
||||
|
|
@ -198,20 +236,24 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
// }, 1000)
|
||||
}
|
||||
|
||||
const zoom = (percentage: number, contentClass: string) => {
|
||||
const content = document.querySelector(`.${contentClass}`);
|
||||
if (content) {
|
||||
(content as HTMLElement).style.zoom = `${percentage}%`;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let indx = fitObj[selFitIndx].value
|
||||
if (indx == "FIT") {
|
||||
indx = "100";
|
||||
}
|
||||
zoom(Number(indx), 'zoom-con')
|
||||
}, [selFitIndx])
|
||||
if (confirmSendModalOpen.confirm) handleSend()
|
||||
}, [confirmSendModalOpen])
|
||||
|
||||
// const zoom = (percentage: number, contentClass: string) => {
|
||||
// const content = document.querySelector(`.${contentClass}`);
|
||||
// if (content) {
|
||||
// (content as HTMLElement).style.zoom = `${percentage}%`;
|
||||
// }
|
||||
// }
|
||||
|
||||
// useEffect(() => {
|
||||
// let indx = fitObj[selFitIndx].value
|
||||
// if (indx == "FIT") {
|
||||
// indx = "100";
|
||||
// }
|
||||
// zoom(Number(indx), 'zoom-con')
|
||||
// }, [selFitIndx])
|
||||
|
||||
useEffect(() => {
|
||||
if (!transactionSaving) return;
|
||||
|
|
@ -227,50 +269,121 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
{closePage && <CloseConfirmationModal
|
||||
open={closePage}
|
||||
onClose={() => setClosePage(false)} // Cancel not closing
|
||||
onConfirm={() => navigate("/")}
|
||||
/>}
|
||||
|
||||
<ConfirmSendModal
|
||||
confirmSendModalOpen={confirmSendModalOpen.open}
|
||||
confirmSendModalOnClose={() => { setConfirmSendModalOpen({ open: false, confirm: false, loading: false }) }}
|
||||
confirmSendModalOnConfirm={() => { setConfirmSendModalOpen({ open: true, confirm: true, loading: true }) }}
|
||||
confirmSendModalLoading={confirmSendModalOpen.loading}
|
||||
documents={selectedDocuments}
|
||||
recipients={selectedRecipients}
|
||||
/>
|
||||
{/* Nav Two - Oprions - START */}
|
||||
<div className='flex justify-between items-center px-4 py-1 bg-white'>
|
||||
<div className='relative flex justify-between items-center px-4 py-1 bg-white'>
|
||||
|
||||
{/* Left Side */}
|
||||
<div className='flex justify-start items-center gap-2'>
|
||||
|
||||
<div>
|
||||
{/* <div>
|
||||
<Link to={"/"}>
|
||||
<img width={120} src="/sme-colored.png" alt="Sign Made Easy" />
|
||||
</Link>
|
||||
</div> */}
|
||||
|
||||
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='flex gap-4 text-xl text-slate-600'>
|
||||
<button
|
||||
className='hover:text-[var(--primary)] durartion-300 cursor-pointer'
|
||||
onClick={() => setClosePage(true)}
|
||||
>
|
||||
<IoMdHome className='text-[var(--primary)] sm:text-slate-600' />
|
||||
{/* <img width={120} src="/sme-colored.png" alt="Sign Made Easy" /> */}
|
||||
</button>
|
||||
<button
|
||||
className='hover:text-[var(--primary)] durartion-300 cursor-pointer'
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<IoArrowBackSharp />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className='w-[0px] md:w-[1px] h-[28px] bg-slate-200'></div>
|
||||
|
||||
|
||||
<div className='hidden md:flex items-center gap-2 text-xs'>
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className='underline font-thin cursor-pointer'>
|
||||
Set Up Transaction
|
||||
</button>
|
||||
<FaAngleRight className='font-thin' />
|
||||
<p className='font-normal'>Add Fields</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
tabIndex={1}
|
||||
onBlur={() => setIsFitDd(false)}
|
||||
className='hidden absolute gap-2 lg:flex items-center left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50'
|
||||
>
|
||||
|
||||
|
||||
|
||||
<div
|
||||
onClick={() => setIsFitDd(!isFitDd)}
|
||||
className='hidden sm:flex md:flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
|
||||
<p className='tracking-wide'>Fit</p>
|
||||
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
|
||||
<div className='flex'>
|
||||
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
||||
<LuUndo2 className='text-sm text-slate-600' />
|
||||
</p>
|
||||
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
||||
<LuRedo2 className='text-sm text-slate-600' />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isFitDd && <div className='absolute bg-white rounded-sm z-[1000] shadow'>
|
||||
{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
|
||||
<div className='w-full h-[20px] border-[1px] border-slate-200'></div>
|
||||
|
||||
<div className='flex'>
|
||||
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
||||
<FaRegCopy className='text-sm text-slate-600' />
|
||||
</p>
|
||||
<p className='p-2 hover:bg-slate-100 cursor-pointer rounded-sm'>
|
||||
<FaRegPaste className='text-sm text-slate-600' />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='w-full h-[20px] border-[1px] border-slate-200'></div>
|
||||
|
||||
<div className='border border-slate-400 rounded-sm ml-1'>
|
||||
<div
|
||||
onClick={() => setIsFitDd(!isFitDd)}
|
||||
className='hidden sm:flex md:flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'
|
||||
>
|
||||
<p className='tracking-wide'>{fitObj[selFitIndx]?.label}</p>
|
||||
<MdKeyboardArrowDown style={{ transform: `${isFitDd ? "rotate(180deg)" : ""}` }} />
|
||||
</div>
|
||||
|
||||
{isFitDd && <div className='absolute bg-white rounded-sm z-[1000] shadow'>
|
||||
{fitObj.map((val, indx) => {
|
||||
return (<div
|
||||
key={indx}
|
||||
// onClick={() => setSelFitIndx(indx)}
|
||||
onClick={() => handleFitSelect(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>
|
||||
|
||||
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>
|
||||
|
||||
|
||||
|
|
@ -311,7 +424,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
setShowActionsDropDown(false)
|
||||
}}
|
||||
className="cursor-pointer w-full text-left px-4 py-2 text-sm hover:bg-gray-100">
|
||||
Save
|
||||
Save Changes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
|
@ -319,7 +432,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
setShowActionsDropDown(false);
|
||||
}}
|
||||
className="cursor-pointer w-full text-left px-4 py-2 text-sm hover:bg-gray-100">
|
||||
Save & Close
|
||||
Save & Exit
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
|
|
@ -402,7 +515,7 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
{!isPreview && <>
|
||||
<div
|
||||
id='send-sign-dash-btn'
|
||||
onClick={() => handleSend()}
|
||||
onClick={() => setConfirmSendModalOpen({ open: true, confirm: false, loading: false })}
|
||||
className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]'>
|
||||
<p className='tracking-wide text-white'>
|
||||
{transactionTemplateToggle == TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES ? "Save" : "Send"}
|
||||
|
|
@ -422,8 +535,9 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
|
||||
|
||||
{editRecipientsModal && accountId && transactionId && <>
|
||||
<div className="w-full h-full absolute top-0 left-0 z-[1000] flex justify-center items-center">
|
||||
<div className="w-full h-full bg-slate-100 opacity-85 blur-2xl absolute top-0 left-0"></div>
|
||||
<div className="w-full h-[100vh] absolute top-0 left-0 z-[1000] flex justify-center items-center">
|
||||
{/* <div className="w-full h-full bg-slate-100 opacity-40 blur-2xl absolute top-0 left-0"></div> */}
|
||||
<div className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50" />
|
||||
<div className="z-50 bg-white p-4 rounded-sm shadow-md">
|
||||
<RecipientsCon
|
||||
transactionTemplateToggle={transactionTemplateToggle}
|
||||
|
|
@ -437,8 +551,9 @@ const NavTwo: React.FC<NavTwoProps> = ({
|
|||
</>}
|
||||
|
||||
{messageModal && accountId && transactionId && <>
|
||||
<div className="w-full h-full absolute top-0 left-0 z-[1000] flex justify-center items-center">
|
||||
<div className="w-full h-full bg-slate-100 opacity-85 blur-2xl absolute top-0 left-0"></div>
|
||||
<div className="w-full h-[100vh] absolute top-0 left-0 z-[1000] flex justify-center items-center">
|
||||
{/* <div className="w-full h-full bg-slate-100 opacity-40 blur-2xl absolute top-0 left-0"></div> */}
|
||||
<div className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50" />
|
||||
<div className="z-50 bg-white p-4 rounded-sm shadow-md">
|
||||
<MessageCon
|
||||
transactionTemplateToggle={transactionTemplateToggle}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const ProcessStepTwo = () => {
|
|||
const windowWidth = window.innerWidth;
|
||||
|
||||
const [isFields, setIsFields] = useState(windowWidth > 638 ? true : false);
|
||||
const [isPages, setIsPages] = useState(false);
|
||||
const [isPages, setIsPages] = useState(windowWidth > 638 ? true : false);
|
||||
|
||||
const [selectedRecipient, setSelectedRecipient] = useState(0);
|
||||
|
||||
|
|
@ -70,21 +70,13 @@ const ProcessStepTwo = () => {
|
|||
|
||||
|
||||
const handleSetIsFields = (setTo: boolean) => {
|
||||
if (setTo != undefined && setTo != isFields) {
|
||||
setIsFields(setTo)
|
||||
if (setTo == true) {
|
||||
setIsPages(false)
|
||||
}
|
||||
};
|
||||
setIsFields(true)
|
||||
setIsPages(false)
|
||||
}
|
||||
|
||||
const handleSetIsPages = (setTo: boolean) => {
|
||||
if (setTo != undefined && setTo != isPages) {
|
||||
setIsPages(setTo)
|
||||
if (setTo == true) {
|
||||
setIsFields(false)
|
||||
}
|
||||
};
|
||||
setIsFields(false)
|
||||
setIsPages(true)
|
||||
}
|
||||
|
||||
const docRef = useRef<DocumentContainerRef>(null);
|
||||
|
|
@ -93,13 +85,59 @@ const ProcessStepTwo = () => {
|
|||
docRef.current?.handleTransactionProcess(isSave, isNavigate, navigateTo);
|
||||
};
|
||||
|
||||
const [zoomScale, setZoomScale] = useState<number>(100); // percentage
|
||||
|
||||
// To center the container on every zoom scale change - START
|
||||
useEffect(() => {
|
||||
const container = document.getElementById("doc-con");
|
||||
if (!container) return;
|
||||
|
||||
const prevScrollRatio = container.scrollTop / (container.scrollHeight || 1);
|
||||
|
||||
const newScrollTop = prevScrollRatio * container.scrollHeight;
|
||||
const newScrollLeft = (container.scrollWidth - container.clientWidth) / 2;
|
||||
|
||||
container.scrollTo({
|
||||
top: newScrollTop,
|
||||
left: newScrollLeft,
|
||||
behavior: "instant" as ScrollBehavior
|
||||
});
|
||||
}, [zoomScale]);
|
||||
// To center the container on every zoom scale change - END
|
||||
|
||||
return (
|
||||
<div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}>
|
||||
<NavTwo transactionSaving={transactionSaving} isFields={isFields} isPages={isPages} setIsPages={setIsPages} setIsFields={setIsFields} isSend={isSend} setIsSend={setIsSend} onSendClick={handleSendClick} />
|
||||
<NavTwo
|
||||
onZoomChange={setZoomScale}
|
||||
transactionSaving={transactionSaving}
|
||||
// isFields={isFields}
|
||||
// isPages={isPages}
|
||||
// setIsPages={setIsPages}
|
||||
// setIsFields={setIsFields}
|
||||
// isSend={isSend}
|
||||
// setIsSend={setIsSend}
|
||||
onSendClick={handleSendClick} />
|
||||
|
||||
<div className='sm:grid sm:grid-cols-12 sm:gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'>
|
||||
<LeftSideBar isFields={isFields} isPages={isPages} selectedRecipientIndx={selectedRecipient} setSelectedRecipient={setSelectedRecipient} handleSetIsFields={handleSetIsFields} handleSetIsPages={handleSetIsPages} />
|
||||
<DocumentContainer ref={docRef} transactionSaving={transactionSaving} setTransactionSaving={setTransactionSaving} isSend={isSend} setIsSend={setIsSend} />
|
||||
<LeftSideBar
|
||||
isFields={isFields}
|
||||
isPages={isPages}
|
||||
selectedRecipientIndx={selectedRecipient}
|
||||
setSelectedRecipient={setSelectedRecipient}
|
||||
handleSetIsFields={handleSetIsFields}
|
||||
handleSetIsPages={handleSetIsPages}
|
||||
/>
|
||||
<DocumentContainer
|
||||
isPages={isPages}
|
||||
setIsPages={setIsPages}
|
||||
ref={docRef}
|
||||
zoomScale={zoomScale}
|
||||
selectedRecipientIndx={selectedRecipient}
|
||||
transactionSaving={transactionSaving}
|
||||
setTransactionSaving={setTransactionSaving}
|
||||
isSend={isSend}
|
||||
setIsSend={setIsSend}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{globalLoader.isLoading && <Loader />}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,351 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { IoClose } from "react-icons/io5";
|
||||
import { FiFileText, FiUsers } from "react-icons/fi";
|
||||
|
||||
interface Recipient {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
interface DocumentItem {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface ConfirmSendModalProps {
|
||||
confirmSendModalOpen: boolean;
|
||||
confirmSendModalOnClose: () => void;
|
||||
confirmSendModalOnConfirm: () => void;
|
||||
confirmSendModalLoading?: boolean;
|
||||
|
||||
documents: DocumentItem[];
|
||||
recipients: Recipient[];
|
||||
}
|
||||
|
||||
const ConfirmSendModal = ({
|
||||
confirmSendModalOpen,
|
||||
confirmSendModalOnClose,
|
||||
confirmSendModalOnConfirm,
|
||||
confirmSendModalLoading = false,
|
||||
documents,
|
||||
recipients,
|
||||
}: ConfirmSendModalProps) => {
|
||||
|
||||
// Close on ESC
|
||||
useEffect(() => {
|
||||
const handleEsc = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
confirmSendModalOnClose();
|
||||
}
|
||||
};
|
||||
|
||||
if (confirmSendModalOpen) {
|
||||
document.addEventListener("keydown", handleEsc);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEsc);
|
||||
};
|
||||
}, [confirmSendModalOpen]);
|
||||
|
||||
// Prevent body scroll when modal open
|
||||
useEffect(() => {
|
||||
if (confirmSendModalOpen) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "auto";
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = "auto";
|
||||
};
|
||||
}, [confirmSendModalOpen]);
|
||||
|
||||
if (!confirmSendModalOpen) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={confirmSendModalOnClose}
|
||||
className="
|
||||
fixed
|
||||
inset-0
|
||||
z-[9999]
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
bg-black/50
|
||||
backdrop-blur-sm
|
||||
p-3
|
||||
sm:p-4
|
||||
"
|
||||
>
|
||||
|
||||
{/* Modal */}
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="
|
||||
w-full
|
||||
max-w-xl
|
||||
max-h-[90vh]
|
||||
flex
|
||||
flex-col
|
||||
rounded-sm
|
||||
bg-white
|
||||
shadow-2xl
|
||||
overflow-hidden
|
||||
animate-in
|
||||
fade-in
|
||||
zoom-in
|
||||
duration-200
|
||||
"
|
||||
>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between border-b border-slate-200 p-5 sm:p-6">
|
||||
|
||||
<div className="pr-4">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold text-slate-800">
|
||||
Review & Send
|
||||
</h2>
|
||||
|
||||
<p className="mt-1 text-sm text-slate-500 leading-relaxed">
|
||||
Please review the documents and recipient details
|
||||
before sending this transaction for signing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={confirmSendModalOnClose}
|
||||
className="
|
||||
shrink-0
|
||||
p-2
|
||||
rounded-sm
|
||||
hover:bg-slate-100
|
||||
transition
|
||||
cursor-pointer
|
||||
"
|
||||
>
|
||||
<IoClose className="text-xl text-slate-500" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-y-auto p-4 sm:p-6 space-y-5">
|
||||
|
||||
{/* Documents */}
|
||||
<div className="rounded-sm border border-slate-200 overflow-hidden">
|
||||
|
||||
<div className="flex items-center gap-2 border-b border-slate-200 bg-slate-50 px-4 py-3">
|
||||
<FiFileText className="text-slate-600" />
|
||||
|
||||
<h3 className="font-medium text-slate-800">
|
||||
Documents ({documents.length})
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="max-h-[220px] overflow-y-auto p-3 space-y-2">
|
||||
|
||||
{documents.map((document) => (
|
||||
<div
|
||||
key={document.id}
|
||||
className="
|
||||
flex
|
||||
items-center
|
||||
gap-3
|
||||
rounded-sm
|
||||
border
|
||||
border-slate-200
|
||||
bg-white
|
||||
px-3
|
||||
py-3
|
||||
"
|
||||
>
|
||||
|
||||
<div
|
||||
className="
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
w-10
|
||||
h-10
|
||||
rounded-sm
|
||||
bg-slate-100
|
||||
shrink-0
|
||||
"
|
||||
>
|
||||
<FiFileText className="text-slate-600" />
|
||||
</div>
|
||||
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium text-slate-800 truncate">
|
||||
{document.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recipients */}
|
||||
<div className="rounded-sm border border-slate-200 overflow-hidden">
|
||||
|
||||
<div className="flex items-center gap-2 border-b border-slate-200 bg-slate-50 px-4 py-3">
|
||||
<FiUsers className="text-slate-600" />
|
||||
|
||||
<h3 className="font-medium text-slate-800">
|
||||
Recipients ({recipients.length})
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="max-h-[260px] overflow-y-auto p-3 space-y-2">
|
||||
|
||||
{recipients.map((recipient) => (
|
||||
<div
|
||||
key={recipient.id}
|
||||
className="
|
||||
flex
|
||||
items-center
|
||||
justify-between
|
||||
gap-3
|
||||
rounded-sm
|
||||
border
|
||||
border-slate-200
|
||||
bg-white
|
||||
px-3
|
||||
py-3
|
||||
"
|
||||
>
|
||||
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
|
||||
<div
|
||||
className="w-3 h-3 rounded-full shrink-0"
|
||||
style={{
|
||||
background:
|
||||
recipient.color || "#6366f1",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium text-slate-800 truncate">
|
||||
{recipient.name}
|
||||
</p>
|
||||
|
||||
<p className="text-xs text-slate-500 truncate">
|
||||
{recipient.email}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="
|
||||
shrink-0
|
||||
rounded-full
|
||||
bg-amber-50
|
||||
border
|
||||
border-amber-200
|
||||
px-2.5
|
||||
py-1
|
||||
text-xs
|
||||
font-medium
|
||||
text-amber-700
|
||||
"
|
||||
>
|
||||
Pending
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<div
|
||||
className="
|
||||
rounded-sm
|
||||
border
|
||||
border-indigo-100
|
||||
bg-indigo-50
|
||||
px-4
|
||||
py-4
|
||||
"
|
||||
>
|
||||
<p className="text-sm leading-relaxed text-indigo-900">
|
||||
Once confirmed, recipients will receive an email
|
||||
invitation to review and sign the documents.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div
|
||||
className="
|
||||
flex
|
||||
flex-col-reverse
|
||||
sm:flex-row
|
||||
items-stretch
|
||||
sm:items-center
|
||||
justify-end
|
||||
gap-3
|
||||
border-t
|
||||
border-slate-200
|
||||
bg-white
|
||||
p-4
|
||||
sm:p-5
|
||||
"
|
||||
>
|
||||
|
||||
<button
|
||||
onClick={confirmSendModalOnClose}
|
||||
className="
|
||||
w-full
|
||||
sm:w-auto
|
||||
text-sm
|
||||
px-4
|
||||
py-2.5
|
||||
rounded-sm
|
||||
border
|
||||
border-slate-300
|
||||
text-slate-700
|
||||
hover:bg-slate-100
|
||||
transition
|
||||
cursor-pointer
|
||||
font-medium
|
||||
"
|
||||
>
|
||||
Continue Reviewing
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={confirmSendModalOnConfirm}
|
||||
disabled={confirmSendModalLoading}
|
||||
className="
|
||||
w-full
|
||||
sm:w-auto
|
||||
text-sm
|
||||
px-5
|
||||
py-2.5
|
||||
rounded-sm
|
||||
bg-[var(--primary)]
|
||||
text-white
|
||||
hover:opacity-100
|
||||
transition
|
||||
opacity-80
|
||||
disabled:opacity-50
|
||||
disabled:cursor-not-allowed
|
||||
cursor-pointer
|
||||
font-medium
|
||||
"
|
||||
>
|
||||
{confirmSendModalLoading
|
||||
? "Sending..."
|
||||
: "Confirm & Send"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmSendModal;
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { RootState } from '../../../../store/store';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { DroppedItemsObj } from '../DocumentContainer';
|
||||
import { MdKeyboardArrowUp, MdMoreVert } from 'react-icons/md';
|
||||
import { PiTagSimpleFill } from 'react-icons/pi';
|
||||
|
||||
const DocumentPages: React.FC<{
|
||||
isPages: boolean;
|
||||
droppedItems: DroppedItemsObj;
|
||||
setIsPages: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}> = ({ isPages, droppedItems, setIsPages }) => {
|
||||
|
||||
type RecipientPageTagInfo = {
|
||||
color: string;
|
||||
recipientIndex: number;
|
||||
recipientTagCount?: number;
|
||||
};
|
||||
|
||||
type DocumentTagsMapping = {
|
||||
[documentId: string]: {
|
||||
[pageIndex: string]: RecipientPageTagInfo[];
|
||||
};
|
||||
};
|
||||
|
||||
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
|
||||
const [documentPagesTagsMapping, setDocumentPagesTagsMapping] = useState<DocumentTagsMapping>({});
|
||||
|
||||
const recipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
|
||||
|
||||
const [openDocs, setOpenDocs] = useState<Record<string, boolean>>(
|
||||
() => Object.fromEntries(selectedDocuments.map((d, i) => [d.id, i === 0]))
|
||||
)
|
||||
const [activePage, setActivePage] = useState<string | null>(
|
||||
selectedDocuments[0] ? `${selectedDocuments[0].id}_0` : null
|
||||
)
|
||||
|
||||
const toggleDoc = (docId: string) => {
|
||||
setOpenDocs(prev => ({ ...prev, [docId]: !prev[docId] }))
|
||||
}
|
||||
|
||||
const goToPage = (docId: string, pageIndex: number) => {
|
||||
const key = `${docId}_${pageIndex}`
|
||||
setActivePage(key)
|
||||
window.location.hash = docId + "" + pageIndex
|
||||
}
|
||||
|
||||
// useEffect(() => {
|
||||
// setTimeout(() => {
|
||||
|
||||
// let documentPagesTagsMappingLocal: {
|
||||
// [documentId: string]: {
|
||||
// [pageIndex: number]: [number, string];
|
||||
// };
|
||||
// }[] = [];
|
||||
// recipients && recipients.length && recipients.forEach((selectedRecipient, selectedRecipientIndx) => {
|
||||
// const selectedRecipientColor = selectedRecipient.color
|
||||
|
||||
// let activeRecipientTagsCount = 0;
|
||||
|
||||
// droppedItems && Object.keys(droppedItems).length > 0 && Object.keys(droppedItems).forEach(key => {
|
||||
// activeRecipientTagsCount = (droppedItems[key] && droppedItems[key].length) > 0 ? droppedItems[key].reduce((acc, item) => !item.isDeleted && item.recipientIndx == selectedRecipientIndx ? acc + 1 : acc, 0) : 0;
|
||||
|
||||
// if (activeRecipientTagsCount == 0) return;
|
||||
|
||||
// const [documentId, pageIndex] = key.split("_");
|
||||
|
||||
// if (!documentPagesTagsMappingLocal[documentId]) {
|
||||
// documentPagesTagsMappingLocal[documentId] = {};
|
||||
// }
|
||||
|
||||
// documentPagesTagsMappingLocal.push({ [documentId]: { [pageIndex]: [activeRecipientTagsCount, selectedRecipientColor] } })
|
||||
// })
|
||||
// })
|
||||
|
||||
// setDocumentPagesTagsMapping(documentPagesTagsMappingLocal)
|
||||
// }, 300)
|
||||
// // }, [droppedItems, selectedRecipientIndx])
|
||||
// }, [droppedItems])
|
||||
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
|
||||
let documentTagsMappingLocal: DocumentTagsMapping = {};
|
||||
|
||||
recipients &&
|
||||
recipients.length &&
|
||||
recipients.forEach((selectedRecipient, selectedRecipientIndx) => {
|
||||
|
||||
const selectedRecipientColor = selectedRecipient.color;
|
||||
|
||||
droppedItems &&
|
||||
Object.keys(droppedItems).length > 0 &&
|
||||
Object.keys(droppedItems).forEach(key => {
|
||||
|
||||
const activeRecipientTagsCount =
|
||||
(droppedItems[key] && droppedItems[key].length) > 0
|
||||
? droppedItems[key].reduce((acc, item) => {
|
||||
return !item.isDeleted &&
|
||||
item.recipientIndx == selectedRecipientIndx
|
||||
? acc + 1
|
||||
: acc;
|
||||
}, 0)
|
||||
: 0;
|
||||
|
||||
if (activeRecipientTagsCount === 0) return;
|
||||
|
||||
const [documentId, pageIndex] = key.split("_");
|
||||
|
||||
// Create document object
|
||||
if (!documentTagsMappingLocal[documentId]) {
|
||||
documentTagsMappingLocal[documentId] = {};
|
||||
}
|
||||
|
||||
// Create page array
|
||||
if (!documentTagsMappingLocal[documentId][pageIndex]) {
|
||||
documentTagsMappingLocal[documentId][pageIndex] = [];
|
||||
}
|
||||
|
||||
// Push recipient info
|
||||
documentTagsMappingLocal[documentId][pageIndex].push({
|
||||
color: selectedRecipientColor,
|
||||
recipientIndex: selectedRecipientIndx,
|
||||
recipientTagCount: activeRecipientTagsCount,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
setDocumentPagesTagsMapping(documentTagsMappingLocal);
|
||||
|
||||
}, 300);
|
||||
|
||||
}, [droppedItems]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (!isPages) return
|
||||
const docPagesCon = document.getElementById("document-pages");
|
||||
|
||||
if (docPagesCon) {
|
||||
docPagesCon.focus();
|
||||
}
|
||||
}, 0)
|
||||
}, [isPages]);
|
||||
|
||||
return (
|
||||
<div id='document-pages'
|
||||
tabIndex={-1}
|
||||
onBlur={() => window.innerWidth <= 638 && setIsPages(false)}
|
||||
className={`h-[100vh] absolute ${isPages || window.innerWidth > 638 ? "" : "hidden"} top-0 left-0 sm:static max-w-max bg-slate-100 z-50 sm:col-span-2 flex flex-col w-full overflow-y-auto`}>
|
||||
{selectedDocuments.map((document, indx) => (
|
||||
<div key={indx} className="border-b border-slate-200 pb-3 mb-1">
|
||||
|
||||
{/* Section label (if any) */}
|
||||
{document.section && (
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-slate-400 px-3 pt-2 pb-1">
|
||||
{document.section}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Document Header */}
|
||||
<div
|
||||
onClick={() => toggleDoc(document.id)}
|
||||
className="flex items-start justify-between px-3 py-2.5 cursor-pointer hover:bg-slate-50 transition-colors"
|
||||
>
|
||||
<div>
|
||||
<p className="text-[13px] font-medium text-slate-800 leading-tight">{document.name}</p>
|
||||
<p className="text-[11px] text-slate-400 mt-0.5">
|
||||
{document.pages?.length} {document.pages?.length === 1 ? 'page' : 'pages'}
|
||||
</p>
|
||||
</div>
|
||||
<MdKeyboardArrowUp
|
||||
size={20}
|
||||
className={`text-slate-400 mt-0.5 flex-shrink-0 transition-transform duration-200 ${openDocs[document.id] ? '' : 'rotate-180'}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Page Thumbnails */}
|
||||
{openDocs[document.id] && (
|
||||
<div className="flex flex-col px-2">
|
||||
{document.pages?.map((_: any, pageIndex: number) => {
|
||||
const key = `${document.id}_${pageIndex}`
|
||||
const isActive = activePage === key
|
||||
|
||||
return (
|
||||
<div
|
||||
key={pageIndex}
|
||||
onClick={() => goToPage(document.id, pageIndex)}
|
||||
className="flex flex-col items-center py-2 px-1 rounded-md cursor-pointer hover:bg-slate-50 transition-colors"
|
||||
>
|
||||
{/* Big Thumbnail */}
|
||||
<div className={`relative sm:p-2 lg:p-4 bg-slate-100 rounded border flex items-center justify-center overflow-hidden
|
||||
${isActive ? 'border-blue-500 border-2' : 'border-slate-200'}`}
|
||||
>
|
||||
{document.thumbnail ? (
|
||||
<img
|
||||
src={"data:image/png;base64," + document.thumbnail}
|
||||
alt={`Page ${pageIndex + 1}`}
|
||||
className="w-[140px] h-[180px] h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-slate-300 text-4xl">Document</div>
|
||||
)}
|
||||
|
||||
{/* Page number + dots menu */}
|
||||
<div className="absolute bottom-0 bg-slate-800 flex items-center justify-between w-full pl-4 pr-2 py-3 bg-white">
|
||||
<span className="text-[13px] text-slate-500">{pageIndex + 1}</span>
|
||||
{
|
||||
// <>
|
||||
// <button
|
||||
// onClick={(e) => { e.stopPropagation(); /* open context menu */ }}
|
||||
// className="text-slate-400 hover:text-slate-600 hover:bg-slate-100 rounded p-0.5"
|
||||
// >
|
||||
// <MdMoreVert size={18} />
|
||||
// </button>
|
||||
// </>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='absolute top-4 left-2 h-[80%] overflow-y-auto overflow-x-hidden'>
|
||||
{documentPagesTagsMapping[document.id]?.[pageIndex]?.length > 0 && (
|
||||
<>
|
||||
{documentPagesTagsMapping[document.id][pageIndex].map((tagInfo, indx) => (
|
||||
<div key={indx} className='relative'>
|
||||
|
||||
{/* Count */}
|
||||
{/* Optional */}
|
||||
{/* <span className='absolute text-white text-[9px] font-medium top-[50%] left-[50%] transform -translate-x-1/2 -translate-y-1/2 pointer-events-none select-none z-10'>
|
||||
{tagInfo.recipientTagCount}
|
||||
</span> */}
|
||||
|
||||
{/* Tag */}
|
||||
<PiTagSimpleFill
|
||||
style={{
|
||||
fontSize: '1.2rem',
|
||||
color: tagInfo.color,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocumentPages
|
||||
|
|
@ -19,6 +19,8 @@ import TextValueFIll from "./attributes/TextValueFIll";
|
|||
import CurrTimeStampAttr from "./attributes/CurrTimeStampAttr";
|
||||
import ToolTipAttr from "./attributes/ToolTipAttr";
|
||||
|
||||
import "./style.css";
|
||||
|
||||
export interface DroppedItemProps {
|
||||
droppedItems: DroppedItem[]
|
||||
selectedTag: DroppedItem
|
||||
|
|
@ -91,7 +93,7 @@ const defaultStyles: StylesInterface = {
|
|||
textSize: 14
|
||||
};
|
||||
|
||||
const RightSideBar: React.FC<DroppedItemProps> = (
|
||||
const TagProperties: React.FC<DroppedItemProps> = (
|
||||
{
|
||||
droppedItems,
|
||||
selectedTag,
|
||||
|
|
@ -140,16 +142,32 @@ const RightSideBar: React.FC<DroppedItemProps> = (
|
|||
setDropDownToggle(null)
|
||||
}
|
||||
|
||||
// useEffect(() => {
|
||||
// if (selectedTag.label == "temporal" && !("dateFormat" in selectedTag)) {
|
||||
// handlePropertyChange("dateFormat", TEMPORAL.DATE);
|
||||
// handlePropertyChange("format", "DD/MM/YY");
|
||||
// }
|
||||
|
||||
// if (selectedTag.label == TagsTypes.TEXT && !("multiLine" in selectedTag)) {
|
||||
// handlePropertyChange("multiLine", true);
|
||||
// }
|
||||
// }, [selectedTag])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTag.label == "temporal" && !("dateFormat" in selectedTag)) {
|
||||
if (!selectedTag) return;
|
||||
|
||||
if (selectedTag.label === "temporal" && !("dateFormat" in selectedTag)) {
|
||||
handlePropertyChange("dateFormat", TEMPORAL.DATE);
|
||||
handlePropertyChange("format", "DD/MM/YY");
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedTag.label == TagsTypes.TEXT && !("multiLine" in selectedTag)) {
|
||||
if (selectedTag.label === TagsTypes.TEXT && !("multiLine" in selectedTag)) {
|
||||
handlePropertyChange("multiLine", true);
|
||||
}
|
||||
}, [selectedTag])
|
||||
|
||||
}, [selectedTag?.id, selectedTag?.label]);
|
||||
|
||||
const handleDecimalPlace = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const val = Number(e.target.value)
|
||||
|
|
@ -301,7 +319,10 @@ const RightSideBar: React.FC<DroppedItemProps> = (
|
|||
return (
|
||||
<>
|
||||
{/* Right Side Bar Start */}
|
||||
<div className='absolute border-l-1 border-t-1 border-slate-300 rounded-tl-lg right-0 top-[48px] sm:rounded-none sm:top-0 sm:border-0 sm:relative sm:col-span-2 sm:w-full h-full bg-white text-sm overflow-y-auto'>
|
||||
<div
|
||||
tabIndex={0}
|
||||
data-tag-properties="true"
|
||||
className='animate-slide-in-right z-50 absolute border-l-1 transition-all duration-600 border-t-1 border-slate-300 rounded-tl-lg right-0 top-[48px] sm:rounded-none sm:top-0 sm:border-0 sm:relative sm:col-span-2 sm:w-full h-full bg-white text-sm overflow-y-auto'>
|
||||
<div className="flex flex-row-reverse justify-end items-center">
|
||||
<p className="p-2 font-semibold text-md"><span className="capitalize">{selectedTag.type.toLowerCase()}</span></p>
|
||||
<p className="ml-2.5 cursor-pointer" >
|
||||
|
|
@ -485,4 +506,4 @@ const RightSideBar: React.FC<DroppedItemProps> = (
|
|||
)
|
||||
}
|
||||
|
||||
export default RightSideBar
|
||||
export default TagProperties
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
import { MdKeyboardArrowDown } from 'react-icons/md';
|
||||
import { DROPDOWNTOGGLE, MASKCHARACTER, MASKTYPE, NUMBER, SELECT, TEMPORAL, TEXT } from '../RightSideBar';
|
||||
import { DROPDOWNTOGGLE, MASKCHARACTER, MASKTYPE, NUMBER, SELECT, TEMPORAL, TEXT } from '../TagProperties';
|
||||
import { DroppedItem, TagsTypes } from '../../../../../utlity/types';
|
||||
import { dateFormats, timeFormats } from '../../../../../utlity/dateTimeFormats';
|
||||
import { FaCommentsDollar } from 'react-icons/fa';
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { MdKeyboardArrowDown } from "react-icons/md";
|
||||
import { DROPDOWNTOGGLE, MASKCHARACTER, MASKTYPE } from "../RightSideBar";
|
||||
import { DROPDOWNTOGGLE, MASKCHARACTER, MASKTYPE } from "../TagProperties";
|
||||
import { LuAsterisk } from "react-icons/lu";
|
||||
import { GoDotFill } from "react-icons/go";
|
||||
import { IoClose } from "react-icons/io5";
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from "react";
|
||||
import { DROPDOWNTOGGLE } from "./RightSideBar";
|
||||
import { DROPDOWNTOGGLE } from "./TagProperties";
|
||||
import { DroppedItemsObj } from "../DocumentContainer";
|
||||
import { IoMdClose } from "react-icons/io";
|
||||
import { TagOptionAttrType } from "../../../../utlity/types";
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { dateFormats, dateTimeFormats, monthFormats, timeFormats, weekFormats, yearFormats } from "../../../../../utlity/dateTimeFormats";
|
||||
import { DROPDOWNTOGGLE, TEMPORAL } from "../RightSideBar";
|
||||
import { DROPDOWNTOGGLE, TEMPORAL } from "../TagProperties";
|
||||
import { MdKeyboardArrowDown } from "react-icons/md";
|
||||
import dayjs from "dayjs";
|
||||
import { DroppedItem } from "../../../../../utlity/types";
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/* globals.css */
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-slide-in-right {
|
||||
animation: slideInRight 0.3s ease-out forwards;
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ interface Signature {
|
|||
}
|
||||
|
||||
interface ClientSignConProps {
|
||||
zoomScale: number,
|
||||
setOpenSign?: (value: boolean) => void;
|
||||
getAllSignatures: () => Promise<void>;
|
||||
// editingSignature?: Signature | null;
|
||||
|
|
@ -90,6 +91,7 @@ const fontsMap = [
|
|||
|
||||
|
||||
const ClientSignCon: React.FC<ClientSignConProps> = ({
|
||||
zoomScale,
|
||||
recipient,
|
||||
setOpenSign,
|
||||
recipientId,
|
||||
|
|
@ -98,7 +100,8 @@ const ClientSignCon: React.FC<ClientSignConProps> = ({
|
|||
signTagId
|
||||
}) => {
|
||||
|
||||
console.log("savedModal", recipient)
|
||||
// const scaleFactor: number = (zoomScale ?? 100) / 100;
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD' | 'SAVED'>('DRAW');
|
||||
const [fullName, setFullName] = useState('');
|
||||
const [initials, setInitials] = useState('');
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ const TransactionView = () => {
|
|||
<>
|
||||
{transactionDetails &&
|
||||
<>
|
||||
<div className='mt-2 grid grid-cols-1 lg:grid-cols-12 items-stretch max-w-90vw min-h-[88vh] h-full bg-white rounded-lg shadow-sm mx-3'>
|
||||
<div className='mt-2 grid grid-cols-1 lg:grid-cols-12 items-stretch max-w-[95vw] min-h-[88vh] h-full bg-white rounded-lg shadow-sm mx-3'>
|
||||
<div className="lg:col-span-10 w-full px-6 py-4 rounded-tl-sm text-xs border-r-2 border-gray-200 overflow-y-auto">
|
||||
{/* Header */}
|
||||
<h1 className='text-lg font-semibold text-gray-800'>
|
||||
|
|
@ -264,12 +264,12 @@ const TransactionView = () => {
|
|||
|
||||
<div className='lg:col-span-2 text-xs rounded-sm max-h-[88vh] overflow-y-auto no-scrollbar'>
|
||||
{documentsData.map((docsD, index) => (
|
||||
<div className='p-4 flex flex-col gap-2 cursor-pointer' key={docsD.id}>
|
||||
<div className='p-4 flex flex-col gap-2 cursor-pointer items-center' key={docsD.id}>
|
||||
<p>{docsD.name}</p>
|
||||
<div className='border-3 rounded-sm border-slate-300 relative text-xs'>
|
||||
<div className='border-3 rounded-sm border-slate-300 relative text-xs w-[160px] h-[168px]'>
|
||||
{docsD?.thumbnail ? (
|
||||
<img
|
||||
style={{ width: "200px", height: "160px" }}
|
||||
style={{ width: "160px", height: "160px" }}
|
||||
src={`data:image/jpeg;base64,${docsD.thumbnail}`} // Adjust MIME type if needed
|
||||
alt=""
|
||||
loading="lazy"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import axiosInstance from "../config/axiosInstance";
|
||||
|
||||
const handleDownloadDocument = async (recipientId: string): Promise<void> => {
|
||||
try {
|
||||
const response = await axiosInstance.get(
|
||||
`/api/v1/sign/${recipientId}/documents`,
|
||||
{
|
||||
responseType: "blob",
|
||||
}
|
||||
);
|
||||
|
||||
const blob = new Blob([response.data]);
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
|
||||
// IMPORTANT
|
||||
link.download = "document.zip";
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
export default handleDownloadDocument;
|
||||
|
|
@ -32,30 +32,30 @@ import { FaUser } from 'react-icons/fa';
|
|||
|
||||
const typeToIconMap = {
|
||||
TEXT:
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M36.6673 18.8333H25.0007C24.5586 18.8333 24.1347 19.0089 23.8221 19.3215C23.5096 19.634 23.334 20.058 23.334 20.5V22.8333C23.334 23.2753 23.5096 23.6993 23.8221 24.0118C24.1347 24.3244 24.5586 24.5 25.0007 24.5C25.4427 24.5 25.8666 24.3244 26.1792 24.0118C26.4917 23.6993 26.6673 23.2753 26.6673 22.8333V22.1666H29.1673V31.6666H27.334C26.892 31.6666 26.468 31.8422 26.1555 32.1548C25.8429 32.4674 25.6673 32.8913 25.6673 33.3333C25.6673 33.7753 25.8429 34.1993 26.1555 34.5118C26.468 34.8244 26.892 35 27.334 35H34.334C34.776 35 35.1999 34.8244 35.5125 34.5118C35.8251 34.1993 36.0007 33.7753 36.0007 33.3333C36.0007 32.8913 35.8251 32.4674 35.5125 32.1548C35.1999 31.8422 34.776 31.6666 34.334 31.6666H32.5007V22.1666H35.0007V22.8333C35.0007 23.2753 35.1762 23.6993 35.4888 24.0118C35.8014 24.3244 36.2253 24.5 36.6673 24.5C37.1093 24.5 37.5333 24.3244 37.8458 24.0118C38.1584 23.6993 38.334 23.2753 38.334 22.8333V20.5C38.334 20.058 38.1584 19.634 37.8458 19.3215C37.5333 19.0089 37.1093 18.8333 36.6673 18.8333Z" fill="#FBC249" />
|
||||
<path d="M22.5927 31.6667H18.3327V8.33333H28.3327V9.62833C28.3327 10.0704 28.5083 10.4943 28.8208 10.8068C29.1334 11.1194 29.5573 11.295 29.9993 11.295C30.4414 11.295 30.8653 11.1194 31.1779 10.8068C31.4904 10.4943 31.666 10.0704 31.666 9.62833V6.66667C31.666 6.22464 31.4904 5.80072 31.1779 5.48816C30.8653 5.17559 30.4414 5 29.9993 5H3.33268C2.89065 5 2.46673 5.17559 2.15417 5.48816C1.84161 5.80072 1.66602 6.22464 1.66602 6.66667V9.62833C1.66602 10.0704 1.84161 10.4943 2.15417 10.8068C2.46673 11.1194 2.89065 11.295 3.33268 11.295C3.77471 11.295 4.19863 11.1194 4.51119 10.8068C4.82375 10.4943 4.99935 10.0704 4.99935 9.62833V8.33333H14.9993V31.6667H10.7393C10.2973 31.6667 9.8734 31.8423 9.56084 32.1548C9.24828 32.4674 9.07268 32.8913 9.07268 33.3333C9.07268 33.7754 9.24828 34.1993 9.56084 34.5118C9.8734 34.8244 10.2973 35 10.7393 35H22.5927C23.0347 35 23.4586 34.8244 23.7712 34.5118C24.0838 34.1993 24.2593 33.7754 24.2593 33.3333C24.2593 32.8913 24.0838 32.4674 23.7712 32.1548C23.4586 31.8423 23.0347 31.6667 22.5927 31.6667Z" fill="#0D6EFD" />
|
||||
</svg>
|
||||
,
|
||||
SIGNATURE:
|
||||
<svg width="40" height="28" viewBox="0 0 40 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 40 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.0801 17.5987C21.5418 19.6325 18.4018 20.647 14.7059 20.647C13.0573 20.647 11.6623 20.2612 10.5509 19.6122C9.23383 20.2583 7.82453 20.647 6.47062 20.647C2.39344 20.647 0 18.453 0 14.7647C0 14.1149 0.52672 13.5882 1.17649 13.5882C1.82625 13.5882 2.35297 14.1149 2.35297 14.7647C2.35297 17.1057 3.64945 18.2941 6.47062 18.2941C7.1664 18.2941 7.89992 18.1431 8.62586 17.8792C7.57188 16.4205 7.22195 14.5556 7.68218 12.7146C8.42484 9.74382 10.361 7.70585 12.9412 7.70585C16.0278 7.70585 17.2912 11.6365 15.7581 14.7026C15.151 15.9167 14.1543 17.0938 12.9456 18.0758C13.4687 18.2168 14.0559 18.2941 14.7059 18.2941C17.8277 18.2941 20.4095 17.4789 22.4945 15.8528L24.1591 9.74928C24.2129 9.55178 24.3174 9.3717 24.4622 9.22694L33.2857 0.403428C33.7451 -0.0560254 34.49 -0.0560254 34.9495 0.403428C35.4089 0.862881 35.4089 1.6078 34.9495 2.06718L27.3366 9.68007C27.98 10.045 28.5577 10.4822 29.0671 10.9917C29.5766 11.5012 30.0138 12.0789 30.3787 12.7222L37.9916 5.10936C38.451 4.64991 39.1959 4.64991 39.6554 5.10936C40.1148 5.56874 40.1148 6.31366 39.6554 6.77311L30.8319 15.5966C30.6871 15.7414 30.507 15.8458 30.3095 15.8997L24.0801 17.5987ZM26.0741 11.6702L25.2062 14.8526L28.3886 13.9848C28.1148 13.4805 27.7864 13.0384 27.4034 12.6554C27.0204 12.2725 26.5784 11.9441 26.0741 11.6702ZM13.6536 13.6503C14.5119 11.9338 13.9092 10.0588 12.9412 10.0588C11.6096 10.0588 10.4722 11.2561 9.96492 13.2854C9.64672 14.5583 9.92117 15.831 10.7534 16.7749C12.0156 15.9148 13.0857 14.7862 13.6536 13.6503Z" fill="#0D6EFD" />
|
||||
<path d="M0 26.1765C0 26.8262 0.52672 27.353 1.17649 27.353H21.1765C21.8263 27.353 22.353 26.8262 22.353 26.1765C22.353 25.5267 21.8263 25 21.1765 25H1.17649C0.52672 25 0 25.5267 0 26.1765Z" fill="#FBC249" />
|
||||
</svg>
|
||||
,
|
||||
INITIALS: <svg width="39" height="36" viewBox="0 0 39 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
INITIALS: <svg width="20" height="20" viewBox="0 0 39 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.91844 27.3C4.31844 27.3 3.10844 26.85 2.28844 25.95C1.46844 25.03 1.05844 23.72 1.05844 22.02V20.82H4.17844V22.26C4.17844 23.62 4.74844 24.3 5.88844 24.3C6.44844 24.3 6.86844 24.14 7.14844 23.82C7.44844 23.48 7.59844 22.94 7.59844 22.2C7.59844 21.32 7.39844 20.55 6.99844 19.89C6.59844 19.21 5.85844 18.4 4.77844 17.46C3.41844 16.26 2.46844 15.18 1.92844 14.22C1.38844 13.24 1.11844 12.14 1.11844 10.92C1.11844 9.26 1.53844 7.98 2.37844 7.08C3.21844 6.16 4.43844 5.7 6.03844 5.7C7.61844 5.7 8.80844 6.16 9.60844 7.08C10.4284 7.98 10.8384 9.28 10.8384 10.98V11.85H7.71844V10.77C7.71844 10.05 7.57844 9.53 7.29844 9.21C7.01844 8.87 6.60844 8.7 6.06844 8.7C4.96844 8.7 4.41844 9.37 4.41844 10.71C4.41844 11.47 4.61844 12.18 5.01844 12.84C5.43844 13.5 6.18844 14.3 7.26844 15.24C8.64844 16.44 9.59844 17.53 10.1184 18.51C10.6384 19.49 10.8984 20.64 10.8984 21.96C10.8984 23.68 10.4684 25 9.60844 25.92C8.76844 26.84 7.53844 27.3 5.91844 27.3ZM12.7905 6H17.5005L19.6005 21.03H19.6605L21.7605 6H26.4705V27H23.3505V11.1H23.2905L20.8905 27H18.1305L15.7305 11.1H15.6705V27H12.7905V6ZM28.9331 6H37.9331V9H32.2331V14.55H36.7631V17.55H32.2331V24H37.9331V27H28.9331V6Z" fill="#0D6EFD" />
|
||||
<path d="M0.398438 29.4H38.5931V31.8H0.398438V29.4Z" fill="#0D6EFD" />
|
||||
<rect y="29" width="39" height="3" fill="#FBC249" />
|
||||
</svg>,
|
||||
TEMPORAL:
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.3333 3.33334V8.33334M26.6667 3.33334V8.33334M5.83333 15.15H34.1667M35 14.1667V28.3333C35 33.3333 32.5 36.6667 26.6667 36.6667H13.3333C7.5 36.6667 5 33.3333 5 28.3333V14.1667C5 9.16668 7.5 5.83334 13.3333 5.83334H26.6667C32.5 5.83334 35 9.16668 35 14.1667Z" stroke="#0D6EFD" stroke-width="2.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M26.1583 22.8333H26.1733M26.1583 27.8333H26.1733M19.9916 22.8333H20.0083M19.9916 27.8333H20.0083M13.8233 22.8333H13.84M13.8233 27.8333H13.84" stroke="#FBC249" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
,
|
||||
NUMBER:
|
||||
<svg width="37" height="37" viewBox="0 0 37 37" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 37 37" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M30.6157 2L21.8867 34.5769" stroke="#FBC249" stroke-width="3.38462" stroke-linecap="round" />
|
||||
<path d="M15.6528 2L6.92383 34.5769" stroke="#FBC249" stroke-width="3.38462" stroke-linecap="round" />
|
||||
<path d="M34.9991 13H3.69141" stroke="#0D6EFD" stroke-width="3.38462" stroke-linecap="round" />
|
||||
|
|
@ -63,7 +63,7 @@ const typeToIconMap = {
|
|||
</svg>
|
||||
,
|
||||
IMAGE: <FaRegImage />,
|
||||
STAMP: <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
STAMP: <svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_575_574)">
|
||||
<mask id="mask0_575_574" maskUnits="userSpaceOnUse" x="0" y="0" width="40" height="40">
|
||||
<path d="M0 3.8147e-06H40V40H0V3.8147e-06Z" fill="white" />
|
||||
|
|
@ -82,13 +82,13 @@ const typeToIconMap = {
|
|||
</svg>
|
||||
,
|
||||
CHECKBOX:
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.3341 26.25C18.1699 26.2504 18.0072 26.2182 17.8556 26.1553C17.704 26.0923 17.5664 25.9999 17.4507 25.8833L12.4507 20.8833C12.2299 20.6464 12.1097 20.333 12.1154 20.0091C12.1211 19.6853 12.2523 19.3763 12.4814 19.1473C12.7104 18.9183 13.0193 18.7871 13.3432 18.7814C13.667 18.7757 13.9804 18.8959 14.2174 19.1167L18.3341 23.2317L27.4507 14.1167C27.6877 13.8959 28.0011 13.7757 28.3249 13.7814C28.6488 13.7871 28.9577 13.9183 29.1868 14.1473C29.4158 14.3763 29.547 14.6853 29.5527 15.0091C29.5584 15.333 29.4382 15.6464 29.2174 15.8833L19.2174 25.8833C19.1018 25.9999 18.9641 26.0923 18.8125 26.1553C18.6609 26.2182 18.4982 26.2504 18.3341 26.25Z" fill="#FBC249" />
|
||||
<path d="M30.0007 37.9167H10.0007C7.90183 37.914 5.88973 37.0791 4.40564 35.595C2.92156 34.1109 2.08663 32.0988 2.08398 30V10C2.08663 7.90119 2.92156 5.88909 4.40564 4.405C5.88973 2.92091 7.90183 2.08599 10.0007 2.08334H30.0007C32.0995 2.08599 34.1116 2.92091 35.5957 4.405C37.0798 5.88909 37.9147 7.90119 37.9173 10V30C37.9147 32.0988 37.0798 34.1109 35.5957 35.595C34.1116 37.0791 32.0995 37.914 30.0007 37.9167ZM10.0007 4.58334C8.5646 4.58511 7.18788 5.15636 6.17244 6.1718C5.157 7.18724 4.58575 8.56396 4.58398 10V30C4.58575 31.4361 5.157 32.8128 6.17244 33.8282C7.18788 34.8437 8.5646 35.4149 10.0007 35.4167H30.0007C31.4367 35.4149 32.8134 34.8437 33.8289 33.8282C34.8443 32.8128 35.4156 31.4361 35.4173 30V10C35.4156 8.56396 34.8443 7.18724 33.8289 6.1718C32.8134 5.15636 31.4367 4.58511 30.0007 4.58334H10.0007Z" fill="#0D6EFD" />
|
||||
</svg>
|
||||
,
|
||||
SELECT:
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 34.1666C19.6667 34.1666 19.3333 34 19 33.8333L5.66666 23.8333C4.99999 23.3333 4.83332 22.1666 5.33332 21.5C5.83332 20.8333 6.99999 20.6666 7.66666 21.1666L20 30.3333L32.3333 21.1666C33 20.6666 34.1667 20.8333 34.6667 21.5C35.1667 22.1666 35 23.3333 34.3333 23.8333L21 33.8333C20.6667 34 20.3333 34.1666 20 34.1666Z" fill="#0D6EFD" />
|
||||
<path d="M33.3333 9.16665H6.66667C5.66667 9.16665 5 8.49998 5 7.49998C5 6.49998 5.66667 5.83331 6.66667 5.83331H33.3333C34.3333 5.83331 35 6.49998 35 7.49998C35 8.49998 34.3333 9.16665 33.3333 9.16665Z" fill="#FBC249" />
|
||||
<path d="M33.3333 15.8333H6.66667C5.66667 15.8333 5 15.1667 5 14.1667C5 13.1667 5.66667 12.5 6.66667 12.5H33.3333C34.3333 12.5 35 13.1667 35 14.1667C35 15.1667 34.3333 15.8333 33.3333 15.8333Z" fill="#FBC249" />
|
||||
|
|
@ -96,31 +96,31 @@ const typeToIconMap = {
|
|||
,
|
||||
FILE: <FaFileAlt />,
|
||||
ATTACHMENT:
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.3329 19.6667L17.9829 22.0167C16.6829 23.3167 16.6829 25.4333 17.9829 26.7333C19.2829 28.0333 21.3996 28.0333 22.6996 26.7333L26.3996 23.0333C27.6482 21.7811 28.3493 20.085 28.3493 18.3167C28.3493 16.5484 27.6482 14.8522 26.3996 13.6C25.1474 12.3514 23.4513 11.6503 21.6829 11.6503C19.9146 11.6503 18.2185 12.3514 16.9663 13.6L12.9329 17.6333C12.4019 18.1639 11.9806 18.794 11.6932 19.4875C11.4058 20.181 11.2578 20.9243 11.2578 21.675C11.2578 22.4257 11.4058 23.169 11.6932 23.8625C11.9806 24.556 12.4019 25.1861 12.9329 25.7167" stroke="#FBC249" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M15.0007 36.6667H25.0007C33.334 36.6667 36.6673 33.3333 36.6673 25V15C36.6673 6.66668 33.334 3.33334 25.0007 3.33334H15.0007C6.66732 3.33334 3.33398 6.66668 3.33398 15V25C3.33398 33.3333 6.66732 36.6667 15.0007 36.6667Z" stroke="#0D6EFD" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
,
|
||||
RADIO:
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 1.125C27.3139 1.125 34.875 8.68612 34.875 18C34.875 27.3139 27.3139 34.875 18 34.875C8.68612 34.875 1.125 27.3139 1.125 18C1.125 8.68612 8.68612 1.125 18 1.125ZM18 3.375C9.92812 3.375 3.375 9.92812 3.375 18C3.375 26.0719 9.92812 32.625 18 32.625C26.0719 32.625 32.625 26.0719 32.625 18C32.625 9.92812 26.0719 3.375 18 3.375Z" fill="#0D6EFD" />
|
||||
<path d="M27 18C27 13.0331 22.9669 9 18 9C13.0331 9 9 13.0331 9 18C9 22.9669 13.0331 27 18 27C22.9669 27 27 22.9669 27 18Z" fill="#FBC249" />
|
||||
</svg>
|
||||
,
|
||||
EMAIL:
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8903 4.5H26.1098C28.845 4.49996 31.0496 4.49992 32.7836 4.73304C34.5838 4.97508 36.0994 5.49286 37.3034 6.6967C38.5072 7.90054 39.025 9.41628 39.267 11.2165C39.5 12.9504 39.5 15.1551 39.5 17.8903V22.1098C39.5 24.845 39.5 27.0496 39.267 28.7836C39.025 30.5838 38.5072 32.0994 37.3034 33.3034C36.0994 34.5072 34.5838 35.025 32.7836 35.267C31.0496 35.5 28.845 35.5 26.1098 35.5H13.8903C11.1551 35.5 8.95044 35.5 7.2165 35.267C5.41628 35.025 3.90054 34.5072 2.6967 33.3034C1.49287 32.0994 0.975079 30.5838 0.733047 28.7836C0.499925 27.0496 0.499959 24.845 0.500001 22.1098V17.8903C0.499959 15.1551 0.499925 12.9504 0.733047 11.2165C0.975079 9.41628 1.49287 7.90054 2.6967 6.6967C3.90054 5.49286 5.41628 4.97508 7.2165 4.73304C8.95044 4.49992 11.1551 4.49996 13.8903 4.5ZM7.61624 7.7063C6.1487 7.9036 5.37154 8.2645 4.81802 8.81802C4.2645 9.37154 3.9036 10.1487 3.7063 11.6162C3.50318 13.1269 3.5 15.1292 3.5 18V22C3.5 24.8708 3.50318 26.873 3.7063 28.3838C3.9036 29.8514 4.2645 30.6284 4.81802 31.182C5.37154 31.7356 6.1487 32.0964 7.61624 32.2938C9.12694 32.4968 11.1292 32.5 14 32.5H26C28.8708 32.5 30.873 32.4968 32.3838 32.2938C33.8514 32.0964 34.6284 31.7356 35.182 31.182C35.7356 30.6284 36.0964 29.8514 36.2938 28.3838C36.4968 26.873 36.5 24.8708 36.5 22V18C36.5 15.1292 36.4968 13.1269 36.2938 11.6162C36.0964 10.1487 35.7356 9.37154 35.182 8.81802C34.6284 8.2645 33.8514 7.9036 32.3838 7.7063C30.873 7.50318 28.8708 7.5 26 7.5H14C11.1292 7.5 9.12694 7.50318 7.61624 7.7063Z" fill="#0D6EFD" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.75183 13.168C7.21135 12.4787 8.14267 12.2924 8.83195 12.7519L14.1756 16.3143C15.816 17.4079 16.9479 18.1601 17.8927 18.6516C18.8049 19.1261 19.4187 19.2889 19.9999 19.2889C20.5811 19.2889 21.1949 19.1261 22.1071 18.6516C23.0519 18.1601 24.1839 17.4079 25.8243 16.3143L31.1679 12.7519C31.8571 12.2924 32.7885 12.4787 33.2479 13.168C33.7075 13.8572 33.5213 14.7886 32.8319 15.2481L27.4191 18.8567C25.8647 19.893 24.5979 20.7374 23.4917 21.313C22.3365 21.914 21.2353 22.289 19.9999 22.289C18.7645 22.289 17.6634 21.914 16.5082 21.313C15.4019 20.7374 14.1351 19.893 12.5807 18.8566L7.16785 15.2481C6.47857 14.7886 6.29231 13.8572 6.75183 13.168Z" fill="#FBC249" />
|
||||
</svg>
|
||||
,
|
||||
NAME:
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M34.3169 36.6667C34.3169 30.2167 27.9003 25 20.0003 25C12.1003 25 5.68359 30.2167 5.68359 36.6667" stroke="#0D6EFD" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M25.8919 17.5592C24.3291 19.122 22.2095 20 19.9993 20C17.7892 20 15.6696 19.122 14.1068 17.5592C12.544 15.9964 11.666 13.8768 11.666 11.6666C11.666 9.45651 12.544 7.33689 14.1068 5.77409C15.6696 4.21129 17.7892 3.33331 19.9993 3.33331C22.2095 3.33331 24.3291 4.21129 25.8919 5.77409C27.4547 7.33689 28.3327 9.45651 28.3327 11.6666C28.3327 13.8768 27.4547 15.9964 25.8919 17.5592Z" stroke="#FBC249" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
,
|
||||
COMPANY:
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="20" height="20" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M37.5 36.25H36.25V5C36.25 2.9375 34.5625 1.25 32.5 1.25H17.5C15.4375 1.25 13.75 2.9375 13.75 5V7.5H7.5C5.4375 7.5 3.75 9.1875 3.75 11.25V36.25H2.5C1.8125 36.25 1.25 36.8125 1.25 37.5C1.25 38.1875 1.8125 38.75 2.5 38.75H37.5C38.1875 38.75 38.75 38.1875 38.75 37.5C38.75 36.8125 38.1875 36.25 37.5 36.25ZM26.25 33.75H33.75V36.25H26.25V33.75ZM16.25 5C16.25 4.3125 16.8125 3.75 17.5 3.75H32.5C33.1875 3.75 33.75 4.3125 33.75 5V31.25H26.25V11.25C26.25 9.1875 24.5625 7.5 22.5 7.5H16.25V5ZM6.25 11.25C6.25 10.5625 6.8125 10 7.5 10H22.5C23.1875 10 23.75 10.5625 23.75 11.25V36.25H20V29.375C20.6875 29.375 21.25 28.8125 21.25 28.125C21.25 27.4375 20.6875 26.875 20 26.875H10C9.3125 26.875 8.75 27.4375 8.75 28.125C8.75 28.8125 9.3125 29.375 10 29.375V36.25H6.25V11.25ZM17.5 29.375V36.25H12.5V29.375H17.5Z" fill="#0D6EFD" />
|
||||
<path d="M9.375 14.375H10.0375C10.725 14.375 11.2875 13.8125 11.2875 13.125C11.2875 12.4375 10.725 11.875 10.0375 11.875H9.375C8.6875 11.875 8.125 12.4375 8.125 13.125C8.125 13.8125 8.6875 14.375 9.375 14.375Z" fill="#FBC249" />
|
||||
<path d="M14.6758 14.375H15.3383C16.0258 14.375 16.5883 13.8125 16.5883 13.125C16.5883 12.4375 16.0258 11.875 15.3383 11.875H14.6758C13.9883 11.875 13.4258 12.4375 13.4258 13.125C13.4258 13.8125 13.9883 14.375 14.6758 14.375Z" fill="#FBC249" />
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ input[type=number] {
|
|||
/* pointer-events: none; */
|
||||
}
|
||||
|
||||
.bottom-border>div {
|
||||
.bottom-border>button {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 50%;
|
||||
|
|
@ -197,7 +197,7 @@ input[type=number] {
|
|||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.bottom-border>div:hover {
|
||||
.bottom-border>button:hover {
|
||||
background-color: var(--primaryLight2);
|
||||
opacity: 0.9;
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
|
@ -428,7 +428,7 @@ input[type=number] {
|
|||
}
|
||||
}
|
||||
|
||||
.bottom-border>div:hover {
|
||||
.bottom-border>button:hover {
|
||||
background-color: var(--primaryLight2);
|
||||
opacity: 0.9;
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,218 @@
|
|||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
// Define the interface matching DocumentContainer.tsx usage
|
||||
export interface DroppedItem {
|
||||
id?: string;
|
||||
icon?: any;
|
||||
label?: string;
|
||||
type: string;
|
||||
subType?: string;
|
||||
dimensions: {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
styles?: any;
|
||||
documentId: string;
|
||||
pageIndex: number;
|
||||
recipientId: string | number;
|
||||
recipientIndx?: number;
|
||||
required?: boolean;
|
||||
update?: boolean;
|
||||
isDeleted?: boolean;
|
||||
group?: string;
|
||||
isGroupExist?: boolean;
|
||||
currentTimeStamp?: boolean;
|
||||
decimalPlace?: number;
|
||||
isCurrency?: boolean;
|
||||
maskType?: string;
|
||||
maskCharacter?: string;
|
||||
format?: string;
|
||||
regex?: string[];
|
||||
[key: string]: any; // Allow other properties
|
||||
}
|
||||
|
||||
export interface DroppedItemsObj {
|
||||
[key: string]: DroppedItem[];
|
||||
}
|
||||
|
||||
export interface DocumentTagsState {
|
||||
items: {
|
||||
[transactionKey: string]: DroppedItemsObj; // Key: `${accountId}_${transactionId}`
|
||||
};
|
||||
counter: {
|
||||
[transactionKey: string]: number;
|
||||
};
|
||||
isLoading: {
|
||||
[transactionKey: string]: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: DocumentTagsState = {
|
||||
items: {},
|
||||
counter: {},
|
||||
isLoading: {},
|
||||
};
|
||||
|
||||
const documentTagsSlice = createSlice({
|
||||
name: 'documentTags',
|
||||
initialState,
|
||||
|
||||
reducers: {
|
||||
/**
|
||||
* Initialize or replace all dropped items for a transaction
|
||||
* Used when fetching tags from API
|
||||
*/
|
||||
initializeDroppedItems: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
transactionKey: string;
|
||||
items: DroppedItemsObj;
|
||||
}>
|
||||
) => {
|
||||
const { transactionKey, items } = action.payload;
|
||||
state.items[transactionKey] = items;
|
||||
state.isLoading[transactionKey] = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set all dropped items (replaces existing)
|
||||
* Used when user adds/modifies tags in current session
|
||||
*/
|
||||
setDroppedItems: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
transactionKey: string;
|
||||
items: DroppedItemsObj;
|
||||
}>
|
||||
) => {
|
||||
const { transactionKey, items } = action.payload;
|
||||
state.items[transactionKey] = items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a dropped item to specific page
|
||||
*/
|
||||
addDroppedItem: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
transactionKey: string;
|
||||
pageKey: string;
|
||||
item: DroppedItem;
|
||||
}>
|
||||
) => {
|
||||
const { transactionKey, pageKey, item } = action.payload;
|
||||
if (!state.items[transactionKey]) {
|
||||
state.items[transactionKey] = {};
|
||||
}
|
||||
if (!state.items[transactionKey][pageKey]) {
|
||||
state.items[transactionKey][pageKey] = [];
|
||||
}
|
||||
state.items[transactionKey][pageKey].push(item);
|
||||
state.counter[transactionKey] = (state.counter[transactionKey] || 0) + 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update dropped items for a specific page
|
||||
*/
|
||||
updatePageItems: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
transactionKey: string;
|
||||
pageKey: string;
|
||||
items: DroppedItem[];
|
||||
}>
|
||||
) => {
|
||||
const { transactionKey, pageKey, items } = action.payload;
|
||||
if (!state.items[transactionKey]) {
|
||||
state.items[transactionKey] = {};
|
||||
}
|
||||
state.items[transactionKey][pageKey] = items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an item by index from a page
|
||||
*/
|
||||
removeDroppedItem: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
transactionKey: string;
|
||||
pageKey: string;
|
||||
index: number;
|
||||
}>
|
||||
) => {
|
||||
const { transactionKey, pageKey, index } = action.payload;
|
||||
if (state.items[transactionKey]?.[pageKey]) {
|
||||
state.items[transactionKey][pageKey].splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a specific item by index
|
||||
*/
|
||||
updateDroppedItem: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
transactionKey: string;
|
||||
pageKey: string;
|
||||
index: number;
|
||||
updates: Partial<DroppedItem>;
|
||||
}>
|
||||
) => {
|
||||
const { transactionKey, pageKey, index, updates } = action.payload;
|
||||
if (state.items[transactionKey]?.[pageKey]?.[index]) {
|
||||
state.items[transactionKey][pageKey][index] = {
|
||||
...state.items[transactionKey][pageKey][index],
|
||||
...updates,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all tags for a specific transaction
|
||||
*/
|
||||
clearTransactionTags: (state, action: PayloadAction<string>) => {
|
||||
const transactionKey = action.payload;
|
||||
delete state.items[transactionKey];
|
||||
delete state.counter[transactionKey];
|
||||
delete state.isLoading[transactionKey];
|
||||
},
|
||||
|
||||
/**
|
||||
* Increment counter for a transaction
|
||||
*/
|
||||
incrementCounter: (state, action: PayloadAction<string>) => {
|
||||
const transactionKey = action.payload;
|
||||
state.counter[transactionKey] = (state.counter[transactionKey] || 0) + 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set loading state
|
||||
*/
|
||||
setLoading: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
transactionKey: string;
|
||||
isLoading: boolean;
|
||||
}>
|
||||
) => {
|
||||
const { transactionKey, isLoading } = action.payload;
|
||||
state.isLoading[transactionKey] = isLoading;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
initializeDroppedItems,
|
||||
setDroppedItems,
|
||||
addDroppedItem,
|
||||
updatePageItems,
|
||||
removeDroppedItem,
|
||||
updateDroppedItem,
|
||||
clearTransactionTags,
|
||||
incrementCounter,
|
||||
setLoading,
|
||||
} = documentTagsSlice.actions;
|
||||
|
||||
export default documentTagsSlice.reducer;
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
|
||||
/* TYPES */
|
||||
export interface Position {
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Mask {
|
||||
maskType: string;
|
||||
maskCharacter: string;
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: string;
|
||||
label: string;
|
||||
|
||||
position: Position;
|
||||
|
||||
mask: Mask;
|
||||
|
||||
type: string;
|
||||
subType: string | null;
|
||||
|
||||
error: string | null;
|
||||
|
||||
regex: string[];
|
||||
|
||||
format: string;
|
||||
|
||||
documentIndex: number;
|
||||
pageNumber: number;
|
||||
signerIndex: number;
|
||||
|
||||
style: Record<string, any>;
|
||||
|
||||
isDeleted?: boolean;
|
||||
}
|
||||
|
||||
|
||||
/* PAYLOAD TYPES */
|
||||
interface AddTagPayload {
|
||||
recipientId: string;
|
||||
tag: Tag;
|
||||
}
|
||||
|
||||
interface UpdateTagPayload {
|
||||
recipientId: string;
|
||||
tagId: string;
|
||||
updatedData: Partial<Tag>;
|
||||
}
|
||||
|
||||
interface RemoveTagPayload {
|
||||
recipientId: string;
|
||||
tagId: string;
|
||||
}
|
||||
|
||||
interface ReplaceRecipientTagsPayload {
|
||||
recipientId: string;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
|
||||
/* STATE */
|
||||
|
||||
|
||||
/*
|
||||
STATE STRUCTURE
|
||||
|
||||
{
|
||||
recipientId: {
|
||||
tagId: Tag
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
export interface LocalTagsState {
|
||||
[recipientId: string]: {
|
||||
[tagId: string]: Tag;
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: LocalTagsState = {};
|
||||
|
||||
|
||||
/* SLICE */
|
||||
|
||||
|
||||
const localTagsSlice = createSlice({
|
||||
name: "localTags",
|
||||
initialState,
|
||||
|
||||
reducers: {
|
||||
|
||||
/* ------------------------------ ADD TAG ----------------------------- */
|
||||
addTag: (state, action: PayloadAction<AddTagPayload>) => {
|
||||
const { recipientId, tag } = action.payload;
|
||||
|
||||
if (!state[recipientId]) {
|
||||
state[recipientId] = {};
|
||||
}
|
||||
|
||||
state[recipientId][tag.id] = tag;
|
||||
},
|
||||
|
||||
/* ---------------------------- UPDATE TAG ---------------------------- */
|
||||
updateTag: (state, action: PayloadAction<UpdateTagPayload>) => {
|
||||
const { recipientId, tagId, updatedData } = action.payload;
|
||||
|
||||
if (!state[recipientId]) return;
|
||||
if (!state[recipientId][tagId]) return;
|
||||
|
||||
state[recipientId][tagId] = {
|
||||
...state[recipientId][tagId],
|
||||
...updatedData,
|
||||
};
|
||||
},
|
||||
|
||||
/* ---------------------------- REMOVE TAG ---------------------------- */
|
||||
removeTag: (state, action: PayloadAction<RemoveTagPayload>) => {
|
||||
const { recipientId, tagId } = action.payload;
|
||||
|
||||
if (!state[recipientId]) return;
|
||||
|
||||
delete state[recipientId][tagId];
|
||||
|
||||
// Optional cleanup
|
||||
if (Object.keys(state[recipientId]).length === 0) {
|
||||
delete state[recipientId];
|
||||
}
|
||||
},
|
||||
|
||||
/* ------------------------- REPLACE ALL TAGS ------------------------- */
|
||||
replaceRecipientTags: (
|
||||
state,
|
||||
action: PayloadAction<ReplaceRecipientTagsPayload>
|
||||
) => {
|
||||
const { recipientId, tags } = action.payload;
|
||||
|
||||
state[recipientId] = {};
|
||||
|
||||
tags.forEach((tag) => {
|
||||
state[recipientId][tag.id] = tag;
|
||||
});
|
||||
},
|
||||
|
||||
/* -------------------------- CLEAR RECIPIENT ------------------------- */
|
||||
clearRecipientTags: (
|
||||
state,
|
||||
action: PayloadAction<string>
|
||||
) => {
|
||||
delete state[action.payload];
|
||||
},
|
||||
|
||||
/* ----------------------------- CLEAR ALL ---------------------------- */
|
||||
clearTags: () => {
|
||||
return initialState;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/* EXPORTS */
|
||||
export const {
|
||||
addTag,
|
||||
updateTag,
|
||||
removeTag,
|
||||
replaceRecipientTags,
|
||||
clearRecipientTags,
|
||||
clearTags,
|
||||
} = localTagsSlice.actions;
|
||||
|
||||
export default localTagsSlice.reducer;
|
||||
|
|
@ -268,11 +268,11 @@ const selectedRecipientsSlice = createSlice({
|
|||
})
|
||||
.addCase(fetchRecipients.rejected, (state, action) => {
|
||||
console.error("Failed to fetch recipients:", action.payload);
|
||||
toast.error("Failed to fetch recipients", {
|
||||
position: "bottom-right",
|
||||
autoClose: 5000,
|
||||
theme: "light",
|
||||
});
|
||||
// toast.error("Failed to fetch recipients", {
|
||||
// position: "bottom-right",
|
||||
// autoClose: 5000,
|
||||
// theme: "light",
|
||||
// });
|
||||
})
|
||||
.addCase(addRecipientAsync.pending, () => { })
|
||||
.addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction<Recipient[]>) => {
|
||||
|
|
@ -283,11 +283,11 @@ const selectedRecipientsSlice = createSlice({
|
|||
})
|
||||
.addCase(addRecipientAsync.rejected, (state, action) => {
|
||||
console.error("Failed to add recipients:", action.payload);
|
||||
toast.error("Failed to add recipients", {
|
||||
position: "bottom-right",
|
||||
autoClose: 5000,
|
||||
theme: "light",
|
||||
});
|
||||
// toast.error("Failed to add recipients", {
|
||||
// position: "bottom-right",
|
||||
// autoClose: 5000,
|
||||
// theme: "light",
|
||||
// });
|
||||
})
|
||||
.addCase(removeRecipientAsync.pending, () => { })
|
||||
.addCase(removeRecipientAsync.fulfilled, (state, action: PayloadAction<string | number>) => {
|
||||
|
|
@ -297,11 +297,11 @@ const selectedRecipientsSlice = createSlice({
|
|||
})
|
||||
.addCase(removeRecipientAsync.rejected, (state, action) => {
|
||||
console.error("Failed to delete recipient:", action.payload);
|
||||
toast.error("Failed to delete recipient", {
|
||||
position: "bottom-right",
|
||||
autoClose: 5000,
|
||||
theme: "light",
|
||||
});
|
||||
// toast.error("Failed to delete recipient", {
|
||||
// position: "bottom-right",
|
||||
// autoClose: 5000,
|
||||
// theme: "light",
|
||||
// });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ import documentReducer from "./slices/documentSlice";
|
|||
import tagsReducer from "./slices/tagsSlice";
|
||||
import transactionReducer from "./slices/transactionSlice";
|
||||
import signatureReducer from "./slices/signatureSlice";
|
||||
import documentTagsReducer from "./slices/documentTagsSlice";
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
user: userReducer,
|
||||
|
|
@ -83,6 +84,7 @@ const rootReducer = combineReducers({
|
|||
tags: tagsReducer,
|
||||
transaction: transactionReducer,
|
||||
signature: signatureReducer,
|
||||
documentTags: documentTagsReducer,
|
||||
});
|
||||
|
||||
export const store = configureStore({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { MASKCHARACTER } from "../components/process/processStepTwo/rightSideBar/RightSideBar";
|
||||
import { MASKCHARACTER } from "../components/process/processStepTwo/tagProperties/TagProperties";
|
||||
|
||||
const type_all = (str: string, changeWith: string): string => {
|
||||
return str.replace(/./g, changeWith);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ export default defineConfig({
|
|||
base: '/',
|
||||
plugins: [react(), tailwindcss()],
|
||||
server: {
|
||||
port: 5173,
|
||||
allowedHosts: [
|
||||
'smashing-albacore-engaging.ngrok-free.app',
|
||||
'quality-friendly-goldfish.ngrok-free.app',
|
||||
'officially-powerful-goat.ngrok-free.app',
|
||||
'89f0-49-36-189-120.ngrok-free.app',
|
||||
|
|
@ -15,6 +17,7 @@ export default defineConfig({
|
|||
'c76f1251c173.ngrok-free.app',
|
||||
'enabled-upright-boxer.ngrok-free.app',
|
||||
'basically-polished-yeti.ngrok-free.app',
|
||||
'grace-antral-shari.ngrok-free.dev',
|
||||
'10.10.1.248:8083'
|
||||
],
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue