jai #18
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -117,7 +117,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
|
||||||
id: account.userId,
|
id: account.userId,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
email: account.userEmail,
|
email: account.userEmail,
|
||||||
language: "", // set it if available
|
language: account.language, // set it if available
|
||||||
account: {
|
account: {
|
||||||
id: account.accountId,
|
id: account.accountId,
|
||||||
isDefault: false, // update as needed
|
isDefault: false, // update as needed
|
||||||
|
|
@ -129,7 +129,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
|
||||||
id: account.userId,
|
id: account.userId,
|
||||||
name: account.name,
|
name: account.name,
|
||||||
email: account.userEmail,
|
email: account.userEmail,
|
||||||
language: "", // set it if available
|
language: account.language, // set it if available
|
||||||
account: {
|
account: {
|
||||||
id: account.accountId,
|
id: account.accountId,
|
||||||
isDefault: false, // update as needed
|
isDefault: false, // update as needed
|
||||||
|
|
@ -154,7 +154,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
|
||||||
id: item?.id,
|
id: item?.id,
|
||||||
name: item?.firstName + " " + item?.lastName,
|
name: item?.firstName + " " + item?.lastName,
|
||||||
email: item?.email,
|
email: item?.email,
|
||||||
language: "",
|
language: item?.language,
|
||||||
account: {
|
account: {
|
||||||
id: item?.account.id,
|
id: item?.account.id,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
|
|
@ -263,7 +263,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
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">
|
<nav className="text-md font-medium text-gray-800 capitalize flex items-center flex-wrap">
|
||||||
{pathnames.length === 0 ? (
|
{pathnames.length === 0 ? (
|
||||||
<span className="text-gray-900 font-semibold">Home</span>
|
<span className="text-gray-900 font-semibold">Home</span>
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,11 @@ import { PiDotsThreeOutlineVerticalFill } from "react-icons/pi";
|
||||||
import AccountNav from "./AccountNav";
|
import AccountNav from "./AccountNav";
|
||||||
import api from "../../config/api";
|
import api from "../../config/api";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
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 Cards from "../home/Cards";
|
||||||
import AddInviteModal from "./AddInviteModal";
|
import AddInviteModal from "./AddInviteModal";
|
||||||
import { Bounce, toast } from "react-toastify";
|
import { Bounce, toast } from "react-toastify";
|
||||||
import {
|
import { FiFileText, FiClock, FiAlertTriangle, FiCheckCircle } from "react-icons/fi";
|
||||||
FaCheckCircle,
|
|
||||||
FaClock,
|
|
||||||
FaHourglassHalf,
|
|
||||||
FaTasks,
|
|
||||||
FaChevronDown,
|
|
||||||
FaPlus,
|
|
||||||
FaUserPlus,
|
|
||||||
FaFilter,
|
|
||||||
FaCalendarAlt
|
|
||||||
} from 'react-icons/fa';
|
|
||||||
import TransactionDataTable from "./TransactionDataTable";
|
import TransactionDataTable from "./TransactionDataTable";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../store/store";
|
import { RootState } from "../../store/store";
|
||||||
|
|
@ -35,34 +25,40 @@ const Account = () => {
|
||||||
const mapApiDataToCards = (data: any): CardData[] => {
|
const mapApiDataToCards = (data: any): CardData[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: 'Action Needed',
|
title: "Action Required",
|
||||||
count: data.actionNeeded || 0,
|
count: data.actionNeeded || 0,
|
||||||
icon: <FaTasks size={18} className="text-indigo-600" />,
|
icon: <FiFileText className="w-5 h-5 text-sky-500" />,
|
||||||
gradient: 'bg-gradient-to-br from-indigo-50 to-indigo-100',
|
iconBg: "bg-sky-100 border-sky-200",
|
||||||
shadowColor: 'shadow-indigo-200/50',
|
textColor: "text-sky-600",
|
||||||
iconBg: 'bg-indigo-100 border-indigo-200',
|
gradient: "bg-white",
|
||||||
textColor: 'text-indigo-700',
|
shadowColor: "shadow-sky-100",
|
||||||
trend: 'up'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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,
|
count: data.expiring || 0,
|
||||||
icon: <FaHourglassHalf size={18} className="text-rose-600" />,
|
icon: <FiAlertTriangle className="w-5 h-5 text-red-500" />,
|
||||||
gradient: 'bg-gradient-to-br from-rose-50 to-rose-100',
|
iconBg: "bg-red-100 border-red-200",
|
||||||
shadowColor: 'shadow-rose-200/50',
|
textColor: "text-red-600",
|
||||||
iconBg: 'bg-rose-100 border-rose-200',
|
gradient: "bg-white",
|
||||||
textColor: 'text-rose-700',
|
shadowColor: "shadow-red-100",
|
||||||
trend: 'up'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Completed',
|
title: "Completed",
|
||||||
count: data.completed || 0,
|
count: data.completed || 0,
|
||||||
icon: <FaCheckCircle size={18} className="text-teal-600" />,
|
icon: <FiCheckCircle className="w-5 h-5 text-green-500" />,
|
||||||
gradient: 'bg-gradient-to-br from-teal-50 to-teal-100',
|
iconBg: "bg-green-100 border-green-200",
|
||||||
shadowColor: 'shadow-teal-200/50',
|
textColor: "text-green-600",
|
||||||
iconBg: 'bg-teal-100 border-teal-200',
|
gradient: "bg-white",
|
||||||
textColor: 'text-teal-700',
|
shadowColor: "shadow-green-100",
|
||||||
trend: 'up'
|
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// title: 'Waiting for Others',
|
// title: 'Waiting for Others',
|
||||||
|
|
@ -94,16 +90,16 @@ const Account = () => {
|
||||||
// textColor: 'text-green-700',
|
// textColor: 'text-green-700',
|
||||||
// trend: 'down'
|
// trend: 'down'
|
||||||
// },
|
// },
|
||||||
{
|
// {
|
||||||
title: 'draft',
|
// title: 'draft',
|
||||||
count: data.draft || 0,
|
// count: data.draft || 0,
|
||||||
icon: <FaClock size={18} className="text-stone-600" />,
|
// icon: <FaClock size={18} className="text-stone-600" />,
|
||||||
gradient: 'bg-gradient-to-br from-stone-50 to-stone-100',
|
// gradient: 'bg-gradient-to-br from-stone-50 to-stone-100',
|
||||||
shadowColor: 'shadow-stone-200/50',
|
// shadowColor: 'shadow-stone-200/50',
|
||||||
iconBg: 'bg-stone-100 border-stone-200',
|
// iconBg: 'bg-stone-100 border-stone-200',
|
||||||
textColor: 'text-stone-700',
|
// textColor: 'text-stone-700',
|
||||||
trend: 'down'
|
// trend: 'down'
|
||||||
}
|
// }
|
||||||
// Add more cards here if needed
|
// Add more cards here if needed
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
@ -252,8 +248,9 @@ const Account = () => {
|
||||||
|
|
||||||
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
|
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
|
||||||
isLoading={isLoading} />
|
isLoading={isLoading} />
|
||||||
|
<section className="my-4 mx-3">
|
||||||
<TransactionDataTable />
|
<TransactionDataTable />
|
||||||
|
</section>
|
||||||
|
|
||||||
{isModalOpen && <AddInviteModal onClose={() => setIsModalOpen(false)} onInvite={handleInvite} inviteLoading={inviteLoading}
|
{isModalOpen && <AddInviteModal onClose={() => setIsModalOpen(false)} onInvite={handleInvite} inviteLoading={inviteLoading}
|
||||||
accountOptions={accountsData} />}
|
accountOptions={accountsData} />}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ import React, { useEffect, useState } from 'react'
|
||||||
import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi';
|
import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi';
|
||||||
import api from '../../config/api';
|
import api from '../../config/api';
|
||||||
import { Transaction } from '../transactions/Transactions';
|
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 { 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();
|
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 TransactionDataTable = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const loaction = useLocation()
|
||||||
|
console.log("loaction", loaction.pathname);
|
||||||
|
|
||||||
const [currentEmailId, setCurrentEmailId] = useState<string | null>('null');
|
const [currentEmailId, setCurrentEmailId] = useState<string | null>('null');
|
||||||
const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]);
|
const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]);
|
||||||
const [activeAccountId, setActiveAccountId] = useState<string | null>(
|
const [activeAccountId, setActiveAccountId] = useState<string | null>(
|
||||||
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
|
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
|
||||||
);
|
);
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
const fetchTransactions = async () => {
|
const fetchTransactions = async () => {
|
||||||
console.log("fetchTransactions")
|
console.log("fetchTransactions")
|
||||||
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`/api/accounts/${activeAccountId}/transactions?sortBy=lastModifiedAt desc`);
|
const response = await api.get(`/api/accounts/${activeAccountId}/transactions?sortBy=lastModifiedAt desc`);
|
||||||
setTransactions(response.data || []);
|
setTransactions(response.data || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching transactions:", 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{transactions.length > 0 && (
|
|
||||||
<div className="overflow-x-auto py-2">
|
<div className="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm">
|
{/* Header */}
|
||||||
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider">
|
<div className="flex justify-between items-center px-6 py-4 border-b border-gray-100">
|
||||||
<tr>
|
<h2 className="text-sm font-semibold text-gray-800">Recent Transactions</h2>
|
||||||
<th className="px-4 py-3 text-left"></th>
|
</div>
|
||||||
<th className="px-4 py-3 text-center">Name</th>
|
|
||||||
<th className="px-4 py-3 text-center">Status</th>
|
{/* Scrollable Table Container */}
|
||||||
<th className="px-4 py-3 text-center">Active</th>
|
<div className="overflow-auto max-h-[67vh] no-scrollbar">
|
||||||
<th className="px-4 py-3 text-center">Last Change</th>
|
<table className="w-full text-sm">
|
||||||
<th className="px-4 py-3 text-center">Expire After (days)</th>
|
{/* Sticky Header */}
|
||||||
<th className="px-4 py-3 text-center">Sign</th>
|
<thead className="bg-gray-50 sticky top-0 z-10">
|
||||||
<th className="px-4 py-3 text-center">Action</th>
|
<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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200">
|
{loading ? (
|
||||||
{transactions.map((tx) => {
|
<tbody>
|
||||||
const lastModifiedAt = new Date(tx.lastModifiedAt, "DD/MM/YYYY");
|
<tr>
|
||||||
return (
|
<td colSpan="6" className="relative h-100">
|
||||||
<tr
|
<LoaderUi />
|
||||||
key={tx.id}
|
</td>
|
||||||
onClick={() => handleTransactionClick(tx.id)}
|
</tr>
|
||||||
className="hover:bg-gray-50 cursor-pointer"
|
</tbody>
|
||||||
>
|
) : transactions.length > 0 ? (
|
||||||
<td className="px-4 py-3">
|
<>
|
||||||
<div className="w-5 h-5 rounded border border-slate-300 flex justify-center items-center">
|
{/* Scrollable Body */}
|
||||||
{selectedTransactions.includes(tx.id) && (
|
<tbody className="bg-white divide-y divide-gray-100">
|
||||||
<FaCheck className="text-purple-600 text-sm" />
|
{transactions.map((tx) => (
|
||||||
)}
|
<tr
|
||||||
</div>
|
key={tx.id}
|
||||||
</td>
|
onClick={() => handleTransactionClick(tx.id)}
|
||||||
<td className="px-4 py-3 text-center">
|
className="hover:bg-gray-50 transition-colors duration-150 cursor-pointer"
|
||||||
{tx.subject || "Untitled"}
|
>
|
||||||
</td>
|
<td className="px-6 py-4 text-left">
|
||||||
<td className="px-4 py-3 text-center">
|
<div className="text-gray-800 font-medium truncate max-w-xs">
|
||||||
<span className="px-2 py-0.5 rounded-xl text-xs font-medium bg-blue-100 text-blue-700">
|
{tx?.subject || "Untitled"}
|
||||||
{tx.status}
|
</div>
|
||||||
</span>
|
</td>
|
||||||
</td>
|
<td className="px-6 py-4 text-center">
|
||||||
<td className="px-4 py-3 text-center">
|
<span
|
||||||
{tx.active ? "Yes" : "No"}
|
className={`inline-block px-3 py-1 rounded-full text-xs font-medium ${getStatusColorClass(
|
||||||
</td>
|
tx.status
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
|
{tx.status}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td className="px-4 py-3 text-center">
|
{location.pathname === '/transactions' && (
|
||||||
{formatTimestamp(tx.lastModifiedAt)}
|
<>
|
||||||
</td>
|
<td className="px-6 py-4">
|
||||||
<td className="px-4 py-3 text-center">
|
<div className="flex items-center justify-center">
|
||||||
{tx.expireAfter ?? "-"}
|
{tx.signedBy ? (
|
||||||
</td>
|
<div className="flex items-center gap-2">
|
||||||
<td className="px-4 py-3 text-center ">
|
<div
|
||||||
{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> : "-"}
|
className="w-7 h-7 rounded-full flex items-center justify-center text-xs font-medium text-white flex-shrink-0"
|
||||||
</td>
|
style={{ backgroundColor: "#d8b4fe" }}
|
||||||
<td className="px-4 py-3 text-center">
|
>
|
||||||
<PiDotsThreeOutlineVerticalFill className="text-lg text-slate-300 hover:text-black mx-auto" />
|
{tx.signedByInitials}
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<span className="text-gray-700 truncate max-w-[120px]">
|
||||||
)
|
{tx.signedBy}
|
||||||
})}
|
</span>
|
||||||
</tbody>
|
</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>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ const Contacts = () => {
|
||||||
setShowModal((prev) => !prev);
|
setShowModal((prev) => !prev);
|
||||||
setActionStatus("Add")
|
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" />
|
<IoMdAdd className="text-white" />
|
||||||
<button
|
<button
|
||||||
className="text-white rounded text-sm hover:opacity-90 cursor-pointer">
|
className="text-white rounded text-sm hover:opacity-90 cursor-pointer">
|
||||||
|
|
@ -134,20 +134,22 @@ const Contacts = () => {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
<tr>
|
||||||
<th className="px-4 py-3 text-left">First Name</th>
|
<th className="px-6 py-3 font-medium text-left">First Name</th>
|
||||||
<th className="px-4 py-3 text-left">Last Name</th>
|
<th className="px-6 py-3 font-medium text-left">Last Name</th>
|
||||||
<th className="px-4 py-3 text-left">Email</th>
|
<th className="px-6 py-3 font-medium text-left">Email</th>
|
||||||
<th className="px-4 py-3 text-center">Actions</th>
|
<th className="px-6 py-3 font-medium text-center">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200">
|
<tbody className="divide-y divide-gray-100">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="4" className="relative h-100"><LoaderUi /></td>
|
<td colSpan="4" className="relative h-100">
|
||||||
|
<LoaderUi />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : contacts.length === 0 ? (
|
) : contacts.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -156,32 +158,40 @@ const Contacts = () => {
|
||||||
<img
|
<img
|
||||||
src={logoRecord}
|
src={logoRecord}
|
||||||
alt="Record Not Found"
|
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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
contacts.map((contact, index) => (
|
contacts.map((contact, index) => (
|
||||||
<tr key={contact.id} className="hover:bg-gray-50">
|
<tr
|
||||||
<td className="px-4 py-3">{contact.firstName}</td>
|
key={contact.id}
|
||||||
<td className="px-4 py-3">{contact.lastName}</td>
|
className="hover:bg-gray-50 transition-colors"
|
||||||
<td className="px-4 py-3">{contact.email}</td>
|
>
|
||||||
<td className="px-4 py-3 text-center">
|
<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
|
<div
|
||||||
className="relative inline-block"
|
className="relative inline-block"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
>
|
>
|
||||||
<button
|
<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)}
|
onClick={() => toggleMenu(contact.id)}
|
||||||
>
|
>
|
||||||
<FaEllipsisV />
|
<FaEllipsisV />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{openMenuId === contact.id && (
|
{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
|
<button
|
||||||
onMouseDown={() => handleEdit(contact.id)}
|
onMouseDown={() => handleEdit(contact.id)}
|
||||||
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"
|
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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{showModal &&
|
|
||||||
<ContactFormModal onSubmit={handleContactSubmit}
|
{showModal && (
|
||||||
loading={createAndInviteLoader} setShowModal={setShowModal} actionStatus={actionStatus} editJsonData={editJsonData} />
|
<ContactFormModal
|
||||||
}
|
onSubmit={handleContactSubmit}
|
||||||
|
loading={createAndInviteLoader}
|
||||||
|
setShowModal={setShowModal}
|
||||||
|
actionStatus={actionStatus}
|
||||||
|
editJsonData={editJsonData}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -171,9 +171,9 @@ function Dashboard() {
|
||||||
<SideBar showSideBar={showSideBar} setShowSideBar={setShowSideBar} />
|
<SideBar showSideBar={showSideBar} setShowSideBar={setShowSideBar} />
|
||||||
|
|
||||||
{/* On Top Main Content - START */}
|
{/* 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 */}
|
{/* Main Content */}
|
||||||
<div className='px-2 py-1.5'>
|
<div className=''>
|
||||||
<CommonNav />
|
<CommonNav />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,10 @@ interface NavLinkProps {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const NavLink: React.FC<NavLinkProps> = ({ to, icon, label, showSideBar, menuPathMap }) => {
|
const NavLink: React.FC<NavLinkProps> = ({ to, icon, label, showSideBar, menuPathMap }) => {
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isActive = menuPathMap[location.pathname] === label;
|
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)
|
// const [transactionFilter, setTransationFilter] = useState<TransactionFilterEnum>(TransactionFilterEnum.ALL)
|
||||||
|
|
||||||
|
|
@ -32,7 +34,7 @@ const NavLink: React.FC<NavLinkProps> = ({ to, icon, label, showSideBar, menuPat
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={to}
|
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'}`}
|
p-2 rounded-md flex justify-left gap-2 items-center transition-all duration-300 ${showSideBar ? 'text-lg' : 'text-lg max-w-max'}`}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { setTransactionId, TRANSACTION_STATUS_ENUMS, updateTransactionStatus } f
|
||||||
import { SignTypeEnum } from '../../utlity/types';
|
import { SignTypeEnum } from '../../utlity/types';
|
||||||
// import { IoMdArrowDropright } from "react-icons/io";
|
// import { IoMdArrowDropright } from "react-icons/io";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
showSideBar: boolean;
|
showSideBar: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +85,7 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
|
||||||
onBlur={() => setSendForSignDropdown(false)}
|
onBlur={() => setSendForSignDropdown(false)}
|
||||||
>
|
>
|
||||||
<div
|
<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
|
<div
|
||||||
onClick={() => handleStartSectionClick(SignTypeEnum.multipleSign)}
|
onClick={() => handleStartSectionClick(SignTypeEnum.multipleSign)}
|
||||||
className={`w-full`}>
|
className={`w-full`}>
|
||||||
|
|
@ -98,7 +99,7 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
|
||||||
</div>
|
</div>
|
||||||
{showSideBar &&
|
{showSideBar &&
|
||||||
<p
|
<p
|
||||||
className='border-l-1 border-[var(--sendForSignTextCol)] px-2 py-2'>
|
className=' border-[var(--sendForSignTextCol)] px-2 py-2'>
|
||||||
<IoMdArrowDropdown
|
<IoMdArrowDropdown
|
||||||
onClick={() => setSendForSignDropdown(!sendForSignDropdown)}
|
onClick={() => setSendForSignDropdown(!sendForSignDropdown)}
|
||||||
className={`text-2xl ${sendForSignDropdown ? 'rotate-180' : ''} transition-all duration-150`} />
|
className={`text-2xl ${sendForSignDropdown ? 'rotate-180' : ''} transition-all duration-150`} />
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { IoSettingsOutline } from "react-icons/io5";
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { MdManageAccounts } from 'react-icons/md';
|
import { MdManageAccounts } from 'react-icons/md';
|
||||||
import { GrContactInfo, GrTransaction } from 'react-icons/gr';
|
import { GrContactInfo, GrTransaction } from 'react-icons/gr';
|
||||||
|
import logoSME from '../../assets/SMELogo.png'
|
||||||
|
|
||||||
type SideBarProps = {
|
type SideBarProps = {
|
||||||
showSideBar: boolean;
|
showSideBar: boolean;
|
||||||
|
|
@ -22,11 +23,11 @@ type MenuPathMap = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuPathMap: MenuPathMap = {
|
const menuPathMap: MenuPathMap = {
|
||||||
'/': 'Home',
|
'/': 'Home',
|
||||||
'/templates': 'Templates',
|
'/templates': 'Templates',
|
||||||
'/s/contact': 'Contact',
|
// '/accounts': 'Accounts',
|
||||||
// '/accounts': 'Accounts',
|
'/transactions': 'Transactions',
|
||||||
'transactions' : "Account"
|
'/contacts': 'Contacts'
|
||||||
};
|
};
|
||||||
|
|
||||||
const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
|
const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
|
||||||
|
|
@ -53,21 +54,21 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Side Bar */}
|
{/* 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 */}
|
{/* Menu */}
|
||||||
<div className={`absolute top-0 ${showSideBar ? 'p-4' : 'py-4'} w-full max-h-max ${showSideBar ? '' : 'text-center'}`}>
|
<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'>
|
<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>
|
</h1>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="nav-link-dash"
|
id="nav-link-dash"
|
||||||
className={`flex flex-col mt-4 ${showSideBar ? '' : 'items-center'}`}>
|
className={`flex flex-col mt-4 ${showSideBar ? '' : 'items-center'}`}>
|
||||||
<SendForSign showSideBar={showSideBar} />
|
<SendForSign showSideBar={showSideBar} />
|
||||||
<NavLink to="/" icon={<FaHome />} label="Home" showSideBar={showSideBar} menuPathMap={menuPathMap} />
|
<NavLink to="/" icon={<FaHome />} label="Home" showSideBar={showSideBar} menuPathMap={menuPathMap} />
|
||||||
<NavLink to="/templates" icon={<HiTemplate />} label="Templates" 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="/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} />
|
<NavLink to="/contacts" icon={<GrContactInfo />} label="Contacts" showSideBar={showSideBar} menuPathMap={menuPathMap} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -75,10 +76,10 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
|
||||||
{/* SideBar Toggle Button */}
|
{/* SideBar Toggle Button */}
|
||||||
<div
|
<div
|
||||||
onClick={() => setShowSideBar(!showSideBar)}
|
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 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 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 shadow-[5px_-5px_0px_0px_rgba(237,234,235,1)]'></div>
|
||||||
{showSideBar ? (
|
{showSideBar ? (
|
||||||
<BsLayoutSidebarInsetReverse className='fill-[var(--primary)] hover:text-[var(--primary)] transition-all text-2xl' />
|
<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'>
|
<div className='absolute bottom-14 right-0 flex flex-col gap-1'>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowLogoutModal(true)}
|
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"
|
title="Log out"
|
||||||
>
|
>
|
||||||
<RiLogoutBoxLine className="text-xl group-hover:scale-110 transition-transform" />
|
<RiLogoutBoxLine className="text-xl group-hover:scale-110 transition-transform" />
|
||||||
|
|
@ -99,7 +100,7 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to="/s/settings"
|
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"
|
title="Settings"
|
||||||
>
|
>
|
||||||
<IoSettingsOutline className="text-xl group-hover:scale-110 transition-transform" />
|
<IoSettingsOutline className="text-xl group-hover:scale-110 transition-transform" />
|
||||||
|
|
|
||||||
|
|
@ -204,74 +204,47 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRa
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full px-3 rounded-md">
|
<div className="w-full">
|
||||||
{/* Header */}
|
{/* Cards */}
|
||||||
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mb-2 gap-4">
|
{isLoading ? (
|
||||||
<div className="flex items-center gap-3">
|
<div className="w-full h-[10vh] px-6 py-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">
|
<div className="animate-pulse">
|
||||||
<FaFilter className="text-white text-lg" />
|
{/* <div className="h-8 bg-gray-200 rounded w-32 mb-8"></div> */}
|
||||||
</div> */}
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<div>
|
{[...Array(4)].map((_, i) => (
|
||||||
{/* <h2 className="text-lg font-semibold text-gray-800">Overview</h2> */}
|
<div key={i} className="h-10 bg-gray-200 border border-gray-100"></div>
|
||||||
{/* <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>
|
</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>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,7 @@ import AccountNav from '../accounts/AccountNav';
|
||||||
import Cards from './Cards';
|
import Cards from './Cards';
|
||||||
import StartSection from './StartSection';
|
import StartSection from './StartSection';
|
||||||
import api from '../../config/api';
|
import api from '../../config/api';
|
||||||
import {
|
import { FiFileText, FiClock, FiAlertTriangle, FiCheckCircle } from "react-icons/fi";
|
||||||
FaCheckCircle,
|
|
||||||
FaClock,
|
|
||||||
FaHourglassHalf,
|
|
||||||
FaTasks,
|
|
||||||
FaChevronDown,
|
|
||||||
FaPlus,
|
|
||||||
FaUserPlus,
|
|
||||||
FaFilter,
|
|
||||||
FaCalendarAlt
|
|
||||||
} from 'react-icons/fa';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { RootState } from '../../store/store';
|
import { RootState } from '../../store/store';
|
||||||
|
|
||||||
|
|
@ -33,44 +23,40 @@ const Home = () => {
|
||||||
const mapApiDataToCards = (data: any): CardData[] => {
|
const mapApiDataToCards = (data: any): CardData[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: 'Action Needed',
|
title: "Action Required",
|
||||||
count: data.actionNeeded || 0,
|
count: data.actionNeeded || 0,
|
||||||
icon: <FaTasks size={15} className="text-indigo-600" />,
|
icon: <FiFileText className="w-5 h-5 text-sky-500" />,
|
||||||
gradient: 'bg-gradient-to-br from-indigo-50 to-indigo-100',
|
iconBg: "bg-sky-100 border-sky-200",
|
||||||
shadowColor: 'shadow-indigo-200/50',
|
textColor: "text-sky-600",
|
||||||
iconBg: 'bg-indigo-100 border-indigo-200',
|
gradient: "bg-white",
|
||||||
textColor: 'text-indigo-700',
|
shadowColor: "shadow-sky-100",
|
||||||
trend: 'up'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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,
|
count: data.expiring || 0,
|
||||||
icon: <FaHourglassHalf size={15} className="text-rose-600" />,
|
icon: <FiAlertTriangle className="w-5 h-5 text-red-500" />,
|
||||||
gradient: 'bg-gradient-to-br from-rose-50 to-rose-100',
|
iconBg: "bg-red-100 border-red-200",
|
||||||
shadowColor: 'shadow-rose-200/50',
|
textColor: "text-red-600",
|
||||||
iconBg: 'bg-rose-100 border-rose-200',
|
gradient: "bg-white",
|
||||||
textColor: 'text-rose-700',
|
shadowColor: "shadow-red-100",
|
||||||
trend: 'up'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Completed',
|
title: "Completed",
|
||||||
count: data.completed || 0,
|
count: data.completed || 0,
|
||||||
icon: <FaCheckCircle size={15} className="text-teal-600" />,
|
icon: <FiCheckCircle className="w-5 h-5 text-green-500" />,
|
||||||
gradient: 'bg-gradient-to-br from-teal-50 to-teal-100',
|
iconBg: "bg-green-100 border-green-200",
|
||||||
shadowColor: 'shadow-teal-200/50',
|
textColor: "text-green-600",
|
||||||
iconBg: 'bg-teal-100 border-teal-200',
|
gradient: "bg-white",
|
||||||
textColor: 'text-teal-700',
|
shadowColor: "shadow-green-100",
|
||||||
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'
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
@ -84,14 +70,14 @@ const Home = () => {
|
||||||
const [cards, setCards] = useState<CardData[]>([]);
|
const [cards, setCards] = useState<CardData[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
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("activeAccountId", activeAccountId);
|
||||||
console.log("cards", cards);
|
console.log("cards", cards);
|
||||||
|
|
||||||
const getDashboardData = async () => {
|
const getDashboardData = async () => {
|
||||||
if (!activeAccountId) return;
|
if (!activeAccountId) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const payload = {
|
const payload = {
|
||||||
account: activeAccountId
|
account: activeAccountId
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { useNavigate } from 'react-router-dom';
|
||||||
import { RootState } from '../../store/store';
|
import { RootState } from '../../store/store';
|
||||||
import api from '../../config/api';
|
import api from '../../config/api';
|
||||||
|
|
||||||
|
|
||||||
const StartNow = () => {
|
const StartNow = () => {
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [files, setFiles] = useState<File[]>([]);
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
|
|
@ -51,7 +52,7 @@ const StartNow = () => {
|
||||||
<div
|
<div
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
id='start-now-upload-con'
|
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 */}
|
{/* Upload icon with subtle animation */}
|
||||||
<div className="p-2 text-blue-600 group-hover:text-blue-700 transition-colors">
|
<div className="p-2 text-blue-600 group-hover:text-blue-700 transition-colors">
|
||||||
<FiUpload className="text-lg" />
|
<FiUpload className="text-lg" />
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { Transaction, TransactionResponse } from '../transactions/Transactions';
|
||||||
import TransactionsTable from '../transactions/TransactionsTable';
|
import TransactionsTable from '../transactions/TransactionsTable';
|
||||||
import TransactionDataTable from '../accounts/TransactionDataTable';
|
import TransactionDataTable from '../accounts/TransactionDataTable';
|
||||||
import StartNow from './StartNow';
|
import StartNow from './StartNow';
|
||||||
|
import TemplateTable from '../templates/TemplateTable';
|
||||||
|
|
||||||
|
|
||||||
export interface StartCard {
|
export interface StartCard {
|
||||||
|
|
@ -81,31 +82,30 @@ const StartSection = () => {
|
||||||
// }, [])
|
// }, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="px-4 pt-2 pb-3 w-full">
|
<section className="px-3 py-2 w-full mt-2">
|
||||||
<h2 className="text-lg font-semibold text-gray-800 mb-2">Get Started</h2>
|
<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 />
|
<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) => (
|
{cards.map((card, index) => (
|
||||||
<div
|
<div
|
||||||
onClick={() => handleStartSectionClick(card.link)}
|
onClick={() => handleStartSectionClick(card.link)}
|
||||||
key={index}
|
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`}
|
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">
|
<div className="w-9 h-9 flex items-center justify-center border border-gray-200 rounded-md bg-white">
|
||||||
{card.icon}
|
{card.icon}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-base font-medium text-gray-900">{card.title}</h3>
|
<h3 className="text-sm font-medium text-gray-500">{card.title}</h3>
|
||||||
<p className="text-sm text-gray-700">{card.description}</p>
|
<p className="text-xs text-gray-700">{card.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom Area */}
|
|
||||||
<div className="flex items-center justify-between mt-2">
|
<div className="flex items-center justify-between mt-2">
|
||||||
{card.buttonText ? (
|
{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">
|
<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>
|
</div> */}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section className='pt-2 pb-3'>
|
{/* <section className='pt-2 pb-3'> */}
|
||||||
<h2 className="mt-6 text-lg font-semibold text-gray-800 mb-2">Recent Activities</h2>
|
{/* <h2 className="mt-6 text-sm font-semibold text-gray-800 mb-2">Recent Activities</h2> */}
|
||||||
<section>
|
<div className='grid grid-cols-12 gap-2 mt-5'>
|
||||||
|
<section className=' col-span-6'>
|
||||||
<TransactionDataTable />
|
<TransactionDataTable />
|
||||||
</section>
|
</section>
|
||||||
</section>
|
<section className=' col-span-6'>
|
||||||
|
<TemplateTable />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{/* </section> */}
|
||||||
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -448,7 +448,7 @@ const DocumentSubmissionOptions = () => {
|
||||||
</div>
|
</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 */}
|
{/* Left Panel - Configuration */}
|
||||||
<div className="space-y-6 ">
|
<div className="space-y-6 ">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import api from '../../../config/api'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
|
|
||||||
interface DocumentCardProps {
|
interface DocumentCardProps {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -157,7 +158,7 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted , tra
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<FaFileAlt className="text-gray-600 text-4xl mb-1" />
|
<FaFileAlt className="text-red-600 text-5xl mb-1" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ const ProcessStepOne = () => {
|
||||||
documents: false,
|
documents: false,
|
||||||
recipients: false,
|
recipients: false,
|
||||||
subject: false,
|
subject: false,
|
||||||
recipientEmailAndLastName:false
|
recipientEmailAndLastName: false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
|
if (transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
|
||||||
|
|
@ -161,7 +161,7 @@ const ProcessStepOne = () => {
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
// alert('All recipients must have both Email and Last Name filled.');
|
// alert('All recipients must have both Email and Last Name filled.');
|
||||||
formError.recipientEmailAndLastName = true;
|
formError.recipientEmailAndLastName = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!emailDtls.subject.length) {
|
if (!emailDtls.subject.length) {
|
||||||
|
|
@ -370,10 +370,10 @@ const ProcessStepOne = () => {
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* Top Navigation - Fixed */}
|
||||||
<div className='fixed top-0 w-full flex items-center justify-between bg-white shadow z-50 px-4 py-3'>
|
<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'>
|
<div className='flex items-center gap-2'>
|
||||||
<IoMdClose
|
<IoMdClose
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
|
|
@ -400,7 +400,6 @@ const ProcessStepOne = () => {
|
||||||
id="add-tags-btn-step-one"
|
id="add-tags-btn-step-one"
|
||||||
className='flex items-center gap-3'>
|
className='flex items-center gap-3'>
|
||||||
|
|
||||||
|
|
||||||
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && <button
|
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && <button
|
||||||
onClick={() => handleNextClick(false, true)}
|
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"
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content - Scrollable */}
|
||||||
<div className="relative max-w-4xl mx-auto pt-[80px] px-4 pb-10">
|
<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 */}
|
{/* Documents */}
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-lg font-medium mb-4 text-[var(--processStepOneTextHead)]">Documents</h2>
|
<h2 className="text-lg font-medium mb-4 pt-3 text-[var(--processStepOneTextHead)]">Documents</h2>
|
||||||
<SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} />
|
<SelectedDocuments transactionTemplateToggle={transactionTemplateToggle} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Recipients */}
|
{/* Recipients */}
|
||||||
<section className="mt-8">
|
<section className="mt-8">
|
||||||
|
<p className="border-t border-gray-200"></p>
|
||||||
{!isTamplate &&
|
{!isTamplate &&
|
||||||
transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES &&
|
transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES &&
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h2 className="text-lg font-medium mb-4 pt-6 border-t text-[var(--processStepOneTextHead)]">Signers</h2>
|
<h2 className=" text-lg font-medium mb-4 pt-6 text-[var(--processStepOneTextHead)]">Signers</h2>
|
||||||
<div className="flex items-center mr-2">
|
<div className="flex items-center mr-3">
|
||||||
<input
|
<input
|
||||||
checked={signType === SignTypeEnum.selfSign}
|
checked={signType === SignTypeEnum.selfSign}
|
||||||
onChange={(e) => handleSelfSignCb(e)}
|
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"
|
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>
|
<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>
|
||||||
</div>
|
}
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
(signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) &&
|
(signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) &&
|
||||||
<div className="flex flex-col gap-4 align-start">
|
<div className="flex flex-col gap-4 align-start">
|
||||||
|
|
||||||
<RecipientCards transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} editRecipient={editRecipient} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} editRecipientCurrentEmail={editRecipientCurrentEmail} />
|
<RecipientCards transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} editRecipient={editRecipient} setEditRecipientCurrentEmail={setEditRecipientCurrentEmail} editRecipientCurrentEmail={editRecipientCurrentEmail} />
|
||||||
{/* {signType !== "self-sign" && <RecipientAddCard />} */}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={topRef}
|
ref={topRef}
|
||||||
className={`relative w-full bg-[var(--mainViewBg)] ${globalLoader.isLoading ? 'pointer-events-none h-screen overflow-hidden' : ''}`}
|
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>
|
</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>
|
</div>
|
||||||
}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Email */}
|
{/* Message */}
|
||||||
<section className="mt-8">
|
<div className='flex flex-col'>
|
||||||
<h2 className="text-lg font-medium mb-4 pt-6 border-t text-[var(--processStepOneTextHead)]">Email</h2>
|
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Message for Signers</label>
|
||||||
|
<textarea
|
||||||
{/* Subject */}
|
rows={4}
|
||||||
<div className='flex flex-col mb-4'>
|
value={emailDtls.message}
|
||||||
<label className='text-sm font-medium text-[var(--greyTextCol)] mb-1'>Subject</label>
|
onChange={(e) => setEmailDtls({ ...emailDtls, message: e.target.value })}
|
||||||
<input
|
placeholder="Message"
|
||||||
type="text"
|
className="w-full px-3 py-2 rounded border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--primary)]"
|
||||||
required
|
/>
|
||||||
value={emailDtls.subject}
|
</div>
|
||||||
onChange={(e) => setEmailDtls({ ...emailDtls, subject: e.target.value })}
|
</section>
|
||||||
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>
|
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{globalLoader.isLoading && <Loader />}
|
{globalLoader.isLoading && <Loader />}
|
||||||
{showSelfSignWarnModal && <SelfSignWarnModal setShowSelfSignWarnModal={setShowSelfSignWarnModal} />}
|
{showSelfSignWarnModal && <SelfSignWarnModal setShowSelfSignWarnModal={setShowSelfSignWarnModal} />}
|
||||||
</div >
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -362,7 +362,7 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
onSubmit={handleRecipientAdd}
|
onSubmit={handleRecipientAdd}
|
||||||
id="add-recipient-step-one"
|
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 && (
|
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import DocumentCard from './DocumentCard';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice';
|
import { toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE } from '../../../store/slices/selectedRecipientsSlice';
|
||||||
|
|
||||||
|
|
||||||
const SelectedDocuments = ({transactionTemplateToggle}) => {
|
const SelectedDocuments = ({transactionTemplateToggle}) => {
|
||||||
// let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
// let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||||
|
|
||||||
|
|
@ -91,6 +92,15 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
|
||||||
id='upload-doc-step-one'
|
id='upload-doc-step-one'
|
||||||
className="flex flex-wrap gap-3">
|
className="flex flex-wrap gap-3">
|
||||||
{/* Uploaded documents */}
|
{/* 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
|
<ReactSortable
|
||||||
className="flex flex-wrap gap-3"
|
className="flex flex-wrap gap-3"
|
||||||
list={documents.map(doc => ({ ...doc }))}
|
list={documents.map(doc => ({ ...doc }))}
|
||||||
|
|
@ -105,13 +115,7 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
|
||||||
))}
|
))}
|
||||||
</ReactSortable>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ const AsideSection = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[16vw] p-4">
|
<div className="">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-lg font-semibold text-gray-800">{mainTabTitle}</h3>
|
<h3 className="text-lg font-semibold text-gray-800">{mainTabTitle}</h3>
|
||||||
<p className="text-sm text-gray-500 mt-1">Navigate through sections</p>
|
<p className="text-sm text-gray-500 mt-1">Navigate through sections</p>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -7,7 +7,8 @@ import { useNavigate } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { FaSpinner } from "react-icons/fa";
|
import { FaSpinner } from "react-icons/fa";
|
||||||
import LoaderUi from "../../helper/LoaderUi";
|
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 (
|
return (
|
||||||
<div className="p-3 text-sm">
|
<div className="p-3 text-sm">
|
||||||
|
|
@ -129,7 +67,7 @@ const Templates = () => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
onClick={handleAddTemplate}
|
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" />
|
<IoMdAdd className="text-white" />
|
||||||
<button
|
<button
|
||||||
className="text-white rounded text-sm hover:opacity-90">
|
className="text-white rounded text-sm hover:opacity-90">
|
||||||
|
|
@ -139,110 +77,8 @@ const Templates = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="max-h-[77vh] pb-16 overflow-y-auto relative no-scrollbar">
|
<section className="my-4 mx-3"></section>
|
||||||
<table className="min-w-full border border-gray-200 bg-white shadow rounded table-fixed">
|
<TemplateTable/>
|
||||||
<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>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,254 +1,321 @@
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
FiX,
|
FiX,
|
||||||
FiCheckCircle,
|
FiCheckCircle,
|
||||||
FiXCircle,
|
FiXCircle,
|
||||||
FiClock,
|
FiClock,
|
||||||
FiUser,
|
FiUser,
|
||||||
FiCalendar,
|
FiCalendar,
|
||||||
FiFileText,
|
FiFileText,
|
||||||
FiAlertTriangle,
|
FiAlertTriangle,
|
||||||
FiTruck,
|
FiTruck,
|
||||||
FiUserCheck,
|
FiUserCheck,
|
||||||
FiUserX,
|
FiUserX,
|
||||||
FiPackage,
|
FiPackage,
|
||||||
FiMapPin,
|
FiMapPin,
|
||||||
FiEye,
|
FiEye,
|
||||||
FiRotateCcw
|
FiRotateCcw,
|
||||||
|
FiSend,
|
||||||
|
FiMail,
|
||||||
|
FiActivity,
|
||||||
|
FiRefreshCw,
|
||||||
|
FiLock,
|
||||||
|
FiUnlock,
|
||||||
|
FiUserPlus,
|
||||||
|
FiEdit
|
||||||
} from "react-icons/fi";
|
} 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 {
|
interface AuditModalProps {
|
||||||
closeModal: () => void;
|
closeModal: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuditModal: React.FC<AuditModalProps> = ({ closeModal }) => {
|
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 = [
|
if (!accountId || !transactionId) {
|
||||||
{
|
window.location.href = "/";
|
||||||
id: 1,
|
return null;
|
||||||
title: "The Accrual status has been successfully changed to Approved.",
|
|
||||||
author: "Alexander Turner",
|
|
||||||
role: "Analyst Perspective on Transaction",
|
|
||||||
time: "34m ago",
|
|
||||||
fromStatus: "Ready to Review",
|
|
||||||
toStatus: "Approved",
|
|
||||||
icon: <FiCheckCircle className="w-4 h-4" />,
|
|
||||||
statusColor: "text-green-600"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "The Accrual has been updated to reflect a status of Reject.",
|
|
||||||
author: "Alexander Turner",
|
|
||||||
time: "3h ago",
|
|
||||||
fromStatus: "Ready to Review",
|
|
||||||
toStatus: "Rejected",
|
|
||||||
icon: <FiXCircle className="w-4 h-4" />,
|
|
||||||
statusColor: "text-red-600"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "The Accrual has been revised for accuracy.",
|
|
||||||
author: "Alex Turner",
|
|
||||||
role: "Manager View Transaction State",
|
|
||||||
time: "7d ago",
|
|
||||||
fromStatus: "Rejected",
|
|
||||||
toStatus: "Reviewed",
|
|
||||||
icon: <FiRotateCcw className="w-4 h-4" />,
|
|
||||||
statusColor: "text-yellow-600"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "The Accrual state has been updated from 'ready' to 'under review'.",
|
|
||||||
author: "Alexander Turner",
|
|
||||||
role: "Analyst Perspective on Transaction",
|
|
||||||
time: "34m ago",
|
|
||||||
fromStatus: "Ready to Review",
|
|
||||||
toStatus: "Reviewed",
|
|
||||||
icon: <FiEye className="w-4 h-4" />,
|
|
||||||
statusColor: "text-blue-600"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: "The Accrual has been updated from rejected to reviewed.",
|
|
||||||
author: "Alex Turner",
|
|
||||||
role: "Analyst View Transaction",
|
|
||||||
time: "34m ago",
|
|
||||||
fromStatus: "Rejected",
|
|
||||||
toStatus: "Reviewed",
|
|
||||||
icon: <FiRotateCcw className="w-4 h-4" />,
|
|
||||||
statusColor: "text-blue-600"
|
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const auditTrail = [
|
let [auditStatus, setAuditStatus] = useState([])
|
||||||
{
|
let [loading, setLoading] = useState(false)
|
||||||
id: 1,
|
const getAuditDetails = async () => {
|
||||||
title: "Batch 11317 Pickup Request Created",
|
setLoading(true)
|
||||||
author: "Sandra Darlington",
|
try {
|
||||||
location: "Lagos General Hospital Gbagada",
|
let res = await api.get(`/api/accounts/${accountId}/transactions/${transactionId}/audit?lang=${userDetails.language || ''}`)
|
||||||
time: "11 Aug, 2023 09:23 AM",
|
if (res?.status === 200) {
|
||||||
icon: <FiPackage className="w-4 h-4" />,
|
// console.log(res?.data?.events)
|
||||||
type: "created"
|
let auditResponse = res?.data?.events
|
||||||
},
|
setAuditStatus(auditResponse)
|
||||||
{
|
}
|
||||||
id: 2,
|
} catch (error) {
|
||||||
title: "Batch 11317 Pickup Request",
|
console.log(error)
|
||||||
author: "Oladejo Israel",
|
} finally {
|
||||||
location: "Institute of Human Virology H3Africa BioRepository",
|
setLoading(false)
|
||||||
time: "12 Aug, 2023 11:19 AM",
|
}
|
||||||
status: "Approved",
|
|
||||||
icon: <FiCheckCircle className="w-4 h-4" />,
|
|
||||||
type: "approved"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "Batch 11317 was Initiated",
|
|
||||||
author: "Sandra Darlington",
|
|
||||||
location: "Lagos General Hospital Gbagada",
|
|
||||||
time: "12 Aug, 2023 11:19 AM",
|
|
||||||
icon: <FiFileText className="w-4 h-4" />,
|
|
||||||
type: "initiated"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "Sandra Darlington Assigned batch 11317",
|
|
||||||
assignee: "Yusuf Ahmed",
|
|
||||||
time: "12 Aug, 2023 11:20 AM",
|
|
||||||
icon: <FiUserCheck className="w-4 h-4" />,
|
|
||||||
type: "assigned"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: "Batch 11317 was rejected by Yusuf Ahmed",
|
|
||||||
reason: "due to faulty vehicle",
|
|
||||||
time: "12 Aug, 2023 11:20 AM",
|
|
||||||
icon: <FiXCircle className="w-4 h-4" />,
|
|
||||||
type: "rejected"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
title: "Sandra Darlington Assigned batch 11317",
|
|
||||||
assignee: "Kolado Jolade",
|
|
||||||
time: "12 Aug, 2023 11:20 AM",
|
|
||||||
icon: <FiUserCheck className="w-4 h-4" />,
|
|
||||||
type: "assigned"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
title: "Logger Synced",
|
|
||||||
deviceId: "ID: LGR-0421",
|
|
||||||
status: "95% remaining",
|
|
||||||
time: "12 Aug, 2023 11:25 AM",
|
|
||||||
icon: <FiCheckCircle className="w-4 h-4" />,
|
|
||||||
type: "synced"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
title: "Logger Disconnected",
|
|
||||||
deviceId: "ID: LGR-0421",
|
|
||||||
time: "12 Aug, 2023 11:25 AM",
|
|
||||||
icon: <FiAlertTriangle className="w-4 h-4" />,
|
|
||||||
type: "disconnected"
|
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
|
||||||
const getStatusBadge = (status: string) => {
|
useEffect(() => {
|
||||||
const statusClasses = {
|
if (accountId && transactionId) {
|
||||||
'Approved': 'bg-green-100 text-green-800',
|
getAuditDetails()
|
||||||
'Rejected': 'bg-red-100 text-red-800',
|
}
|
||||||
'Reviewed': 'bg-yellow-100 text-yellow-800',
|
}, [accountId, transactionId])
|
||||||
'Ready to Review': 'bg-blue-100 text-blue-800'
|
|
||||||
|
|
||||||
|
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 getActionColor = (action: string, status: string) => {
|
||||||
const colors = {
|
if (status === 'COMPLETED') return 'text-green-600';
|
||||||
'created': 'text-blue-600',
|
if (status === 'FAILED' || status === 'REJECTED') return 'text-red-600';
|
||||||
'approved': 'text-green-600',
|
if (status === 'PENDING') return 'text-yellow-600';
|
||||||
'initiated': 'text-purple-600',
|
|
||||||
'assigned': 'text-indigo-600',
|
const colors = {
|
||||||
'rejected': 'text-red-600',
|
'SENT': 'text-blue-600',
|
||||||
'synced': 'text-green-600',
|
'INVITED': 'text-indigo-600',
|
||||||
'disconnected': 'text-orange-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 (
|
const getStatusBadge = (status: string) => {
|
||||||
<div className="fixed inset-0 flex items-center justify-center backdrop-blur-sm bg-white/10 z-50">
|
const statusClasses = {
|
||||||
<div className="bg-white rounded-md shadow-md w-full max-w-[75vw] max-h-[95vh] min-h-[85vh] flex flex-col">
|
'SENT': 'bg-blue-100 text-blue-800',
|
||||||
{/* Header */}
|
'COMPLETED': 'bg-green-100 text-green-800',
|
||||||
<div className="flex items-center justify-between p-3 border-b border-gray-200 bg-[var(--primary)] rounded-t-md">
|
'FAILED': 'bg-red-100 text-red-800',
|
||||||
<div className="flex items-center">
|
'PENDING': 'bg-yellow-100 text-yellow-800',
|
||||||
<h2 className="text-xl font-semibold text-white">
|
'VIEWED': 'bg-purple-100 text-purple-800',
|
||||||
Audit Status
|
'REJECTED': 'bg-red-100 text-red-800'
|
||||||
</h2>
|
};
|
||||||
</div>
|
return statusClasses[status as keyof typeof statusClasses] || 'bg-gray-100 text-gray-800';
|
||||||
<button
|
};
|
||||||
onClick={closeModal}
|
|
||||||
className="text-white cursor-pointer transition-colors"
|
|
||||||
>
|
|
||||||
<FiX className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Content */}
|
const formatTimestamp = (timestamp: string) => {
|
||||||
<div className="flex-1 overflow-y-auto p-4">
|
try {
|
||||||
|
const date = new Date(timestamp);
|
||||||
<div className="space-y-1.5">
|
if (isNaN(date.getTime())) {
|
||||||
<div className="text-sm text-gray-500 font-medium mb-2">TODAY</div>
|
return timestamp;
|
||||||
|
}
|
||||||
{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">
|
const now = new Date();
|
||||||
<div className={`${item.statusColor} mt-1`}>
|
const diffMs = now.getTime() - date.getTime();
|
||||||
{item.icon}
|
const diffMins = Math.floor(diffMs / (1000 * 60));
|
||||||
</div>
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||||
<div className="flex-1">
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||||
<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">
|
if (diffMins < 60) {
|
||||||
<span>By {item.author}</span>
|
return `${diffMins}m ago`;
|
||||||
{item.role && (
|
} else if (diffHours < 24) {
|
||||||
<>
|
return `${diffHours}h ago`;
|
||||||
<span>•</span>
|
} else if (diffDays < 7) {
|
||||||
<span className="text-blue-600 text-xs">({item.role})</span>
|
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>
|
||||||
<div className="flex items-center space-x-3">
|
<button
|
||||||
<span className="text-xs text-gray-500">{item.time}</span>
|
onClick={closeModal}
|
||||||
<div className="flex items-center space-x-2">
|
className="text-white cursor-pointer transition-colors hover:text-gray-200"
|
||||||
<span className={`px-2 py-0.5 text-xs rounded-full ${getStatusBadge(item.fromStatus)}`}>
|
>
|
||||||
{item.fromStatus}
|
<FiX className="w-5 h-5" />
|
||||||
</span>
|
</button>
|
||||||
<span className="text-gray-400">→</span>
|
|
||||||
<span className={`px-2 py-0.5 text-xs rounded-full ${getStatusBadge(item.toStatus)}`}>
|
|
||||||
{item.toStatus}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
|
|
||||||
</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;
|
export default AuditModal;
|
||||||
|
|
@ -62,7 +62,7 @@ const TransactionView = () => {
|
||||||
: "?auditStatus";
|
: "?auditStatus";
|
||||||
|
|
||||||
window.history.pushState({}, "", pathname + newSearch);
|
window.history.pushState({}, "", pathname + newSearch);
|
||||||
setIsModalOpen(true); // ✅ update state immediately
|
setIsModalOpen(true); // update state immediately
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
|
|
@ -72,7 +72,7 @@ const TransactionView = () => {
|
||||||
.replace(/\?$/, "");
|
.replace(/\?$/, "");
|
||||||
|
|
||||||
window.history.pushState({}, "", location.pathname + search);
|
window.history.pushState({}, "", location.pathname + search);
|
||||||
setIsModalOpen(false); // ✅ update state immediately
|
setIsModalOpen(false); // update state immediately
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(transactionDetails, "line 37");
|
console.log(transactionDetails, "line 37");
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary: #3D74B6;
|
--primary: #005AB5;
|
||||||
--primaryLight: #ffffff;
|
--primaryLight: #ffffff;
|
||||||
--primaryLight2: #6a92c2;
|
--primaryLight2: #6a92c2;
|
||||||
--primaryLight3: #dae9de;
|
--primaryLight3: #dae9de;
|
||||||
--dashBoardBg: #ffffff;
|
--dashBoardBg: #ffffff;
|
||||||
--mainViewBg: #edeaeb;
|
--mainViewBg: #F8F9FC;
|
||||||
--highlightBg1: #edeaeb;
|
--highlightBg1: #edeaeb;
|
||||||
--menuTextCol: #98a0a8;
|
--menuTextCol: #98a0a8;
|
||||||
--menuTextColActive: #3D74B6;
|
--menuTextColActive: #005AB5;
|
||||||
--menuBgColActive: #edeaeb;
|
--menuBgColActive: #FFFFFF;
|
||||||
--menuTextHoverCol: #e7e7e7;
|
--menuTextHoverCol: #e7e7e7;
|
||||||
|
|
||||||
--greyTextCol: #98a0a8;
|
--greyTextCol: #98a0a8;
|
||||||
|
|
@ -46,6 +46,17 @@
|
||||||
|
|
||||||
--modalSubmitBG: #1919d7;
|
--modalSubmitBG: #1919d7;
|
||||||
--modalSubmitBGHover: #395ee1;
|
--modalSubmitBGHover: #395ee1;
|
||||||
|
|
||||||
|
/* Figma Design */
|
||||||
|
--sidebarBg: #005AB5;
|
||||||
|
--sidebarDropDownBg : #003C78;
|
||||||
|
--sidebarMenuCol : #FFFFFF;
|
||||||
|
--documentUploadBg :#FFFFFF;
|
||||||
|
--mainPrimaryBtn : #077DEC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue