Compare commits

..

No commits in common. "8bb43d6ee57d262450d1d7e094d0b9968b2760ba" and "73273865f47c693de3361d57572ed1a10a1f029f" have entirely different histories.

36 changed files with 1053 additions and 2173 deletions

View File

@ -47,7 +47,6 @@ const App = () => {
<Route element={<AccountTransactionWrapper />}> <Route element={<AccountTransactionWrapper />}>
<Route path="/accounts" element={<Accounts />} /> <Route path="/accounts" element={<Accounts />} />
<Route path="/accounts/:accountId/transactions" element={<Account />} /> <Route path="/accounts/:accountId/transactions" element={<Account />} />
<Route path="/transactions" element={<Account />} />
<Route path="/accounts/:accountId/transactions/:transactionId" element={<TransactionView />} /> <Route path="/accounts/:accountId/transactions/:transactionId" element={<TransactionView />} />
<Route path="/transactions/:filterParam" element={<Transactions />} /> <Route path="/transactions/:filterParam" element={<Transactions />} />
</Route> </Route>

View File

@ -7,17 +7,6 @@ import { FaCheck } 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 {
FaCheckCircle,
FaClock,
FaHourglassHalf,
FaTasks,
FaChevronDown,
FaPlus,
FaUserPlus,
FaFilter,
FaCalendarAlt
} from 'react-icons/fa';
interface Transaction { interface Transaction {
id: number; id: number;
@ -32,152 +21,31 @@ const formatDate = (timestamp: number) => {
return new Date(timestamp * 1000).toLocaleString(); return new Date(timestamp * 1000).toLocaleString();
}; };
const Account = () => { const Account = () => {
const mapApiDataToCards = (data: any): CardData[] => { const { accountId } = useParams<{ accountId: string }>();
return [
{
title: 'Action Needed',
count: data.actionNeeded || 0,
icon: <FaTasks size={18} className="text-indigo-600" />,
gradient: 'bg-gradient-to-br from-indigo-50 to-indigo-100',
shadowColor: 'shadow-indigo-200/50',
iconBg: 'bg-indigo-100 border-indigo-200',
textColor: 'text-indigo-700',
trend: 'up'
},
{
title: 'Expiring Soon',
count: data.expiring || 0,
icon: <FaHourglassHalf size={18} className="text-rose-600" />,
gradient: 'bg-gradient-to-br from-rose-50 to-rose-100',
shadowColor: 'shadow-rose-200/50',
iconBg: 'bg-rose-100 border-rose-200',
textColor: 'text-rose-700',
trend: 'up'
},
{
title: 'Completed',
count: data.completed || 0,
icon: <FaCheckCircle size={18} className="text-teal-600" />,
gradient: 'bg-gradient-to-br from-teal-50 to-teal-100',
shadowColor: 'shadow-teal-200/50',
iconBg: 'bg-teal-100 border-teal-200',
textColor: 'text-teal-700',
trend: 'up'
},
{
title: 'Waiting for Others',
count: data.waitingForOthers || 0,
icon: <FaClock size={18} 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'
},
{
title: 'declined',
count: data.declined || 0,
icon: <FaClock size={18} className="text-purple-600" />,
gradient: 'bg-gradient-to-br from-purple-50 to-purple-100',
shadowColor: 'shadow-purple-200/50',
iconBg: 'bg-purple-100 border-purple-200',
textColor: 'text-purple-700',
trend: 'down'
},
{
title: 'cancelled',
count: data.cancelled || 0,
icon: <FaClock size={18} className="text-green-600" />,
gradient: 'bg-gradient-to-br from-green-50 to-green-100',
shadowColor: 'shadow-green-200/50',
iconBg: 'bg-green-100 border-green-200',
textColor: 'text-green-700',
trend: 'down'
},
{
title: 'draft',
count: data.draft || 0,
icon: <FaClock size={18} className="text-stone-600" />,
gradient: 'bg-gradient-to-br from-stone-50 to-stone-100',
shadowColor: 'shadow-stone-200/50',
iconBg: 'bg-stone-100 border-stone-200',
textColor: 'text-stone-700',
trend: 'down'
}
// Add more cards here if needed
];
};
// const { accountId } = useParams<{ accountId: string }>();
const [transactions, setTransactions] = useState<Transaction[]>([]); const [transactions, setTransactions] = useState<Transaction[]>([]);
const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]); const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]);
const [accountsData, setAccountsData] = useState([]) const [accountsData, setAccountsData] = useState([])
const [inviteLoading, setInviteLoading] = useState(false); const [inviteLoading, setInviteLoading] = useState(false);
const [cards, setCards] = useState<CardData[]>([]);
const [accountID, setAccountID] = useState<number>()
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const getUserDetails = async () => {
try {
let res = await api.get('/api/user/details')
let accountIDData = res?.data?.account?.id
setAccountID(accountIDData)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getUserDetails()
}, [])
const getDashboardData = async () => {
setIsLoading(true)
const payload = {
account: accountID
};
try {
const res = await api.get('/api/dashboard', { params: payload });
console.log("Dashboard Data:", res.data);
const cardDataArray = mapApiDataToCards(res.data);
setCards(cardDataArray);
} catch (error) {
console.error("Error fetching dashboard data:", error);
} finally {
setIsLoading(false)
}
};
useEffect(() => {
if (accountID) {
getDashboardData()
fetchTransactions();
}
}, [accountID]);
const handleOpenModal = () => { const handleOpenModal = () => {
setIsModalOpen(true); setIsModalOpen(true);
}; };
const fetchTransactions = async () => { const fetchTransactions = async () => {
console.log("fetchTransactions")
try { try {
const response = await api(`/api/accounts/${accountID}/transactions`); const response = await api(`/api/accounts/${accountId}/transactions`);
setTransactions(response.data || []); setTransactions(response.data || []);
} catch (error) { } catch (error) {
console.error("Error fetching transactions:", error); console.error("Error fetching transactions:", error);
} }
}; };
// useEffect(() => { useEffect(() => {
// if (accountID) fetchTransactions(); if (accountId) fetchTransactions();
// }, [accountID]); }, [accountId]);
const getAccountDetails = async () => { const getAccountDetails = async () => {
try { try {
@ -197,7 +65,7 @@ const Account = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const handleTransactionClick = (id: number) => { const handleTransactionClick = (id: number) => {
navigate(`/accounts/${accountID}/transactions/${id}`); navigate(`/accounts/${accountId}/transactions/${id}`);
}; };
const handleInvite = async (data: { i_accountName: number; email: string; role: string }) => { const handleInvite = async (data: { i_accountName: number; email: string; role: string }) => {
@ -230,12 +98,9 @@ const Account = () => {
} }
}; };
return ( return (
<> <>
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards} <Cards type="transactions" onOpenModal={handleOpenModal} />
isLoading={isLoading}/>
{transactions.length > 0 && ( {transactions.length > 0 && (
<div className="overflow-x-auto py-2"> <div className="overflow-x-auto py-2">
<table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm"> <table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm">

View File

@ -1,15 +1,12 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
// import { useSelector } from 'react-redux'; // import { useSelector } from 'react-redux';
import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Link, useLocation, useNavigate } from 'react-router-dom';
import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } from "react-icons/fa"; import { FaSignOutAlt, FaUser } from "react-icons/fa";
import api from '../../config/api'; import api from '../../config/api';
// import { FaUser, FaSignOutAlt, FaBuilding, FaChevronDown, FaCheck } from 'react-icons/fa';
// import { RootState } from '../../store/store'; // import { RootState } from '../../store/store';
// import { UserState } from '../../store/slices/userSlice'; // import { UserState } from '../../store/slices/userSlice';
interface AccountNavProps { interface AccountNavProps {
accountName?: string | null; accountName?: string | null;
transactionName?: string | null; transactionName?: string | null;
@ -31,20 +28,9 @@ interface UserData {
account: Account; account: Account;
} }
interface AccountDataArray {
accountId: number, accountName: string, role: string, status: boolean
}
const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transactionName = null }) => { const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transactionName = null }) => {
// const user: UserState = useSelector((state: RootState) => state.user); // const user: UserState = useSelector((state: RootState) => state.user);
const [open, setOpen] = useState(false);
const [accountsOpen, setAccountsOpen] = useState(false);
const [loadingAccounts, setLoadingAccounts] = useState(false);
const [accounts, setAccounts] = useState<AccountDataArray[]>([]);
const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false); // Track if modal was closed
const dropdownRef = useRef(null);
const [user, setUser] = useState<UserData>({ const [user, setUser] = useState<UserData>({
id: "", id: "",
firstName: "", firstName: "",
@ -58,62 +44,6 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
}, },
}); });
const getAccountDetails = async () => {
try {
let res = await api.get('/api/user/accounts')
let items = res?.data
console.log('items', items)
// Extract only the fields you want
const cleanedData: AccountDataArray[] = items.map((item: any) => ({
accountId: item.accountId,
accountName: item.accountName,
role: item.role,
status: item.status,
}));
setAccounts(cleanedData);
} catch (error) {
console.log(error)
}
}
const handleAccountsClick = async () => {
setAccountsOpen(!accountsOpen);
if (!accountsOpen) {
setLoadingAccounts(true);
// Fetch API if:
// 1. No accounts data exists (first time), OR
// 2. Modal was previously closed (need fresh data)
if (accounts.length === 0 || hasModalBeenClosed) {
await getAccountDetails();
setHasModalBeenClosed(false); // Reset the flag after fetching
}
setLoadingAccounts(false);
}
};
const handleAccountSwitch = (account) => {
setUser(prev => ({
...prev,
account: { name: account.name, id: account.id }
}));
// Update accounts active status
setAccounts(prevAccounts =>
prevAccounts.map(acc => ({
...acc,
isActive: acc.id === account.id
}))
);
setAccountsOpen(false);
setOpen(false);
};
const navigate = useNavigate() const navigate = useNavigate()
const getUserDetails = async () => { const getUserDetails = async () => {
@ -144,24 +74,20 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
// Add more routes here if needed // Add more routes here if needed
}; };
// const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
// const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
// Close dropdown when clicked outside // Close dropdown when clicked outside
useEffect(() => { useEffect(() => {
function handleClickOutside(event: MouseEvent) { function handleClickOutside(event: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
if (open) {
setHasModalBeenClosed(true); // Mark modal as closed when clicking outside
}
setOpen(false); setOpen(false);
setAccountsOpen(false); // Also close accounts dropdown
} }
} }
document.addEventListener("mousedown", handleClickOutside); document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside);
}, [open]); }, []);
const handleLogout = async() => { const handleLogout = async() => {
try { try {
@ -175,37 +101,12 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
} }
}; };
// Handle main dropdown toggle
const handleMainDropdownToggle = () => {
if (open) {
setHasModalBeenClosed(true); // Mark as closed when manually closing
}
setOpen(!open);
if (!open) {
setAccountsOpen(false); // Close accounts dropdown when opening main dropdown
}
};
const formatSegment = (segment: string) => const formatSegment = (segment: string) =>
segment segment
.replace(/[-_]/g, " ") .replace(/[-_]/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase()); .replace(/\b\w/g, (char) => char.toUpperCase());
const ShimmerRow = () => (
<div className="animate-pulse px-4 py-3 border-b border-gray-100 last:border-b-0">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-gray-200 rounded-full"></div>
<div className="flex-1 space-y-2">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
</div>
<div className="w-16 h-6 bg-gray-200 rounded"></div>
</div>
</div>
);
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-2 flex justify-between items-center bg-white border-b rounded-sm 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">
@ -236,6 +137,10 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
)} )}
</nav> </nav>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{/* User Dropdown */} {/* User Dropdown */}
@ -243,7 +148,7 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
{/* Avatar Button */} {/* Avatar Button */}
<button <button
className="flex items-center space-x-2 p-1 cursor-pointer rounded-lg transition-colors" className="flex items-center space-x-2 p-1 cursor-pointer rounded-lg transition-colors"
onClick={handleMainDropdownToggle} onClick={() => setOpen(!open)}
aria-expanded={open} aria-expanded={open}
aria-haspopup="true" aria-haspopup="true"
> >
@ -284,63 +189,6 @@ const AccountNav: React.FC<AccountNavProps> = ({ accountName = null, transaction
</div> </div>
</div> </div>
{/* Account List Section */}
<div className="border-b border-gray-100">
<button
onClick={handleAccountsClick}
className="w-full flex items-center justify-between px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer"
>
<div className="flex items-center">
<FaBuilding className="mr-3 text-gray-500" />
<span>Switch Account</span>
</div>
<FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} />
</button>
{/* Accounts Dropdown */}
{accountsOpen && (
<div className="bg-gray-50 max-h-64 overflow-y-auto">
{loadingAccounts ? (
<div className="space-y-0">
<ShimmerRow />
<ShimmerRow />
</div>
) : (
<div className="divide-y divide-gray-200">
{accounts.length > 0 ? (accounts?.map((account) => (
<button
key={account.accountId}
// onClick={() => handleAccountSwitch(account)}
className="w-full px-4 py-3 hover:bg-white transition-colors cursor-pointer text-left"
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-gray-200 to-gray-400 flex items-center justify-center text-white font-semibold text-xs">
{account.accountName.slice(0, 2).toUpperCase()}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2">
<span className="font-medium text-gray-900 text-sm">{account.accountName}</span>
{account.status && (
<FaCheck className="text-green-500 text-xs" />
)}
</div>
<div className="text-xs text-gray-600 truncate">{account.company}</div>
</div>
</div>
<div className="text-xs text-gray-500 bg-gray-200 px-2 py-1 rounded">
{account.role}
</div>
</div>
</button>
))) : <span className='text-gray-400 font-medium text-[12px] flex justify-center p-3'>No Account...</span>}
</div>
)}
</div>
)}
</div>
{/* Logout Section */} {/* Logout Section */}
<div className="border-t border-gray-100 "> <div className="border-t border-gray-100 ">
<button <button

View File

@ -34,7 +34,6 @@ const getStatusLabel = (
}; };
const Accounts = () => { const Accounts = () => {
const [allAccounts, setAllAccounts] = useState<AccountData[]>([]); const [allAccounts, setAllAccounts] = useState<AccountData[]>([]);
const [selectedAccounts, setSelectedAccounts] = useState<number[]>([]); const [selectedAccounts, setSelectedAccounts] = useState<number[]>([]);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@ -123,7 +122,7 @@ const Accounts = () => {
return ( return (
<> <>
{/* <Cards type="account" onOpenModal={handleOpenModal} /> */} <Cards type="account" onOpenModal={handleOpenModal} />
{allAccounts.length > 0 && ( {allAccounts.length > 0 && (
<div className="overflow-x-auto py-2"> <div className="overflow-x-auto py-2">
<table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm"> <table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm">

View File

@ -109,14 +109,14 @@ function LoginForm() {
console.log(res.status, "Response Status Line 42 LoginForm.tsx", res); console.log(res.status, "Response Status Line 42 LoginForm.tsx", res);
if (res.status === 200) { if (res.status === 200) {
toast.dismiss(); toast.dismiss();
// console.log('User Logined Successfully Line 45 LoginForm.tsx headerCookie:', res.headers.authorization); console.log('User Logined Successfully Line 45 LoginForm.tsx headerCookie:', res.headers.authorization);
// Cookies.set("authorization", res.headers.authorization, { // Cookies.set("authorization", res.headers.authorization, {
// path: "/", // path: "/",
// secure: true, // Required for HTTPS // secure: true, // Required for HTTPS
// sameSite: "Lax", // Required for cross-origin requests // sameSite: "Lax", // Required for cross-origin requests
// }); // });
// dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization })); dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization }));
setTimeout(() => navigate('/'), 1000); // Redirect after 1 second setTimeout(() => navigate('/'), 1000); // Redirect after 1 second
toast.success("Login Successful", { toast.success("Login Successful", {
position: "top-center", position: "top-center",

View File

@ -65,7 +65,7 @@ function RegisterForm() {
console.log(res, "line 58"); console.log(res, "line 58");
toast.dismiss(); toast.dismiss();
// dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization })); // dispatch(updateUser({ ...res.data, authorized: true, accessToken: res.headers.authorization }));
// dispatch(updateUser({ ...res.data, authorized: true })); dispatch(updateUser({ ...res.data, authorized: true }));
navigate('/') navigate('/')
} }
}) })

View File

@ -258,8 +258,6 @@ const SignDocument = () => {
} }
console.log(tagJson, "Line 261")
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
const submitData: TagsSubmitDataObj = { const submitData: TagsSubmitDataObj = {
@ -617,10 +615,9 @@ const SignDocument = () => {
> >
{ {
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => { selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc, indxDoc) => {
console.log(tagJson, "tagJsontagJsontagJson")
return ( return (
<div key={doc.documentId} className='flex flex-col'> <div key={indxDoc} className='flex flex-col'>
<div className='py-2 text-sm'> <div className='py-2 text-sm'>
<p className='text-center'>{doc.name}</p> <p className='text-center'>{doc.name}</p>
</div> </div>
@ -629,13 +626,12 @@ const SignDocument = () => {
{ {
tagJson && tagJson.length > 0 && doc.images && doc.images.length > 0 && doc.images.map((page, indx) => { tagJson && tagJson.length > 0 && doc.images && doc.images.length > 0 && doc.images.map((page, indx) => {
const key = `${doc.documentId}_${page.index}`; const key = `${doc.documentId}_${page.index}`;
const currDocTags = tagJson.filter((val) => val.pageIndex === page.index) const currDocTags = tagJson.filter((val, indx) => val.documentIndex === indxDoc)
console.log(currDocTags, tagJson, page, doc, "innnnnnnnnnnnn")
return ( return (
<div <div
key={indx} key={indx}
id={doc.documentId + indx} id={doc.documentId + indx}
// onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)} // onDrop={(e) => handleDrop(e, doc.documentId, indxDoc, page.index)}
// onDragOver={handleDragOver} // onDragOver={handleDragOver}
className='drop-page-in shadow-md relative' className='drop-page-in shadow-md relative'
> >
@ -644,7 +640,7 @@ const SignDocument = () => {
{currDocTags.map((item, index) => { {currDocTags.map((item, index) => {
if (item.pageIndex !== page.index) return; if (item.pageNumber !== page.index) return;
return ( return (
<div <div
tabIndex={1} tabIndex={1}
@ -654,11 +650,11 @@ const SignDocument = () => {
className="absolute rounded dropped-item text-sm" className="absolute rounded dropped-item text-sm"
// onClick={() => handleTagClick(key, index)} // onClick={() => handleTagClick(key, index)}
style={{ style={{
left: `${item.dimensions.left}px`, left: `${item.dimension.left}px`,
top: `${item.dimensions.top}px`, top: `${item.dimension.top}px`,
width: `${item.dimensions.width}px`, width: `${item.dimension.width}px`,
height: `${item.dimensions.height}px`, height: `${item.dimension.height}px`,
border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`, border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentIndex === item.documentIndex && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`,
// display: `${hideTags ? 'none' : 'block'}` // display: `${hideTags ? 'none' : 'block'}`
}} }}
> >
@ -669,7 +665,7 @@ const SignDocument = () => {
id={item.id} id={item.id}
tabIndex={1} tabIndex={1}
className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-none" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`} className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-none" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimension.height + "px" }}
> >
{/* <input {/* <input
@ -681,7 +677,7 @@ const SignDocument = () => {
{ {
item.subType === "SINGLE_LINE" && <p item.subType === "SINGLE_LINE" && <p
// name={item.id} // name={item.id}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimension.height + "px" }}
onInput={(e) => { onInput={(e) => {
const val = e.currentTarget.textContent; const val = e.currentTarget.textContent;
handleTagUpdate(index, val as string); handleTagUpdate(index, val as string);
@ -738,11 +734,11 @@ const SignDocument = () => {
<div <div
id={item.id} id={item.id}
className={`h-full w-full rounded flex justify-center items-center ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} className={`h-full w-full rounded flex justify-center items-center ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimension.height + "px" }}
> >
<input <input
name={item.id} name={item.id}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimension.height + "px" }}
className="w-full px-2 outline-none" className="w-full px-2 outline-none"
type="checkbox" /> type="checkbox" />
</div>} </div>}
@ -756,7 +752,7 @@ const SignDocument = () => {
id={item.id} id={item.id}
onClick={() => handleSignClick(index)} onClick={() => handleSignClick(index)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none bg-transparent" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none bg-transparent" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimension.height + "px" }}
> >
{/* <input type="checkbox" className="" /> */} {/* <input type="checkbox" className="" /> */}
{ {
@ -767,8 +763,8 @@ const SignDocument = () => {
: :
<img <img
style={{ style={{
width: `${item.dimensions.width}px`, width: `${item.dimension.width}px`,
height: `${item.dimensions.height}px`, height: `${item.dimension.height}px`,
}} }}
src={saved.signature as string} /> src={saved.signature as string} />
} }
@ -783,15 +779,15 @@ const SignDocument = () => {
id={item.id} id={item.id}
onClick={() => handleSignClick(index)} onClick={() => handleSignClick(index)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimension.height + "px" }}
> >
{((!transparentTags && !hideTags) && !(saved.save && (index in signConfObj) && signConfObj[index].accepted)) ? {((!transparentTags && !hideTags) && !(saved.save && (index in signConfObj) && signConfObj[index].accepted)) ?
<TbSignature className="text-lg text-slate-600" /> <TbSignature className="text-lg text-slate-600" />
: :
<img <img
style={{ style={{
width: `${item.dimensions.width}px`, width: `${item.dimension.width}px`,
height: `${item.dimensions.height}px`, height: `${item.dimension.height}px`,
}} }}
className="w-[100%] h-full" className="w-[100%] h-full"
src={saved.initial as string} /> src={saved.initial as string} />
@ -807,7 +803,7 @@ const SignDocument = () => {
id={item.id} id={item.id}
onClick={() => handleStampClick(index)} onClick={() => handleStampClick(index)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimension.height + "px" }}
> >
{!transparentTags && !hideTags ? {!transparentTags && !hideTags ?
<FaStamp className="text-lg text-slate-600" /> <FaStamp className="text-lg text-slate-600" />
@ -824,7 +820,7 @@ const SignDocument = () => {
id={item.id} id={item.id}
// onClick={() => handleSignClick(index)} // onClick={() => handleSignClick(index)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`} className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }} // style={{ height: item.dimension.height + "px" }}
> >
{!transparentTags && !hideTags ? {!transparentTags && !hideTags ?
<FaFileAlt className="text-lg text-slate-600" /> <FaFileAlt className="text-lg text-slate-600" />
@ -913,7 +909,7 @@ const SignDocument = () => {
{/* Focus Show Element */} {/* Focus Show Element */}
{(!transparentTags && !hideTags) && (focusElement && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) && {(!transparentTags && !hideTags) && (focusElement && focusElement.documentIndex === item.documentIndex && focusElement.focusElementIndx === index) &&
<> <>
<div <div
className="absolute left-0 z-50 bg-blue-900 text-xs text-slate-100 px-2 py-[2px] rounded-sm whitespace-nowrap" className="absolute left-0 z-50 bg-blue-900 text-xs text-slate-100 px-2 py-[2px] rounded-sm whitespace-nowrap"
@ -923,7 +919,7 @@ const SignDocument = () => {
</div> </div>
<div <div
className="absolute left-[-10px] z-50 flex max-w-max" className="absolute left-[-10px] z-50 flex max-w-max"
style={{ left: -(item.dimensions.left + 20) + "px", top: ((item.dimensions.height / 2) - 18) + "px" }} style={{ left: -(item.dimension.left + 20) + "px", top: ((item.dimension.height / 2) - 18) + "px" }}
> >
{/* <FaArrowRight className="text-[var(--primary)] text-xl" /> {/* <FaArrowRight className="text-[var(--primary)] text-xl" />
*/} */}

View File

@ -40,7 +40,7 @@ function Dashboard() {
// window.location.href = '/login'; // window.location.href = '/login';
// return; // return;
// } // }
// console.log("OUT MIC CHECK", transactionStatus, TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS, TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED); console.log("OUT MIC CHECK", transactionStatus, TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS, TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED);
// Clear all if transaction success // Clear all if transaction success
if (transactionStatus === TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS || transactionStatus === TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED || transactionStatus === TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_FAILURE) { if (transactionStatus === TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS || transactionStatus === TRANSACTION_STATUS_ENUMS.TRANSACTION_INITIATED || transactionStatus === TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_FAILURE) {

View File

@ -10,7 +10,6 @@ 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 { GrTransaction } from 'react-icons/gr';
type SideBarProps = { type SideBarProps = {
showSideBar: boolean; showSideBar: boolean;
@ -25,8 +24,7 @@ const menuPathMap: MenuPathMap = {
'/': 'Home', '/': 'Home',
'/templates': 'Templates', '/templates': 'Templates',
'/s/contact': 'Contact', '/s/contact': 'Contact',
// '/accounts': 'Accounts', '/accounts': 'Accounts',
'transactions' : "Account"
}; };
const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => { const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
@ -63,8 +61,7 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
<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} />
</div> </div>
</div> </div>

View File

@ -24,30 +24,54 @@ interface CardData {
trend?: 'up' | 'down' | 'stable'; trend?: 'up' | 'down' | 'stable';
} }
// interface CardData {
// title: string;
// count: number;
// icon: React.ReactNode;
// gradient: string;
// shadowColor: string;
// iconBg: string;
// textColor: string;
// trend: 'up' | 'down';
// }
interface CardsProps { interface CardsProps {
type?: 'dashboard' | 'account' | 'transactions'; type?: 'dashboard' | 'account' | 'transactions';
onOpenModal?: () => void; onOpenModal?: () => void;
dashboardData: any;
fromDate: string;
toDate: string;
onDateRangeChange: (range: { from: string; to: string }) => void;
cards: CardData[];
isLoading: boolean
} }
const cards: CardData[] = [
{
title: 'Action Required',
count: 12,
icon: <FaTasks size={18} className="text-indigo-600" />,
gradient: 'bg-gradient-to-br from-indigo-50 to-indigo-100',
shadowColor: 'shadow-indigo-200/50',
iconBg: 'bg-indigo-100 border-indigo-200',
textColor: 'text-indigo-700',
trend: 'up'
},
{
title: 'Expiring Soon',
count: 5,
icon: <FaHourglassHalf size={18} className="text-rose-600" />,
gradient: 'bg-gradient-to-br from-rose-50 to-rose-100',
shadowColor: 'shadow-rose-200/50',
iconBg: 'bg-rose-100 border-rose-200',
textColor: 'text-rose-700',
trend: 'up'
},
{
title: 'Completed',
count: 27,
icon: <FaCheckCircle size={18} className="text-teal-600" />,
gradient: 'bg-gradient-to-br from-teal-50 to-teal-100',
shadowColor: 'shadow-teal-200/50',
iconBg: 'bg-teal-100 border-teal-200',
textColor: 'text-teal-700',
trend: 'up'
},
{
title: 'Waiting for Others',
count: 8,
icon: <FaClock size={18} className="text-stone-600" />,
gradient: 'bg-gradient-to-br from-stone-50 to-stone-100',
shadowColor: 'shadow-stone-200/50',
iconBg: 'bg-stone-100 border-stone-200',
textColor: 'text-stone-700',
trend: 'down'
},
];
const AccountData: CardData[] = [ const AccountData: CardData[] = [
{ {
@ -96,53 +120,23 @@ const AccountData: CardData[] = [
const filters = ['Last 7 Days', 'Last 30 Days', 'Last 6 Months', 'This Year']; const filters = ['Last 7 Days', 'Last 30 Days', 'Last 6 Months', 'This Year'];
const accounts = ['All Accounts', 'Account 1', 'Account 2', 'Account 3']; const accounts = ['All Accounts', 'Account 1', 'Account 2', 'Account 3'];
const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRangeChange, cards, isLoading }) => { const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal }) => {
const [selectedFilter, setSelectedFilter] = useState<string>('Last 7 Days'); const [selectedFilter, setSelectedFilter] = useState<string>('Last 7 Days');
const [selectedAccount, setSelectedAccount] = useState<string>('All Accounts'); const [selectedAccount, setSelectedAccount] = useState<string>('All Accounts');
const [showCards, setShowCards] = useState<CardData[]>([]); const [showCards, setShowCards] = useState<CardData[]>([]);
// const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
// useEffect(() => {
// setIsLoading(true);
// setTimeout(() => {
// if (type === 'account') {
// setShowCards(AccountData);
// } else {
// setShowCards(cards);
// }
// setIsLoading(false);
// }, 500);
// }, [type]);
useEffect(() => { useEffect(() => {
setIsLoading(true);
if (type === 'dashboard') { setTimeout(() => {
const now = new Date(); if (type === 'account') {
let fromDate = new Date(); setShowCards(AccountData);
} else {
switch (selectedFilter) { setShowCards(cards);
case 'Last 7 Days':
fromDate.setDate(now.getDate() - 7);
break;
case 'Last 30 Days':
fromDate.setDate(now.getDate() - 30);
break;
case 'Last 6 Months':
fromDate.setMonth(now.getMonth() - 6);
break;
case 'This Year':
fromDate = new Date(now.getFullYear(), 0, 1);
break;
default:
fromDate.setDate(now.getDate() - 7);
} }
setIsLoading(false);
const from = fromDate.toISOString().split('T')[0]; }, 500);
const to = now.toISOString().split('T')[0]; }, [type]);
onDateRangeChange({ from, to });
}
}, [selectedFilter]);
const getButtonConfig = () => { const getButtonConfig = () => {
switch (type) { switch (type) {
@ -188,105 +182,37 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRa
); );
}; };
// if (isLoading) { if (isLoading) {
// return (
// <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>
// );
// }
return ( return (
<div className="w-full px-4 py-3 mt-2 rounded-md"> <div className="w-full px-6 py-8">
{/* Header */}
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mb-2 gap-4">
<div className="flex items-center gap-3">
{/* <div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
<FaFilter className="text-white text-lg" />
</div> */}
<div>
<h2 className="text-lg font-semibold text-gray-800">Overview</h2>
{/* <p className="text-gray-600 text-sm">Monitor performance metrics</p> */}
</div>
</div>
</div>
{/* Cards */}
{isLoading ? <div className="w-full [h-50vh] px-6 py-8">
<div className="animate-pulse"> <div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-32 mb-8"></div> <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"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{[...Array(7)].map((_, i) => ( {[...Array(4)].map((_, i) => (
<div key={i} className="h-32 bg-gray-200 rounded-xl"></div> <div key={i} className="h-32 bg-gray-200 rounded-xl"></div>
))} ))}
</div> </div>
</div> </div>
</div> :
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
{cards.map((card, index) => (
<div
key={card.title}
className={`group relative overflow-hidden rounded-xl ${card.gradient} border border-white/40 p-4 transition-all duration-300 hover:scale-105 hover:shadow-lg ${card.shadowColor} cursor-pointer backdrop-blur-sm`}
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 z-10">
<div className="flex items-center justify-between mb-2">
<div className={`p-2 rounded-lg border ${card.iconBg} group-hover:scale-110 transition-transform duration-200`}>
{card.icon}
</div>
{renderTrendIndicator(card.trend, card.percentage)}
</div>
<div className="space-y-1">
<h3 className="text-xs font-medium text-gray-600 group-hover:text-gray-700 transition-colors">
{card.title}
</h3>
<div className="flex items-baseline gap-1">
<p className={`text-xl font-bold ${card.textColor} group-hover:scale-105 transition-transform duration-200`}>
{card.count}
</p>
<span className="text-[10px] text-gray-500">items</span>
</div>
</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>
); );
}; }
export default Cards; return (
<div className="w-full px-6 py-6 bg-gradient-to-br from-slate-50 to-slate-100 mt-2 rounded-t-md">
{/* Header */}
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mb-8 gap-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
<FaFilter className="text-white text-lg" />
</div>
<div>
<h2 className="text-2xl font-bold text-gray-800">Overview</h2>
<p className="text-gray-600 text-sm">Monitor performance metrics</p>
</div>
</div>
<div className="flex flex-wrap gap-3 w-full lg:w-auto">
{/* Account Selector */}
{/* <div className="flex flex-wrap gap-3 w-full lg:w-auto">
{type === 'dashboard' && <>
Account Selector
<div className="relative"> <div className="relative">
<select <select
value={selectedAccount} value={selectedAccount}
@ -300,7 +226,7 @@ export default Cards;
<FaChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs pointer-events-none" /> <FaChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs pointer-events-none" />
</div> </div>
Date Filter {/* Date Filter */}
<div className="relative"> <div className="relative">
<select <select
value={selectedFilter} value={selectedFilter}
@ -313,16 +239,65 @@ export default Cards;
</select> </select>
<FaCalendarAlt className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs pointer-events-none" /> <FaCalendarAlt className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs pointer-events-none" />
</div> </div>
</>}
Action Button {/* Action Button */}
{buttonConfig && ( {buttonConfig && (
<button <button
className={`flex items-center gap-1 px-3 py-3 text-sm font-semibold text-white rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl transform hover:scale-105 active:scale-95 cursor-pointer ${buttonConfig.gradient}`} className={`flex items-center gap-2 px-6 py-3 text-sm font-semibold text-white rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl transform hover:scale-105 active:scale-95 cursor-pointer ${buttonConfig.gradient}`}
onClick={onOpenModal} onClick={onOpenModal}
> >
{buttonConfig.icon} {buttonConfig.icon}
{buttonConfig.text} {buttonConfig.text}
</button> </button>
)} )}
</div> */} </div>
</div>
{/* Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-6">
{showCards.map((card, index) => (
<div
key={card.title}
className={`group relative overflow-hidden rounded-2xl ${card.gradient} border border-white/50 p-6 transition-all duration-300 hover:scale-105 hover:shadow-xl ${card.shadowColor} cursor-pointer backdrop-blur-sm`}
style={{
animationDelay: `${index * 100}ms`
}}
>
{/* Background Pattern */}
<div className="absolute inset-0 opacity-5">
<div className="absolute -top-10 -right-10 w-20 h-20 rounded-full bg-current"></div>
<div className="absolute -bottom-10 -left-10 w-16 h-16 rounded-full bg-current"></div>
</div>
{/* Content */}
<div className="relative z-10">
<div className="flex items-center justify-between mb-4">
<div className={`p-3 rounded-xl border ${card.iconBg} group-hover:scale-110 transition-transform duration-200`}>
{card.icon}
</div>
{renderTrendIndicator(card.trend, card.percentage)}
</div>
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-600 group-hover:text-gray-700 transition-colors">
{card.title}
</h3>
<div className="flex items-baseline gap-2">
<p className={`text-3xl font-bold ${card.textColor} group-hover:scale-105 transition-transform duration-200`}>
{card.count}
</p>
<span className="text-xs text-gray-500">items</span>
</div>
</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>
);
};
export default Cards;

View File

@ -1,193 +1,24 @@
import React, { useEffect, useState, useCallback } from 'react'; import React from 'react'
import AccountNav from '../accounts/AccountNav'; import HomeTopNav from './HomeTopNav'
import Cards from './Cards'; import UploadDoc from './UploadDoc'
import StartSection from './StartSection'; import FilterSort from './FilterSort'
import api from '../../config/api'; import ShowDocs from './ShowDocs'
import { import Cards from './Cards'
FaCheckCircle, import StartSection from './StartSection'
FaClock, import AccountNav from '../accounts/AccountNav'
FaHourglassHalf,
FaTasks,
FaChevronDown,
FaPlus,
FaUserPlus,
FaFilter,
FaCalendarAlt
} from 'react-icons/fa';
interface CardData {
title: string;
count: number;
icon: JSX.Element;
gradient: string;
shadowColor: string;
iconBg: string;
textColor: string;
percentage?: number;
trend?: 'up' | 'down' | 'stable';
}
const Home = () => { const Home = () => {
const mapApiDataToCards = (data: any): CardData[] => {
return [
{
title: 'Action Needed',
count: data.actionNeeded || 0,
icon: <FaTasks size={18} className="text-indigo-600" />,
gradient: 'bg-gradient-to-br from-indigo-50 to-indigo-100',
shadowColor: 'shadow-indigo-200/50',
iconBg: 'bg-indigo-100 border-indigo-200',
textColor: 'text-indigo-700',
trend: 'up'
},
{
title: 'Expiring Soon',
count: data.expiring || 0,
icon: <FaHourglassHalf size={18} className="text-rose-600" />,
gradient: 'bg-gradient-to-br from-rose-50 to-rose-100',
shadowColor: 'shadow-rose-200/50',
iconBg: 'bg-rose-100 border-rose-200',
textColor: 'text-rose-700',
trend: 'up'
},
{
title: 'Completed',
count: data.completed || 0,
icon: <FaCheckCircle size={18} className="text-teal-600" />,
gradient: 'bg-gradient-to-br from-teal-50 to-teal-100',
shadowColor: 'shadow-teal-200/50',
iconBg: 'bg-teal-100 border-teal-200',
textColor: 'text-teal-700',
trend: 'up'
},
{
title: 'Waiting for Others',
count: data.waitingForOthers || 0,
icon: <FaClock size={18} 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'
},
// {
// title: 'declined',
// count: data.declined || 0,
// icon: <FaClock size={18} className="text-purple-600" />,
// gradient: 'bg-gradient-to-br from-purple-50 to-purple-100',
// shadowColor: 'shadow-purple-200/50',
// iconBg: 'bg-purple-100 border-purple-200',
// textColor: 'text-purple-700',
// trend: 'down'
// },
// {
// title: 'cancelled',
// count: data.cancelled || 0,
// icon: <FaClock size={18} className="text-green-600" />,
// gradient: 'bg-gradient-to-br from-green-50 to-green-100',
// shadowColor: 'shadow-green-200/50',
// iconBg: 'bg-green-100 border-green-200',
// textColor: 'text-green-700',
// trend: 'down'
// },
// {
// title: 'draft',
// count: data.draft || 0,
// icon: <FaClock size={18} className="text-stone-600" />,
// gradient: 'bg-gradient-to-br from-stone-50 to-stone-100',
// shadowColor: 'shadow-stone-200/50',
// iconBg: 'bg-stone-100 border-stone-200',
// textColor: 'text-stone-700',
// trend: 'down'
// }
// Add more cards here if needed
];
};
const [dashboardData, setDashboardData] = useState<any>(null);
const [fromDate, setFromDate] = useState<string>(''); // ISO string
const [toDate, setToDate] = useState<string>(''); // ISO string
const [cards, setCards] = useState<CardData[]>([]);
const [accountID, setAccountID] = useState<number>()
const [isLoading, setIsLoading] = useState<boolean>(false);
console.log("cards", cards)
const getUserDetails = async () => {
try {
let res = await api.get('/api/user/details')
let accountIDData = res?.data?.account?.id
setAccountID(accountIDData)
} catch (error) {
console.log(error)
}
}
useEffect(() => {
getUserDetails()
}, [])
const getDashboardData = async (from: string, to: string) => {
setIsLoading(true)
const payload = {
fromDate: from, // already ISO string
toDate: to,
account : accountID
};
try {
const res = await api.get('/api/dashboard', { params: payload });
setDashboardData(res.data);
console.log("Dashboard Data:", res.data);
const cardDataArray = mapApiDataToCards(res.data);
setCards(cardDataArray);
} catch (error) {
console.error("Error fetching dashboard data:", error);
}finally{
setIsLoading(false)
}
};
const handleDateRangeChange = useCallback(async (range: { from: string; to: string }) => {
const isoFrom = new Date(range.from).toISOString();
const isoTo = new Date(range.to).toISOString();
setFromDate(isoFrom);
setToDate(isoTo);
}, []);
useEffect(() => {
if (fromDate && toDate && accountID) {
getDashboardData(fromDate, toDate);
}
}, [fromDate, toDate , accountID]);
// Set default range on mount (last 7 days)
useEffect(() => {
const now = new Date();
const from = new Date();
from.setDate(now.getDate() - 7);
setFromDate(from.toISOString());
setToDate(now.toISOString());
}, []);
return ( return (
<> <>
{/* <HomeTopNav /> */}
<AccountNav/> <AccountNav/>
<Cards <Cards />
onDateRangeChange={handleDateRangeChange}
dashboardData={dashboardData}
fromDate={fromDate}
toDate={toDate}
cards={cards}
isLoading={isLoading}
/>
<StartSection /> <StartSection />
{/* <UploadDoc /> */}
{/* <FilterSort /> */}
{/* <ShowDocs /> */}
</> </>
); )
}; }
export default Home; export default Home

View File

@ -77,7 +77,7 @@ const StartSection = () => {
return ( return (
<section className="px-4 py-3 w-full"> <section className="px-4 py-3 w-full">
<h2 className="text-lg font-semibold text-gray-800 mb-2">Get Started</h2> <h2 className="text-lg font-semibold text-gray-800 mb-6">Get Started</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{cards.map((card, index) => ( {cards.map((card, index) => (

View File

@ -8,7 +8,6 @@ import { AppDispatch } from '../../../store/store'
import { IoMdClose } from 'react-icons/io' import { IoMdClose } from 'react-icons/io'
import api from '../../../config/api' import api from '../../../config/api'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import axios from 'axios'
interface DocumentCardProps { interface DocumentCardProps {
id: string; id: string;
@ -34,7 +33,7 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted }) =>
let isCompleted = false; let isCompleted = false;
let updateTimeout: ReturnType<typeof setTimeout> | null = null; let updateTimeout: ReturnType<typeof setTimeout> | null = null;
let lastUpdate = 0; let lastUpdate = 0;
const delay = 1000; const delay = 2000;
const docProgSSE = new EventSource(`http://192.168.2.112:8083/api/progress/${id}`); const docProgSSE = new EventSource(`http://192.168.2.112:8083/api/progress/${id}`);
@ -67,8 +66,6 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted }) =>
lastUpdate = Date.now(); lastUpdate = Date.now();
}, delay - sinceLast); }, delay - sinceLast);
} }
}); });
docProgSSE.onerror = (error) => { docProgSSE.onerror = (error) => {

View File

@ -112,26 +112,32 @@ const ProcessStepOne = () => {
name: `${recipient.firstName} ${recipient.lastName}` name: `${recipient.firstName} ${recipient.lastName}`
})); }));
console.log(recipientsData, "printing Recipient Data Line 114");
// await api.post(`/api/accounts/${accountId}/transactions/${transactionId}/recipients`, recipientsData).then((res) => {
// console.log(res, "Response Line 117")
// }).catch((err) => {
// console.log(err)
// })
dispatch(addRecipientAsync({recipientsData, accountId, transactionId})); dispatch(addRecipientAsync({recipientsData, accountId, transactionId}));
dispatch(showLoader("Processing Message..."));
await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { subject: emailDtls.subject, message: emailDtls.message, status: "CREATED" }).then((res) => {
console.log(res);
})
dispatch(hideLoader()); dispatch(hideLoader());
if (isSendNow) { if (isSendNow) {
dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS)); dispatch(updateTransactionStatus(TRANSACTION_STATUS_ENUMS.TRANSACTION_SEND_SUCCESS));
setTimeout(() => { setTimeout(() => {
// navigate("/"); navigate("/");
}, 1000); }, 1000);
} else { } else {
setTimeout(() => { // Preview & Tag
// Assuming you'd fetch or pass doc/recipients response here
// dispatch(setImages(...));
// dispatch(updateRecipientIds(...));
// dispatch(setTransactionId(...));
dispatch(resetDocuments()) dispatch(resetDocuments())
dispatch(resetRecipients()) dispatch(resetRecipients())
dispatch(hideLoader()); setTimeout(() => {
navigate(`/process-step-two/${accountId}/${transactionId}`); navigate(`/process-step-two/${accountId}/${transactionId}`);
}, 1000); }, 1000);
} }

View File

@ -4,17 +4,17 @@ import { RootState } from '../../../store/store'
import api from '../../../config/api'; import api from '../../../config/api';
import RightSideBar from './rightSideBar/RightSideBar'; import RightSideBar from './RightSideBar';
import { DroppedItem } from '../../../utlity/types'; import { DroppedItem } from '../../../utlity/types';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from '../../../store/slices/transactionSlice';
import { hideLoader, showLoader } from '../../../store/slices/loaderSlice';
import { IoMdClose } from 'react-icons/io'; import { IoMdClose } from 'react-icons/io';
import fields from '../../../helper/fieldsListData';
import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice'; import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice';
import { fetchDocuments } from '../../../store/slices/documentSlice'; import { fetchDocuments } from '../../../store/slices/documentSlice';
import { FaQuestionCircle } from 'react-icons/fa'; import { FaQuestionCircle } from 'react-icons/fa';
import typeToIconMap from '../../../helper/iconMap'; import typeToIconMap from '../../../helper/iconMap';
import Loader from '../../dashboard/Loader';
export interface DroppedItemsObj { export interface DroppedItemsObj {
[key: string]: DroppedItem[] [key: string]: DroppedItem[]
@ -45,6 +45,8 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
return null; return null;
} }
// const transactionId = useSelector((state: RootState) => state.transaction.transactionId) // const transactionId = useSelector((state: RootState) => state.transaction.transactionId)
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -125,29 +127,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
accountId: string, accountId: string,
transactionId: string transactionId: string
) => { ) => {
// tagsData = tagsData.filter((tag, indx) => (("update" in tag && tag.update) || !("id" in tag)) ) const requests = Object.entries(tagsData).map(([recipientId, tags]) => {
// Filter Tags that needs to update
const updatedData = {}
// debugger
for (const [recipientId, tags] of Object.entries(tagsData)) {
console.log(recipientId, tags, "line 133")
const updateTags = []
for (const tag of tags) {
if ("update" in tag && tag.update) {
updateTags.push(tag)
}
if (updateTags.length && "update" in updateTags[updateTags.length - 1]) {
delete updateTags[updateTags.length - 1].update
}
}
updatedData[recipientId] = updateTags
}
const requests = Object.entries(updatedData).map(([recipientId, tags]) => {
return api.put( return api.put(
`/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipientId}/tags`, `/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipientId}/tags`,
tags tags
@ -156,12 +136,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
try { try {
const responses = await Promise.all(requests); const responses = await Promise.all(requests);
const droppedItemsUpdateFalse = Object.fromEntries( console.log("All recipient tags submitted:", responses);
Object.entries(droppedItems).map(([recipientId, tags]) => {
return [recipientId, tags.map(tag => ({ ...tag, update: false }))];
})
)
setDroppedItems(droppedItemsUpdateFalse);
} catch (error) { } catch (error) {
console.error("Error submitting recipient tags:", error); console.error("Error submitting recipient tags:", error);
} }
@ -186,6 +161,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// const remove = [] // const remove = []
// console.log(updatedTags, "updatedTags"); // console.log(updatedTags, "updatedTags");
for (const dropPageIds of Object.keys(updatedTags)) { for (const dropPageIds of Object.keys(updatedTags)) {
for (const dropTag of updatedTags[dropPageIds]) { for (const dropTag of updatedTags[dropPageIds]) {
let mask = null let mask = null
@ -201,8 +177,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
} }
// } // }
console.log(dropTag, "line 204")
const isDeleted = "isDeleted" in dropTag ? dropTag.isDeleted : false const isDeleted = "isDeleted" in dropTag ? dropTag.isDeleted : false
const updatedTagData = { const updatedTagData = {
@ -211,28 +185,26 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
"dimensions": dropTag.dimensions, "dimensions": dropTag.dimensions,
"type": dropTag.type.toUpperCase(), "type": dropTag.type.toUpperCase(),
// "subType": (dropTag.subType ? dropTag.subType.toUpperCase() : null), // "subType": (dropTag.subType ? dropTag.subType.toUpperCase() : null),
// validation: { validation: {
"minLength": "minLength" in dropTag ? dropTag.minLength : 0, "minLength": 0,
"maxLength": "maxLength" in dropTag ? dropTag.maxLength : 0, "maxLength": 0,
"error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error", "error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error",
"regex": "regex" in dropTag ? dropTag.regex : [] "regex": "regex" in dropTag ? dropTag.regex : []
// } },
,
"documentId": dropTag.documentId, "documentId": dropTag.documentId,
"options": ("options" in dropTag) ? dropTag.options : [], "options": ("options" in dropTag) ? dropTag.options : [],
"pageIndex": dropTag.pageIndex, "pageIndex": dropTag.pageIndex,
"recipientId": dropTag.recipientId, "recipientId": dropTag.recipientId,
"styles": "styles" in dropTag ? dropTag.styles : { "styles": "styles" in dropTag ? dropTag.styles : {
"font": "", "font": "Great Vibes",
"italic": false, "italic": false,
"bold": false, "bold": false,
"underline": false, "underline": false,
"color": "black", "color": "black",
"textSize": 0 "textSize": 14.0
}, },
// "required": (dropTag.type.toUpperCase() !== "SIGNATURE" && dropTag.type.toUpperCase() !== "INITIALS") ? dropTag.required : true, // "required": (dropTag.type.toUpperCase() !== "SIGNATURE" && dropTag.type.toUpperCase() !== "INITIALS") ? dropTag.required : true,
"required": "required" in dropTag ? dropTag.required : true, "required": false,
"update": "update" in dropTag ? dropTag.update : false,
"readOnly": false, "readOnly": false,
} }
@ -274,13 +246,9 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// for(const recipient of selectedRecipients) { // for(const recipient of selectedRecipients) {
// const recrecipient // const recrecipient
// } // }
tagsData = groupByRecipientId(tagsData); tagsData = groupByRecipientId(tagsData);
sendTagsToRecipients(tagsData, accountId, transactionId); sendTagsToRecipients(tagsData, accountId, transactionId);
// (async () => {
// const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, {status: "SENT"})
// console.log(res, "Check123")
// })()
}, [isSend]) }, [isSend])
// Save Tags - END // Save Tags - END
@ -290,7 +258,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// HANDLE DROP - START // HANDLE DROP - START
const handleDrop = (event: React.DragEvent<HTMLDivElement>, docId: string, documentId: number, pageIndex: number) => { const handleDrop = (event: React.DragEvent<HTMLDivElement>, docId: string, documentId: number, pageIndex: number) => {
console.log("// HANDLE DROP - START")
event.preventDefault(); event.preventDefault();
const data = window.dragElemData; const data = window.dragElemData;
@ -312,7 +279,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />; // const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />;
let prepData = { let prepData = {
properties: data.properties,
icon: typeToIconMap[field.label.toUpperCase()] || <FaQuestionCircle />, icon: typeToIconMap[field.label.toUpperCase()] || <FaQuestionCircle />,
type: dragElemData.label, type: dragElemData.label,
subType: (dragElemData.label in droppedTagsDefaultField) ? droppedTagsDefaultField[dragElemData.label] : '', subType: (dragElemData.label in droppedTagsDefaultField) ? droppedTagsDefaultField[dragElemData.label] : '',
@ -335,7 +301,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
pageIndex: pageIndex, pageIndex: pageIndex,
recipientId: dragElemData.recipientId, recipientId: dragElemData.recipientId,
recipientIndx: dragElemData.recipientIndx, recipientIndx: dragElemData.recipientIndx,
update: true,
isDeleted: false isDeleted: false
} }
@ -422,7 +387,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
...droppedItems, ...droppedItems,
[key]: Array.isArray(droppedItems[key]) // Ensure it's an array [key]: Array.isArray(droppedItems[key]) // Ensure it's an array
? droppedItems[key].map((item, i) => ? droppedItems[key].map((item, i) =>
i === index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item i === index ? { ...item, dimensions: { ...item.dimensions, left, top } } : item
) )
: [], // Provide a fallback in case it's undefined or not an array : [], // Provide a fallback in case it's undefined or not an array
}); });
@ -442,7 +407,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// HANDLE EXPAND TAG - START // HANDLE EXPAND TAG - START
const handleExpandTag = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => { const handleExpandTag = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
console.log("HANDLE EXPAND TAG - START")
event.preventDefault(); // Prevent text selection issues event.preventDefault(); // Prevent text selection issues
const expandingElemIcon = event.currentTarget; const expandingElemIcon = event.currentTarget;
@ -479,7 +443,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
...droppedItems, ...droppedItems,
[key]: Array.isArray(droppedItems[key]) // Ensure it's an array [key]: Array.isArray(droppedItems[key]) // Ensure it's an array
? droppedItems[key].map((item, i) => ? droppedItems[key].map((item, i) =>
i === index ? { ...item, dimensions: { ...item.dimensions, width: newWidth, height: newHeight }, update: true } : item i === index ? { ...item, dimensions: { ...item.dimensions, width: newWidth, height: newHeight } } : item
) )
: [], // Provide a fallback in case it's undefined or not an array : [], // Provide a fallback in case it's undefined or not an array
}); });
@ -521,153 +485,110 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
) )
})); }));
})() })()
} }
// const handleAddRadio = (e: React.MouseEvent<HTMLElement>, key: string, index: number) => {
// // const dataToSet: DroppedItemsObj = {
// // ...droppedItems,
// // [key]: [...(droppedItems[key] || []),
// // {
// // ...prepData,
// // radioGroups: dragElemData.label === "radio" ? [{ ...prepData }] : []
// // }],
// // }
// // const lastRadioElemRect = e.currentTarget?.parentElement.previousSibling.getBoundingClientRect(); const handleAddRadio = (e: React.MouseEvent<HTMLElement>, key: string, index: number) => {
// // console.log(lastRadioElemRect, "lastRadioElemRect") // const dataToSet: DroppedItemsObj = {
// ...droppedItems,
// const setNewData = droppedItems; // [key]: [...(droppedItems[key] || []),
// {
// if (Array.isArray(setNewData[key][index].radioGroups) && setNewData[key][index].radioGroups?.length > 0) { // ...prepData,
// setNewData[key][index].radioGroups?.push(setNewData[key][index].radioGroups[0]) // radioGroups: dragElemData.label === "radio" ? [{ ...prepData }] : []
// }],
// } // }
// setDroppedItems(setNewData); // const lastRadioElemRect = e.currentTarget?.parentElement.previousSibling.getBoundingClientRect();
// }; // console.log(lastRadioElemRect, "lastRadioElemRect")
const setNewData = droppedItems;
if (Array.isArray(setNewData[key][index].radioGroups) && setNewData[key][index].radioGroups?.length > 0) {
// const handleRadioTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number, radioIndex: number) => { setNewData[key][index].radioGroups?.push(setNewData[key][index].radioGroups[0])
// event.preventDefault();
// let targetContainer = event?.currentTarget?.parentElement;
// let targetElement = event?.currentTarget;
// if (!targetContainer || !targetElement) return;
// const containerRect = targetContainer.getBoundingClientRect();
// const elementRect = targetElement.getBoundingClientRect();
// // Calculate the initial offset (where the mouse was clicked inside the element)
// const offsetX = event.clientX - elementRect.left;
// const offsetY = event.clientY - elementRect.top;
// const handleMouseMove = (event: PointerEvent) => {
// // Get new X and Y positions relative to the container
// // let x = offsetX;
// // let y = offsetY;
// let x = event.clientX - elementRect.left;
// let y = event.clientY - elementRect.top;
// const newData = droppedItems;
// if (newData[key][index].radioGroups && newData[key][index].radioGroups.length > 0) {
// newData[key][index].radioGroups[radioIndex].dimensions.left = x;
// newData[key][index].radioGroups[radioIndex].dimensions.top = y;
// let topMin = Number.POSITIVE_INFINITY;
// let topMax = Number.NEGATIVE_INFINITY;
// let leftMin = Number.POSITIVE_INFINITY;
// let leftMax = Number.NEGATIVE_INFINITY;
// for (const radio of newData[key][index].radioGroups) {
// if (radio.dimensions.left < leftMin) {
// leftMin = radio.dimensions.left;
// }
// if (radio.dimensions.left > leftMax) {
// leftMax = radio.dimensions.left;
// }
// if (radio.dimensions.top < topMin) {
// topMin = radio.dimensions.top;
// }
// if (radio.dimensions.top > topMax) {
// topMax = radio.dimensions.top;
// }
// }
// newData[key][index].radioPosition = {
// topMin: topMin,
// topMax: topMax,
// leftMin: leftMin,
// leftMax: leftMax,
// width: leftMax - leftMin,
// height: topMax - topMin
// }
// newData[key][index].dimensions.left = x;
// newData[key][index].dimensions.top = y;
// }
// setDroppedItems({ ...newData });
// };
// const handleMouseUp = () => {
// document.removeEventListener("pointermove", handleMouseMove);
// document.removeEventListener("pointerup", handleMouseUp);
// };
// // Add event listeners
// document.addEventListener("pointermove", handleMouseMove);
// document.addEventListener("pointerup", handleMouseUp);
// }
// NEW DIRECTIONAL MOVEMENT HANDLER - START
const handleDirectionalMove = (direction: 'up' | 'down' | 'left' | 'right', stepSize: number = 5) => {
if (!selectedTag) return;
const { key, index } = selectedTag;
const currentItem = droppedItems[key]?.[index];
if (!currentItem) return;
let newLeft = currentItem.dimensions.left;
let newTop = currentItem.dimensions.top;
switch (direction) {
case 'up':
newTop = Math.max(0, newTop - stepSize);
break;
case 'down':
newTop = newTop + stepSize;
break;
case 'left':
newLeft = Math.max(0, newLeft - stepSize);
break;
case 'right':
newLeft = newLeft + stepSize;
break;
} }
setDroppedItems({ setDroppedItems(setNewData);
...droppedItems,
[key]: Array.isArray(droppedItems[key])
? droppedItems[key].map((item, i) =>
i === index
? { ...item, dimensions: { ...item.dimensions, left: newLeft, top: newTop }, update: true }
: item
)
: [],
});
}; };
// Create a wrapper that matches DirectionalControls' expected signature
const directionalControlsHandler = (direction: 'up' | 'down' | 'left' | 'right') => {
handleDirectionalMove(direction, 5); // Use default stepSize of 5 const handleRadioTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number, radioIndex: number) => {
event.preventDefault();
let targetContainer = event?.currentTarget?.parentElement;
let targetElement = event?.currentTarget;
if (!targetContainer || !targetElement) return;
const containerRect = targetContainer.getBoundingClientRect();
const elementRect = targetElement.getBoundingClientRect();
// Calculate the initial offset (where the mouse was clicked inside the element)
const offsetX = event.clientX - elementRect.left;
const offsetY = event.clientY - elementRect.top;
const handleMouseMove = (event: PointerEvent) => {
// Get new X and Y positions relative to the container
// let x = offsetX;
// let y = offsetY;
let x = event.clientX - elementRect.left;
let y = event.clientY - elementRect.top;
const newData = droppedItems;
if (newData[key][index].radioGroups && newData[key][index].radioGroups.length > 0) {
newData[key][index].radioGroups[radioIndex].dimensions.left = x;
newData[key][index].radioGroups[radioIndex].dimensions.top = y;
let topMin = Number.POSITIVE_INFINITY;
let topMax = Number.NEGATIVE_INFINITY;
let leftMin = Number.POSITIVE_INFINITY;
let leftMax = Number.NEGATIVE_INFINITY;
for (const radio of newData[key][index].radioGroups) {
if (radio.dimensions.left < leftMin) {
leftMin = radio.dimensions.left;
}
if (radio.dimensions.left > leftMax) {
leftMax = radio.dimensions.left;
}
if (radio.dimensions.top < topMin) {
topMin = radio.dimensions.top;
}
if (radio.dimensions.top > topMax) {
topMax = radio.dimensions.top;
}
}
newData[key][index].radioPosition = {
topMin: topMin,
topMax: topMax,
leftMin: leftMin,
leftMax: leftMax,
width: leftMax - leftMin,
height: topMax - topMin
}
newData[key][index].dimensions.left = x;
newData[key][index].dimensions.top = y;
}
setDroppedItems({ ...newData });
}; };
const handleMouseUp = () => {
document.removeEventListener("pointermove", handleMouseMove);
document.removeEventListener("pointerup", handleMouseUp);
};
// Add event listeners
document.addEventListener("pointermove", handleMouseMove);
document.addEventListener("pointerup", handleMouseUp);
}
return ( return (
<> <>
@ -675,7 +596,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
<div id="doc-con" className='zoom-con col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth'> <div id="doc-con" className='zoom-con col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth'>
{ {
selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => { selectedDocuments.length > 0 && selectedDocuments.map((doc, indxDoc) => {
return ( return (
<div key={indxDoc} className='flex flex-col'> <div key={indxDoc} className='flex flex-col'>
<div className='py-2 text-sm'> <div className='py-2 text-sm'>
@ -799,18 +720,13 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
</div> </div>
) )
}) : <Loader /> })
} }
</div> </div>
{selectedTag && <RightSideBar selectedTag={droppedItems[selectedTag.key][selectedTag.index]} setDroppedItems={setDroppedItems} selTagKey={selectedTag.key} selTagIndx={selectedTag.index} DirectionalControlsProps={{ {selectedTag && <RightSideBar selectedTag={droppedItems[selectedTag.key][selectedTag.index]} setDroppedItems={setDroppedItems} selTagKey={selectedTag.key} selTagIndx={selectedTag.index} />}
onMove: directionalControlsHandler,
disabled: false,
stepSize: 5,
className: ''
}} />}
{/* Center Doc Container - END */} {/* Center Doc Container - END */}
</> </>
) )

View File

@ -4,9 +4,9 @@ import api from '../../../config/api';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import typeToIconMap from '../../../helper/iconMap'; import typeToIconMap from '../../../helper/iconMap';
import { FaQuestionCircle } from 'react-icons/fa'; import { FaQuestionCircle } from 'react-icons/fa';
import { TagsProperties } from '../../../utlity/types';
const handlePointerDown = (event: React.DragEvent<HTMLDivElement>, label: string, recipientId: number, recipientIndx: number, tagsProperties: TagsProperties) => { const handlePointerDown = (event: React.DragEvent<HTMLDivElement>, label: string, recipientId: number, recipientIndx: number) => {
const dragElemRect = event.currentTarget.getBoundingClientRect(); const dragElemRect = event.currentTarget.getBoundingClientRect();
let width = dragElemRect.width let width = dragElemRect.width
let height = dragElemRect.height let height = dragElemRect.height
@ -27,8 +27,7 @@ const handlePointerDown = (event: React.DragEvent<HTMLDivElement>, label: string
recipientId: recipientId, recipientId: recipientId,
recipientIndx: recipientIndx, recipientIndx: recipientIndx,
width, width,
height, height
properties: tagsProperties
} }
window.dragElemData = JSON.stringify(obj); window.dragElemData = JSON.stringify(obj);
@ -42,7 +41,7 @@ interface FieldListProps {
const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx }) => { const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx }) => {
const [tagsData, setTagsData] = useState<TagsProperties[]>([]); const [tagsData, setTagsData] = useState([]);
const { accountId } = useParams<{ accountId: string }>(); const { accountId } = useParams<{ accountId: string }>();
if (!accountId) { if (!accountId) {
@ -92,7 +91,7 @@ const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx
<div <div
draggable={true} draggable={true}
key={index} key={index}
onPointerDown={(e) => handlePointerDown(e, field.type.toLowerCase(), recipientId, recipientIndx, field)} onPointerDown={(e) => handlePointerDown(e, field.type.toLowerCase(), recipientId, recipientIndx)}
className="p-1 flex justify-start items-center gap-2 rounded-sm cursor-grab hover:bg-slate-200 transition-all duration-300"> className="p-1 flex justify-start items-center gap-2 rounded-sm cursor-grab hover:bg-slate-200 transition-all duration-300">
<div <div
className={`p-1 text-sm rounded-sm text-white`} className={`p-1 text-sm rounded-sm text-white`}

View File

@ -1,9 +1,10 @@
import { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import NavTwo from './NavTwo'; import NavTwo from './NavTwo';
import LeftSideBar from './LeftSideBar'; import LeftSideBar from './LeftSideBar';
import DocumentContainer from './DocumentContainer'; import DocumentContainer from './DocumentContainer';
import { IoMdClose } from 'react-icons/io'
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../../../store/store'; import { RootState } from '../../../store/store';
import Loader from '../../dashboard/Loader'; import Loader from '../../dashboard/Loader';
@ -24,6 +25,28 @@ const ProcessStepTwo = () => {
return ( return (
<div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}> <div className={`min-w-full relative h-screen bg-slate-100 overflow-hidden ${(globalLoader.isLoading) ? 'pointer-events-none' : ''}`}>
{/* Top Header One */}
{/* <div className='flex items-center shadow-sm pb-3 bg-slate-100'>
<div className='flex w-full justify-center items-center px-4'>
<div className='cursor-default flex justify-center items-center gap-4 border-b-3 relative border-[var(--primary)] py-1 px-4'>
<p className="text-xs tracking-wider">Setup your Document</p>
<div className='absolute bottom-[-7px] w-3 h-3 rounded-full right-[-4px] bg-[var(--primary)] z-50' ></div>
</div>
<div className='cursor-default flex justify-center items-center gap-4 border-b-3 relative border-[var(--primary)] py-1 px-4'>
<p className="text-xs tracking-wider">Prepare</p>
<div className='absolute bottom-[-7px] w-3 h-3 rounded-full right-[-4px] bg-[var(--primary)] z-50' ></div>
</div>
<div className='cursor-default flex justify-center items-center gap-4 border-b-3 relative border-[var(--primaryLight)] py-1 px-4'>
<p className="text-xs tracking-wider">Hooray !</p>
<div className='absolute bottom-[-7px] w-3 h-3 rounded-full right-[-4px] bg-[var(--primaryLight)] z-50' ></div>
</div>
</div>
</div> */}
{/* Top Header One */}
<NavTwo isFields={isFields} setIsFields={setIsFields} isSend={isSend} setIsSend={setIsSend} /> <NavTwo isFields={isFields} setIsFields={setIsFields} isSend={isSend} setIsSend={setIsSend} />
<div className='grid grid-cols-12 gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'> <div className='grid grid-cols-12 gap-1 mt-1 h-[calc(100vh-46px)] overflow-hidden'>

View File

@ -0,0 +1,653 @@
import { MdKeyboardArrowDown } from "react-icons/md";
import { DroppedItemsObj } from "./DocumentContainer";
import React, { ReactHTMLElement, useEffect, useState } from "react";
import { RiAsterisk } from "react-icons/ri";
import { GoDotFill } from "react-icons/go";
import { IoClose } from "react-icons/io5";
import { LuAsterisk } from "react-icons/lu";
import { dateFormats, dateTimeFormats, monthFormats, timeFormats, weekFormats, yearFormats } from "../../../utlity/dateTimeUtils";
import { DroppedItem } from "../../../utlity/types";
import { IoMdClose } from "react-icons/io";
import dayjs from "dayjs";
interface DroppedItemProps {
selectedTag: DroppedItem
setDroppedItems: React.Dispatch<React.SetStateAction<DroppedItemsObj>>
selTagKey: string,
selTagIndx: number
}
enum TEXT {
PARAGRAPH = "paragraph",
SINGLE_LINE = "single_line",
PASSWORD = "password",
}
// enum SIGNATURE {
// TYPED = "typed",
// DRAW = "draw",
// UPLOAD = "upload",
// }
// enum INITIALS {
// TYPED = "typed",
// DRAW = "draw",
// UPLOAD = "upload",
// }
export enum TEMPORAL {
DATE = "date",
TIME = "time",
DATE_TIME = "date_time",
// YEAR = "year",
// MONTH = "month",
// WEEK = "week",
}
enum NUMBER {
INTEGER = "integer",
DECIMAL = "decimal",
CURRENCY = "currency"
}
// enum IMAGE {
// STAMP = "stamp",
// LOGO = "logo"
// }
enum SELECT {
SINGLE_SELECT = "single select",
MULTI_SELECT = "multi select"
}
enum MASKTYPE {
ALL = "all",
LAST_FOUR = "last_four",
CREDIT_CARD = "credit_Card",
NATIONAL_INSURANCE = "national_insurance",
SOCIAL_SECURITY = "social_security",
SOCIAL_INSURANCE = "social_insurance"
}
enum MASKCHARACTER {
ASTERISK = "ASTERISK",
DOT = "DOT",
X = "CROSS",
}
const enumMap = {
text: TEXT,
temporal: TEMPORAL,
number: NUMBER,
image: String,
select: SELECT
}
enum DROPDOWNTOGGLE {
TYPE,
MASK_TYPE,
MASK_CHARACTER,
DDMMYY_FORMAT
}
const dateTimeFormatMap = {
"date": dateFormats,
"time": timeFormats,
"date_time": dateTimeFormats,
"year": yearFormats,
"month": monthFormats,
"week": weekFormats
}
const RightSideBar: React.FC<DroppedItemProps> = ({ selectedTag, setDroppedItems, selTagKey, selTagIndx }) => {
console.log(selectedTag, "selectedTag Line 102")
const selectedType = selectedTag.type in enumMap && enumMap[selectedTag.type as keyof typeof enumMap];
const [dropDownToggle, setDropDownToggle] = useState<DROPDOWNTOGGLE | null>(null);
// const [showFormatEg, setShowFormatEg] = useState<boolean>(false);
const [is24Hrs, setIs24Hrs] = useState<boolean>(true);
const [isCurrentTimeStamp, setIsCurrentTimeStamp] = useState<boolean>(true);
// const [selectedOptions, setSelectedOptions] = useState<string[]>([])
const [decimalPlace, setDecimalPlace] = useState<number>(2)
const [selectedOption, setSelectedOption] = useState<string>()
const handlePropertyChange = (changeTo: string, changeWith: string | boolean | number) => {
console.log("Handle Property Change Called", "changeTo", changeTo, "changeWith", changeWith)
if (changeTo === '' || changeWith === '') return
if (changeTo === "subType" && (changeWith === TEMPORAL.DATE || changeWith === TEMPORAL.DATE_TIME || changeWith === TEMPORAL.TIME)) {
// let change: string = changeWith;
// if (change === "DATE TIME") {
// change = "DATE_TIME"
// }
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey]) // Ensure it's an array
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith || "", format: "" } : item
)
: [],
}// Provide a fallback in case it's undefined or not an array
));
} else if (changeTo === "subType" && typeof changeWith === "string" && changeWith.toLowerCase() === "password") {
console.log("Line 161")
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey])
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith, maskType: "All", maskCharacter: "ASTERISK" } : item
)
: [],
}
));
}
else if (changeTo === "subType" && (changeWith === NUMBER.CURRENCY || changeWith === NUMBER.INTEGER || changeWith === NUMBER.DECIMAL)) {
let decPlace = changeWith !== NUMBER.DECIMAL ? 0 : decimalPlace;
const isCurrency = changeWith === NUMBER.CURRENCY;
// const currencyType = null
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey]) // Ensure it's an array
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith, decimalPlace: decPlace, isCurrency } : item
)
: [],
}// Provide a fallback in case it's undefined or not an array
));
} else if (changeTo === "format" && selectedTag.subType === TEMPORAL.DATE_TIME) {
let val = timeFormats[0].format;
if (!is24Hrs) {
val = timeFormats[1].format
}
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey]) // Ensure it's an array
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith + " " + val } : item
)
: [],
}// Provide a fallback in case it's undefined or not an array
));
} else {
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey]) // Ensure it's an array
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith } : item
)
: [],
}// Provide a fallback in case it's undefined or not an array
));
}
setDropDownToggle(null)
}
// const handleSelectTagOptions = () => {
// if (!selectedOptions.length) return
// setDroppedItems((prev) => (
// {
// ...prev,
// [selTagKey]: Array.isArray(prev[selTagKey])
// ? prev[selTagKey].map((item, i) => {
// if (i === selTagIndx) {
// return { ...item, options: [...selectedOptions] };
// }
// return item;
// })
// : [],
// }
// ));
// }
// useEffect(() => {
// handleSelectTagOptions();
// }, [selectedOptions])
useEffect(() => {
if (selectedTag.subType === TEMPORAL.DATE || selectedTag.subType === TEMPORAL.DATE_TIME || selectedTag.subType === TEMPORAL.TIME) {
if (selectedTag.subType === TEMPORAL.DATE) {
handlePropertyChange("format", dateFormats[0].format)
}
else if (selectedTag.subType === TEMPORAL.DATE_TIME) {
handlePropertyChange("format", dateTimeFormats[0].format)
}
else if (selectedTag.subType === TEMPORAL.TIME) {
handlePropertyChange("format", timeFormats[0].format)
}
}
// else if(selectedTag.subType === TEXT.PASSWORD) {
// handlePropertyChange("format", timeFormats[0].format)
// }
}, [selectedTag.subType])
useEffect(() => {
if (selectedTag.format === timeFormats[1].format && is24Hrs) {
setIs24Hrs(false);
} else if (selectedTag.format === timeFormats[0].format && !is24Hrs) {
setIs24Hrs(true);
}
}, [selectedTag.format])
useEffect(() => {
if (selectedTag.label === "temporal") {
handlePropertyChange("subType", TEMPORAL.DATE);
handlePropertyChange("format", "DD/MM/YY");
}
}, [])
const handleIs24HrsClick = (checked: boolean) => {
if (selectedTag.subType === TEMPORAL.TIME) {
if (!checked) {
handlePropertyChange("format", timeFormats[1].format)
} else {
handlePropertyChange("format", timeFormats[0].format)
}
}
setIs24Hrs(checked)
}
const handleIsCurrentTimeStamp = (checked: boolean) => {
if (selectedTag.type.toLowerCase() === "temporal") {
setIsCurrentTimeStamp(checked)
handlePropertyChange("currentTimeStamp", checked);
}
}
const handleAddSelectOption = () => {
if (selectedOption && selectedOption.length) {
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey])
? prev[selTagKey].map((item, i) => {
if (i === selTagIndx) {
return { ...item, options: item.options ? [...item.options, selectedOption] : [selectedOption] };
}
return item;
})
: [],
}
));
}
}
const handleSelectOptionDel = (indx: number) => {
// setSelectedOptions(prev =>
// prev.filter((_, i) => i !== indx)
// )
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey])
? prev[selTagKey].map((item, i) => {
if (i === selTagIndx) {
return { ...item, options: item.options ? item.options.filter((_, i) => i !== indx) : [] };
}
return item;
})
: [],
}
));
}
const handleDecimalPlace = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = Number(e.target.value)
setDecimalPlace(val)
handlePropertyChange("decimalPlace", val)
}
useEffect(() => {
if ("styles" in selectedTag) {
const fontSize = selectedTag?.styles?.textSize;
const tagHeight = selectedTag?.dimensions?.height - 18;
console.log(fontSize, tagHeight, "Line 323", selectedTag?.styles)
if (selectedTag.subType !== "password" && selectedTag.subType !== "paragraph" && tagHeight > (fontSize + fontSize)) {
console.log("Change subType Called from if")
handlePropertyChange("subType", "paragraph")
} else if (selectedTag.subType !== "password" && selectedTag.subType !== "single_line" && tagHeight <= (fontSize + fontSize)) {
console.log("Change subType Called from elseif")
handlePropertyChange("subType", "single_line")
}
}
}, [selectedTag])
console.log("RightSideBar - selectedTag", selectedTag)
return (
<>
{/* Right Side Bar Start */}
<div className='col-span-2 w-full h-full bg-white text-sm'>
<p className="p-2 font-semibold text-md">Properties</p>
<hr className="text-slate-200" />
<div className="p-3 text-sm text-slate-600 flex flex-col gap-2">
{/* label */}
<div className="flex flex-col">
<label htmlFor="" className="">Label</label>
<input
value={selectedTag.label}
onChange={(e) => handlePropertyChange('label', e.target.value)}
type="text"
className="outline-none border-1 rounded px-2 py-1 border-slate-200" />
</div>
{/* Required */}
{(selectedTag.type !== "signature" && selectedTag.type !== "initials") && <div className="flex justify-between items-center">
<label htmlFor="required" className="text-xs text-slate-600 transitions-all duration-150">Required</label>
<input
id="required"
checked={selectedTag.required ? selectedTag.required : false}
onChange={(e) => handlePropertyChange('required', e.target.checked)}
type="checkbox"
className="outline-none border-1 rounded px-2 py-1 border-slate-200"
/>
</div>}
{/* Use Current TimeStamp */}
{selectedTag.type.toLowerCase() === "temporal" && <div className="flex justify-left items-center gap-1.5 mt-0.5">
<label
className={`text-xs ${isCurrentTimeStamp ? "text-slate-600" : "text-slate-400"} transitions-all duration-150`}
htmlFor="use_current_timestamp"
>
Use Current Timestamp
</label>
<input
checked={isCurrentTimeStamp ? isCurrentTimeStamp : false}
onChange={(e) => handleIsCurrentTimeStamp(e.target.checked)}
id="use_current_timestamp"
type="checkbox"
/>
</div>}
{/* Type | SubType */}
{selectedType && Object.keys(selectedType).length > 0 &&
<div className="flex flex-col">
<label htmlFor="" className="">Type</label>
<div
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.TYPE ? null : DROPDOWNTOGGLE.TYPE)}
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
<p className="capitalize cursor-pointer">{selectedTag?.subType ? (selectedTag.subType === "date_time" ? "Date Time" : selectedTag.subType) : selectedType[Object.keys(selectedType)[0] as keyof typeof selectedType]}</p>
<MdKeyboardArrowDown />
</div>
{dropDownToggle === DROPDOWNTOGGLE.TYPE &&
<div className="shadow bg-slate-100 mt-1 border-[1px] border-slate-100 rounded">
{Object.keys(selectedType).map((val, indx) => (
<div
key={indx}
onClick={() => handlePropertyChange("subType", selectedType[val as keyof typeof selectedType])}
className={`capitalize px-2 py-1 hover:bg-slate-200 cursor-pointer`}
>
{selectedType[val as keyof typeof selectedType] === "date_time" ? "Date Time" : selectedType[val as keyof typeof selectedType]}
</div>
))}
</div>
}
</div>
}
{/* Mask Type */}
{
selectedTag?.subType === TEXT.PASSWORD &&
<div>
<label htmlFor="" className="">Mask Type</label>
<div
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.MASK_TYPE ? null : DROPDOWNTOGGLE.MASK_TYPE)}
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
<p className="capitalize cursor-pointer">{(selectedTag.maskType && selectedTag.maskType.replace("_", " ")) ? selectedTag.maskType : MASKTYPE.ALL}</p>
<MdKeyboardArrowDown />
</div>
{
dropDownToggle === DROPDOWNTOGGLE.MASK_TYPE &&
<div className="shadow bg-slate-100 mt-1 border-[1px] border-slate-100 rounded">
{(Object.keys(MASKTYPE) as (keyof typeof MASKTYPE)[]).map((val, indx) =>
(
<div
key={indx}
onClick={() => handlePropertyChange("maskType", MASKTYPE[val])}
className="capitalize px-2 py-1 hover:bg-slate-200 cursor-pointer"
>
{(MASKTYPE[val]).replace("_", " ")}
</div>
)
)}
</div>
}
</div>
}
{/* Mask Character - START */}
{
selectedTag?.subType === TEXT.PASSWORD &&
<div>
<label htmlFor="" className="">Mask Character</label>
<div
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.MASK_CHARACTER ? null : DROPDOWNTOGGLE.MASK_CHARACTER)}
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
<p className="capitalize cursor-pointer py-1">
{selectedTag?.maskCharacter
?
selectedTag.maskCharacter === MASKCHARACTER.ASTERISK && <LuAsterisk /> ||
selectedTag.maskCharacter === MASKCHARACTER.DOT && <GoDotFill /> ||
selectedTag.maskCharacter === MASKCHARACTER.X && <IoClose />
:
<LuAsterisk />
}
</p>
<MdKeyboardArrowDown />
</div>
{
dropDownToggle === DROPDOWNTOGGLE.MASK_CHARACTER &&
<div className="shadow bg-slate-100 mt-1 border-[1px] border-slate-100 rounded">
{(Object.keys(MASKCHARACTER) as (keyof typeof MASKCHARACTER)[]).map((val, indx) =>
(
<div
key={indx}
onClick={() => handlePropertyChange("maskCharacter", MASKCHARACTER[val])}
className="capitalize p-2 hover:bg-slate-200 cursor-pointer"
>
{
MASKCHARACTER[val] === MASKCHARACTER.ASTERISK &&
<LuAsterisk />
}
{
MASKCHARACTER[val] === MASKCHARACTER.DOT &&
<GoDotFill />
}
{
MASKCHARACTER[val] === MASKCHARACTER.X &&
<IoClose />
}
</div>
)
)}
</div>
}
</div>
}
{/* Mask Character - END */}
{/* TEMPORAL DATE FORMAT - START */}
{
(selectedTag?.subType === TEMPORAL.DATE ||
selectedTag?.subType === TEMPORAL.TIME ||
selectedTag?.subType === TEMPORAL.DATE_TIME) &&
<div>
{/* <div className="flex justify-between">
<label htmlFor="" className="">Formats</label>
<div className="flex justify-center items-center gap-1.5">
<input
checked={showFormatEg ? showFormatEg : false}
onChange={(e) => setShowFormatEg(e.target.checked)}
id="data_time_format"
type="checkbox" />
<label className="text-xs text-slate-400" htmlFor="data_time_format">
Show Example
</label>
</div>
</div> */}
<div className="flex justify-between">
<label htmlFor="" className="">Formats</label>
{(selectedTag.subType === TEMPORAL.DATE_TIME || selectedTag.subType === TEMPORAL.TIME) &&
<div className="flex justify-center items-center gap-1.5">
<input
checked={is24Hrs ? is24Hrs : false}
onChange={(e) => handleIs24HrsClick(e.target.checked)}
id="data_time_format"
type="checkbox" />
<label className={`text-xs ${is24Hrs ? "text-slate-600" : "text-slate-400"} transitions-all duration-150`} htmlFor="data_time_format">
24 Hrs
</label>
</div>
}
</div>
<div
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.DDMMYY_FORMAT ? null : DROPDOWNTOGGLE.DDMMYY_FORMAT)}
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
<p className="cursor-pointer py-1">
{selectedTag.format ? selectedTag.format : dateTimeFormatMap['date'][0].format}
{/* {(selectedTag.subType !== TEMPORAL.DATE && selectedTag.subType !== TEMPORAL.TIME) ? (is24Hrs ? " " + timeFormats[0].format : " " + timeFormats[1].format) : ""} */}
</p>
<MdKeyboardArrowDown />
</div>
{
dropDownToggle === DROPDOWNTOGGLE.DDMMYY_FORMAT &&
<div className="shadow bg-slate-100 mt-1 border-[1px] max-h-[200px] overflow-y-auto border-slate-100 rounded">
{dateTimeFormatMap[selectedTag.subType].map((valObj, indx) => {
// let val = showFormatEg ? valObj["example"] : valObj["format"]
let val = valObj["format"]
if (selectedTag.subType === TEMPORAL.DATE_TIME) {
if (is24Hrs) {
val = val + " " + timeFormats[0].format;
} else {
val = val + " " + timeFormats[1].format;
}
}
return (
<div
key={indx}
onClick={() => handlePropertyChange("format", valObj["format"])}
className="px-2 py-1 hover:bg-slate-200 cursor-pointer"
>
{val}
</div>
)
}
)}
</div>
}
{selectedTag.format && <div className="pt-4">
{/* <p>
Example
</p> */}
<p className="text-center">{dayjs().format(selectedTag.format)}</p>
</div>}
</div>
}
{/* TEMPORAL DATE FORMAT - END */}
{/* NUMBER - DECIMAL PLACES */}
{
selectedTag?.subType === NUMBER.DECIMAL &&
<div>
<label htmlFor="" className="">Decimal Places</label>
<input
type="number"
onChange={(e) => handleDecimalPlace(e)}
value={decimalPlace}
className="bg-slate-100 w-full outline-none p-2 rounded" />
</div>
}
{(selectedTag?.subType === "single select" || selectedTag?.subType === "multi select") &&
<>
<div>
<label htmlFor="" className="">Add Options</label>
<input
type="text"
onChange={(e) => setSelectedOption(e.target.value)}
value={selectedOption}
onKeyUp={(e) => {
if (e.code === "Enter") {
// if (selectedOption) {
// setSelectedOptions((prev) => [...prev, selectedOption]);
// setSelectedOption("");
// }
handleAddSelectOption()
}
}}
className="bg-slate-100 w-full outline-none p-2 rounded" />
<div className="h-[150px] mt-1 overflow-hidden overflow-y-auto">
{
selectedTag.options && selectedTag.options.map((val, indx) => (
<div
key={indx}
className="flex justify-between items-center px-2 py-1 my-1 rounded bg-slate-100">
<p>{val}</p>
<IoMdClose
onClick={() =>
// setSelectedOptions(prev =>
// prev.filter((_, i) => i !== indx)
// )
handleSelectOptionDel(indx)
}
className="cursor-pointer text-slate-500 hover:text-slate-600" />
</div>
))
}
</div>
</div>
</>
}
</div>
</div>
{/* Right Side Bar End */}
</>
)
}
export default RightSideBar

