SME-Web/src/components/transactions/TransactionView.tsx

320 lines
17 KiB
TypeScript

import { AiFillSignature } from 'react-icons/ai';
import { Link, useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { LuGitGraph } from 'react-icons/lu';
import AccountNav from '../accounts/AccountNav';
import { useEffect, useState } from 'react';
import api from '../../config/api';
import SigningOrderDiagram from '../../SigningOrderDiagram ';
import { IoCloseOutline } from 'react-icons/io5';
import { MdEditDocument } from "react-icons/md";
import { useSelector } from 'react-redux';
import { RootState } from '../../store/store';
import { SignTypeEnum } from '../../utlity/types';
import AuditModal from './AuditModal';
import { FaFile } from 'react-icons/fa6';
import { IoMdDownload } from 'react-icons/io';
import downloadDocumentUtils from '../../utlity/downloadDocumentsUtils';
import signSessionRedirect from '../../utlity/signSessionRedirect';
// Sample recipient data — you can replace this with actual API data later
const recipients = [
{
id: 1,
name: "Shanu.R",
email: "shanu.r@example.com",
signedAt: "5/1/2025 | 04:24:35 pm",
status: "Signed",
viewLink: "#"
},
{
id: 2,
name: "Amit B",
email: "amit.b@example.com",
signedAt: "5/1/2025 | 04:24:35 pm",
status: "Signed",
viewLink: "#"
}
];
const TransactionView = () => {
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const navigate = useNavigate();
if (!accountId || !transactionId) {
window.location.href = "/";
return null;
}
const [transactionDetails, setTransactionDetails] = useState(null);
const [signingOrderModal, setSigningOrderModal] = useState({ open: false });
const user = useSelector((state: RootState) => state.user)
const location = useLocation();
const [isModalOpen, setIsModalOpen] = useState(false);
const [documentsData, setDocumentsData] = useState([])
useEffect(() => {
// update whenever location changes
setIsModalOpen(location.search.includes("auditStatus"));
}, [location]);
const handleOpenAuditModal = () => {
const { pathname, search } = location;
let newSearch = search
? search.includes("auditStatus")
? search
: `${search}&auditStatus`
: "?auditStatus";
window.history.pushState({}, "", pathname + newSearch);
setIsModalOpen(true); // update state immediately
};
const closeModal = () => {
let search = location.search
.replace(/[?&]auditStatus\b/, "")
.replace(/^&/, "?")
.replace(/\?$/, "");
window.history.pushState({}, "", location.pathname + search);
setIsModalOpen(false); // update state immediately
};
const fetchData = async () => {
try {
const [res1, res2] = await Promise.all([api.get(`/api/accounts/${accountId}/transactions/${transactionId}`),
api.get(`/api/accounts/${accountId}/transactions/${transactionId}/recipients`)])
setTransactionDetails({ transaction: res1.data, recipients: res2.data });
} catch (error) {
console.error("Error Gettign Transaction and its Recipient: ", error.message)
}
}
console.log(transactionDetails, "transactionDetails")
const fetchDocuments = async () => {
try {
const res = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/documents`)
if (res?.status === 200) {
let resDoc = res?.data
setDocumentsData(resDoc)
}
} catch (error) {
console.log(error)
}
}
useEffect(() => {
fetchData()
fetchDocuments()
}, [accountId, transactionId])
const handleNavigateToSign = (id: number) => {
signSessionRedirect(accountId, transactionId, id+"" as string)
}
const getStatusBadge = (status: string) => {
const statusClasses = {
'SENT': 'bg-blue-100 text-blue-800',
'COMPLETED': 'bg-green-100 text-green-800',
'FAILED': 'bg-red-100 text-red-800',
'PENDING': 'bg-yellow-100 text-yellow-800',
'VIEWED': 'bg-purple-100 text-purple-800',
'REJECTED': 'bg-red-100 text-red-800'
};
return statusClasses[status as keyof typeof statusClasses] || 'bg-gray-100 text-gray-800';
};
return (
<>
{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="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'>
Complete with Sign Made Easy: <span className='text-slate-600'>{transactionDetails?.transaction?.subject ?? "Untitled"}</span>
</h1>
<div className='mt-2'>
{/* new Date(transactionDetails?.transaction?.lastModifiedAt * 1000) */}
<p className='text-slate-600'>From <span className='text-blue-600'>{transactionDetails?.transaction.createdBy.firstName + " " + transactionDetails?.transaction.createdBy.lastName}</span></p>
<p className=''><span className='text-slate-600 font-semibold'>Last Change on</span> <span className='text-slate-400'>{(transactionDetails?.transaction?.lastModifiedAt)}</span></p>
<p className='text-slate-600'>Created at <span>{(transactionDetails?.transaction?.createdAt)}</span></p>
</div>
<p className='text-slate-600 text-[0.98rem] mt-2 font-semibold'>{transactionDetails?.transaction.status}</p>
{/* Buttons */}
<div className='my-2 flex gap-2'>
{/* <button
type="button"
aria-label="Sign the document"
className='cursor-pointer border border-gray-300 rounded-sm px-4 py-1 bg-[var(--primary)] text-white'
>
Sign
</button> */}
<button className='cursor-pointer border border-gray-300 rounded-sm px-4 py-1 hover:bg-gray-100 font-bold'>
More
</button>
{transactionDetails.transaction.status === "CREATED" && <Link className='cursor-pointer border text-green-700 border-gray-300 rounded-sm px-4 py-1 hover:bg-gray-100 font-bold' to={`/process-step-one/${accountId}/${transactionId}`}>
Edit
</Link>}
<button className='cursor-pointer border text-[var(--primary)] border-gray-300 rounded-sm px-4 py-1 hover:bg-gray-100 font-bold' onClick={handleOpenAuditModal}>
Audit
</button>
</div>
<hr className='my-6 border-slate-200' />
{/* Recipients Section */}
{transactionDetails.recipients.length > 0 &&
<div>
<div className='flex justify-between items-center text-md'>
<h2 className='text-lg font-semibold text-gray-800 cursor-pointer'>Signers</h2>
<p className='font-bold flex items-center gap-1.5 text-slate-800 cursor-pointer' onClick={() => setSigningOrderModal({ ...signingOrderModal, open: true })}>
<LuGitGraph className='text-[0.9rem] text-[var(--primary)]' />
Signing Graph
</p>
</div>
{/* Recipients List */}
{transactionDetails.recipients.map((recipient, index) => (
<div key={index}>
<div className="mt-2 flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-0 ">
{/* Left section: recipient info */}
<div className="flex flex-col sm:flex-row gap-4 sm:items-center">
<div>
<p className="text-slate-600 font-semibold text-xs sm:xs">{recipient.name}</p>
<p className="text-slate-600 text-xs sm:text-xs">{recipient.email} | {recipient.type}</p>
{recipient.hostEmail && (
<p className="text-xs text-gray-500 italic">
<strong>Host:</strong> {recipient.hostEmail}
</p>
)}
</div>
</div>
{/* Right section: status and actions */}
<div className="flex flex-col items-start sm:items-end gap-1 text-sm text-slate-600">
<div className="flex flex-wrap gap-3 items-center relative">
<div className="pl-6 relative">
<AiFillSignature className="absolute left-0 top-0 text-[1rem]" />
<p className={`text-black text-xs font-semibold rounded-lg px-2 py-0.5 ${getStatusBadge(recipient.status)}`}>{recipient.status}</p>
</div>
{transactionDetails.transaction.status === "SENT" && recipient.email === user.email && recipient.status !== "COMPLETED" && (
<button
onClick={() => handleNavigateToSign(recipient.id)}
className="text-white rounded-sm bg-[var(--primary)] px-3 py-1 font-semibold text-xs hover:bg-opacity-90 transition"
>
Sign
</button>
)}
</div>
{recipient.status !== "PENDING" && (
<p className="text-xs text-gray-500">on {recipient.completedAt}</p>
)}
{/* Optional signed document link */}
{/* {recipient.viewLink && (
<a href={recipient.viewLink} className="text-blue-600 text-xs hover:underline">
View signed location
</a>
)} */}
</div>
</div>
<hr className="my-3 border-slate-200" />
</div>
))}
</div>
}
{transactionDetails?.transaction?.subject !== "" &&
<div>
<h2 className='text-lg mt-4 font-semibold text-gray-800 cursor-pointer'>Subject</h2>
<p className='mt-2 text-slate-600'>{`${transactionDetails?.transaction?.subject ? transactionDetails?.transaction?.subject : "Not Found"} `}</p>
</div>}
{transactionDetails?.transaction?.message !== "" &&
<div>
<h2 className='text-lg mt-4 font-semibold text-gray-800 cursor-pointer'>Message</h2>
<p className='mt-2 text-slate-600'>{`${transactionDetails?.transaction?.message ? transactionDetails?.transaction?.message : "Not Found"} `}</p>
</div>}
</div>
<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}>
<p>{docsD.name}</p>
<div className='border-3 rounded-sm border-slate-300 relative text-xs'>
{docsD?.thumbnail ? (
<img
style={{ width: "200px", height: "160px" }}
src={`data:image/jpeg;base64,${docsD.thumbnail}`} // Adjust MIME type if needed
alt=""
loading="lazy"
/>
) : (
<FaFile className="h-35 w-20 mx-5 text-gray-300" />
)}
<div className='bg-slate-100 p-2 absolute w-full bottom-[0px]'>
<p className='text-[0.7rem] text-slate-500 flex justify-end items-center'>
{/* <span> 1 of {docsD?.pages?.length}</span> */}
<IoMdDownload
className='cursor-pointer'
onClick={() => downloadDocumentUtils(accountId, transactionId, docsD.id)}
size={18} />
</p>
</div>
</div>
</div>
))}
</div>
</div>
{signingOrderModal.open &&
<div className="fixed top-0 left-0 inset-0 z-50 bg-black/50 backdrop-blur-sm flex justify-center items-center overflow-hidden">
<div className="flex-col relative justify-center items-center max-w-[80%] max-h-[60vh] h-full ">
<div className="pb-2 text-white flex justify-between items-center bg-[var(--primary)] px-2 py-3 rounded-t-lg">
<p className="font-semibold">Signing Order</p>
<div
onClick={() => setSigningOrderModal({ ...signingOrderModal, open: false })}
className="text-xl cursor-pointer hover:text-gray-200 transition-all">
<IoCloseOutline />
</div>
</div>
<span className='overflow-y-auto'>
<SigningOrderDiagram signers={transactionDetails.recipients} />
</span>
</div>
</div>
}
{isModalOpen && <AuditModal closeModal={closeModal} />}
</>
}
</>
);
};
export default TransactionView;