new ui figma

This commit is contained in:
JaiPandey425 2025-08-14 17:34:39 +05:30
parent 6ef3e36141
commit 8ba1d6c468
23 changed files with 673 additions and 532 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

@ -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

@ -37,6 +37,7 @@ interface AuditModalProps {
const AuditModal: React.FC<AuditModalProps> = ({ closeModal }) => {
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
const userDetails = JSON.parse(sessionStorage.getItem('userDetails') || '{}');
if (!accountId || !transactionId) {
window.location.href = "/";
@ -48,7 +49,7 @@ const AuditModal: React.FC<AuditModalProps> = ({ closeModal }) => {
const getAuditDetails = async () => {
setLoading(true)
try {
let res = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/audit`)
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

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
}