View File

@ -1,42 +0,0 @@
import { MdKeyboardArrowDown } from 'react-icons/md';
import { DROPDOWNTOGGLE } from './RightSideBar';
interface CommonAttrDropDownInt {
selectedType: any;
subType: string | undefined;
dropDownToggle: DROPDOWNTOGGLE | null;
setDropDownToggle: React.Dispatch<React.SetStateAction<DROPDOWNTOGGLE | null>>;
handlePropertyChange: (changeTo: string, changeWith: string | boolean | number) => void;
}
const CommonAttrDropDown: React.FC<CommonAttrDropDownInt> = ({selectedType, subType, dropDownToggle, setDropDownToggle, handlePropertyChange}) => {
return (
<div className="flex flex-col">
<label htmlFor="" className="">Type</label>
<div
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.TYPE ? null : DROPDOWNTOGGLE.TYPE)}
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
<p className="capitalize cursor-pointer">{subType ? (subType === "date_time" ? "Date Time" : subType) : selectedType[Object.keys(selectedType)[0] as keyof typeof selectedType]}</p>
<MdKeyboardArrowDown />
</div>
{dropDownToggle === DROPDOWNTOGGLE.TYPE &&
<div className="shadow bg-slate-100 mt-1 border-[1px] border-slate-100 rounded">
{Object.keys(selectedType).map((val, indx) => (
<div
key={indx}
onClick={() => handlePropertyChange("subType", selectedType[val as keyof typeof selectedType])}
className={`capitalize px-2 py-1 hover:bg-slate-200 cursor-pointer`}
>
{selectedType[val as keyof typeof selectedType] === "date_time" ? "Date Time" : selectedType[val as keyof typeof selectedType]}
</div>
))}
</div>
}
</div>
)
}
export default CommonAttrDropDown

