Merge branch 'jai'

This commit is contained in:
=bhagwanSingh 2025-08-12 16:26:30 +05:30
commit ac6c42e50a
16 changed files with 788 additions and 190 deletions

View File

@ -22,7 +22,6 @@ import Templates from "./components/templates/Templates";
import DocumentCompletionScreen from "./components/process/CompletionScreen/DocumentCompletionScreen";
import JoyrideController from "./utlity/JoyRideTour";
import Contacts from "./components/contacts/Contacts";
const App = () => {
return (

View File

@ -1,5 +1,5 @@
import React from "react";
import { FaCheck, FaLock, FaClock, FaEnvelope, FaTimes } from "react-icons/fa";
import { FaCheck, FaLock, FaClock, FaEnvelope, FaTimes, FaUserTimes } from "react-icons/fa";
import { IoCloseOutline } from "react-icons/io5";
type Signer = {
@ -48,8 +48,10 @@ const groupByRoutingOrder = (data: Signer[]) => {
};
const getInitials = (name: string) => {
if (!name.trim()) return "";
return name
.split(" ")
.filter(n => n)
.map((n) => n[0].toUpperCase())
.join("");
};
@ -63,6 +65,8 @@ const SigningOrderDiagram : React.FC<Signer[]> = (signers) => {
.map(Number)
.sort((a, b) => a - b);
console.log("sortedOrders",sortedOrders,grouped)
const currentProcessingOrder =
sortedOrders.find((order) =>
grouped[order].some((signer) => !signer.completed)
@ -109,14 +113,14 @@ const SigningOrderDiagram : React.FC<Signer[]> = (signers) => {
<div key={signer.id} className="relative">
<div
className={`w-10 h-10 rounded-full flex items-center justify-center text-white text-sm font-semibold ${bgColor}`}
title={signer.name}
title={signer?.name}
>
{getInitials(signer.name)}
{(signer?.name !== '' || signer?.roleName) ? getInitials(signer?.name || signer?.roleName) : ""}
<span className="absolute bottom-[12px] right-[-12px] p-1 rounded-full bg-white shadow">
{icon}
</span>
</div>
<p className="mt-1 text-xs text-gray-600 text-center">{signer.name.split(" ")[0]}</p>
<p className="mt-1 text-xs text-gray-600 text-center">{(signer?.name?.split(" ")[0] || signer?.roleName)}</p>
</div>
);
})}

View File

@ -17,6 +17,32 @@ const SignCompletionScreen: React.FC = () => {
const [showDownloadModal, setShowDownloadModal] = useState(false);
const [animationPhase, setAnimationPhase] = useState(0);
useEffect(() => {
const currentPath = window.location.pathname;
const handlePopState = (event) => {
// Show warning
// alert("Back navigation is blocked on this screen.");
// Push the current URL back to prevent navigating away
window.history.pushState(null, "", currentPath);
};
// Push extra states to build history buffer
for (let i = 0; i < 50; i++) {
window.history.pushState(null, "", currentPath);
}
// Add popstate listener
window.addEventListener("popstate", handlePopState);
// Cleanup on unmount
return () => {
window.removeEventListener("popstate", handlePopState);
};
}, []);
useEffect(() => {
// Trigger animation phases sequentially
const timers = [
@ -321,7 +347,7 @@ const SignCompletionScreen: React.FC = () => {
{/* Additional Info */}
<div className={`mt-8 pt-3 border-t border-gray-200 border-opacity-50`}>
<p className="text-sm text-gray-500">
Your document is signed and ready. You can download it or return to the sign made easy.
Your document is signed and saved. Download it or return to Sign Made Easy to continue — navigation is paused until an action is taken.
</p>
</div>
</div>

View File

@ -157,6 +157,7 @@ const SignDocument = () => {
const [stamp, setStamp] = useState<StampTypeObj>({ open: false, blob: "" });
console.log(recSignStatus, "recSignStatus")
// Handling Date Opacity
const [dateValues, setDateValues] = useState<{ [key: string]: string }>({});
@ -259,6 +260,8 @@ const SignDocument = () => {
}
}, [saved])
const handleSignClick = (index: number, id: number) => {
setSignTagId(id)
if (saved.save) {
@ -303,6 +306,35 @@ const SignDocument = () => {
}
}
// Step 2: Prevent browser back when status === 'COMPLETED'
// useEffect(() => {
// if (recSignStatus !== "COMPLETED") return;
// const currentPath = window.location.pathname;
// const handlePopState = (event) => {
// // Show warning
// alert("Back navigation is blocked on this screen.");
// // Push the current URL back to prevent navigating away
// window.history.pushState(null, "", currentPath);
// };
// // Push extra states to build history buffer
// for (let i = 0; i < 50; i++) {
// window.history.pushState(null, "", currentPath);
// }
// // Add popstate listener
// window.addEventListener("popstate", handlePopState);
// // Cleanup on unmount
// return () => {
// window.removeEventListener("popstate", handlePopState);
// };
// }, [recSignStatus]);
const fetchData = (isSign: boolean = false) => {
axios.get(`${import.meta.env.VITE_BASE_URI}/api/sign?recipientId=${recipientId}`,
{
@ -313,17 +345,17 @@ const SignDocument = () => {
if (res.status === 200) {
setSelectedDocuments(res.data.documents)
setRecipient(res.data.details)
setDroppedItems(res.data.tagJsons)
setTransaction(res.data.transaction)
setRecSignStatus(res.data.status)
setSelectedDocuments(res?.data?.documents)
setRecipient(res?.data?.details)
setDroppedItems(res?.data?.tagJsons)
setTransaction(res?.data?.transaction)
setRecSignStatus(res?.data?.status)
if ("provideOwnTags" in res.data.details && res.data.details.provideOwnTags) {
if ("provideOwnTags" in res?.data?.details && res?.data?.details.provideOwnTags) {
setProvideTags(true)
}
if (res.data.status === "COMPLETED") {
if (res?.data?.status === "COMPLETED") {
setShowCompletionScreen(true)
}
@ -335,6 +367,7 @@ const SignDocument = () => {
})
.catch((err) => {
console.log(err)
});
}
@ -866,6 +899,8 @@ const SignDocument = () => {
};
// const [showTerms, setShowTerms] = useState<boolean>(true);
return (
<div className={`w-full h-screen overflow-hidden relative ${(globalLoader.isLoading) ? 'pointer-events-none overflow-hidden' : ''}`}>

View File

@ -134,7 +134,7 @@ const Contacts = () => {
</button>
</div>
</div>
<div className="overflow-y-auto max-h-[79vh] border border-gray-200 rounded shadow-sm no-scrollbar">
<div className="overflow-y-auto max-h-[79vh] border border-gray-200 rounded shadow-sm no-scrollbar ">
<table className="min-w-full text-sm bg-white">
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider sticky top-0 z-10">
<tr>
@ -162,7 +162,7 @@ const Contacts = () => {
</td>
</tr>
) : (
contacts.map((contact) => (
contacts.map((contact, index) => (
<tr key={contact.id} className="hover:bg-gray-50">
<td className="px-4 py-3">{contact.firstName}</td>
<td className="px-4 py-3">{contact.lastName}</td>
@ -181,7 +181,7 @@ const Contacts = () => {
</button>
{openMenuId === contact.id && (
<div className="absolute top-full right-0 mt-1 bg-white border border-gray-200 rounded shadow-lg z-20 min-w-[100px]">
<div className={`absolute mt-1 bg-white border border-gray-200 rounded shadow-lg z-20 min-w-[100px] ${(contacts.length - 1) === index ? "bottom-1 right-4" : "bottom-0 right-5"}`}>
<button
onMouseDown={() => handleEdit(contact.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"

View File

@ -1,6 +1,6 @@
import { useRef, useState } from 'react';
import { FiUpload } from 'react-icons/fi'
import { uploadDocuments } from '../../store/slices/documentSlice';
import { resetDocuments, uploadDocuments } from '../../store/slices/documentSlice';
import { useDispatch, useSelector } from 'react-redux';
import { toast } from 'react-toastify';
import { useNavigate } from 'react-router-dom';
@ -37,7 +37,8 @@ const StartNow = () => {
});
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
await dispatch(resetDocuments())
const selectedFiles = e.target.files ? Array.from(e.target.files) : [];
if (selectedFiles.length > 0) {
setFiles((prev) => [...prev, ...selectedFiles]);

View File

@ -1,27 +1,40 @@
import React, { useEffect } from "react";
import React, { useState, useEffect } from 'react';
import {
FaArrowLeft,
FaCheck,
FaFileAlt,
FaExternalLinkAlt,
FaEye,
FaEyeSlash,
FaRedo,
FaImage,
FaUsers,
FaFileAlt,
FaEnvelope,
FaClock,
FaCog,
FaShieldAlt,
FaBell,
FaDownload,
FaShare,
FaShieldAlt,
FaLock,
FaPen,
FaCheckCircle
FaEdit,
FaSave,
FaClock,
FaLock
} from 'react-icons/fa';
import { RootState } from '../../../store/store';
import api from '../../../config/api';
import { fetchRecipients } from "../../../store/slices/selectedRecipientsSlice";
import { fetchDocuments } from "../../../store/slices/documentSlice";
import { useDispatch, useSelector } from "react-redux";
import { Link, useNavigate, useParams } from "react-router-dom";
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
import Loader from "../../dashboard/Loader";
import confetti from 'canvas-confetti'
import { IoClose } from "react-icons/io5";
import { IoMdClose, IoMdHome } from "react-icons/io";
import { Bounce, toast } from "react-toastify";
type Signer = {
id: number;
name: string;
@ -33,7 +46,7 @@ type Signer = {
// Signers List Component
const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
return (
<div className="bg-white/60 rounded-md shadow-sm border border-gray-100">
// <div className="bg-white/60 rounded-md shadow-sm border border-gray-100 ">
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-800 flex items-center gap-2">
@ -45,9 +58,9 @@ const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
</span>
</div>
<div className="space-y-3 max-h-[29vh] overflow-y-auto no-scrollbar">
<div className="space-y-3 max-h-[50vh] overflow-y-auto no-scrollbar">
{signers.map((signer) => (
<div key={signer.id} className="flex items-center gap-4 p-3 bg-white/40 rounded-lg">
<div key={signer.id} className="flex items-center gap-4 p-3 bg-gradient-to-br from-gray-50 to-blue-100 rounded-lg">
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center text-blue-600 font-medium">
{signer.name.length > 2 ? signer.name.split(" ").map(n => n[0].toUpperCase()).join("") : signer.email.split("").map(n => n[0].toUpperCase()).join("")}
</div>
@ -73,7 +86,7 @@ const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
))}
</div>
</div>
</div>
// </div>
);
};
@ -108,10 +121,10 @@ const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => {
) || -1;
return (
<div className="bg-white/60 rounded-md shadow-sm border border-gray-100">
// <div className="bg-white/60 rounded-md shadow-sm border border-gray-100">
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">Signing Flow</h3>
<div className="overflow-x-auto overflow-y-auto h-[69vh] no-scrollbar flex justify-center items-center">
<div className="overflow-x-auto overflow-y-auto h-[50vh] no-scrollbar flex justify-center items-center">
<div className="flex justify-center items-center gap-4 min-w-max">
{/* Mail icon */}
<div className="flex flex-col items-center">
@ -162,7 +175,7 @@ const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => {
</span>
</div>
<p className="mt-1 text-xs text-gray-600 text-center max-w-16 truncate">
{signer.name.split(" ")[0]}
{signer.name.split(" ")[0] || signer.firstName.split(0,1)}
</p>
</div>
);
@ -181,26 +194,13 @@ const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => {
</div>
</div>
</div>
</div>
// </div>
);
};
// Main DocumentCompletionScreen Component
const DocumentCompletionScreen = () => {
const documentList = [
{ title: 'Document 1', pages: 12 },
{ title: 'Document 2', pages: 8 },
{ title: 'Document 3', pages: 5 },
{ title: 'Document 4', pages: 22 },
{ title: 'Document 5', pages: 10 },
{ title: 'Document 6', pages: 15 },
{ title: 'Document 7', pages: 7 },
{ title: 'Document 8', pages: 9 },
{ title: 'Document 9', pages: 6 },
{ title: 'Document 10', pages: 11 },
{ title: 'Document 11', pages: 4 }, // Won't be shown due to slice
];
const DocumentSubmissionOptions = () => {
const location = useLocation();
const [status, setStatus] = useState(null);
console.log("location.pathname", location.pathname, status)
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const [showConfetti, setShowConfetti] = React.useState(true)
if (!accountId || !transactionId) {
@ -215,6 +215,8 @@ const DocumentCompletionScreen = () => {
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
console.log('Line 218', selectedDocuments)
const [transactionData,setTransactionData] = useState({})
useEffect(() => {
@ -265,93 +267,278 @@ const DocumentCompletionScreen = () => {
}, [showConfetti]);
console.log("History length:", window.history.length);
// Step 1: Fetch transaction status
useEffect(() => {
const fetchTransaction = async () => {
// /api/accounts/{accountId}/transactions/{trxId}
try {
const res = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}`);
if (res?.data) {
setStatus(res?.data?.status);
setTransactionData(res?.data)
}
} catch (error) {
console.error("Failed to fetch transaction:", error);
}
};
if (accountId && transactionId) {
fetchTransaction();
}
}, [accountId, transactionId]);
// Step 2: Prevent browser back when status === 'SENT'
useEffect(() => {
if (status === "SENT") {
const currentPath = window.location.pathname;
const handlePopState = (event) => {
// Show warning
alert("Back navigation is blocked on this screen.");
// Push the current URL back to prevent navigating away
window.history.pushState(null, "", currentPath);
};
// Push extra states to build history buffer
for (let i = 0; i < 50; i++) {
window.history.pushState(null, "", currentPath);
}
// Add popstate listener
window.addEventListener("popstate", handlePopState);
// Cleanup on unmount
return () => {
window.removeEventListener("popstate", handlePopState);
};
}
}, [status]);
// useEffect(() => {
// if (status === 'SENT') {
// // Step 1: Push a dummy state to trap the user
// window.history.pushState(null, '', window.location.href);
// // Step 2: Intercept back button
// const handlePopState = (event) => {
// // Push the same dummy state again to block back
// window.history.pushState(null, '', window.location.href);
// };
// window.addEventListener('popstate', handlePopState);
// return () => {
// window.removeEventListener('popstate', handlePopState);
// };
// }
// }, [status]);
const [selectedOption, setSelectedOption] = useState('success');
const [isVisible, setIsVisible] = useState(false);
const [previewData, setPreviewData] = useState({
documentName: "Contract Agreement.pdf",
submissionCount: 1,
signerName: "Sumit Kumar",
signerEmail: "sumit.kumar@hicglobalsolutions.com",
submissionTime: new Date().toLocaleString()
});
useEffect(() => {
setIsVisible(true);
}, []);
const options = [
{
id: 'success',
icon: FaCheck,
title: 'Success screen',
description: 'Display confirmation message after successful document submission',
color: 'bg-green-50 border-green-200 hover:bg-green-100',
iconColor: 'text-green-600',
isPremium: false
},
{
id: 'redirect',
icon: FaExternalLinkAlt,
title: 'Signing Flow',
description: 'Navigate user to view their transaction details',
color: 'bg-blue-50 border-blue-200 hover:bg-blue-100',
iconColor: 'text-blue-600',
isPremium: true
},
{
id: 'public',
icon: FaEye,
title: 'Signature status',
description: 'Show transaction status on public dashboard',
color: 'bg-purple-50 border-purple-200 hover:bg-purple-100',
iconColor: 'text-purple-600',
isPremium: true
},
];
const getPreviewContent = () => {
switch (selectedOption) {
case 'success':
return (
<div className="text-center space-y-6">
<div className="w-24 h-24 bg-gradient-to-br from-teal-500 to-green-400 rounded-full flex items-center justify-center mt-6 mx-auto shadow-lg animate-bounce">
<FaCheck className="text-white text-4xl" />
</div>
<div>
<h3 className="text-2xl font-bold text-gray-800 mb-2">Document Send Successfully!</h3>
{selectedDocuments?.map((docs) => (
<p className="text-gray-600">Your document "{docs.name}" has been sent for signature.</p>
) )}
</div>
<div className="bg-gray-50 rounded-lg p-4 space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-500">createdBy:</span>
<span className="font-medium">{transactionData?.createdBy?.name}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500">Email:</span>
<span className="font-medium">{transactionData?.createdBy?.email}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500">createdAt:</span>
<span className="font-medium">{transactionData.createdAt}</span>
</div>
</div>
</div>
);
case 'redirect':
return (
<>
<SigningOrderDiagram signers={selectedRecipients} />
</>
);
case 'public':
return (
<>
<SignersList signers={selectedRecipients} />
</>
);
default:
return null;
}
};
return (
<>
<div className="min-h-screen bg-[var(--mainViewBg)]">
{/* Top Banner */}
<div className='fixed top-0 w-full flex items-center justify-between bg-white shadow z-50 px-4 py-3'>
<div className='flex items-center gap-2'>
<Link to={"/"}>
<IoMdHome
className='text-2xl text-slate-500 cursor-pointer hover:text-[var(--primary)] transition'
/>
</Link>
<p className="text-base font-normal text-gray-700 border-l pl-4">🎉 Awesome! The document’s been sent for signature. 🎉</p>
</div>
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50 p-6">
<div className={`max-w-6xl mx-auto transition-all duration-700 ${isVisible ? 'translate-y-0 opacity-100' : 'translate-y-8 opacity-0'}`}>
<div
id="add-tags-btn-step-one"
className='flex items-center gap-3'>
<Link to={`/accounts/${accountId}/transactions/${transactionId}`} className="flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]">
{/* <FaPen className=" text-white text-md" /> */}
<span className="tracking-wide text-white">View Transaction</span>
</Link>
{/* <button
onClick={() => handleNextClick(false)}
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition cursor-pointer"
>
{signType === SignTypeEnum.selfSign ? "Sign" : "Next"}
</button> */}
</div>
{/* Header */}
<div className="flex items-center space-x-4 mb-5">
<Link to={'/'} className="flex items-center space-x-2 text-gray-600 hover:text-[var(--primary)] transition-colors duration-200 group">
<IoMdHome
className='text-2xl text-slate-500 cursor-pointer hover:text-[var(--primary)] transition'
/>
</Link>
<div className="h-8 w-px bg-gray-300"></div>
<h1 className="text-2xl font-bold text-gray-800">🎉 Awesome! The document’s been sent for signature. 🎉</h1>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3 ">
{/* Left Panel - Configuration */}
<div className="space-y-6 ">
{/* Action Selection */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200 min-h-[81vh] ">
<div className="p-6">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Choose an action after a signer completes the document.
</h3>
<div className="space-y-3">
{options.map((option) => {
const Icon = option.icon;
const isSelected = selectedOption === option.id;
{(selectedRecipients.length > 0 && selectedDocuments.length > 0) ?
<div className="p-3 mt-[55px]">
<div className="max-w-[97vw] mx-auto">
{/* Header */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-2">
{/* Left Section - Document Details */}
<div className="lg:col-span-6 space-y-2">
{/* Document Preview Card */}
<div className="bg-white/60 rounded-md shadow-sm border border-gray-100 overflow-hidden max-h-[88vh] no-scrollbar overflow-y-auto">
<div className="p-3 ">
{selectedDocuments?.map((item, index) => (
<div key={item.id} className="flex items-start justify-between mb-2 px-2 py-3">
<div className="flex items-center gap-4">
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
{/* <FaFileAlt className="text-blue-600 text-xl" /> */}
<img src={item.pages[0].src} alt="document" className="h-10 w-10" />
</div>
<div>
<h3 className="text-md font-semibold text-gray-800">
Name : {item.name ?? '--'}
</h3>
<div className="text-gray-600 text-sm">
Status : {item.status ?? '--'}
</div>
return (
<div
key={option.id}
onClick={() => setSelectedOption(option.id)}
className={`
relative cursor-pointer p-4 rounded-xl border-2 transition-all duration-200 group
${isSelected
? 'border-[var(--primary)] bg-blue-50 shadow-md scale-[1.02]'
: option.color + ' border-2'
}
hover:shadow-md hover:scale-[1.01] transform
`}
>
<div className="flex items-start space-x-4">
<div className={`
w-12 h-12 rounded-full flex items-center justify-center transition-all duration-200
${isSelected
? 'bg-[var(--primary)] text-white shadow-lg'
: 'bg-white ' + option.iconColor + ' group-hover:scale-110'
}
`}>
<Icon className="text-lg" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2">
<h4 className={`font-semibold transition-colors duration-200 ${isSelected ? 'text-[var(--primary)]' : 'text-gray-800'
}`}>
{option.title}
</h4>
{option.isPremium && (
<span className="text-purple-500 text-sm">★</span>
)}
</div>
<p className="text-sm text-gray-600 mt-1">
{option.description}
</p>
</div>
</div>
))}
</div>
</div>
<div><SignersList signers={selectedRecipients} /></div>
{/* Selection indicator */}
{isSelected && (
<div className="absolute top-4 right-4">
<div className="w-6 h-6 bg-[var(--primary)] rounded-full flex items-center justify-center">
<FaCheck className="text-white text-xs" />
</div>
</div>
)}
</div>
);
})}
</div>
</div>
</div>
{/* Right Section - Signer Components */}
<div className="lg:col-span-6 space-y-1.5">
{/* Signing Order Diagram */}
<SigningOrderDiagram signers={selectedRecipients} />
{/* Custom Image Section */}
</div>
{/* Signers List */}
{/* Right Panel - Preview */}
<div className="space-y-6">
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden sticky top-6">
<div className="bg-gradient-to-r from-gray-50 to-blue-50 px-6 py-2 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-800">Live Preview</h3>
<p className="text-sm text-gray-500">See how your users will experience the completion</p>
</div>
<div className="p-3 min-h-[70vh] flex justify-center">
<div className="w-full transition-all duration-500 transform">
{getPreviewContent()}
</div>
</div>
</div>
</div>
: <Loader />}
</div>
</div>
</>
</div>
);
};
export default DocumentCompletionScreen;
export default DocumentSubmissionOptions;

View File

@ -60,6 +60,7 @@ const ProcessStepOne = () => {
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients)
const [showSelfSignWarnModal, setShowSelfSignWarnModal] = useState(false)
const [editRecipientCurrentEmail, setEditRecipientCurrentEmail] = useState('')
console.log()
const userData = useSelector((state: RootState) => state.user)
@ -142,7 +143,8 @@ const ProcessStepOne = () => {
let formError = {
documents: false,
recipients: false,
subject: false
subject: false,
recipientEmailAndLastName:false
}
if (transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
@ -150,20 +152,32 @@ const ProcessStepOne = () => {
formError.documents = true
}
if (!selectedRecipients.length) {
formError.recipients = true
formError.recipients = true;
} else {
// Check if all recipients have email and lastName
const isValid = selectedRecipients.every(
(item) => item.email?.trim() !== '' && item.lastName?.trim() !== ''
);
if (!isValid) {
// alert('All recipients must have both Email and Last Name filled.');
formError.recipientEmailAndLastName = true;
}
}
if (!emailDtls.subject.length) {
formError.subject = true
}
if (SignTypeEnum.selfSign === signType) formError.recipients = false;
if (formError.documents || formError.recipients || formError.subject) {
if (formError.documents || formError.recipients || formError.subject || formError.recipientEmailAndLastName) {
console.log(selectedDocuments, 'line 193')
toast.dismiss();
toast.error(`${formError.documents ? "Add Document" : ""}
${(formError.documents && formError.recipients) ? " | " : ""}
${formError.recipients ? "Add Recipient" : ""}
${formError.recipientEmailAndLastName ? "All recipients must have both Email and Last Name." : ""}
${(formError.documents || formError.recipients) ? " | " : ""}
${formError.subject ? "Add Subject" : ""}`, {
position: "top-center",
@ -434,7 +448,8 @@ const ProcessStepOne = () => {
{
(signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) &&
<div className="flex flex-col gap-4 align-start">
<RecipientCards editRecipient={editRecipient} transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} />
<RecipientCards transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} editRecipient={editRecipient} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} editRecipientCurrentEmail={editRecipientCurrentEmail} />
{/* {signType !== "self-sign" && <RecipientAddCard />} */}
<div
@ -442,7 +457,7 @@ const ProcessStepOne = () => {
className={`relative w-full bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none h-screen overflow-hidden' : ''}`}
>
<RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} editRecipient={editRecipient} editCase={editCase} setEditRecipient={setEditRecipient} setEditCase={setEditCase} topRef={topRef} />
<RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} editRecipient={editRecipient} editCase={editCase} setEditRecipient={setEditRecipient} setEditCase={setEditCase} topRef={topRef} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} />
</div>
</div>

View File

@ -22,10 +22,11 @@ interface ContactType {
export interface ContactTypeObj {
type: string;
contacts: ContactType[];
setEditRecipientCurrentEmail: any
}
const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase, setEditRecipient, setEditCase, topRef }) => {
const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase, setEditRecipient, setEditCase, topRef, setEditRecipientCurrentEmail }) => {
const [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
@ -112,7 +113,7 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
hostName: editRecipient.hostName || "",
hostEmail: editRecipient.hostEmail || "",
roleName: editRecipient.roleName || "",
type: editRecipient.type || recipientType.SIGNER,
type: editRecipient.type,
allowSenderToEdit: editRecipient.allowSenderToEdit ?? true,
allowSenderToDelete: editRecipient.allowSenderToDelete ?? true,
uuid: String(editRecipient?.id ?? editRecipient?.uuid ?? ''),
@ -209,11 +210,22 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
submitForm();
function submitForm() {
if (editCase === 'Edit') {
dispatch(updateRecipient(recipient));
let updatedRecipient = { ...recipient };
// Clear hostName and hostEmail if type is SIGNER or CC
if (
updatedRecipient.type === recipientType.SIGNER ||
updatedRecipient.type === recipientType.CC
) {
updatedRecipient.hostName = "";
updatedRecipient.hostEmail = "";
}
if (editCase === "Edit") {
dispatch(updateRecipient(updatedRecipient));
} else {
dispatch(addRecipient({
...recipient,
...updatedRecipient,
uuid: uuidv4(),
newOne: true,
}));
@ -241,6 +253,7 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
type: false,
});
setEditRecipientCurrentEmail("")
setEditCase("");
setManualFocusTriggered(false);
formRef.current?.reset();
@ -578,7 +591,7 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
type="radio"
name="recipient-type"
value={option.value}
defaultChecked={option.value === recipientType.SIGNER}
checked={recipient.type === option.value}
onClick={(e) => setRecipients({ ...recipient, type: e.target.value })}
className="accent-[var(--primary)] cursor-pointer"
/>

View File

@ -30,12 +30,17 @@ type CardsData = {
setEditRecipient: (recipient: any) => void; // Replace `any` with the actual type if known
setEditCase: (string: any) => void;
transactionTemplateToggle: string
editRecipient: any
setEditRecipientCurrentEmail: any,
editRecipientCurrentEmail: string
};
const RecipientCards = ({ editRecipient, setEditRecipient, setEditCase, transactionTemplateToggle }: CardsData) => {
const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateToggle, editRecipient, editCase, setEditRecipientCurrentEmail, editRecipientCurrentEmail }: CardsData) => {
const templateId = useParams<{ templateId: string }>().templateId;
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const dispatch = useDispatch();
if (!transactionId) {
@ -57,6 +62,11 @@ const RecipientCards = ({ editRecipient, setEditRecipient, setEditCase, transact
// alert(JSON.stringify(recipient))
setEditCase('Edit')
setEditRecipient(recipient)
if (recipient.uuid) {
setEditRecipientCurrentEmail(recipient.uuid)
}else{
setEditRecipientCurrentEmail(recipient.id)
}
}
const handleRemoveRecipient = (recipient) => {
@ -128,40 +138,36 @@ const RecipientCards = ({ editRecipient, setEditRecipient, setEditCase, transact
)}
<div className="flex flex-col divide-y divide-gray-200">
{recipients.map((recipient, idx) => {
// if(recipient.id === editRecipient.id) return
return (
<div
key={recipient.email + idx}
className="flex items-center justify-between px-4 py-3 hover:bg-gray-50 transition-colors"
>
<div className="flex items-center gap-3 min-w-0">
{/* {signingOrder && ( */}
{recipients.length > 1 ? <div className="relative">
<input
type="number"
value={recipient.routingOrder || 1}
min={1}
onChange={(e) =>
handleOrderChange(recipient.id || recipient.email, Number(e.target.value))
}
className="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 bg-slate-100"
/>
{/* <RiPencilFill className="absolute top-1.5 right-0 text-[14px] text-gray-500" /> */}
</div>
: <input
type="number"
disabled
value={recipient.routingOrder || 1}
min={1}
onChange={(e) =>
handleOrderChange(recipient.id || recipient.email, Number(e.target.value))
}
className="w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)]"
/>}
{/* )} */}
{recipients.map((recipient, idx) => (
<div
key={recipient.email + idx}
className={`flex items-center justify-between px-4 py-3 hover:bg-gray-50 transition-colors ${(recipient.uuid || recipient.id) === editRecipientCurrentEmail ? "bg-neutral-100 opacity-40 " : ""}`}
>
<div className="flex items-center gap-3 min-w-0">
{/* {signingOrder && ( */}
{recipients.length > 1 ? <div className="relative">
<input
type="number"
value={recipient.routingOrder || 1}
min={1}
onChange={(e) =>
handleOrderChange(recipient.id || recipient.email, Number(e.target.value))
}
className="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 bg-slate-100"
/>
{/* <RiPencilFill className="absolute top-1.5 right-0 text-[14px] text-gray-500" /> */}
</div>
: <input
type="number"
disabled
value={recipient.routingOrder || 1}
min={1}
onChange={(e) =>
handleOrderChange(recipient.id || recipient.email, Number(e.target.value))
}
className="w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)]"
/>}
<div className="flex-shrink-0 w-8 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>
@ -219,7 +225,7 @@ const RecipientCards = ({ editRecipient, setEditRecipient, setEditCase, transact
)}
</div>
)
})}
)}
</div>
</>
) : (

View File

@ -63,6 +63,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
return null;
}
// const [transactionSaving, setTransactionSaving] = useState<boolean>(false);
const [dotCount, setDotCount] = useState(0);
@ -125,10 +126,10 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
setTimeout(() => {
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
if (isSignSend) {
navigate(`./../../../sign/${selfUserSign.recipientId}`);
navigate(`./../../../sign/${selfUserSign.recipientId}`,{ replace: true });
}
else {
navigate(`/document-completion-screen/${accountId}/${transactionId}`)
else {
navigate(`/document-completion-screen/${accountId}/${transactionId}`, { replace: true })
}
} else {
navigate(`/templates`);
@ -311,7 +312,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
return (<div key={indx} className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-default'>
<div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p>
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{recipient.firstName} {recipient.lastName} segfsdtzgszrdtgrs serdgvsrdgs dtrgv</p>
<p className='whitespace-nowrap overflow-hidden text-ellipsis max-w-[12ch]'>{recipient.firstName} {recipient.lastName}</p>
</div>
</div>)
})}

View File

@ -9,6 +9,7 @@ import { RootState } from '../../../store/store';
import Loader from '../../dashboard/Loader';
import { hideLoader } from '../../../store/slices/loaderSlice';
import { useNavigate } from 'react-router-dom';
import api from '../../../config/api';
export interface IsSendInterface {
send: boolean | null;
@ -25,7 +26,7 @@ const ProcessStepTwo = () => {
const [isSend, setIsSend] = useState<boolean | null>(null);
const globalLoader = useSelector((state: RootState) => state.loader)
const dispatch = useDispatch();
const navigate = useNavigate();
const navigate = useNavigate();
useEffect(() => {
setTimeout(() => {
@ -33,6 +34,8 @@ const ProcessStepTwo = () => {
}, 800)
}, [])
return (
<div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}>

View File

@ -55,7 +55,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
getAllSignatures,
editingSignature
}) => {
const [activeTab, setActiveTab] = useState<'TYPE' | 'DRAW' | 'UPLOAD' >('TYPE');
const [activeTab, setActiveTab] = useState<'SELECT' | 'DRAW' | 'UPLOAD'>('SELECT');
const [fullName, setFullName] = useState('');
const [initials, setInitials] = useState('');
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
@ -107,7 +107,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
// Navigation items
const navigationItems = [
{ id: 'TYPE', label: 'TYPE', icon: <GrCheckboxSelected className="w-4 h-4" /> },
{ id: 'SELECT', label: 'SELECT', icon: <GrCheckboxSelected className="w-4 h-4" /> },
{ id: 'DRAW', label: 'DRAW', icon: <AiOutlineEdit className="w-5 h-5" /> },
{ id: 'UPLOAD', label: 'UPLOAD', icon: <AiOutlineUpload className="w-5 h-5" /> },
// { id: 'SELECT', label: 'SELECT', icon: <GrCheckboxSelected className="w-4 h-4" /> },
@ -118,7 +118,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
if (editingSignature) {
setFullName(`${editingSignature.firstName} ${editingSignature.lastName}`);
setInitials(`${editingSignature.firstName.slice(0, 1)}${editingSignature.lastName.slice(0, 1)}`);
setActiveTab(editingSignature.signatureType as 'TYPE' | 'DRAW' | 'UPLOAD');
setActiveTab(editingSignature.signatureType as 'SELECT' | 'DRAW' | 'UPLOAD');
// Set font for SELECT tab
const fontOption = signatureOptions.find(option => option.style === editingSignature.font);
@ -193,10 +193,12 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
const handleSubmit = async () => {
setIsSubmitting(true);
if (!signatures.fullSignature || !signatures.initials) {
alert('Please provide both signature and initials');
setIsSubmitting(false);
return;
if (activeTab === 'DRAW' || activeTab === 'UPLOAD') {
if (!signatures.fullSignature || !signatures.initials) {
alert('Please provide both signature and initials');
setIsSubmitting(false);
return;
}
}
try {
@ -207,7 +209,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
firstName: fullName.split(' ')[0] || user.firstName,
lastName: fullName.split(' ')[1] || user.lastName,
font: selectedSignature.style,
default:checked
default: checked
};
let res;
@ -273,7 +275,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
const renderContent = () => {
switch (activeTab) {
case 'TYPE':
case 'SELECT':
return (
<div className="h-full flex flex-col ">
<div className="mb-6">
@ -481,7 +483,7 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
{navigationItems.map((item) => (
<button
key={item.id}
onClick={() => setActiveTab(item.id as 'TYPE' | 'DRAW' | 'UPLOAD')}
onClick={() => setActiveTab(item.id as 'SELECT' | 'DRAW' | 'UPLOAD')}
className={`w-full flex items-center space-x-3 px-2 py-2 text-sm font-medium rounded-lg cursor-pointer transition-colors ${activeTab === item.id
? 'bg-blue-50 text-blue-700 border border-blue-200'
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'
@ -534,9 +536,9 @@ const AddSignatureModal: React.FC<AddSignatureModalProps> = ({
</div>
<p className="text-sm text-gray-700 opacity-80 ms-1">
{checked
? 'Signature will be used by default.'
: 'Signature will not be used by default.'}
Signature will be used by default.
</p>
</label>

View File

@ -90,6 +90,22 @@ const Templates = () => {
navigate(`/process-step-one/${accountId}/template/${templateId}`);
}
function formatTimestamp(isoString, options = { timeZone: 'local' }) {
// Parse the ISO string to a Date object
const date = new Date(isoString);
if (isNaN(date)) {
return 'Invalid date';
}
// Return formatted date string
if (options.timeZone === 'UTC') {
return date.toUTCString(); // e.g. "Mon, 04 Aug 2025 13:46:46 GMT"
} else {
return date.toLocaleString(); // e.g. "8/4/2025, 9:46:46 AM" (based on local time zone)
}
}
return (
<div className="p-3 text-sm">
{/* Header */}
@ -159,8 +175,8 @@ const Templates = () => {
</div>
</td>
<td className="px-4 py-2 text-sm text-center">{item.name || 'Untitled'}</td>
<td className="px-4 py-2 text-sm text-center">{formatDate(item.createdAt)}</td>
<td className="px-4 py-2 text-sm text-center">{formatDate(item.lastModifiedAt)}</td>
<td className="px-4 py-2 text-sm text-center">{formatTimestamp(item.createdAt)}</td>
<td className="px-4 py-2 text-sm text-center">{formatTimestamp(item.lastModifiedAt)}</td>
<td className="px-4 py-2 text-sm text-center" onClick={() => handleUseClick(item)}><span className="bg-[var(--lightGreyCol)] cursor-pointer border-gray-300 text-gray-600 hover:bg-gray-100 px-3 py-1 rounded-lg font-semibold text-[12px]">Use</span></td>
{/* Dropdown Trigger + Tooltip */}
<td className="px-4 py-2 text-center relative">
@ -173,7 +189,7 @@ const Templates = () => {
{showMenu === index && (
<div
className="absolute right-15 top-5 mt-2 w-32 bg-white border border-gray-200 rounded shadow-lg z-10 transition-opacity duration-300 ease-in-out"
className="absolute right-21 top-0 mt-2 w-20 bg-white border border-gray-200 rounded shadow-lg z-10 transition-opacity duration-300 ease-in-out"
tabIndex={0} // Make it focusable
onBlur={(e) => {
// Ensure it only closes if focus truly leaves the container
@ -183,7 +199,7 @@ const Templates = () => {
}}
>
<button
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
className="block w-full text-left px-4 py-1 text-sm text-gray-700 hover:bg-gray-100"
onClick={() => handleEditClick(item.id)}
>
Edit

View File

@ -0,0 +1,254 @@
import React, { useState } from "react";
import {
FiX,
FiCheckCircle,
FiXCircle,
FiClock,
FiUser,
FiCalendar,
FiFileText,
FiAlertTriangle,
FiTruck,
FiUserCheck,
FiUserX,
FiPackage,
FiMapPin,
FiEye,
FiRotateCcw
} from "react-icons/fi";
interface AuditModalProps {
closeModal: () => void;
}
const AuditModal: React.FC<AuditModalProps> = ({ closeModal }) => {
const [activeTab, setActiveTab] = useState<'status' | 'audit'>('status');
const statusHistory = [
{
id: 1,
title: "The Accrual status has been successfully changed to Approved.",
author: "Alexander Turner",
role: "Analyst Perspective on Transaction",
time: "34m ago",
fromStatus: "Ready to Review",
toStatus: "Approved",
icon: <FiCheckCircle className="w-4 h-4" />,
statusColor: "text-green-600"
},
{
id: 2,
title: "The Accrual has been updated to reflect a status of Reject.",
author: "Alexander Turner",
time: "3h ago",
fromStatus: "Ready to Review",
toStatus: "Rejected",
icon: <FiXCircle className="w-4 h-4" />,
statusColor: "text-red-600"
},
{
id: 3,
title: "The Accrual has been revised for accuracy.",
author: "Alex Turner",
role: "Manager View Transaction State",
time: "7d ago",
fromStatus: "Rejected",
toStatus: "Reviewed",
icon: <FiRotateCcw className="w-4 h-4" />,
statusColor: "text-yellow-600"
},
{
id: 4,
title: "The Accrual state has been updated from 'ready' to 'under review'.",
author: "Alexander Turner",
role: "Analyst Perspective on Transaction",
time: "34m ago",
fromStatus: "Ready to Review",
toStatus: "Reviewed",
icon: <FiEye className="w-4 h-4" />,
statusColor: "text-blue-600"
},
{
id: 5,
title: "The Accrual has been updated from rejected to reviewed.",
author: "Alex Turner",
role: "Analyst View Transaction",
time: "34m ago",
fromStatus: "Rejected",
toStatus: "Reviewed",
icon: <FiRotateCcw className="w-4 h-4" />,
statusColor: "text-blue-600"
}
];
const auditTrail = [
{
id: 1,
title: "Batch 11317 Pickup Request Created",
author: "Sandra Darlington",
location: "Lagos General Hospital Gbagada",
time: "11 Aug, 2023 09:23 AM",
icon: <FiPackage className="w-4 h-4" />,
type: "created"
},
{
id: 2,
title: "Batch 11317 Pickup Request",
author: "Oladejo Israel",
location: "Institute of Human Virology H3Africa BioRepository",
time: "12 Aug, 2023 11:19 AM",
status: "Approved",
icon: <FiCheckCircle className="w-4 h-4" />,
type: "approved"
},
{
id: 3,
title: "Batch 11317 was Initiated",
author: "Sandra Darlington",
location: "Lagos General Hospital Gbagada",
time: "12 Aug, 2023 11:19 AM",
icon: <FiFileText className="w-4 h-4" />,
type: "initiated"
},
{
id: 4,
title: "Sandra Darlington Assigned batch 11317",
assignee: "Yusuf Ahmed",
time: "12 Aug, 2023 11:20 AM",
icon: <FiUserCheck className="w-4 h-4" />,
type: "assigned"
},
{
id: 5,
title: "Batch 11317 was rejected by Yusuf Ahmed",
reason: "due to faulty vehicle",
time: "12 Aug, 2023 11:20 AM",
icon: <FiXCircle className="w-4 h-4" />,
type: "rejected"
},
{
id: 6,
title: "Sandra Darlington Assigned batch 11317",
assignee: "Kolado Jolade",
time: "12 Aug, 2023 11:20 AM",
icon: <FiUserCheck className="w-4 h-4" />,
type: "assigned"
},
{
id: 7,
title: "Logger Synced",
deviceId: "ID: LGR-0421",
status: "95% remaining",
time: "12 Aug, 2023 11:25 AM",
icon: <FiCheckCircle className="w-4 h-4" />,
type: "synced"
},
{
id: 8,
title: "Logger Disconnected",
deviceId: "ID: LGR-0421",
time: "12 Aug, 2023 11:25 AM",
icon: <FiAlertTriangle className="w-4 h-4" />,
type: "disconnected"
}
];
const getStatusBadge = (status: string) => {
const statusClasses = {
'Approved': 'bg-green-100 text-green-800',
'Rejected': 'bg-red-100 text-red-800',
'Reviewed': 'bg-yellow-100 text-yellow-800',
'Ready to Review': 'bg-blue-100 text-blue-800'
};
return statusClasses[status as keyof typeof statusClasses] || 'bg-gray-100 text-gray-800';
};
const getAuditTypeColor = (type: string) => {
const colors = {
'created': 'text-blue-600',
'approved': 'text-green-600',
'initiated': 'text-purple-600',
'assigned': 'text-indigo-600',
'rejected': 'text-red-600',
'synced': 'text-green-600',
'disconnected': 'text-orange-600'
};
return colors[type as keyof typeof colors] || 'text-gray-600';
};
return (
<div className="fixed inset-0 flex items-center justify-center backdrop-blur-sm bg-white/10 z-50">
<div className="bg-white rounded-md shadow-md w-full max-w-[75vw] max-h-[95vh] min-h-[85vh] flex flex-col">
{/* Header */}
<div className="flex items-center justify-between p-3 border-b border-gray-200 bg-[var(--primary)] rounded-t-md">
<div className="flex items-center">
<h2 className="text-xl font-semibold text-white">
Audit Status
</h2>
</div>
<button
onClick={closeModal}
className="text-white cursor-pointer transition-colors"
>
<FiX className="w-5 h-5" />
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-4">
<div className="space-y-1.5">
<div className="text-sm text-gray-500 font-medium mb-2">TODAY</div>
{statusHistory.slice(0, 2).map((item) => (
<div key={item.id} className="flex items-start space-x-4 pb-2 border-b border-gray-100 last:border-b-0">
<div className={`${item.statusColor} mt-1`}>
{item.icon}
</div>
<div className="flex-1">
<p className="text-gray-800 font-medium text-sm mb-0.5">{item.title}</p>
<div className="flex items-center space-x-2 text-xs text-gray-600 mb-1">
<span>By {item.author}</span>
{item.role && (
<>
<span>•</span>
<span className="text-blue-600 text-xs">({item.role})</span>
</>
)}
</div>
<div className="flex items-center space-x-3">
<span className="text-xs text-gray-500">{item.time}</span>
<div className="flex items-center space-x-2">
<span className={`px-2 py-0.5 text-xs rounded-full ${getStatusBadge(item.fromStatus)}`}>
{item.fromStatus}
</span>
<span className="text-gray-400">→</span>
<span className={`px-2 py-0.5 text-xs rounded-full ${getStatusBadge(item.toStatus)}`}>
{item.toStatus}
</span>
</div>
</div>
</div>
</div>
))}
</div>
</div>
{/* Footer */}
<div className="flex justify-end p-2 border-t border-gray-200">
<button
onClick={closeModal}
className="cursor-pointer px-4 py-1.5 bg-[var(--sendForSignBgCol)] text-white rounded-lg hover:bg-red-700 transition-colors font-medium"
>
Close
</button>
</div>
</div>
</div>
);
};
export default AuditModal;

View File

@ -1,5 +1,5 @@
import { AiFillSignature } from 'react-icons/ai';
import { Link, useNavigate, useParams } from 'react-router-dom';
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';
@ -10,6 +10,7 @@ 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';
// Sample recipient data — you can replace this with actual API data later
const recipients = [
@ -44,6 +45,35 @@ const TransactionView = () => {
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);
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
};
console.log(transactionDetails, "line 37");
@ -99,9 +129,13 @@ const TransactionView = () => {
More
</button>
<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-two/${accountId}/${transactionId}`}>
{/* <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-two/${accountId}/${transactionId}`}>
Edit
</Link>
</Link> */}
<button className='cursor-pointer border text-green-700 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' />
@ -138,7 +172,7 @@ const TransactionView = () => {
</div>
{recipient.email === user.email && recipient.status !== "COMPLETED" &&
<p
onClick={() => handleNavigateToSign(recipient.id)}
onClick={() => handleNavigateToSign(recipient.id)}
className='text-white rounded-sm bg-[var(--primary)] px-3 py-1 font-semibold cursor-pointer'>
Sign
</p>}
@ -207,6 +241,8 @@ const TransactionView = () => {
</div>
</div>
}
{isModalOpen && <AuditModal closeModal={closeModal} />}
</>
}
</>