Merge pull request 'jai' (#18) from jai into main

Reviewed-on: http://git.hicglobalsolutions.in/BhawanSingh/SME-Web/pulls/18
This commit is contained in:
jaipandey 2025-08-14 12:06:05 +00:00
commit 467bc0266f
24 changed files with 970 additions and 763 deletions

BIN
src/assets/SMELogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -117,7 +117,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
id: account.userId,
name: account.name,
email: account.userEmail,
language: "", // set it if available
language: account.language, // set it if available
account: {
id: account.accountId,
isDefault: false, // update as needed
@ -129,7 +129,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
id: account.userId,
name: account.name,
email: account.userEmail,
language: "", // set it if available
language: account.language, // set it if available
account: {
id: account.accountId,
isDefault: false, // update as needed
@ -154,7 +154,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
id: item?.id,
name: item?.firstName + " " + item?.lastName,
email: item?.email,
language: "",
language: item?.language,
account: {
id: item?.account.id,
isDefault: false,
@ -263,7 +263,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
);
return (
<header className="w-full px-6 py-2 flex justify-between items-center bg-white border-b rounded-sm border-gray-200">
<header className="w-full px-6 py-1.5 flex justify-between items-center bg-white border-b border-gray-200">
<nav className="text-md font-medium text-gray-800 capitalize flex items-center flex-wrap">
{pathnames.length === 0 ? (
<span className="text-gray-900 font-semibold">Home</span>

View File

@ -3,21 +3,11 @@ import { PiDotsThreeOutlineVerticalFill } from "react-icons/pi";
import AccountNav from "./AccountNav";
import api from "../../config/api";
import { useNavigate, useParams } from "react-router-dom";
import { FaCheck } from "react-icons/fa";
import { FaCheck, FaClock } from "react-icons/fa";
import Cards from "../home/Cards";
import AddInviteModal from "./AddInviteModal";
import { Bounce, toast } from "react-toastify";
import {
FaCheckCircle,
FaClock,
FaHourglassHalf,
FaTasks,
FaChevronDown,
FaPlus,
FaUserPlus,
FaFilter,
FaCalendarAlt
} from 'react-icons/fa';
import { FiFileText, FiClock, FiAlertTriangle, FiCheckCircle } from "react-icons/fi";
import TransactionDataTable from "./TransactionDataTable";
import { useSelector } from "react-redux";
import { RootState } from "../../store/store";
@ -35,34 +25,40 @@ const Account = () => {
const mapApiDataToCards = (data: any): CardData[] => {
return [
{
title: 'Action Needed',
title: "Action Required",
count: data.actionNeeded || 0,
icon: <FaTasks size={18} className="text-indigo-600" />,
gradient: 'bg-gradient-to-br from-indigo-50 to-indigo-100',
shadowColor: 'shadow-indigo-200/50',
iconBg: 'bg-indigo-100 border-indigo-200',
textColor: 'text-indigo-700',
trend: 'up'
icon: <FiFileText className="w-5 h-5 text-sky-500" />,
iconBg: "bg-sky-100 border-sky-200",
textColor: "text-sky-600",
gradient: "bg-white",
shadowColor: "shadow-sky-100",
},
{
title: 'Expiring Soon',
title: "Waiting for Response",
count: data.waitingForOthers || 0,
icon: <FiClock className="w-5 h-5 text-yellow-500" />,
iconBg: "bg-yellow-100 border-yellow-200",
textColor: "text-yellow-600",
gradient: "bg-white",
shadowColor: "shadow-yellow-100",
},
{
title: "Expiring Soon",
count: data.expiring || 0,
icon: <FaHourglassHalf size={18} className="text-rose-600" />,
gradient: 'bg-gradient-to-br from-rose-50 to-rose-100',
shadowColor: 'shadow-rose-200/50',
iconBg: 'bg-rose-100 border-rose-200',
textColor: 'text-rose-700',
trend: 'up'
icon: <FiAlertTriangle className="w-5 h-5 text-red-500" />,
iconBg: "bg-red-100 border-red-200",
textColor: "text-red-600",
gradient: "bg-white",
shadowColor: "shadow-red-100",
},
{
title: 'Completed',
title: "Completed",
count: data.completed || 0,
icon: <FaCheckCircle size={18} className="text-teal-600" />,
gradient: 'bg-gradient-to-br from-teal-50 to-teal-100',
shadowColor: 'shadow-teal-200/50',
iconBg: 'bg-teal-100 border-teal-200',
textColor: 'text-teal-700',
trend: 'up'
icon: <FiCheckCircle className="w-5 h-5 text-green-500" />,
iconBg: "bg-green-100 border-green-200",
textColor: "text-green-600",
gradient: "bg-white",
shadowColor: "shadow-green-100",
},
// {
// title: 'Waiting for Others',
@ -94,16 +90,16 @@ const Account = () => {
// textColor: 'text-green-700',
// trend: 'down'
// },
{
title: 'draft',
count: data.draft || 0,
icon: <FaClock size={18} className="text-stone-600" />,
gradient: 'bg-gradient-to-br from-stone-50 to-stone-100',
shadowColor: 'shadow-stone-200/50',
iconBg: 'bg-stone-100 border-stone-200',
textColor: 'text-stone-700',
trend: 'down'
}
// {
// title: 'draft',
// count: data.draft || 0,
// icon: <FaClock size={18} className="text-stone-600" />,
// gradient: 'bg-gradient-to-br from-stone-50 to-stone-100',
// shadowColor: 'shadow-stone-200/50',
// iconBg: 'bg-stone-100 border-stone-200',
// textColor: 'text-stone-700',
// trend: 'down'
// }
// Add more cards here if needed
];
};
@ -252,8 +248,9 @@ const Account = () => {
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
isLoading={isLoading} />
<TransactionDataTable />
<section className="my-4 mx-3">
<TransactionDataTable />
</section>
{isModalOpen && <AddInviteModal onClose={() => setIsModalOpen(false)} onInvite={handleInvite} inviteLoading={inviteLoading}
accountOptions={accountsData} />}

View File

@ -2,8 +2,10 @@ import React, { useEffect, useState } from 'react'
import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi';
import api from '../../config/api';
import { Transaction } from '../transactions/Transactions';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { FaCheck } from 'react-icons/fa';
import LoaderUi from '../../helper/LoaderUi';
import logoRecord from '../../assets/record.svg';
@ -12,21 +14,36 @@ const formatDate = (timestamp: number) => {
return new Date(timestamp * 1000).toLocaleString();
};
const statusColorMap = {
CREATED: "bg-blue-100 text-blue-600",
SENT: "bg-green-100 text-green-600",
COMPLETED: "bg-purple-100 text-purple-600",
EXPIRED: "bg-red-100 text-red-600",
};
const TransactionDataTable = () => {
const navigate = useNavigate()
const loaction = useLocation()
console.log("loaction", loaction.pathname);
const [currentEmailId, setCurrentEmailId] = useState<string | null>('null');
const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]);
const [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
const [loading, setLoading] = useState(false)
const fetchTransactions = async () => {
console.log("fetchTransactions")
setLoading(true)
try {
const response = await api.get(`/api/accounts/${activeAccountId}/transactions?sortBy=lastModifiedAt desc`);
setTransactions(response.data || []);
} catch (error) {
console.error("Error fetching transactions:", error);
} finally {
setLoading(false)
}
};
@ -64,72 +81,141 @@ const TransactionDataTable = () => {
}
}
function getStatusColorClass(status) {
return statusColorMap[status] || "bg-gray-100 text-gray-600";
}
return (
<>
{transactions.length > 0 && (
<div className="overflow-x-auto py-2">
<table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm">
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider">
<tr>
<th className="px-4 py-3 text-left"></th>
<th className="px-4 py-3 text-center">Name</th>
<th className="px-4 py-3 text-center">Status</th>
<th className="px-4 py-3 text-center">Active</th>
<th className="px-4 py-3 text-center">Last Change</th>
<th className="px-4 py-3 text-center">Expire After (days)</th>
<th className="px-4 py-3 text-center">Sign</th>
<th className="px-4 py-3 text-center">Action</th>
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
{/* Header */}
<div className="flex justify-between items-center px-6 py-4 border-b border-gray-100">
<h2 className="text-sm font-semibold text-gray-800">Recent Transactions</h2>
</div>
{/* Scrollable Table Container */}
<div className="overflow-auto max-h-[67vh] no-scrollbar">
<table className="w-full text-sm">
{/* Sticky Header */}
<thead className="bg-gray-50 sticky top-0 z-10">
<tr className="text-gray-600 text-xs uppercase tracking-wider border-b border-gray-200">
<th className="px-6 py-4 text-left font-semibold">Document</th>
<th className="px-6 py-4 text-center font-semibold">Status</th>
{location.pathname === '/transactions' && (
<>
<th className="px-6 py-4 text-center font-semibold">Signed By</th>
<th className="px-6 py-4 text-center font-semibold">Last Change</th>
<th className="px-6 py-4 text-center font-semibold">Expire After (Days)</th>
<th className="px-6 py-4 text-center font-semibold">Actions</th>
</>
)}
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{transactions.map((tx) => {
const lastModifiedAt = new Date(tx.lastModifiedAt, "DD/MM/YYYY");
return (
<tr
key={tx.id}
onClick={() => handleTransactionClick(tx.id)}
className="hover:bg-gray-50 cursor-pointer"
>
<td className="px-4 py-3">
<div className="w-5 h-5 rounded border border-slate-300 flex justify-center items-center">
{selectedTransactions.includes(tx.id) && (
<FaCheck className="text-purple-600 text-sm" />
)}
</div>
</td>
<td className="px-4 py-3 text-center">
{tx.subject || "Untitled"}
</td>
<td className="px-4 py-3 text-center">
<span className="px-2 py-0.5 rounded-xl text-xs font-medium bg-blue-100 text-blue-700">
{tx.status}
</span>
</td>
<td className="px-4 py-3 text-center">
{tx.active ? "Yes" : "No"}
</td>
{loading ? (
<tbody>
<tr>
<td colSpan="6" className="relative h-100">
<LoaderUi />
</td>
</tr>
</tbody>
) : transactions.length > 0 ? (
<>
{/* Scrollable Body */}
<tbody className="bg-white divide-y divide-gray-100">
{transactions.map((tx) => (
<tr
key={tx.id}
onClick={() => handleTransactionClick(tx.id)}
className="hover:bg-gray-50 transition-colors duration-150 cursor-pointer"
>
<td className="px-6 py-4 text-left">
<div className="text-gray-800 font-medium truncate max-w-xs">
{tx?.subject || "Untitled"}
</div>
</td>
<td className="px-6 py-4 text-center">
<span
className={`inline-block px-3 py-1 rounded-full text-xs font-medium ${getStatusColorClass(
tx.status
)}`}
>
{tx.status}
</span>
</td>
<td className="px-4 py-3 text-center">
{formatTimestamp(tx.lastModifiedAt)}
</td>
<td className="px-4 py-3 text-center">
{tx.expireAfter ?? "-"}
</td>
<td className="px-4 py-3 text-center ">
{tx?.createdBy?.email === currentEmailId ? <button className="px-2 py-0.5 rounded-xl text-xs font-medium bg-green-100 text-green-700 cursor-pointer">Sign now</button> : "-"}
</td>
<td className="px-4 py-3 text-center">
<PiDotsThreeOutlineVerticalFill className="text-lg text-slate-300 hover:text-black mx-auto" />
</td>
</tr>
)
})}
</tbody>
{location.pathname === '/transactions' && (
<>
<td className="px-6 py-4">
<div className="flex items-center justify-center">
{tx.signedBy ? (
<div className="flex items-center gap-2">
<div
className="w-7 h-7 rounded-full flex items-center justify-center text-xs font-medium text-white flex-shrink-0"
style={{ backgroundColor: "#d8b4fe" }}
>
{tx.signedByInitials}
</div>
<span className="text-gray-700 truncate max-w-[120px]">
{tx.signedBy}
</span>
</div>
) : (
<span className="text-gray-400">-</span>
)}
</div>
</td>
<td className="px-6 py-4 text-center">
<span className="text-gray-600 whitespace-nowrap">
{formatTimestamp(tx.lastModifiedAt)}
</span>
</td>
<td className="px-6 py-4 text-center">
<span className="text-gray-700">
{tx.expireAfter ?? "-"}
</span>
</td>
<td className="px-6 py-4 text-center">
<button
className="text-gray-400 hover:text-gray-600 transition-colors duration-150 p-1 rounded-md hover:bg-gray-100"
onClick={(e) => {
e.stopPropagation();
// Handle actions menu
}}
>
<PiDotsThreeOutlineVerticalFill className="text-lg" />
</button>
</td>
</>
)}
</tr>
))}
</tbody>
</>) : (
<tbody>
<tr>
<td colSpan="6" className="text-center py-10 text-gray-500">
<div className="flex flex-col items-center justify-center">
<img
src={logoRecord}
alt="Record Not Found"
className="w-70 h-70 mb-4 opacity-70"
/>
</div>
</td>
</tr>
</tbody>
)}
</table>
</div>
)}
</div>
</>
)
}

View File

@ -126,7 +126,7 @@ const Contacts = () => {
setShowModal((prev) => !prev);
setActionStatus("Add")
}}
className="flex items-center gap-2 bg-[var(--primary)] px-3 py-1.5 rounded-sm hover:opacity-90 ">
className="flex items-center gap-2 bg-[var(--mainPrimaryBtn)] px-3 py-1.5 rounded-sm hover:opacity-90 ">
<IoMdAdd className="text-white" />
<button
className="text-white rounded text-sm hover:opacity-90 cursor-pointer">
@ -134,20 +134,22 @@ 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 mx-2 max-h-[79vh] rounded-xl shadow-sm border border-gray-100 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">
<thead className="text-gray-500 uppercase text-xs tracking-wider sticky top-0 z-10">
<tr>
<th className="px-4 py-3 text-left">First Name</th>
<th className="px-4 py-3 text-left">Last Name</th>
<th className="px-4 py-3 text-left">Email</th>
<th className="px-4 py-3 text-center">Actions</th>
<th className="px-6 py-3 font-medium text-left">First Name</th>
<th className="px-6 py-3 font-medium text-left">Last Name</th>
<th className="px-6 py-3 font-medium text-left">Email</th>
<th className="px-6 py-3 font-medium text-center">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
<tbody className="divide-y divide-gray-100">
{loading ? (
<tr>
<td colSpan="4" className="relative h-100"><LoaderUi /></td>
<td colSpan="4" className="relative h-100">
<LoaderUi />
</td>
</tr>
) : contacts.length === 0 ? (
<tr>
@ -156,32 +158,40 @@ const Contacts = () => {
<img
src={logoRecord}
alt="Record Not Found"
className="w-70 h-70 mb-4 opacity-70" // adjust size and appearance
className="w-70 h-70 mb-4 opacity-70"
/>
</div>
</td>
</tr>
) : (
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>
<td className="px-4 py-3">{contact.email}</td>
<td className="px-4 py-3 text-center">
<tr
key={contact.id}
className="hover:bg-gray-50 transition-colors"
>
<td className="px-6 py-4 text-gray-800">{contact.firstName}</td>
<td className="px-6 py-4 text-gray-800">{contact.lastName}</td>
<td className="px-6 py-4 text-gray-800">{contact.email}</td>
<td className="px-6 py-4 text-center">
<div
className="relative inline-block"
tabIndex={0}
onBlur={handleBlur}
>
<button
className="text-gray-600 hover:text-black p-1 focus:outline-none"
className="text-gray-400 hover:text-gray-600 p-1 focus:outline-none"
onClick={() => toggleMenu(contact.id)}
>
<FaEllipsisV />
</button>
{openMenuId === contact.id && (
<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"}`}>
<div
className={`absolute mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-20 min-w-[100px] ${contacts.length - 1 === index
? "bottom-1 right-4"
: "bottom-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"
@ -203,11 +213,18 @@ const Contacts = () => {
)}
</tbody>
</table>
{showModal &&
<ContactFormModal onSubmit={handleContactSubmit}
loading={createAndInviteLoader} setShowModal={setShowModal} actionStatus={actionStatus} editJsonData={editJsonData} />
}
{showModal && (
<ContactFormModal
onSubmit={handleContactSubmit}
loading={createAndInviteLoader}
setShowModal={setShowModal}
actionStatus={actionStatus}
editJsonData={editJsonData}
/>
)}
</div>
</div>
</>
);

View File

@ -171,9 +171,9 @@ function Dashboard() {
<SideBar showSideBar={showSideBar} setShowSideBar={setShowSideBar} />
{/* On Top Main Content - START */}
<div className={`overflow-y-auto absolute ${showSideBar ? 'w-[calc(100%-250px)]' : 'w-[calc(100%-60px)]'} h-full right-0 bottom-0 overflow-hidden bg-[var(--mainViewBg)] transition-all duration-300`}>
<div className={`overflow-y-auto absolute no-scrollbar ${showSideBar ? 'w-[calc(100%-250px)]' : 'w-[calc(100%-60px)]'} h-full right-0 bottom-0 overflow-hidden bg-[var(--mainViewBg)] transition-all duration-300`}>
{/* Main Content */}
<div className='px-2 py-1.5'>
<div className=''>
<CommonNav />
<Outlet />
</div>

View File

@ -22,8 +22,10 @@ interface NavLinkProps {
// }
const NavLink: React.FC<NavLinkProps> = ({ to, icon, label, showSideBar, menuPathMap }) => {
const location = useLocation();
const isActive = menuPathMap[location.pathname] === label;
console.log('Line 26' , to, icon, label, showSideBar, menuPathMap , "Check" , menuPathMap[location.pathname] === label)
// const [transactionFilter, setTransationFilter] = useState<TransactionFilterEnum>(TransactionFilterEnum.ALL)
@ -32,7 +34,7 @@ const NavLink: React.FC<NavLinkProps> = ({ to, icon, label, showSideBar, menuPat
<Link
to={to}
className={`${isActive ? 'text-[var(--menuTextColActive)] bg-[var(--menuBgColActive)]' : 'text-[var(--menuTextCol)]'}
className={`${isActive ? 'text-[var(--menuTextColActive)] bg-[var(--menuBgColActive)]' : 'text-[var(--sidebarMenuCol)]'}
p-2 rounded-md flex justify-left gap-2 items-center transition-all duration-300 ${showSideBar ? 'text-lg' : 'text-lg max-w-max'}`}
>
{icon}

View File

@ -12,6 +12,7 @@ import { setTransactionId, TRANSACTION_STATUS_ENUMS, updateTransactionStatus } f
import { SignTypeEnum } from '../../utlity/types';
// import { IoMdArrowDropright } from "react-icons/io";
interface Props {
showSideBar: boolean;
}
@ -84,7 +85,7 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
onBlur={() => setSendForSignDropdown(false)}
>
<div
className={`flex justify-between items-center gap-2 cursor-pointer bg-[var(--primary)] rounded text-[var(--sendForSignTextCol)] ${showSideBar ? "" : "pt-[0.4rem] pr-[0.6rem] pb-[0.4rem] pl-[0.2rem]"}`}>
className={`flex justify-between items-center gap-2 cursor-pointer bg-[var(--sidebarDropDownBg)] rounded text-[var(--sendForSignTextCol)] ${showSideBar ? "" : "pt-[0.4rem] pr-[0.6rem] pb-[0.4rem] pl-[0.2rem]"}`}>
<div
onClick={() => handleStartSectionClick(SignTypeEnum.multipleSign)}
className={`w-full`}>
@ -98,7 +99,7 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
</div>
{showSideBar &&
<p
className='border-l-1 border-[var(--sendForSignTextCol)] px-2 py-2'>
className=' border-[var(--sendForSignTextCol)] px-2 py-2'>
<IoMdArrowDropdown
onClick={() => setSendForSignDropdown(!sendForSignDropdown)}
className={`text-2xl ${sendForSignDropdown ? 'rotate-180' : ''} transition-all duration-150`} />

View File

@ -11,6 +11,7 @@ import { IoSettingsOutline } from "react-icons/io5";
import axios from 'axios';
import { MdManageAccounts } from 'react-icons/md';
import { GrContactInfo, GrTransaction } from 'react-icons/gr';
import logoSME from '../../assets/SMELogo.png'
type SideBarProps = {
showSideBar: boolean;
@ -22,11 +23,11 @@ type MenuPathMap = {
}
const menuPathMap: MenuPathMap = {
'/': 'Home',
'/templates': 'Templates',
'/s/contact': 'Contact',
// '/accounts': 'Accounts',
'transactions' : "Account"
'/': 'Home',
'/templates': 'Templates',
// '/accounts': 'Accounts',
'/transactions': 'Transactions',
'/contacts': 'Contacts'
};
const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
@ -53,21 +54,21 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
return (
<>
{/* Side Bar */}
<div className={`relative h-screen ${showSideBar ? 'w-[250px]' : 'w-[60px]'} transition-all duration-300 bg-white shadow-md`}>
<div className={`relative h-screen ${showSideBar ? 'w-[250px]' : 'w-[60px]'} transition-all duration-300 bg-[var(--sidebarBg)] shadow-md`}>
{/* Menu */}
<div className={`absolute top-0 ${showSideBar ? 'p-4' : 'py-4'} w-full max-h-max ${showSideBar ? '' : 'text-center'}`}>
<h1 className='w-full text-lg text-center tracking-widest font-medium text-gray-800'>
{showSideBar ? 'Sign Made Easy' : 'SME'}
{showSideBar ? <img src={logoSME} loading='lazy' className='h-7 ms-6' /> : <p className='text-white'>SME</p>}
</h1>
<div
id="nav-link-dash"
className={`flex flex-col mt-4 ${showSideBar ? '' : 'items-center'}`}>
id="nav-link-dash"
className={`flex flex-col mt-4 ${showSideBar ? '' : 'items-center'}`}>
<SendForSign showSideBar={showSideBar} />
<NavLink to="/" icon={<FaHome />} label="Home" showSideBar={showSideBar} menuPathMap={menuPathMap} />
<NavLink to="/templates" icon={<HiTemplate />} label="Templates" showSideBar={showSideBar} menuPathMap={menuPathMap} />
{/* <NavLink to="/accounts" icon={<MdManageAccounts />} label="Accounts" showSideBar={showSideBar} menuPathMap={menuPathMap} /> */}
<NavLink to="transactions" icon={<GrTransaction />} label="Transactions" showSideBar={showSideBar} menuPathMap={menuPathMap} />
<NavLink to="/transactions" icon={<GrTransaction />} label="Transactions" showSideBar={showSideBar} menuPathMap={menuPathMap} />
<NavLink to="/contacts" icon={<GrContactInfo />} label="Contacts" showSideBar={showSideBar} menuPathMap={menuPathMap} />
</div>
</div>
@ -75,10 +76,10 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
{/* SideBar Toggle Button */}
<div
onClick={() => setShowSideBar(!showSideBar)}
className='absolute top-[100%] translate-y-[-100%] right-0 bg-[var(--highlightBg1)] p-2 rounded-tl-sm rounded-bl-sm cursor-pointer transition-colors'
className='absolute top-[100%] translate-y-[-100%] right-0 bg-[var(--dashBoardBg)] p-2 rounded-tl-sm rounded-bl-sm cursor-pointer transition-colors'
>
<div className='absolute top-[-14px] right-0 w-[14px] h-[14px] rounded-full bg-white shadow-[5px_5px_0px_0px_rgba(237,234,235,1)]'></div>
<div className='absolute bottom-[-14px] right-0 w-[14px] h-[14px] rounded-full bg-white shadow-[5px_-5px_0px_0px_rgba(237,234,235,1)]'></div>
<div className='absolute top-[-14px] right-0 w-[14px] h-[14px] rounded-full shadow-[5px_5px_0px_0px_rgba(237,234,235,1)]'></div>
<div className='absolute bottom-[-14px] right-0 w-[14px] h-[14px] rounded-full shadow-[5px_-5px_0px_0px_rgba(237,234,235,1)]'></div>
{showSideBar ? (
<BsLayoutSidebarInsetReverse className='fill-[var(--primary)] hover:text-[var(--primary)] transition-all text-2xl' />
) : (
@ -90,7 +91,7 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
<div className='absolute bottom-14 right-0 flex flex-col gap-1'>
<button
onClick={() => setShowLogoutModal(true)}
className='flex items-center gap-2 px-3 py-2 text-gray-600 hover:bg-red-50 hover:text-red-600 rounded-l-sm transition-all duration-200 group'
className='flex items-center gap-2 px-3 py-2 text-[var(--sidebarMenuCol)] hover:bg-red-50 hover:text-red-600 rounded-l-sm transition-all duration-200 group'
title="Log out"
>
<RiLogoutBoxLine className="text-xl group-hover:scale-110 transition-transform" />
@ -99,7 +100,7 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
<Link
to="/s/settings"
className='flex items-center gap-2 px-3 py-2 text-gray-600 hover:bg-blue-50 hover:text-blue-600 rounded-l-sm transition-all duration-200 group'
className='flex items-center gap-2 px-3 py-2 text-[var(--sidebarMenuCol)] hover:bg-blue-50 hover:text-blue-600 rounded-l-sm transition-all duration-200 group'
title="Settings"
>
<IoSettingsOutline className="text-xl group-hover:scale-110 transition-transform" />

View File

@ -204,74 +204,47 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRa
// }
return (
<div className="w-full px-3 rounded-md">
{/* Header */}
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mb-2 gap-4">
<div className="flex items-center gap-3">
{/* <div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
<FaFilter className="text-white text-lg" />
</div> */}
<div>
{/* <h2 className="text-lg font-semibold text-gray-800">Overview</h2> */}
{/* <p className="text-gray-600 text-sm">Monitor performance metrics</p> */}
</div>
</div>
</div>
{/* Cards */}
{isLoading ? <div className="w-full [h-50vh] px-6 py-8">
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-32 mb-8"></div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{[...Array(7)].map((_, i) => (
<div key={i} className="h-32 bg-gray-200 rounded-xl"></div>
))}
</div>
</div>
</div> :
<div className="flex flex-wrap gap-2 justify-end">
{cards.map((card, index) => (
<div
key={card.title}
className={`group relative overflow-hidden rounded-lg ${card.gradient} border border-white/40 px-3 py-1 transition-all duration-300 hover:scale-105 hover:shadow-lg ${card.shadowColor} cursor-pointer backdrop-blur-sm min-w-[200px] max-w-[220px]`}
style={{
animationDelay: `${index * 100}ms`
}}
>
{/* Background Pattern */}
<div className="absolute inset-0 opacity-5">
<div className="absolute -top-6 -right-6 w-12 h-12 rounded-full bg-current"></div>
<div className="absolute -bottom-6 -left-6 w-10 h-10 rounded-full bg-current"></div>
</div>
{/* Content */}
<div className="relative flex justify-between items-center z-10">
<div className="flex items-center gap-2">
<div className={`p-2 rounded-lg border ${card.iconBg} group-hover:scale-110 transition-transform duration-200`}>
{card.icon}
</div>
<h3 className="text-sm font-medium text-gray-600 group-hover:text-gray-700 transition-colors">
{card.title}
</h3>
</div>
<div className="flex items-center">
<p className={`text-xl font-bold ${card.textColor} group-hover:scale-105 transition-transform duration-200 leading-none mb-1`}>
{card.count}
</p>
</div>
</div>
{/* Hover Effect */}
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</div>
<div className="w-full">
{/* Cards */}
{isLoading ? (
<div className="w-full h-[10vh] px-6 py-3">
<div className="animate-pulse">
{/* <div className="h-8 bg-gray-200 rounded w-32 mb-8"></div> */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
{[...Array(4)].map((_, i) => (
<div key={i} className="h-10 bg-gray-200 border border-gray-100"></div>
))}
</div>
}
</div>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
{cards.map((card, index) => (
<div
key={card.title}
className="flex items-center justify-between border border-gray-200 bg-white px-4 py-1.5 hover:shadow-md transition-all duration-300 cursor-pointer"
style={{
animationDelay: `${index * 100}ms`,
}}
>
{/* Left side: Icon + Title */}
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${card.iconBg}`}>
{card.icon}
</div>
<h3 className="text-xs font-medium text-gray-600">{card.title}</h3>
</div>
{/* Count */}
<p className={`text-sm font-bold ${card.textColor}`}>
{card.count}
</p>
</div>
))}
</div>
)}
</div>
);
};

View File

@ -3,17 +3,7 @@ import AccountNav from '../accounts/AccountNav';
import Cards from './Cards';
import StartSection from './StartSection';
import api from '../../config/api';
import {
FaCheckCircle,
FaClock,
FaHourglassHalf,
FaTasks,
FaChevronDown,
FaPlus,
FaUserPlus,
FaFilter,
FaCalendarAlt
} from 'react-icons/fa';
import { FiFileText, FiClock, FiAlertTriangle, FiCheckCircle } from "react-icons/fi";
import { useSelector } from 'react-redux';
import { RootState } from '../../store/store';
@ -33,44 +23,40 @@ const Home = () => {
const mapApiDataToCards = (data: any): CardData[] => {
return [
{
title: 'Action Needed',
title: "Action Required",
count: data.actionNeeded || 0,
icon: <FaTasks size={15} className="text-indigo-600" />,
gradient: 'bg-gradient-to-br from-indigo-50 to-indigo-100',
shadowColor: 'shadow-indigo-200/50',
iconBg: 'bg-indigo-100 border-indigo-200',
textColor: 'text-indigo-700',
trend: 'up'
icon: <FiFileText className="w-5 h-5 text-sky-500" />,
iconBg: "bg-sky-100 border-sky-200",
textColor: "text-sky-600",
gradient: "bg-white",
shadowColor: "shadow-sky-100",
},
{
title: 'Expiring Soon',
title: "Waiting for Response",
count: data.waitingForOthers || 0,
icon: <FiClock className="w-5 h-5 text-yellow-500" />,
iconBg: "bg-yellow-100 border-yellow-200",
textColor: "text-yellow-600",
gradient: "bg-white",
shadowColor: "shadow-yellow-100",
},
{
title: "Expiring Soon",
count: data.expiring || 0,
icon: <FaHourglassHalf size={15} className="text-rose-600" />,
gradient: 'bg-gradient-to-br from-rose-50 to-rose-100',
shadowColor: 'shadow-rose-200/50',
iconBg: 'bg-rose-100 border-rose-200',
textColor: 'text-rose-700',
trend: 'up'
icon: <FiAlertTriangle className="w-5 h-5 text-red-500" />,
iconBg: "bg-red-100 border-red-200",
textColor: "text-red-600",
gradient: "bg-white",
shadowColor: "shadow-red-100",
},
{
title: 'Completed',
title: "Completed",
count: data.completed || 0,
icon: <FaCheckCircle size={15} className="text-teal-600" />,
gradient: 'bg-gradient-to-br from-teal-50 to-teal-100',
shadowColor: 'shadow-teal-200/50',
iconBg: 'bg-teal-100 border-teal-200',
textColor: 'text-teal-700',
trend: 'up'
},
{
title: 'Draft',
count: data.draft || 0,
icon: <FaClock size={15} className="text-stone-600" />,
gradient: 'bg-gradient-to-br from-blue-50 to-blue-100',
shadowColor: 'shadow-blue-200/50',
iconBg: 'bg-blue-100 border-blue-200',
textColor: 'text-blue-700',
trend: 'down'
icon: <FiCheckCircle className="w-5 h-5 text-green-500" />,
iconBg: "bg-green-100 border-green-200",
textColor: "text-green-600",
gradient: "bg-white",
shadowColor: "shadow-green-100",
},
];
};
@ -84,7 +70,7 @@ const Home = () => {
const [cards, setCards] = useState<CardData[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const userData = useSelector((state : RootState) => state.user);
const userData = useSelector((state: RootState) => state.user);
console.log("activeAccountId", activeAccountId);
console.log("cards", cards);

View File

@ -7,6 +7,7 @@ import { useNavigate } from 'react-router-dom';
import { RootState } from '../../store/store';
import api from '../../config/api';
const StartNow = () => {
const fileInputRef = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<File[]>([]);
@ -51,7 +52,7 @@ const StartNow = () => {
<div
onClick={() => fileInputRef.current?.click()}
id='start-now-upload-con'
className="group col-span-2 cursor-pointer border-slate-300 rounded-lg bg-white/60 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-2 border-dashed border-slate-300 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">
{/* Upload icon with subtle animation */}
<div className="p-2 text-blue-600 group-hover:text-blue-700 transition-colors">
<FiUpload className="text-lg" />

View File

@ -10,6 +10,7 @@ import { Transaction, TransactionResponse } from '../transactions/Transactions';
import TransactionsTable from '../transactions/TransactionsTable';
import TransactionDataTable from '../accounts/TransactionDataTable';
import StartNow from './StartNow';
import TemplateTable from '../templates/TemplateTable';
export interface StartCard {
@ -81,31 +82,30 @@ const StartSection = () => {
// }, [])
return (
<section className="px-4 pt-2 pb-3 w-full">
<h2 className="text-lg font-semibold text-gray-800 mb-2">Get Started</h2>
<section className="px-3 py-2 w-full mt-2">
<h2 className="text-sm font-semibold text-gray-800 px-1 pb-2">Upload Document</h2>
<div className="grid grid-cols-3 gap-4 h-[12vw]">
<div className="grid grid-cols-1 gap-4 h-[12vw] mx-1">
<StartNow />
<div className='col-span-1 grid grid-cols-1 justify-between gap-4'>
{/* <div className='col-span-1 grid grid-cols-1 justify-between gap-4 bg-white rounded-xl'>
{cards.map((card, index) => (
<div
onClick={() => handleStartSectionClick(card.link)}
key={index}
className={`${card.bg} w-full cursor-pointer relative border border-gray-200 rounded-xl p-4 shadow-sm hover:shadow-md transition-all flex flex-col justify-between`}
>
{/* Icon + Text */}
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-3 ">
<div className="w-9 h-9 flex items-center justify-center border border-gray-200 rounded-md bg-white">
{card.icon}
</div>
<h3 className="text-base font-medium text-gray-900">{card.title}</h3>
<p className="text-sm text-gray-700">{card.description}</p>
<h3 className="text-sm font-medium text-gray-500">{card.title}</h3>
<p className="text-xs text-gray-700">{card.description}</p>
</div>
{/* Bottom Area */}
<div className="flex items-center justify-between mt-2">
{card.buttonText ? (
<button className="text-sm cursor-pointer font-medium text-white bg-gradient-to-r from-blue-600 to-blue-500 px-4 py-2 rounded-md shadow hover:opacity-90 transition">
@ -119,16 +119,21 @@ const StartSection = () => {
</div>
</div>
))}
</div>
</div> */}
</div>
<section className='pt-2 pb-3'>
<h2 className="mt-6 text-lg font-semibold text-gray-800 mb-2">Recent Activities</h2>
<section>
{/* <section className='pt-2 pb-3'> */}
{/* <h2 className="mt-6 text-sm font-semibold text-gray-800 mb-2">Recent Activities</h2> */}
<div className='grid grid-cols-12 gap-2 mt-5'>
<section className=' col-span-6'>
<TransactionDataTable />
</section>
</section>
<section className=' col-span-6'>
<TemplateTable />
</section>
</div>
{/* </section> */}
</section>

View File

@ -448,7 +448,7 @@ const DocumentSubmissionOptions = () => {
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3 ">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3 max-w-5xl mx-auto">
{/* Left Panel - Configuration */}
<div className="space-y-6 ">

View File

@ -10,6 +10,7 @@ import api from '../../../config/api'
import { useParams } from 'react-router-dom'
import axios from 'axios'
interface DocumentCardProps {
id: string;
name: string;
@ -157,7 +158,7 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted , tra
</div>
</div>
) : (
<FaFileAlt className="text-gray-600 text-4xl mb-1" />
<FaFileAlt className="text-red-600 text-5xl mb-1" />
)}
</div>

View File

@ -144,7 +144,7 @@ const ProcessStepOne = () => {
documents: false,
recipients: false,
subject: false,
recipientEmailAndLastName:false
recipientEmailAndLastName: false
}
if (transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
@ -370,10 +370,10 @@ const ProcessStepOne = () => {
return (
<>
<div className={`relative w-full bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none h-screen overflow-hidden' : ''}`}>
<div className={`relative w-full h-screen bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none overflow-hidden' : ''}`}>
{/* Top Navigation */}
<div className='fixed top-0 w-full flex items-center justify-between bg-white shadow z-50 px-4 py-3'>
{/* Top Navigation - Fixed */}
<div className='fixed top-0 left-0 right-0 w-full flex items-center justify-between bg-white shadow z-50 px-4 py-3'>
<div className='flex items-center gap-2'>
<IoMdClose
onClick={handleClose}
@ -400,7 +400,6 @@ const ProcessStepOne = () => {
id="add-tags-btn-step-one"
className='flex items-center gap-3'>
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && <button
onClick={() => handleNextClick(false, true)}
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition cursor-pointer"
@ -418,87 +417,88 @@ const ProcessStepOne = () => {
</div>
</div>
{/* Main Content */}
<div className="relative max-w-4xl mx-auto pt-[80px] px-4 pb-10">
{/* Main Content - Scrollable */}
<div className="h-screen pt-[80px] overflow-y-auto no-scrollbar">
<div className="max-w-4xl rounded-xl shadow-md mx-auto px-6 pb-10 bg-[var(--documentUploadBg)] mb-5">
{/* Documents */}
<section>
<h2 className="text-lg font-medium mb-4 text-[var(--processStepOneTextHead)]">Documents</h2>
<SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} />
</section>
{/* Documents */}
<section>
<h2 className="text-lg font-medium mb-4 pt-3 text-[var(--processStepOneTextHead)]">Documents</h2>
<SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} />
</section>
{/* Recipients */}
<section className="mt-8">
{!isTamplate &&
transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES &&
<div className="flex justify-between items-center">
<h2 className="text-lg font-medium mb-4 pt-6 border-t text-[var(--processStepOneTextHead)]">Signers</h2>
<div className="flex items-center mr-2">
<input
checked={signType === SignTypeEnum.selfSign}
onChange={(e) => handleSelfSignCb(e)}
id="default-checkbox" type="checkbox" value="" className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm outline-none"
/>
<label htmlFor="default-checkbox" className="ms-2 text-sm text-slate-600">SELF SIGN</label>
{/* Recipients */}
<section className="mt-8">
<p className="border-t border-gray-200"></p>
{!isTamplate &&
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">
<input
checked={signType === SignTypeEnum.selfSign}
onChange={(e) => handleSelfSignCb(e)}
id="default-checkbox" type="checkbox" value="" className="w-4 h-4 text-blue-600 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">
<span>SELF </span><span>SIGN</span></label>
</div>
</div>
</div>
}
}
{
(signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) &&
<div className="flex flex-col gap-4 align-start">
{
(signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) &&
<div className="flex flex-col gap-4 align-start">
<RecipientCards transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} editRecipient={editRecipient} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} editRecipientCurrentEmail={editRecipientCurrentEmail} />
{/* {signType !== "self-sign" && <RecipientAddCard />} */}
<RecipientCards transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} editRecipient={editRecipient} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} editRecipientCurrentEmail={editRecipientCurrentEmail} />
<div
ref={topRef}
className={`relative w-full bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none h-screen overflow-hidden' : ''}`}
>
<div
ref={topRef}
className={`relative w-full bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none' : ''}`}
>
<RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} editRecipient={editRecipient} editCase={editCase} setEditRecipient={setEditRecipient} setEditCase={setEditCase} topRef={topRef} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} />
</div>
<RecipientAddCard transactionTemplateToggle={transactionTemplateToggle} editRecipient={editRecipient} editCase={editCase} setEditRecipient={setEditRecipient} setEditCase={setEditCase} topRef={topRef} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} />
</div>
}
</section>
{/* 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>
{/* Subject */}
<div className='flex flex-col mb-4'>
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Subject</label>
<input
type="text"
required
value={emailDtls.subject}
onChange={(e) => setEmailDtls({ ...emailDtls, subject: e.target.value })}
placeholder="Email Subject"
className="w-full px-3 py-2 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
/>
</div>
}
</section>
{/* Email */}
<section className="mt-8">
<h2 className="text-lg font-medium mb-4 pt-6 border-t text-[var(--processStepOneTextHead)]">Email</h2>
{/* Subject */}
<div className='flex flex-col mb-4'>
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Subject</label>
<input
type="text"
required
value={emailDtls.subject}
onChange={(e) => setEmailDtls({ ...emailDtls, subject: e.target.value })}
placeholder="Email Subject"
className="w-full px-3 py-2 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
/>
</div>
{/* Message */}
<div className='flex flex-col'>
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Message for Signers</label>
<textarea
rows={4}
value={emailDtls.message}
onChange={(e) => setEmailDtls({ ...emailDtls, message: e.target.value })}
placeholder="Message"
className="w-full px-3 py-2 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
/>
</div>
</section>
{/* Message */}
<div className='flex flex-col'>
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Message for Signers</label>
<textarea
rows={4}
value={emailDtls.message}
onChange={(e) => setEmailDtls({ ...emailDtls, message: e.target.value })}
placeholder="Message"
className="w-full px-3 py-2 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
/>
</div>
</section>
</div>
</div>
{globalLoader.isLoading && <Loader />}
{showSelfSignWarnModal && <SelfSignWarnModal setShowSelfSignWarnModal={setShowSelfSignWarnModal} />}
</div >
</div>
</>
);
}

View File

@ -362,7 +362,7 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
ref={formRef}
onSubmit={handleRecipientAdd}
id="add-recipient-step-one"
className="w-full bg-white border border-gray-200 rounded-lg shadow-md px-6 py-4 space-y-3 relative"
className="w-full bg-white border border-gray-200 rounded-lg shadow-sm px-6 py-4 space-y-3 relative"
>
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && (
<div className="space-y-1">

View File

@ -13,6 +13,7 @@ import DocumentCard from './DocumentCard';
import { useParams } from 'react-router-dom';
import { toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice';
const SelectedDocuments = ({transactionTemplateToggle}) => {
// let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
@ -91,6 +92,15 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
id='upload-doc-step-one'
className="flex flex-wrap gap-3">
{/* Uploaded documents */}
<div
onClick={() => setModalOpen(true)}
className="cursor-pointer w-[160px] h-[170px] flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl bg-white hover:bg-gray-50 transition shadow-sm hover:shadow-md"
>
<FaPlus className="text-[var(--primary)] text-3xl mb-2" />
<p className="text-sm text-gray-600 text-center px-2">Upload Document</p>
</div>
{/* Document Show */}
<ReactSortable
className="flex flex-wrap gap-3"
list={documents.map(doc => ({ ...doc }))}
@ -105,13 +115,7 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
))}
</ReactSortable>
<div
onClick={() => setModalOpen(true)}
className="cursor-pointer w-[160px] h-[170px] flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-xl bg-white hover:bg-gray-50 transition shadow-sm hover:shadow-md"
>
<FaPlus className="text-[var(--primary)] text-3xl mb-2" />
<p className="text-sm text-gray-600 text-center px-2">Upload Document</p>
</div>
</div>
{/* Modal */}

View File

@ -22,7 +22,7 @@ const AsideSection = ({
}
return (
<div className="w-[16vw] p-4">
<div className="">
<div className="mb-4">
<h3 className="text-lg font-semibold text-gray-800">{mainTabTitle}</h3>
<p className="text-sm text-gray-500 mt-1">Navigate through sections</p>

View File

@ -0,0 +1,219 @@
import React, { useEffect, useState } from 'react'
import { useSelector } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom';
import logoRecord from '../../assets/record.svg';
import LoaderUi from '../../helper/LoaderUi';
import api from '../../config/api';
import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi';
const TemplateTable = () => {
const loaction = useLocation()
const [loading, setLoading] = useState(false)
const [templates, setTemplates] = useState([])
const accountDetails = useSelector((state: any) => state.user.account);
const accountId = accountDetails.id || "";
const [showMenu, setShowMenu] = useState(null);
const [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
const navigate = useNavigate();
const getAllTemplates = async () => {
setLoading(true)
try {
let res = await api.get(`/api/accounts/${activeAccountId}/templates?sortBy=lastModifiedAt desc`)
let templateData = res?.data
setTemplates(templateData)
} catch (error) {
console.log(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (activeAccountId) {
getAllTemplates()
}
}, [activeAccountId])
const formatDate = (timestamp: number) => {
return new Date(timestamp * 1000).toLocaleString();
};
const handleUseClick = async (item: any) => {
try {
const payload = {
name: item?.name ?? "",
subject: item.subject,
message: item.message,
description: item.description,
expireAfter: item.expireAfter,
enableIdStamping: item.enableIdStamping,
allowViewAudit: item.allowViewAudit,
allowReassign: item.allowReassign,
enableSignatureIdStamping: item.enableSignatureIdStamping,
templateId: item.id,
};
let res = await api.post(`/api/accounts/${activeAccountId}/transactions`, payload)
console.log("handleUseClick", res.data)
// /process-step-one/:accountId/:transactionId
navigate(`/process-step-one/${activeAccountId}/${res?.data?.id}?useTemplate=true`);
} catch (error) {
console.log(error)
}
}
const handleEditClick = (templateId: string) => {
setShowMenu(null);
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="overflow-y-auto max-h-[79vh] rounded-xl shadow-sm border border-gray-100 no-scrollbar">
<div className="flex justify-between items-center px-6 py-4 border-b border-gray-100 bg-white">
<h2 className="text-sm font-semibold text-gray-800 ">Recent Template</h2>
</div>
<table className="min-w-full text-sm bg-white">
<thead className=" sticky top-0 z-10 bg-gray-100 text-gray-600 text-xs uppercase tracking-wider border-b border-gray-200">
<tr>
{/* <th className="px-4 py-3 w-10 text-left font-medium">
<div className="w-4 h-4 border border-slate-300 rounded flex items-center justify-center">
<FaCheck className="text-purple-600 text-xs" />
</div>
</th> */}
<th className="px-6 py-4 font-semibold text-left">Name</th>
{loaction.pathname === '/templates' && (<>
<th className="px-6 py-4 font-semibold text-left">Created</th>
<th className="px-6 py-4 font-semibold text-left">Last Updated</th>
</>)}
<th className="px-6 py-4 font-semibold text-left">Use</th>
<th className="px-6 py-4 font-semibold text-left">Action</th>
</tr>
</thead>
{loading ? (
<tbody>
<tr>
<td colSpan="6" className="relative h-100">
<LoaderUi />
</td>
</tr>
</tbody>
) : templates.length > 0 ? (
<tbody className="divide-y divide-gray-100">
{templates.map((item, index) => (
<tr key={index} className="hover:bg-gray-50 transition-colors">
{/* <td className="px-4 py-4">
<div className="w-4 h-4 border border-slate-300 rounded flex items-center justify-center">
<FaCheck className="text-purple-600 text-xs" />
</div>
</td> */}
<td className="px-4 py-4 text-gray-800 text-left">
{item.name || 'Untitled'}
</td>
{loaction.pathname === '/templates' && (<>
<td className="px-4 py-4 text-gray-800 text-left">
{formatTimestamp(item.createdAt)}
</td>
<td className="px-4 py-4 text-gray-800 text-left">
{formatTimestamp(item.lastModifiedAt)}
</td>
</>)}
<td
className="px-4 py-4 text-left"
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>
{/* Action Menu */}
<td className="px-4 py-4 text-left">
<div
className="relative inline-block"
tabIndex={0}
onBlur={(e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
setShowMenu(null);
}
}}
>
<button
className="text-gray-400 hover:text-gray-600 p-1 focus:outline-none"
onClick={() =>
setShowMenu(showMenu === index ? null : index)
}
>
<PiDotsThreeOutlineVerticalFill className="text-lg ms-4" />
</button>
{showMenu === index && (
<div
className="absolute -top-9 right-4 bg-white border border-gray-200 rounded-lg shadow-lg z-20 min-w-[100px]"
>
<button
onMouseDown={() => handleEditClick(item.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"
>
Edit
</button>
<button
onMouseDown={() => {
setShowMenu(null);
console.log('Delete clicked', item);
}}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600"
>
Delete
</button>
</div>
)}
</div>
</td>
</tr>
))}
</tbody>
) : (
<tbody>
<tr>
<td colSpan="6" className="text-center py-10 text-gray-500">
<div className="flex flex-col items-center justify-center">
<img
src={logoRecord}
alt="Record Not Found"
className="w-70 h-70 mb-4 opacity-70"
/>
</div>
</td>
</tr>
</tbody>
)}
</table>
</div>
</>
)
}
export default TemplateTable

View File

@ -7,7 +7,8 @@ import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { FaSpinner } from "react-icons/fa";
import LoaderUi from "../../helper/LoaderUi";
import logoRecord from '../../assets/record.svg';
import TemplateTable from "./TemplateTable";
@ -38,73 +39,10 @@ const Templates = () => {
}
const getAllTemplates = async () => {
setLoading(true)
try {
let res = await api.get(`/api/accounts/${activeAccountId}/templates?sortBy=lastModifiedAt desc`)
let templateData = res?.data
setTemplates(templateData)
} catch (error) {
console.log(error)
} finally {
setLoading(false)
}
}
useEffect(() => {
if (activeAccountId) {
getAllTemplates()
}
}, [activeAccountId])
const formatDate = (timestamp: number) => {
return new Date(timestamp * 1000).toLocaleString();
};
const handleUseClick = async (item: any) => {
try {
const payload = {
name: item?.name ?? "",
subject: item.subject,
message: item.message,
description: item.description,
expireAfter: item.expireAfter,
enableIdStamping: item.enableIdStamping,
allowViewAudit: item.allowViewAudit,
allowReassign: item.allowReassign,
enableSignatureIdStamping: item.enableSignatureIdStamping,
templateId: item.id,
};
let res = await api.post(`/api/accounts/${activeAccountId}/transactions`, payload)
console.log("handleUseClick", res.data)
// /process-step-one/:accountId/:transactionId
navigate(`/process-step-one/${activeAccountId}/${res?.data?.id}?useTemplate=true`);
} catch (error) {
console.log(error)
}
}
const handleEditClick = (templateId: string) => {
setShowMenu(null);
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">
@ -129,7 +67,7 @@ const Templates = () => {
<div
onClick={handleAddTemplate}
className="flex items-center gap-2 bg-[var(--primary)] px-3 py-1.5 rounded-sm cursor-pointer hover:opacity-90">
className="flex items-center gap-2 bg-[var(--mainPrimaryBtn)] px-3 py-1.5 rounded-sm cursor-pointer hover:opacity-90">
<IoMdAdd className="text-white" />
<button
className="text-white rounded text-sm hover:opacity-90">
@ -139,110 +77,8 @@ const Templates = () => {
</div>
{/* Table */}
<div className="max-h-[77vh] pb-16 overflow-y-auto relative no-scrollbar">
<table className="min-w-full border border-gray-200 bg-white shadow rounded table-fixed">
<thead className="bg-gray-100 text-xs text-gray-700 uppercase tracking-wider sticky top-0 z-10">
<tr>
<th className="px-4 py-3 w-10 bg-gray-100">
<div className="w-4 h-4 border border-slate-300 rounded flex items-center justify-center">
{/* <FaCheck className="text-purple-600 text-xs" /> */}
</div>
</th>
<th className="px-4 py-2 text-center bg-gray-100">Name</th>
<th className="px-4 py-2 text-center bg-gray-100">Created</th>
<th className="px-4 py-2 text-center bg-gray-100">Last Updated</th>
<th className="px-4 py-2 text-center bg-gray-100">Use</th>
<th className="px-4 py-2 text-center bg-gray-100">Action</th>
</tr>
</thead>
{loading ? (
<tbody>
<tr>
<td colSpan="6" className="relative h-100">
<LoaderUi />
</td>
</tr>
</tbody>
) : templates.length > 0 ? (
<tbody className="divide-y divide-gray-200">
{templates.map((item, index) => {
return (
<tr key={index} className="hover:bg-gray-50 relative">
<td className="px-4 py-2">
<div className="w-4 h-4 border border-slate-300 rounded flex items-center justify-center">
{/* <FaCheck className="text-purple-600 text-xs" /> */}
</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">{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">
<button
onClick={() => setShowMenu(showMenu === index ? null : index)}
className="text-slate-400 hover:text-black focus:outline-none"
>
<PiDotsThreeOutlineVerticalFill className="text-lg" />
</button>
{showMenu === index && (
<div
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
if (!e.currentTarget.contains(e.relatedTarget)) {
setShowMenu(null);
}
}}
>
<button
className="block w-full text-left px-4 py-1 text-sm text-gray-700 hover:bg-gray-100"
onClick={() => handleEditClick(item.id)}
>
Edit
</button>
<button
className="block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-100"
onClick={() => {
setShowMenu(null);
console.log('Delete clicked', item);
}}
>
Delete
</button>
</div>
)}
</td>
</tr>
);
})}
</tbody>
) : (
<tbody>
<tr>
<td colSpan="6" className="text-center py-10 text-gray-500">
<div className="flex flex-col items-center justify-center">
<img
src={logoRecord}
alt="Record Not Found"
className="w-70 h-70 mb-4 opacity-70" // adjust size and appearance
/>
</div>
</td>
</tr>
</tbody>
)}
</table>
</div>
<section className="my-4 mx-3"></section>
<TemplateTable/>
</div>
);
};

View File

@ -1,254 +1,321 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import {
FiX,
FiCheckCircle,
FiXCircle,
FiClock,
FiUser,
FiCalendar,
FiFileText,
FiAlertTriangle,
FiTruck,
FiUserCheck,
FiUserX,
FiPackage,
FiMapPin,
FiEye,
FiRotateCcw
FiX,
FiCheckCircle,
FiXCircle,
FiClock,
FiUser,
FiCalendar,
FiFileText,
FiAlertTriangle,
FiTruck,
FiUserCheck,
FiUserX,
FiPackage,
FiMapPin,
FiEye,
FiRotateCcw,
FiSend,
FiMail,
FiActivity,
FiRefreshCw,
FiLock,
FiUnlock,
FiUserPlus,
FiEdit
} from "react-icons/fi";
import api from "../../config/api";
import { useParams } from "react-router-dom";
import logoRecord from '../../assets/record.svg'
import LoaderUi from "../../helper/LoaderUi";
import { FaRegEyeSlash } from "react-icons/fa";
import { MdAccessTime, MdCancel } from "react-icons/md";
interface AuditModalProps {
closeModal: () => void;
closeModal: () => void;
}
const AuditModal: React.FC<AuditModalProps> = ({ closeModal }) => {
const [activeTab, setActiveTab] = useState<'status' | 'audit'>('status');
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const userDetails = JSON.parse(sessionStorage.getItem('userDetails') || '{}');
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"
if (!accountId || !transactionId) {
window.location.href = "/";
return null;
}
];
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"
let [auditStatus, setAuditStatus] = useState([])
let [loading, setLoading] = useState(false)
const getAuditDetails = async () => {
setLoading(true)
try {
let res = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/audit?lang=${userDetails.language || ''}`)
if (res?.status === 200) {
// console.log(res?.data?.events)
let auditResponse = res?.data?.events
setAuditStatus(auditResponse)
}
} catch (error) {
console.log(error)
} finally {
setLoading(false)
}
}
];
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'
useEffect(() => {
if (accountId && transactionId) {
getAuditDetails()
}
}, [accountId, transactionId])
const statusHistory = [
{
"action": "SENT",
"message": "Transaction Sent by John Doe <john@email.com>",
"status": "SENT",
"eventData": {
"ip": "192.168.1.1",
"longitude": "3.3792",
"latitude": "6.5244",
"timezone": "WAT"
},
"timestamp": "2025-01-15T10:30:00Z",
"language": "en"
},
{
"action": "INVITED",
"message": "Invitation Sent to Jane Smith <jane@email.com> to sign documents",
"status": "SENT",
"eventData": {
"ip": "0.0.0.0",
"longitude": "11",
"latitude": "12",
"timezone": ""
},
"timestamp": "2025-01-15T11:45:00Z",
"language": "es"
},
{
"action": "VIEWED",
"message": "Document viewed by recipient Alice Johnson <alice@email.com>",
"status": "VIEWED",
"eventData": {
"ip": "203.0.113.5",
"longitude": "-74.0059",
"latitude": "40.7128",
"timezone": "EST"
},
"timestamp": "2025-01-15T14:20:00Z",
"language": "en"
},
{
"action": "SIGNED",
"message": "Document signed by Bob Wilson <bob@email.com>",
"status": "COMPLETED",
"eventData": {
"ip": "198.51.100.10",
"longitude": "-122.4194",
"latitude": "37.7749",
"timezone": "PST"
},
"timestamp": "2025-01-15T16:10:00Z",
"language": "en"
}
];
const getActionIcon = (action: string) => {
const icons = {
'CREATED': <FiFileText className="w-4 h-4 text-sky-500" />,
'SENT': <FiSend className="w-4 h-4" />,
'INVITED': <FiMail className="w-4 h-4" />,
'COMPLETED': <FiCheckCircle className="w-4 h-4" />,
'RE_SENT_INVITATION': <FiRefreshCw className="w-4 h-4" />,
'RE_SENT_EXPIRED_INVITATION': <FiRefreshCw className="w-4 h-4" />,
'VIEWED': <FiEye className="w-4 h-4" />,
'VIEWED_IN_PERSON': <FaRegEyeSlash className="w-4 h-4" />, // Slightly different eye icon
'AUTH_FAILED': <FiXCircle className="w-4 h-4 text-red-500" />,
'ACCESS_CODE_FAILED': <FiLock className="w-4 h-4 text-red-500" />,
'ACCESS_CODE_VALIDATED': <FiUnlock className="w-4 h-4 text-green-500" />,
'AUTH_VALIDATED': <FiCheckCircle className="w-4 h-4 text-green-500" />,
'SIGNED': <FiCheckCircle className="w-4 h-4" />,
'EXPIRED': <MdAccessTime className="w-4 h-4 text-gray-500" />,
'CANCELLED': <MdCancel className="w-4 h-4 text-red-500" />,
'DECLINED': <FiXCircle className="w-4 h-4 text-red-500" />,
'PENDING': <FiClock className="w-4 h-4" />,
'ASSIGNED': <FiUserCheck className="w-4 h-4" />,
'REASSIGNED': <FiUserPlus className="w-4 h-4" />,
'EDITED': <FiEdit className="w-4 h-4" />,
};
return icons[action as keyof typeof icons] || <FiActivity className="w-4 h-4" />;
};
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'
const getActionColor = (action: string, status: string) => {
if (status === 'COMPLETED') return 'text-green-600';
if (status === 'FAILED' || status === 'REJECTED') return 'text-red-600';
if (status === 'PENDING') return 'text-yellow-600';
const colors = {
'SENT': 'text-blue-600',
'INVITED': 'text-indigo-600',
'VIEWED': 'text-purple-600',
'SIGNED': 'text-green-600',
'REJECTED': 'text-red-600'
};
return colors[action as keyof typeof colors] || 'text-gray-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>
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';
};
{/* Content */}
<div className="flex-1 overflow-y-auto p-4">
const formatTimestamp = (timestamp: string) => {
try {
const date = new Date(timestamp);
if (isNaN(date.getTime())) {
return timestamp;
}
<div className="space-y-1.5">
<div className="text-sm text-gray-500 font-medium mb-2">TODAY</div>
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
{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>
</>
)}
if (diffMins < 60) {
return `${diffMins}m ago`;
} else if (diffHours < 24) {
return `${diffHours}h ago`;
} else if (diffDays < 7) {
return `${diffDays}d ago`;
} else {
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
} catch (error) {
return timestamp;
}
};
const extractEmailFromMessage = (message: string) => {
const emailMatch = message.match(/<([^>]+)>/);
return emailMatch ? emailMatch[1] : '';
};
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>
<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>
<button
onClick={closeModal}
className="text-white cursor-pointer transition-colors hover:text-gray-200"
>
<FiX className="w-5 h-5" />
</button>
</div>
))}
{/* Content */}
<div className="flex-1 overflow-y-auto p-4 no-scrollbar">
{/* {loading ? <LoaderUi /> : ""} */}
{auditStatus.length ?
<div className="space-y-1.5">
{/* <div className="text-sm text-gray-500 font-medium mb-2">TODAY</div> */}
{auditStatus.map((item, index) => (
<div key={index} className="flex items-start space-x-4 pb-3 border-b border-gray-100 last:border-b-0">
<div className={`${getActionColor(item.action)} mt-1`}>
{getActionIcon(item.action)}
</div>
<div className="flex-1">
<p className="text-gray-600 font-medium text-sm mb-1">{item.message}</p>
<div className="flex items-center space-x-2 text-xs text-gray-600 mb-1">
<span>Action: {item.action}</span>
{extractEmailFromMessage(item.message) && (
<>
<span>•</span>
<span className="text-blue-600">{extractEmailFromMessage(item.message)}</span>
</>
)}
</div>
<div className="flex items-center space-x-2 mb-2">
<span className="text-xs text-gray-600">Status:</span> <span className={`px-2 py-0.5 text-xs rounded-full ${getStatusBadge(item.status)}`}>
{item.status}
</span>
<span className="text-xs text-gray-500">{formatTimestamp(item.timestamp)}</span>
{item.language && (
<span className="text-xs text-gray-400 uppercase">{item.language}</span>
)}
</div>
{/* Event Data Details */}
{/* <div className="grid grid-cols-2 gap-4 text-xs text-gray-600 bg-gray-50 p-2 rounded">
{item.eventData.ip && (
<div>
<span className="font-medium text-gray-700">IP:</span> {item.eventData.ip}
</div>
)}
{item.eventData.timezone && (
<div>
<span className="font-medium text-gray-700">Timezone:</span> {item.eventData.timezone}
</div>
)}
{item.eventData.latitude && item.eventData.longitude && (
<div className="col-span-2">
<span className="font-medium text-gray-700">Location:</span>
<span className="ml-1">Lat: {item.eventData.latitude}, Lng: {item.eventData.longitude}</span>
</div>
)}
</div> */}
</div>
</div>
))}
</div> :
<div className="flex justify-center items-center">
<img
src={logoRecord}
alt="Record Not Found"
className="w-80 h-80 opacity-70" // adjust size and appearance
/>
</div>
}
</div>
{/* Footer */}
<div className="flex justify-between items-center p-3 border-t border-gray-200 bg-gray-50">
<div className="text-sm text-gray-600">
Showing {auditStatus.length} event{auditStatus.length !== 1 ? 's' : ''}
</div>
<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>
{/* 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

@ -62,7 +62,7 @@ const TransactionView = () => {
: "?auditStatus";
window.history.pushState({}, "", pathname + newSearch);
setIsModalOpen(true); // ✅ update state immediately
setIsModalOpen(true); // update state immediately
};
const closeModal = () => {
@ -72,7 +72,7 @@ const TransactionView = () => {
.replace(/\?$/, "");
window.history.pushState({}, "", location.pathname + search);
setIsModalOpen(false); // ✅ update state immediately
setIsModalOpen(false); // update state immediately
};
console.log(transactionDetails, "line 37");

View File

@ -1,16 +1,16 @@
@import "tailwindcss";
:root {
--primary: #3D74B6;
--primary: #005AB5;
--primaryLight: #ffffff;
--primaryLight2: #6a92c2;
--primaryLight3: #dae9de;
--dashBoardBg: #ffffff;
--mainViewBg: #edeaeb;
--mainViewBg: #F8F9FC;
--highlightBg1: #edeaeb;
--menuTextCol: #98a0a8;
--menuTextColActive: #3D74B6;
--menuBgColActive: #edeaeb;
--menuTextColActive: #005AB5;
--menuBgColActive: #FFFFFF;
--menuTextHoverCol: #e7e7e7;
--greyTextCol: #98a0a8;
@ -46,6 +46,17 @@
--modalSubmitBG: #1919d7;
--modalSubmitBGHover: #395ee1;
/* Figma Design */
--sidebarBg: #005AB5;
--sidebarDropDownBg : #003C78;
--sidebarMenuCol : #FFFFFF;
--documentUploadBg :#FFFFFF;
--mainPrimaryBtn : #077DEC
}