View File

@ -1,37 +0,0 @@
import React, { useState } from 'react'
interface CurrTimeStampAttrInt {
type: string;
handlePropertyChange: (changeTo: string, changeWith: string | boolean | number) => void;
}
const CurrTimeStampAttr: React.FC<CurrTimeStampAttrInt> = ({ type, handlePropertyChange}) => {
const [isCurrentTimeStamp, setIsCurrentTimeStamp] = useState<boolean>(true);
const handleIsCurrentTimeStamp = (checked: boolean) => {
if (type.toLowerCase() === "temporal") {
setIsCurrentTimeStamp(checked)
handlePropertyChange("currentTimeStamp", checked);
}
}
return (
<div className="flex justify-left items-center gap-1.5 mt-0.5">
<label
className={`text-xs ${isCurrentTimeStamp ? "text-slate-600" : "text-slate-400"} transitions-all duration-150`}
htmlFor="use_current_timestamp"
>
Use Current Timestamp
</label>
<input
checked={isCurrentTimeStamp ? isCurrentTimeStamp : false}
onChange={(e) => handleIsCurrentTimeStamp(e.target.checked)}
id="use_current_timestamp"
type="checkbox"
/>
</div>
)
}
export default CurrTimeStampAttr

View File

@ -1,120 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { FaArrowUp, FaArrowDown, FaArrowLeft, FaArrowRight, FaRegCircle } from 'react-icons/fa';
interface DirectionalControlsProps {
onMove: (direction: 'up' | 'down' | 'left' | 'right', stepSize?: number) => void;
disabled?: boolean;
stepSize?: number;
className?: string;
selectedType: {
left?: number;
top?: number;
width?: number;
height?: number;
};
}
const DirectionalControls: React.FC<DirectionalControlsProps> = ({
onMove,
disabled = false,
stepSize = 1,
className = '',
selectedType,
}) => {
const onMoveRef = useRef(onMove);
useEffect(() => {
onMoveRef.current = onMove;
}, [onMove]);
const handleMove = (direction: 'up' | 'down' | 'left' | 'right') => {
if (!disabled) {
onMoveRef.current(direction, stepSize);
}
};
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (disabled) return;
switch (event.key) {
case 'ArrowUp':
event.preventDefault();
handleMove('up');
break;
case 'ArrowDown':
event.preventDefault();
handleMove('down');
break;
case 'ArrowLeft':
event.preventDefault();
handleMove('left');
break;
case 'ArrowRight':
event.preventDefault();
handleMove('right');
break;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [disabled, stepSize]);
const buttonClass = `
p-1 rounded-sm transition-all duration-200 border shadow-sm text-[10px]
${disabled
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-white text-gray-600 hover:bg-blue-100 active:bg-blue-200 cursor-pointer'}
flex items-center justify-center w-6 h-6
`;
const { top = 0, left = 0, width = 0, height = 0 } = selectedType || {};
const right = left + width;
const bottom = top + height;
return (
<div className={`grid grid-cols-3 grid-rows-1 text-[10px] text-gray-700 ${className}`}>
{/* Top Row */}
<div></div>
<div className="flex flex-col items-center">
<button onClick={() => handleMove('up')} disabled={disabled} className={buttonClass} title="Move up" type="button">
<FaArrowUp size={12} />
</button>
<span className="mt-1">Top: {Math.round(top)}px</span>
</div>
<div></div>
{/* Middle Row */}
<div className="flex flex-col items-center justify-center">
<button onClick={() => handleMove('left')} disabled={disabled} className={buttonClass} title="Move left" type="button">
<FaArrowLeft size={12} />
</button>
<span className="mt-1">Left: {Math.round(left)}px</span>
</div>
<div className="w-8 h-8 flex items-center justify-center text-xs text-gray-400 ps-5"><FaRegCircle/></div>
<div className="flex flex-col items-center justify-center">
<button onClick={() => handleMove('right')} disabled={disabled} className={buttonClass} title="Move right" type="button">
<FaArrowRight size={12} />
</button>
<span className="mt-1">Right: {Math.round(right)}px</span>
</div>
{/* Bottom Row */}
<div></div>
<div className="flex flex-col items-center">
<button onClick={() => handleMove('down')} disabled={disabled} className={buttonClass} title="Move down" type="button">
<FaArrowDown size={12} />
</button>
<span className="mt-1">Bottom: {Math.round(bottom)}px</span>
</div>
<div></div>
</div>
);
};
export default DirectionalControls;

View File

@ -1,21 +0,0 @@
import React from 'react'
interface LabelAttrInt {
label: string;
handlePropertyChange: (changeTo: string, changeWith: string | boolean | number) => void;
}
const LabelAttr: React.FC<LabelAttrInt> = ({ label, handlePropertyChange }) => {
return (
<div className="flex flex-col">
<label htmlFor="" className="">Label</label>
<input
value={label}
onChange={(e) => handlePropertyChange('label', e.target.value)}
type="text"
className="outline-none border-1 rounded px-2 py-1 border-slate-200" />
</div>
)
}
export default LabelAttr

View File

@ -1,100 +0,0 @@
import { MdKeyboardArrowDown } from "react-icons/md";
import { DROPDOWNTOGGLE, MASKCHARACTER, MASKTYPE } from "./RightSideBar";
import { LuAsterisk } from "react-icons/lu";
import { GoDotFill } from "react-icons/go";
import { IoClose } from "react-icons/io5";
interface MaskTypePassAttrInt {
maskType: string;
maskCharacter: string;
dropDownToggle: DROPDOWNTOGGLE | null;
setDropDownToggle: React.Dispatch<React.SetStateAction<DROPDOWNTOGGLE | null>>;
handlePropertyChange: (changeTo: string, changeWith: string | boolean | number) => void;
}
const MaskTypePassAttr: React.FC<MaskTypePassAttrInt> = ({ maskType, maskCharacter, dropDownToggle, setDropDownToggle, handlePropertyChange }) => {
return (
<>
{/* Mask Type */}
<div>
<label htmlFor="" className="">Mask Type</label>
<div
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.MASK_TYPE ? null : DROPDOWNTOGGLE.MASK_TYPE)}
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
<p className="capitalize cursor-pointer">{(maskType && maskType.replace("_", " ")) ? maskType : MASKTYPE.ALL}</p>
<MdKeyboardArrowDown />
</div>
{
dropDownToggle === DROPDOWNTOGGLE.MASK_TYPE &&
<div className="shadow bg-slate-100 mt-1 border-[1px] border-slate-100 rounded">
{(Object.keys(MASKTYPE) as (keyof typeof MASKTYPE)[]).map((val, indx) =>
(
<div
key={indx}
onClick={() => handlePropertyChange("maskType", MASKTYPE[val])}
className="capitalize px-2 py-1 hover:bg-slate-200 cursor-pointer"
>
{(MASKTYPE[val]).replace("_", " ")}
</div>
)
)}
</div>
}
</div>
{/* Mask Character */}
<div>
<label htmlFor="" className="">Mask Character</label>
<div
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.MASK_CHARACTER ? null : DROPDOWNTOGGLE.MASK_CHARACTER)}
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
<p className="capitalize cursor-pointer py-1">
{maskCharacter
?
maskCharacter === MASKCHARACTER.ASTERISK && <LuAsterisk /> ||
maskCharacter === MASKCHARACTER.DOT && <GoDotFill /> ||
maskCharacter === MASKCHARACTER.X && <IoClose />
:
<LuAsterisk />
}
</p>
<MdKeyboardArrowDown />
</div>
{
dropDownToggle === DROPDOWNTOGGLE.MASK_CHARACTER &&
<div className="shadow bg-slate-100 mt-1 border-[1px] border-slate-100 rounded">
{(Object.keys(MASKCHARACTER) as (keyof typeof MASKCHARACTER)[]).map((val, indx) =>
(
<div
key={indx}
onClick={() => handlePropertyChange("maskCharacter", MASKCHARACTER[val])}
className="capitalize p-2 hover:bg-slate-200 cursor-pointer"
>
{
MASKCHARACTER[val] === MASKCHARACTER.ASTERISK &&
<LuAsterisk />
}
{
MASKCHARACTER[val] === MASKCHARACTER.DOT &&
<GoDotFill />
}
{
MASKCHARACTER[val] === MASKCHARACTER.X &&
<IoClose />
}
</div>
)
)}
</div>
}
</div>
</>
)
}
export default MaskTypePassAttr

View File

@ -1,68 +0,0 @@
import React, { useRef } from 'react';
interface MaxMinLenAttrIntr {
maxLength: number;
minLength: number;
handlePropertyChange: (changeTo: string, changeWith: string | boolean | number) => void;
}
const MaxMinLenAttr: React.FC<MaxMinLenAttrIntr> = ({ maxLength, minLength, handlePropertyChange }) => {
const firstChangedField = useRef<null | 'minLength' | 'maxLength'>(null);
const handleMaxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newMax = Number(e.target.value);
if (!firstChangedField.current) {
firstChangedField.current = 'maxLength';
}
if (firstChangedField.current === 'minLength' && newMax < minLength) {
alert(`Max Length must be greater than or equal to Min Length (${minLength})`);
return;
}
handlePropertyChange('maxLength', newMax);
};
const handleMinChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newMin = Number(e.target.value);
if (!firstChangedField.current) {
firstChangedField.current = 'minLength';
}
if (firstChangedField.current === 'maxLength' && newMin > maxLength) {
alert(`Min Length must be less than or equal to Max Length (${maxLength})`);
return;
}
handlePropertyChange('minLength', newMin);
};
return (
<>
<div>
<label htmlFor="maxLength">Max Length</label>
<input
id="maxLength"
value={maxLength}
type="number"
min={0}
className="outline-none border-1 rounded px-2 py-1 border-slate-200"
onChange={handleMaxChange}
/>
</div>
<div className="mt-2">
<label htmlFor="minLength">Min Length</label>
<input
id="minLength"
value={minLength}
type="number"
min={0}
className="outline-none border-1 rounded px-2 py-1 border-slate-200"
onChange={handleMinChange}
/>
</div>
</>
);
};
export default MaxMinLenAttr;

View File

@ -1,88 +0,0 @@
import React, { useEffect, useState } from 'react'
import { IoCheckmark } from 'react-icons/io5';
import { MdKeyboardArrowDown } from 'react-icons/md';
import { useSelector } from 'react-redux';
import { RootState } from '../../../../store/store';
interface SelectedRecipientsIntr {
id: number;
firstName: string;
lastName: string;
color: string;
}
interface RecipientsAttrConIntr {
selectedRecipientId: number;
handlePropertyChange: (changeTo: string, changeWith: string | boolean | number) => void;
}
const RecipientsAttrCon: React.FC<RecipientsAttrConIntr> = ({ selectedRecipientId, handlePropertyChange }) => {
const [selectedRecipient, setSelectedRecipient] = useState(null);
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
const [dropDownToggle, setDropDownToggle] = useState(false);
useEffect(() => {
if (selectedRecipients.length) {
const filter = selectedRecipients.filter((obj, indx) => obj.id == selectedRecipientId)
setSelectedRecipient(filter[0]);
}
}, [selectedRecipientId])
if (!selectedRecipient) {
return (
<>
</>
)
}
return (
<>
<div className='relative'
tabIndex={0}
onBlur={() => setDropDownToggle(false)}>
{/* Selected Recipient */}
<div
onClick={() => setDropDownToggle(!dropDownToggle)}
className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm border-1 border-slate-200 cursor-pointer'>
<div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: selectedRecipient.color }}></p>
{/* <FaUserTie className='w-3 h-3 text-2xl mt-[1px]' style={{ color: selectedRecipient.color }} /> */}
<p>{"firstName" in selectedRecipient && selectedRecipient.firstName} {"lastName" in selectedRecipient && selectedRecipient.lastName}</p>
</div>
<MdKeyboardArrowDown />
</div>
<div
className={`absolute w-full shadow-md rounded-md mt-1 px-2 py-1 z-20 bg-white ${dropDownToggle ? '' : 'hidden'}`}>
{selectedRecipients.length && selectedRecipients.map((recipient, indx) => {
return (<div
key={indx}
className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm cursor-pointer hover:bg-slate-100'
onClick={() => {
handlePropertyChange('recipientId', recipient.id);
setTimeout(() => {
setDropDownToggle(false);
}, 0)
}}
>
<div className='flex items-center gap-1.5'>
<p className='w-3 h-3 rounded-sm' style={{ background: recipient.color }}></p>
{/* <FaUser className='w-3 h-3 text-2xl mt-[1px]' style={{ color: recipient.color }} /> */}
<p>{recipient.firstName} {recipient.lastName} </p>
</div>
{selectedRecipientId === recipient.id && <IoCheckmark className='text-[var(--primary)]' />}
</div>)
})}
</div>
</div>
</>
)
}
export default RecipientsAttrCon

View File

@ -1,23 +0,0 @@
import React from 'react'
interface RequiredAttrInt {
required: boolean;
handlePropertyChange: (changeTo: string, changeWith: string | boolean | number) => void;
}
const RequiredAttr : React.FC<RequiredAttrInt> = ({required, handlePropertyChange}) => {
return (
<div className="flex justify-between items-center">
<label htmlFor="required" className="text-xs text-slate-600 transitions-all duration-150">Required</label>
<input
id="required"
checked={required ? required : false}
onChange={(e) => handlePropertyChange('required', e.target.checked)}
type="checkbox"
className="outline-none border-1 rounded px-2 py-1 border-slate-200"
/>
</div>
)
}
export default RequiredAttr

View File

@ -1,472 +0,0 @@
import { DroppedItemsObj } from "../DocumentContainer";
import React, { useEffect, useState } from "react";
import { dateFormats, dateTimeFormats, timeFormats } from "../../../../utlity/dateTimeFormats";
import { DroppedItem } from "../../../../utlity/types";
import { IoIosArrowDown } from "react-icons/io";
import { TagsTypes } from "../../../../utlity/types";
import LabelAttr from "./LabelAttr";
import RequiredAttr from "./RequiredAttr";
import CurrTimeStampAttr from "./currTimeStampAttr";
import CommonAttrDropDown from "./CommonAttrDropDown";
import MaskTypePassAttr from "./MaskTypePassAttr";
import TemporalDateAttr from "./TemporalDateAttr";
import SelectTagAttr from "./SelectTagAttr";
import MaxMinLenAttr from "./MaxMinLenAttr";
import RecipientsAttrCon from "./RecipientsAttrCon";
import MovableFieldContainer from "./MovableField";
import DirectionalControls from "./DirectionalControls";
// interface DirectionalControlsProps {
// onMove: (direction: 'up' | 'down' | 'left' | 'right') => void;
// disabled?: boolean;
// stepSize?: number;
// className?: string;
// }
export interface DroppedItemProps {
selectedTag: DroppedItem
setDroppedItems: React.Dispatch<React.SetStateAction<DroppedItemsObj>>
selTagKey: string,
selTagIndx: number,
DirectionalControlsProps: {
onMove: (direction: 'up' | 'down' | 'left' | 'right', stepSize?: number) => void;
disabled?: boolean;
stepSize?: number;
className?: string;
}
}
export enum TEXT {
PARAGRAPH = "paragraph",
SINGLE_LINE = "single_line",
PASSWORD = "password",
}
export enum TEMPORAL {
DATE = "date",
TIME = "time",
DATE_TIME = "date_time",
}
export enum NUMBER {
INTEGER = "integer",
DECIMAL = "decimal",
CURRENCY = "currency"
}
export enum SELECT {
SINGLE_SELECT = "single select",
MULTI_SELECT = "multi select"
}
export enum MASKTYPE {
ALL = "all",
LAST_FOUR = "last_four",
CREDIT_CARD = "credit_Card",
NATIONAL_INSURANCE = "national_insurance",
SOCIAL_SECURITY = "social_security",
SOCIAL_INSURANCE = "social_insurance"
}
export enum MASKCHARACTER {
ASTERISK = "ASTERISK",
DOT = "DOT",
X = "CROSS",
}
export const enumMap = {
text: TEXT,
temporal: TEMPORAL,
number: NUMBER,
image: String,
select: SELECT
}
export enum DROPDOWNTOGGLE {
TYPE,
MASK_TYPE,
MASK_CHARACTER,
DDMMYY_FORMAT
}
const defaultStyles = {
font: "",
italic: false,
bold: false,
underline: false,
color: "#000000",
textSize: 0
};
const RightSideBar: React.FC<DroppedItemProps> = ({ selectedTag, setDroppedItems, selTagKey, selTagIndx, DirectionalControlsProps }) => {
const selectedType = selectedTag.type in enumMap && enumMap[selectedTag.type as keyof typeof enumMap];
console.log("selectedTag", selectedTag, selectedType)
const [styles, setStyles] = useState(defaultStyles);
const [dropDownToggle, setDropDownToggle] = useState<DROPDOWNTOGGLE | null>(null);
const [decimalPlace, setDecimalPlace] = useState<number>(2)
const [styleOpen, setStyleOpen] = useState(false);
const handlePropertyChange = (changeTo: string, changeWith: string | boolean | number, is24Hrs = null) => {
console.log("Handle Property Change Called", "changeTo", changeTo, "changeWith", changeWith)
if (changeTo === '' || changeWith === '') return
if (changeTo === "subType" && (changeWith === TEMPORAL.DATE || changeWith === TEMPORAL.DATE_TIME || changeWith === TEMPORAL.TIME)) {
// let change: string = changeWith;
// if (change === "DATE TIME") {
// change = "DATE_TIME"
// }
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey]) // Ensure it's an array
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith || "", format: "", update: true } : item
)
: [],
}// Provide a fallback in case it's undefined or not an array
));
} else if (changeTo === "subType" && typeof changeWith === "string" && changeWith.toLowerCase() === "password") {
console.log("Line 161")
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey])
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith, maskType: "All", maskCharacter: "ASTERISK", update: true } : item
)
: [],
}
));
}
else if (changeTo === "subType" && (changeWith === NUMBER.CURRENCY || changeWith === NUMBER.INTEGER || changeWith === NUMBER.DECIMAL)) {
let decPlace = changeWith !== NUMBER.DECIMAL ? 0 : decimalPlace;
const isCurrency = changeWith === NUMBER.CURRENCY;
// const currencyType = null
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey]) // Ensure it's an array
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith, decimalPlace: decPlace, isCurrency, update: true } : item
)
: [],
}// Provide a fallback in case it's undefined or not an array
));
} else if (changeTo === "format" && selectedTag.subType === TEMPORAL.DATE_TIME) {
let val = timeFormats[0].format;
if (!is24Hrs) {
val = timeFormats[1].format
}
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey]) // Ensure it's an array
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith + " " + val, update: true } : item
)
: [],
}// Provide a fallback in case it's undefined or not an array
));
} else {
setDroppedItems((prev) => {
console.log('Previous state:', prev);
const updatedItems = Array.isArray(prev[selTagKey])
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith, update: true } : item
)
: [];
console.log('Updated items:', updatedItems);
return {
...prev,
[selTagKey]: updatedItems,
};
});
}
setDropDownToggle(null)
}
useEffect(() => {
if (selectedTag.subType === TEMPORAL.DATE || selectedTag.subType === TEMPORAL.DATE_TIME || selectedTag.subType === TEMPORAL.TIME) {
if (selectedTag.subType === TEMPORAL.DATE) {
handlePropertyChange("format", dateFormats[0].format)
}
else if (selectedTag.subType === TEMPORAL.DATE_TIME) {
handlePropertyChange("format", dateTimeFormats[0].format)
}
else if (selectedTag.subType === TEMPORAL.TIME) {
handlePropertyChange("format", timeFormats[0].format)
}
}
}, [selectedTag.subType])
useEffect(() => {
if (selectedTag.label === "temporal") {
handlePropertyChange("subType", TEMPORAL.DATE);
handlePropertyChange("format", "DD/MM/YY");
}
}, [])
const handleDecimalPlace = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = Number(e.target.value)
setDecimalPlace(val)
handlePropertyChange("decimalPlace", val)
}
// useEffect(() => {
// if ("styles" in selectedTag) {
// console.log("styles" in selectedTag)
// const fontSize = selectedTag?.styles?.textSize;
// const tagHeight = selectedTag?.dimensions?.height - 18;
// console.log(fontSize)
// if (selectedTag.subType !== TEXT.PASSWORD && selectedTag.subType !== TEXT.PARAGRAPH && tagHeight > (fontSize + fontSize)) {
// handlePropertyChange("subType", TEXT.PARAGRAPH)
// } else if (selectedTag.subType !== TEXT.PASSWORD && selectedTag.subType !== TEXT.SINGLE_LINE && tagHeight <= (fontSize + fontSize)) {
// console.log("Change subType Called from elseif")
// handlePropertyChange("subType", TEXT.SINGLE_LINE)
// }
// }
// }, [selectedTag])
useEffect(() => {
if (!selectedTag) return;
if (!selectedTag.styles) {
handlePropertyChange("styles", defaultStyles);
setStyles(defaultStyles);
} else {
setStyles({ ...defaultStyles, ...selectedTag.styles });
}
// Optional subType logic
const fontSize = selectedTag?.styles?.textSize || defaultStyles.textSize;
const tagHeight = selectedTag?.dimensions?.height - 18;
if (
selectedTag.subType !== TEXT.PASSWORD &&
selectedTag.subType !== TEXT.PARAGRAPH &&
tagHeight > fontSize * 2
) {
handlePropertyChange("subType", TEXT.PARAGRAPH);
} else if (
selectedTag.subType !== TEXT.PASSWORD &&
selectedTag.subType !== TEXT.SINGLE_LINE &&
tagHeight <= fontSize * 2
) {
handlePropertyChange("subType", TEXT.SINGLE_LINE);
}
}, [selectedTag]);
const handleStyleChange = (key, value) => {
const updatedStyles = {
...(selectedTag.styles ?? defaultStyles),
[key]: value
};
setStyles(updatedStyles);
handlePropertyChange("styles", updatedStyles);
};
return (
<>
{/* Right Side Bar Start */}
<div className='col-span-2 w-full h-full bg-white text-sm overflow-y-auto'>
<p className="p-2 font-semibold text-md">Properties</p>
<hr className="text-slate-200" />
<div className="p-3 text-sm text-slate-600 flex flex-col gap-2">
{
<RecipientsAttrCon selectedRecipientId={selectedTag.recipientId} handlePropertyChange={handlePropertyChange} />
}
<DirectionalControls {...DirectionalControlsProps} selectedType={selectedTag.dimensions}/>
{selectedTag.label &&
<LabelAttr label={selectedTag.label} handlePropertyChange={handlePropertyChange} />}
{(selectedTag.type !== TagsTypes.SIGNATURE && selectedTag.type !== TagsTypes.INITIALS) &&
<>
<RequiredAttr required={selectedTag.required ? selectedTag.required : false} handlePropertyChange={handlePropertyChange} />
</>
}
{selectedTag.type.toLowerCase() === TagsTypes.TEMPORAL &&
<CurrTimeStampAttr type={selectedTag.type} handlePropertyChange={handlePropertyChange} />}
{
selectedType && Object.keys(selectedType).length > 0 &&
<CommonAttrDropDown
selectedType={selectedType}
subType={selectedTag.subType}
dropDownToggle={dropDownToggle}
setDropDownToggle={setDropDownToggle}
handlePropertyChange={handlePropertyChange} />
}
{/* Mask Type */}
{
selectedTag?.subType === TEXT.PASSWORD &&
<MaskTypePassAttr
maskType={selectedTag.maskType ? selectedTag.maskType : MASKTYPE.ALL}
maskCharacter={selectedTag.maskCharacter ? selectedTag.maskCharacter : MASKCHARACTER.ASTERISK}
dropDownToggle={dropDownToggle}
setDropDownToggle={setDropDownToggle}
handlePropertyChange={handlePropertyChange} />
}
{/* TEMPORAL DATE FORMAT - START */}
{
(selectedTag?.subType === TEMPORAL.DATE ||
selectedTag?.subType === TEMPORAL.TIME ||
selectedTag?.subType === TEMPORAL.DATE_TIME) &&
<TemporalDateAttr
subType={selectedTag.subType}
format={selectedTag.format}
dropDownToggle={dropDownToggle}
setDropDownToggle={setDropDownToggle}
handlePropertyChange={handlePropertyChange} />
}
{/* TEMPORAL DATE FORMAT - END */}
{/* NUMBER - DECIMAL PLACES */}
{
selectedTag?.subType === NUMBER.DECIMAL &&
<div>
<label htmlFor="" className="">Decimal Places</label>
<input
type="number"
onChange={(e) => handleDecimalPlace(e)}
value={decimalPlace}
className="bg-slate-100 w-full outline-none p-2 rounded" />
</div>
}
{
(selectedTag?.subType === SELECT.SINGLE_SELECT || selectedTag?.subType === SELECT.MULTI_SELECT) &&
<SelectTagAttr
options={selectedTag.options}
selTagKey={selTagKey}
selTagIndx={selTagIndx}
setDroppedItems={setDroppedItems}
handlePropertyChange={handlePropertyChange} />
}
{
(selectedTag.type.toLowerCase() === TagsTypes.TEXT || selectedTag.type.toLowerCase() === TagsTypes.COMPANY) &&
<MaxMinLenAttr maxLength={selectedTag.maxLength} minLength={selectedTag.minLength} handlePropertyChange={handlePropertyChange} />
}
{
<div className="">
<div className="flex items-center justify-between mr-2 cursor-pointer"
onClick={() => setStyleOpen(!styleOpen)}
>
<label
className="block mb-2">Styles</label>
<IoIosArrowDown className={`${styleOpen ? "rotate-[-90deg]" : ""} transition-all`} />
</div>
{
styleOpen && <>
{/* Font */}
<div className="mb-2">
<label className="mr-2">Font</label>
<input
type="text"
placeholder="e.g. Arial"
className="outline-none border-1 rounded px-2 py-1 border-slate-200"
value={styles.font}
onChange={(e) => handleStyleChange("font", e.target.value)}
/>
</div>
{/* Text Size */}
<div className="mb-2">
<label className="mr-2">Text Size</label>
<input
// value={}
type="number"
className="outline-none border-1 rounded px-2 py-1 border-slate-200"
value={styles.textSize}
onChange={(e) =>
handleStyleChange("textSize", parseInt(e.target.value, 10) || 0)
}
/>
</div>
{/* Color */}
<div className="mb-2">
<label className="mr-2">Color</label>
<input
type="color"
className="outline-none border-1 rounded px-2 py-1 border-slate-200 w-10 h-8"
value={styles.color}
onChange={(e) => handleStyleChange("color", e.target.value)}
/>
</div>
{/* Toggles for Bold, Italic, Underline */}
<div className="flex gap-4 items-center mt-2">
<label className="flex gap-1 items-center">
<input type="checkbox" checked={styles.bold}
onChange={(e) => handleStyleChange("bold", e.target.checked)} />
Bold
</label>
<label className="flex gap-1 items-center">
<input type="checkbox" checked={styles.italic} onChange={(e) => handleStyleChange("italic", e.target.checked)} />
Italic
</label>
<label className="flex gap-1 items-center">
<input type="checkbox" checked={styles.underline}
onChange={(e) =>
handleStyleChange("underline", e.target.checked)
} />
Underline
</label>
</div>
</>
}
</div>
}
</div>
</div>
{/* Right Side Bar End */}
</>
)
}
export default RightSideBar

View File

@ -1,105 +0,0 @@
import { useState } from "react";
import { DROPDOWNTOGGLE } from "./RightSideBar";
import { DroppedItemsObj } from "../DocumentContainer";
import { IoMdClose } from "react-icons/io";
interface SelectTagAttrInt {
options: string[] | undefined;
selTagKey: string;
selTagIndx: number;
setDroppedItems: React.Dispatch<React.SetStateAction<DroppedItemsObj>>;
handlePropertyChange: (changeTo: string, changeWith: string | boolean | number) => void;
}
const SelectTagAttr: React.FC<SelectTagAttrInt> = ({ options, selTagKey, selTagIndx, setDroppedItems, handlePropertyChange }) => {
const [selectedOption, setSelectedOption] = useState<string>()
const handleAddSelectOption = () => {
if (selectedOption && selectedOption.length) {
setDroppedItems((prev) => {
console.log("Previous droppedItems:", prev);
const newState = {
...prev,
[selTagKey]: Array.isArray(prev[selTagKey])
? prev[selTagKey].map((item, i) => {
if (i === selTagIndx) {
return {
...item,
options: item.options ? [...item.options, selectedOption] : [selectedOption],
update: true,
};
}
return item;
})
: [],
};
console.log("New droppedItems state:", newState);
return newState;
});
}
};
const handleSelectOptionDel = (indx: number) => {
setDroppedItems((prev) => (
{
...prev,
[selTagKey]: Array.isArray(prev[selTagKey])
? prev[selTagKey].map((item, i) => {
if (i === selTagIndx) {
return { ...item, options: item.options ? item.options.filter((_, i) => i !== indx) : [] };
}
return item;
})
: [],
}
));
}
return (
<>
<div>
<label htmlFor="" className="">Add Options</label>
<input
type="text"
onChange={(e) => setSelectedOption(e.target.value)}
value={selectedOption}
onKeyUp={(e) => {
if (e.code === "Enter") {
// if (selectedOption) {
// setSelectedOptions((prev) => [...prev, selectedOption]);
// setSelectedOption("");
// }
handleAddSelectOption()
}
}}
className="bg-slate-100 w-full outline-none p-2 rounded" />
<div className="h-[150px] mt-1 overflow-hidden overflow-y-auto">
{
options && options.map((val, indx) => (
<div
key={indx}
className="flex justify-between items-center px-2 py-1 my-1 rounded bg-slate-100">
<p>{val}</p>
<IoMdClose
onClick={() =>
// setSelectedOptions(prev =>
// prev.filter((_, i) => i !== indx)
// )
handleSelectOptionDel(indx)
}
className="cursor-pointer text-slate-500 hover:text-slate-600" />
</div>
))
}
</div>
</div>
</>
)
}
export default SelectTagAttr

View File

@ -1,108 +0,0 @@
import { useEffect, useState } from "react";
import { dateFormats, dateTimeFormats, monthFormats, timeFormats, weekFormats, yearFormats } from "../../../../utlity/dateTimeFormats";
import { DROPDOWNTOGGLE, TEMPORAL } from "./RightSideBar";
import { MdKeyboardArrowDown } from "react-icons/md";
import dayjs from "dayjs";
const dateTimeFormatMap = {
"date": dateFormats,
"time": timeFormats,
"date_time": dateTimeFormats,
"year": yearFormats,
"month": monthFormats,
"week": weekFormats
}
interface TemporalDateAttrInt {
subType: string;
format: string | undefined;
dropDownToggle: DROPDOWNTOGGLE | null;
setDropDownToggle: React.Dispatch<React.SetStateAction<DROPDOWNTOGGLE | null>>;
handlePropertyChange: (changeTo: string, changeWith: string | boolean | number, is24Hrs: boolean) => void;
}
const TemporalDateAttr: React.FC<TemporalDateAttrInt> = ({ subType, format, dropDownToggle, setDropDownToggle, handlePropertyChange }) => {
const [is24Hrs, setIs24Hrs] = useState<boolean>(true);
const handleIs24HrsClick = (checked: boolean) => {
if (subType === TEMPORAL.TIME) {
if (!checked) {
handlePropertyChange("format", timeFormats[1].format, is24Hrs)
} else {
handlePropertyChange("format", timeFormats[0].format, is24Hrs)
}
}
setIs24Hrs(checked)
}
useEffect(() => {
if (format === timeFormats[1].format && is24Hrs) {
setIs24Hrs(false);
} else if (format === timeFormats[0].format && !is24Hrs) {
setIs24Hrs(true);
}
}, [format])
return (
<div>
<div className="flex justify-between">
<label htmlFor="" className="">Formats</label>
{(subType === TEMPORAL.DATE_TIME || subType === TEMPORAL.TIME) &&
<div className="flex justify-center items-center gap-1.5">
<input
checked={is24Hrs ? is24Hrs : false}
onChange={(e) => handleIs24HrsClick(e.target.checked)}
id="data_time_format"
type="checkbox" />
<label className={`text-xs ${is24Hrs ? "text-slate-600" : "text-slate-400"} transitions-all duration-150`} htmlFor="data_time_format">
24 Hrs
</label>
</div>
}
</div>
<div
onClick={() => setDropDownToggle(dropDownToggle === DROPDOWNTOGGLE.DDMMYY_FORMAT ? null : DROPDOWNTOGGLE.DDMMYY_FORMAT)}
className="flex justify-between items-center border-1 rounded px-2 py-1 border-slate-200">
<p className="cursor-pointer py-1">
{format ? format : dateTimeFormatMap['date'][0].format}
{/* {(subType !== TEMPORAL.DATE && subType !== TEMPORAL.TIME) ? (is24Hrs ? " " + timeFormats[0].format : " " + timeFormats[1].format) : ""} */}
</p>
<MdKeyboardArrowDown />
</div>
{
dropDownToggle === DROPDOWNTOGGLE.DDMMYY_FORMAT &&
<div className="shadow bg-slate-100 mt-1 border-[1px] max-h-[200px] overflow-y-auto border-slate-100 rounded">
{dateTimeFormatMap[subType].map((valObj, indx) => {
// let val = showFormatEg ? valObj["example"] : valObj["format"]
let val = valObj["format"]
if (subType === TEMPORAL.DATE_TIME) {
if (is24Hrs) {
val = val + " " + timeFormats[0].format;
} else {
val = val + " " + timeFormats[1].format;
}
}
return (
<div
key={indx}
onClick={() => handlePropertyChange("format", valObj["format"])}
className="px-2 py-1 hover:bg-slate-200 cursor-pointer"
>
{val}
</div>
)
}
)}
</div>
}
{format && <div className="pt-4">
<p className="text-center">{dayjs().format(format)}</p>
</div>}
</div>
)
}
export default TemporalDateAttr

View File

@ -18,9 +18,7 @@ interface Signature {
signatureHash: string; signatureHash: string;
signatureBlob: string; signatureBlob: string;
signatureStampedBlob: string; signatureStampedBlob: string;
default: boolean; default: boolean
signatureMimeType:string;
initialsMimeType:string
} }
const SignatureManager: React.FC = () => { const SignatureManager: React.FC = () => {
@ -157,7 +155,7 @@ const SignatureManager: React.FC = () => {
{/* Signature Image */} {/* Signature Image */}
<div className="bg-white rounded-lg p-1 flex items-center justify-center "> <div className="bg-white rounded-lg p-1 flex items-center justify-center ">
<img <img
src={`data:${signature.signatureMimeType};base64,${signature.signatureBlob}`} src={`data:image/png;base64,${signature.signatureBlob}`}
className="h-[50px] w-[250px] object-contain" className="h-[50px] w-[250px] object-contain"
alt="Signature" alt="Signature"
/> />
@ -181,7 +179,7 @@ const SignatureManager: React.FC = () => {
{/* Signature Image */} {/* Signature Image */}
<div className="bg-white rounded-lg p-1 flex items-center justify-center "> <div className="bg-white rounded-lg p-1 flex items-center justify-center ">
<img <img
src={`data:${signature.initialsMimeType};base64,${signature.initialsBlob}`} src={`data:image/png;base64,${signature.initialsBlob}`}
className="h-[50px] object-contain" className="h-[50px] object-contain"
alt="DS Signature" alt="DS Signature"
/> />

View File

@ -28,7 +28,6 @@ api.interceptors.response.use(
async error => { async error => {
const originalRequest = error.config; const originalRequest = error.config;
// Prevent multiple refresh attempts // Prevent multiple refresh attempts
// if (error.response?.status === 401 || error.response?.status === 500) {
if (error.response?.status === 401) { if (error.response?.status === 401) {
originalRequest._retry = true; originalRequest._retry = true;
try { try {

View File

@ -134,6 +134,7 @@ const documentSlice = createSlice({
}) })
.addCase(fetchDocuments.fulfilled, (state, action: PayloadAction<Document[]>) => { .addCase(fetchDocuments.fulfilled, (state, action: PayloadAction<Document[]>) => {
state.loading = false; state.loading = false;
console.log(action.payload, "action.payload")
state.documents = action.payload; state.documents = action.payload;
}) })
.addCase(fetchDocuments.rejected, (state, action) => { .addCase(fetchDocuments.rejected, (state, action) => {

View File

@ -180,9 +180,10 @@ const selectedRecipientsSlice = createSlice({
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
.addCase(fetchRecipients.pending, (state) => { .addCase(fetchRecipients.pending, (state) => {
console.log("Fetching recipients...");
}) })
.addCase(fetchRecipients.fulfilled, (state, action: PayloadAction<Recipient[]>) => { .addCase(fetchRecipients.fulfilled, (state, action: PayloadAction<Recipient[]>) => {
// console.log("Fetched recipients successfully:", action.payload); console.log("Fetched recipients successfully:", action.payload);
// Assign colors to recipients // Assign colors to recipients
const assignedRecipients = action.payload.map((recipient, index) => ({ const assignedRecipients = action.payload.map((recipient, index) => ({
@ -208,7 +209,7 @@ const selectedRecipientsSlice = createSlice({
.addCase(addRecipientAsync.pending, (state) => { .addCase(addRecipientAsync.pending, (state) => {
console.log("Fetching recipients...");
}) })
.addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction<Recipient>) => { .addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction<Recipient>) => {
console.log("Added recipient successfully:", action.payload); console.log("Added recipient successfully:", action.payload);

View File

@ -79,41 +79,3 @@ export interface AccountData {
role: UserRole; role: UserRole;
status: AccountStatus; status: AccountStatus;
} }
export interface TagsProperties {
"editable": boolean;
"required": boolean;
"navigationOrder": number;
"type": string;
"lastName"?: boolean;
"firstName"?: boolean;
"maxLength"?: number;
"minLength"?: number;
"readOnly"?: boolean;
"multiLine"?: boolean;
"regex"?: string[];
"format"?: string;
"dateFormat"?: string;
"currentTimeStamp"?: boolean;
"decimalPlace"?: number;
"multiSelect"?: boolean;
}
export enum TagsTypes {
TEXT= "text",
SIGNATURE= "signature",
INITIALS= "initials",
TEMPORAL= "temporal",
NUMBER= "number",
IMAGE= "image",
STAMP= "stamp",
CHECKBOX= "checkbox",
SELECT= "select",
FILE= "file",
ATTACHMENT= "attachment",
RADIO= "radio",
EMAIL= "email",
NAME= "name",
COMPANY= "company",
}

View File

@ -10,8 +10,7 @@ export default defineConfig({
allowedHosts: [ allowedHosts: [
'quality-friendly-goldfish.ngrok-free.app', 'quality-friendly-goldfish.ngrok-free.app',
'officially-powerful-goat.ngrok-free.app', 'officially-powerful-goat.ngrok-free.app',
'89f0-49-36-189-120.ngrok-free.app', '89f0-49-36-189-120.ngrok-free.app'
'c8ee-49-36-191-130.ngrok-free.app'
], ],
}, },
build: { build: {