Compare commits
10 Commits
73273865f4
...
8bb43d6ee5
| Author | SHA1 | Date |
|---|---|---|
|
|
8bb43d6ee5 | |
|
|
8ec313e6d8 | |
|
|
54ee5e163d | |
|
|
1ecbca5fce | |
|
|
063201751c | |
|
|
3d72931007 | |
|
|
8296cc8006 | |
|
|
e2369a4548 | |
|
|
902964881c | |
|
|
9f40388c57 |
|
|
@ -47,6 +47,7 @@ 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>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,17 @@ 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;
|
||||||
|
|
@ -21,31 +32,152 @@ const formatDate = (timestamp: number) => {
|
||||||
return new Date(timestamp * 1000).toLocaleString();
|
return new Date(timestamp * 1000).toLocaleString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Account = () => {
|
const Account = () => {
|
||||||
const { accountId } = useParams<{ accountId: string }>();
|
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 { 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 {
|
||||||
|
|
@ -65,7 +197,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 }) => {
|
||||||
|
|
@ -98,9 +230,12 @@ const Account = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Cards type="transactions" onOpenModal={handleOpenModal} />
|
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
|
||||||
|
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">
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
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 } from "react-icons/fa";
|
import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck } 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;
|
||||||
|
|
@ -28,9 +31,20 @@ 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: "",
|
||||||
|
|
@ -44,6 +58,62 @@ 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 () => {
|
||||||
|
|
@ -74,22 +144,26 @@ 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 {
|
||||||
let res = await api.post('/api/auth/logout')
|
let res = await api.post('/api/auth/logout')
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
|
@ -101,12 +175,37 @@ 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">
|
||||||
|
|
@ -137,10 +236,6 @@ 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 */}
|
||||||
|
|
@ -148,7 +243,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={() => setOpen(!open)}
|
onClick={handleMainDropdownToggle}
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
>
|
>
|
||||||
|
|
@ -189,6 +284,63 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ 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);
|
||||||
|
|
@ -122,7 +123,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">
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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('/')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,8 @@ 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 = {
|
||||||
|
|
@ -615,9 +617,10 @@ const SignDocument = () => {
|
||||||
>
|
>
|
||||||
|
|
||||||
{
|
{
|
||||||
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc, indxDoc) => {
|
selectedDocuments && selectedDocuments.length > 0 && selectedDocuments.map((doc) => {
|
||||||
|
console.log(tagJson, "tagJsontagJsontagJson")
|
||||||
return (
|
return (
|
||||||
<div key={indxDoc} className='flex flex-col'>
|
<div key={doc.documentId} 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>
|
||||||
|
|
@ -626,12 +629,13 @@ 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, indx) => val.documentIndex === indxDoc)
|
const currDocTags = tagJson.filter((val) => val.pageIndex === page.index)
|
||||||
|
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, indxDoc, page.index)}
|
// onDrop={(e) => handleDrop(e, doc.documentId, doc.documentId, page.index)}
|
||||||
// onDragOver={handleDragOver}
|
// onDragOver={handleDragOver}
|
||||||
className='drop-page-in shadow-md relative'
|
className='drop-page-in shadow-md relative'
|
||||||
>
|
>
|
||||||
|
|
@ -640,7 +644,7 @@ const SignDocument = () => {
|
||||||
|
|
||||||
|
|
||||||
{currDocTags.map((item, index) => {
|
{currDocTags.map((item, index) => {
|
||||||
if (item.pageNumber !== page.index) return;
|
if (item.pageIndex !== page.index) return;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
tabIndex={1}
|
tabIndex={1}
|
||||||
|
|
@ -650,11 +654,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.dimension.left}px`,
|
left: `${item.dimensions.left}px`,
|
||||||
top: `${item.dimension.top}px`,
|
top: `${item.dimensions.top}px`,
|
||||||
width: `${item.dimension.width}px`,
|
width: `${item.dimensions.width}px`,
|
||||||
height: `${item.dimension.height}px`,
|
height: `${item.dimensions.height}px`,
|
||||||
border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentIndex === item.documentIndex && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`,
|
border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`,
|
||||||
// display: `${hideTags ? 'none' : 'block'}`
|
// display: `${hideTags ? 'none' : 'block'}`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -665,7 +669,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.dimension.height + "px" }}
|
// style={{ height: item.dimensions.height + "px" }}
|
||||||
>
|
>
|
||||||
|
|
||||||
{/* <input
|
{/* <input
|
||||||
|
|
@ -677,7 +681,7 @@ const SignDocument = () => {
|
||||||
{
|
{
|
||||||
item.subType === "SINGLE_LINE" && <p
|
item.subType === "SINGLE_LINE" && <p
|
||||||
// name={item.id}
|
// name={item.id}
|
||||||
// style={{ height: item.dimension.height + "px" }}
|
// style={{ height: item.dimensions.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);
|
||||||
|
|
@ -734,11 +738,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.dimension.height + "px" }}
|
// style={{ height: item.dimensions.height + "px" }}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
name={item.id}
|
name={item.id}
|
||||||
// style={{ height: item.dimension.height + "px" }}
|
// style={{ height: item.dimensions.height + "px" }}
|
||||||
className="w-full px-2 outline-none"
|
className="w-full px-2 outline-none"
|
||||||
type="checkbox" />
|
type="checkbox" />
|
||||||
</div>}
|
</div>}
|
||||||
|
|
@ -752,7 +756,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.dimension.height + "px" }}
|
// style={{ height: item.dimensions.height + "px" }}
|
||||||
>
|
>
|
||||||
{/* <input type="checkbox" className="" /> */}
|
{/* <input type="checkbox" className="" /> */}
|
||||||
{
|
{
|
||||||
|
|
@ -763,8 +767,8 @@ const SignDocument = () => {
|
||||||
:
|
:
|
||||||
<img
|
<img
|
||||||
style={{
|
style={{
|
||||||
width: `${item.dimension.width}px`,
|
width: `${item.dimensions.width}px`,
|
||||||
height: `${item.dimension.height}px`,
|
height: `${item.dimensions.height}px`,
|
||||||
}}
|
}}
|
||||||
src={saved.signature as string} />
|
src={saved.signature as string} />
|
||||||
}
|
}
|
||||||
|
|
@ -779,15 +783,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.dimension.height + "px" }}
|
// style={{ height: item.dimensions.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.dimension.width}px`,
|
width: `${item.dimensions.width}px`,
|
||||||
height: `${item.dimension.height}px`,
|
height: `${item.dimensions.height}px`,
|
||||||
}}
|
}}
|
||||||
className="w-[100%] h-full"
|
className="w-[100%] h-full"
|
||||||
src={saved.initial as string} />
|
src={saved.initial as string} />
|
||||||
|
|
@ -803,7 +807,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.dimension.height + "px" }}
|
// style={{ height: item.dimensions.height + "px" }}
|
||||||
>
|
>
|
||||||
{!transparentTags && !hideTags ?
|
{!transparentTags && !hideTags ?
|
||||||
<FaStamp className="text-lg text-slate-600" />
|
<FaStamp className="text-lg text-slate-600" />
|
||||||
|
|
@ -820,7 +824,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.dimension.height + "px" }}
|
// style={{ height: item.dimensions.height + "px" }}
|
||||||
>
|
>
|
||||||
{!transparentTags && !hideTags ?
|
{!transparentTags && !hideTags ?
|
||||||
<FaFileAlt className="text-lg text-slate-600" />
|
<FaFileAlt className="text-lg text-slate-600" />
|
||||||
|
|
@ -909,7 +913,7 @@ const SignDocument = () => {
|
||||||
|
|
||||||
|
|
||||||
{/* Focus Show Element */}
|
{/* Focus Show Element */}
|
||||||
{(!transparentTags && !hideTags) && (focusElement && focusElement.documentIndex === item.documentIndex && focusElement.focusElementIndx === index) &&
|
{(!transparentTags && !hideTags) && (focusElement && focusElement.documentId === item.documentId && 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"
|
||||||
|
|
@ -919,7 +923,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.dimension.left + 20) + "px", top: ((item.dimension.height / 2) - 18) + "px" }}
|
style={{ left: -(item.dimensions.left + 20) + "px", top: ((item.dimensions.height / 2) - 18) + "px" }}
|
||||||
>
|
>
|
||||||
{/* <FaArrowRight className="text-[var(--primary)] text-xl" />
|
{/* <FaArrowRight className="text-[var(--primary)] text-xl" />
|
||||||
*/}
|
*/}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { IoSettingsOutline } from "react-icons/io5";
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { MdManageAccounts } from 'react-icons/md';
|
import { MdManageAccounts } from 'react-icons/md';
|
||||||
|
import { GrTransaction } from 'react-icons/gr';
|
||||||
|
|
||||||
type SideBarProps = {
|
type SideBarProps = {
|
||||||
showSideBar: boolean;
|
showSideBar: boolean;
|
||||||
|
|
@ -24,7 +25,8 @@ 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 }) => {
|
||||||
|
|
@ -61,7 +63,8 @@ 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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FaCheckCircle,
|
FaCheckCircle,
|
||||||
FaClock,
|
FaClock,
|
||||||
FaHourglassHalf,
|
FaHourglassHalf,
|
||||||
FaTasks,
|
FaTasks,
|
||||||
FaChevronDown,
|
FaChevronDown,
|
||||||
FaPlus,
|
FaPlus,
|
||||||
|
|
@ -24,54 +24,30 @@ 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[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -94,7 +70,7 @@ const AccountData: CardData[] = [
|
||||||
textColor: 'text-purple-700',
|
textColor: 'text-purple-700',
|
||||||
trend: 'stable'
|
trend: 'stable'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: 'Completed',
|
title: 'Completed',
|
||||||
count: 27,
|
count: 27,
|
||||||
|
|
@ -120,23 +96,53 @@ 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 }) => {
|
const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRangeChange, cards, isLoading }) => {
|
||||||
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);
|
|
||||||
setTimeout(() => {
|
if (type === 'dashboard') {
|
||||||
if (type === 'account') {
|
const now = new Date();
|
||||||
setShowCards(AccountData);
|
let fromDate = new Date();
|
||||||
} else {
|
|
||||||
setShowCards(cards);
|
switch (selectedFilter) {
|
||||||
|
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);
|
|
||||||
}, 500);
|
const from = fromDate.toISOString().split('T')[0];
|
||||||
}, [type]);
|
const to = now.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
onDateRangeChange({ from, to });
|
||||||
|
}
|
||||||
|
}, [selectedFilter]);
|
||||||
|
|
||||||
const getButtonConfig = () => {
|
const getButtonConfig = () => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -161,7 +167,7 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal }) => {
|
||||||
|
|
||||||
const renderTrendIndicator = (trend: string | undefined, percentage: number | undefined) => {
|
const renderTrendIndicator = (trend: string | undefined, percentage: number | undefined) => {
|
||||||
if (!trend || !percentage) return null;
|
if (!trend || !percentage) return null;
|
||||||
|
|
||||||
const trendColors = {
|
const trendColors = {
|
||||||
up: 'text-emerald-500',
|
up: 'text-emerald-500',
|
||||||
down: 'text-red-500',
|
down: 'text-red-500',
|
||||||
|
|
@ -182,122 +188,141 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
// if (isLoading) {
|
||||||
return (
|
// return (
|
||||||
<div className="w-full px-6 py-8">
|
// <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 (
|
||||||
|
<div className="w-full px-4 py-3 mt-2 rounded-md">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mb-2 gap-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{/* <div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
|
||||||
|
<FaFilter className="text-white text-lg" />
|
||||||
|
</div> */}
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-gray-800">Overview</h2>
|
||||||
|
{/* <p className="text-gray-600 text-sm">Monitor performance metrics</p> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cards */}
|
||||||
|
{isLoading ? <div className="w-full [h-50vh] px-6 py-8">
|
||||||
<div className="animate-pulse">
|
<div className="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(4)].map((_, i) => (
|
{[...Array(7)].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> :
|
||||||
);
|
<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>
|
||||||
|
|
||||||
return (
|
{/* Content */}
|
||||||
<div className="w-full px-6 py-6 bg-gradient-to-br from-slate-50 to-slate-100 mt-2 rounded-t-md">
|
<div className="relative z-10">
|
||||||
{/* Header */}
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mb-8 gap-4">
|
<div className={`p-2 rounded-lg border ${card.iconBg} group-hover:scale-110 transition-transform duration-200`}>
|
||||||
<div className="flex items-center gap-3">
|
{card.icon}
|
||||||
<div className="w-10 h-10 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
|
</div>
|
||||||
<FaFilter className="text-white text-lg" />
|
{renderTrendIndicator(card.trend, card.percentage)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-bold text-gray-800">Overview</h2>
|
<div className="space-y-1">
|
||||||
<p className="text-gray-600 text-sm">Monitor performance metrics</p>
|
<h3 className="text-xs font-medium text-gray-600 group-hover:text-gray-700 transition-colors">
|
||||||
</div>
|
{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 className="flex flex-wrap gap-3 w-full lg:w-auto">
|
}
|
||||||
{/* Account Selector */}
|
</div>
|
||||||
<div className="relative">
|
);
|
||||||
<select
|
};
|
||||||
value={selectedAccount}
|
|
||||||
onChange={(e) => setSelectedAccount(e.target.value)}
|
|
||||||
className="appearance-none text-sm text-gray-700 border border-gray-300 rounded-xl px-4 py-3 pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white shadow-sm hover:shadow-md transition-all cursor-pointer min-w-[140px]"
|
|
||||||
>
|
|
||||||
{accounts.map((account) => (
|
|
||||||
<option key={account} value={account}>{account}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<FaChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs pointer-events-none" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Date Filter */}
|
export default Cards;
|
||||||
<div className="relative">
|
|
||||||
<select
|
|
||||||
value={selectedFilter}
|
|
||||||
onChange={(e) => setSelectedFilter(e.target.value)}
|
|
||||||
className="appearance-none text-sm text-gray-700 border border-gray-300 rounded-xl px-4 py-3 pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white shadow-sm hover:shadow-md transition-all cursor-pointer min-w-[140px]"
|
|
||||||
>
|
|
||||||
{filters.map((filter) => (
|
|
||||||
<option key={filter} value={filter}>{filter}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<FaCalendarAlt className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs pointer-events-none" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Action Button */}
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* <div className="flex flex-wrap gap-3 w-full lg:w-auto">
|
||||||
|
{type === 'dashboard' && <>
|
||||||
|
Account Selector
|
||||||
|
<div className="relative">
|
||||||
|
<select
|
||||||
|
value={selectedAccount}
|
||||||
|
onChange={(e) => setSelectedAccount(e.target.value)}
|
||||||
|
className="appearance-none text-sm text-gray-700 border border-gray-300 rounded-xl px-4 py-3 pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white shadow-sm hover:shadow-md transition-all cursor-pointer min-w-[140px]"
|
||||||
|
>
|
||||||
|
{accounts.map((account) => (
|
||||||
|
<option key={account} value={account}>{account}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<FaChevronDown className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Date Filter
|
||||||
|
<div className="relative">
|
||||||
|
<select
|
||||||
|
value={selectedFilter}
|
||||||
|
onChange={(e) => setSelectedFilter(e.target.value)}
|
||||||
|
className="appearance-none text-sm text-gray-700 border border-gray-300 rounded-xl px-4 py-3 pr-10 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white shadow-sm hover:shadow-md transition-all cursor-pointer min-w-[140px]"
|
||||||
|
>
|
||||||
|
{filters.map((filter) => (
|
||||||
|
<option key={filter} value={filter}>{filter}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<FaCalendarAlt className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs pointer-events-none" />
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
|
||||||
|
Action Button
|
||||||
{buttonConfig && (
|
{buttonConfig && (
|
||||||
<button
|
<button
|
||||||
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}`}
|
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}`}
|
||||||
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;
|
|
||||||
|
|
@ -1,24 +1,193 @@
|
||||||
import React from 'react'
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import HomeTopNav from './HomeTopNav'
|
import AccountNav from '../accounts/AccountNav';
|
||||||
import UploadDoc from './UploadDoc'
|
import Cards from './Cards';
|
||||||
import FilterSort from './FilterSort'
|
import StartSection from './StartSection';
|
||||||
import ShowDocs from './ShowDocs'
|
import api from '../../config/api';
|
||||||
import Cards from './Cards'
|
import {
|
||||||
import StartSection from './StartSection'
|
FaCheckCircle,
|
||||||
import AccountNav from '../accounts/AccountNav'
|
FaClock,
|
||||||
|
FaHourglassHalf,
|
||||||
|
FaTasks,
|
||||||
|
FaChevronDown,
|
||||||
|
FaPlus,
|
||||||
|
FaUserPlus,
|
||||||
|
FaFilter,
|
||||||
|
FaCalendarAlt
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
|
||||||
const Home = () => {
|
interface CardData {
|
||||||
return (
|
title: string;
|
||||||
<>
|
count: number;
|
||||||
{/* <HomeTopNav /> */}
|
icon: JSX.Element;
|
||||||
<AccountNav/>
|
gradient: string;
|
||||||
<Cards />
|
shadowColor: string;
|
||||||
<StartSection />
|
iconBg: string;
|
||||||
{/* <UploadDoc /> */}
|
textColor: string;
|
||||||
{/* <FilterSort /> */}
|
percentage?: number;
|
||||||
{/* <ShowDocs /> */}
|
trend?: 'up' | 'down' | 'stable';
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default 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 (
|
||||||
|
<>
|
||||||
|
<AccountNav />
|
||||||
|
<Cards
|
||||||
|
onDateRangeChange={handleDateRangeChange}
|
||||||
|
dashboardData={dashboardData}
|
||||||
|
fromDate={fromDate}
|
||||||
|
toDate={toDate}
|
||||||
|
cards={cards}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
<StartSection />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
|
|
||||||
|
|
@ -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-6">Get Started</h2>
|
<h2 className="text-lg font-semibold text-gray-800 mb-2">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) => (
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ 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;
|
||||||
|
|
@ -33,7 +34,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 = 2000;
|
const delay = 1000;
|
||||||
|
|
||||||
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}`);
|
||||||
|
|
||||||
|
|
@ -66,6 +67,8 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted }) =>
|
||||||
lastUpdate = Date.now();
|
lastUpdate = Date.now();
|
||||||
}, delay - sinceLast);
|
}, delay - sinceLast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
docProgSSE.onerror = (error) => {
|
docProgSSE.onerror = (error) => {
|
||||||
|
|
@ -112,7 +115,7 @@ const DocumentCard: React.FC<DocumentCardProps> = ({ id, name, isCompleted }) =>
|
||||||
draggable
|
draggable
|
||||||
>
|
>
|
||||||
<IoMdClose
|
<IoMdClose
|
||||||
onClick={() => setDocumentDel({ docId: id, isDel: false }) }
|
onClick={() => setDocumentDel({ docId: id, isDel: false })}
|
||||||
className='absolute right-2.5 top-2.5 text-slate-400 text-xl hover:text-red-500 cursor-pointer transition-all' />
|
className='absolute right-2.5 top-2.5 text-slate-400 text-xl hover:text-red-500 cursor-pointer transition-all' />
|
||||||
{/* Draggable handle */}
|
{/* Draggable handle */}
|
||||||
<RiDraggable
|
<RiDraggable
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const ProcessStepOne = () => {
|
||||||
|
|
||||||
// const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder)
|
// const signingOrder = useSelector((state: RootState) => state.selectedRecipients.signingOrder)
|
||||||
// const user = useSelector((state: RootState) => state.user)
|
// const user = useSelector((state: RootState) => state.user)
|
||||||
|
|
||||||
const [emailDtls, setEmailDtls] = useState<EmailDtls>({ subject: "Documents for Signature", message: "" });
|
const [emailDtls, setEmailDtls] = useState<EmailDtls>({ subject: "Documents for Signature", message: "" });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -112,32 +112,26 @@ const ProcessStepOne = () => {
|
||||||
name: `${recipient.firstName} ${recipient.lastName}`
|
name: `${recipient.firstName} ${recipient.lastName}`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(recipientsData, "printing Recipient Data Line 114");
|
dispatch(addRecipientAsync({ recipientsData, accountId, transactionId }));
|
||||||
|
|
||||||
// await api.post(`/api/accounts/${accountId}/transactions/${transactionId}/recipients`, recipientsData).then((res) => {
|
dispatch(showLoader("Processing Message..."));
|
||||||
// console.log(res, "Response Line 117")
|
|
||||||
// }).catch((err) => {
|
|
||||||
// console.log(err)
|
|
||||||
// })
|
|
||||||
|
|
||||||
dispatch(addRecipientAsync({recipientsData, accountId, transactionId}));
|
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 {
|
||||||
// Preview & Tag
|
|
||||||
// Assuming you'd fetch or pass doc/recipients response here
|
|
||||||
// dispatch(setImages(...));
|
|
||||||
// dispatch(updateRecipientIds(...));
|
|
||||||
// dispatch(setTransactionId(...));
|
|
||||||
dispatch(resetDocuments())
|
|
||||||
dispatch(resetRecipients())
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
dispatch(resetDocuments())
|
||||||
|
dispatch(resetRecipients())
|
||||||
|
dispatch(hideLoader());
|
||||||
navigate(`/process-step-two/${accountId}/${transactionId}`);
|
navigate(`/process-step-two/${accountId}/${transactionId}`);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@ import { RootState } from '../../../store/store'
|
||||||
|
|
||||||
import api from '../../../config/api';
|
import api from '../../../config/api';
|
||||||
|
|
||||||
import RightSideBar from './RightSideBar';
|
import RightSideBar from './rightSideBar/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,8 +45,6 @@ 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();
|
||||||
|
|
@ -127,7 +125,29 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
||||||
accountId: string,
|
accountId: string,
|
||||||
transactionId: string
|
transactionId: string
|
||||||
) => {
|
) => {
|
||||||
const requests = Object.entries(tagsData).map(([recipientId, tags]) => {
|
// tagsData = tagsData.filter((tag, indx) => (("update" in tag && tag.update) || !("id" in tag)) )
|
||||||
|
// 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
|
||||||
|
|
@ -136,7 +156,12 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const responses = await Promise.all(requests);
|
const responses = await Promise.all(requests);
|
||||||
console.log("All recipient tags submitted:", responses);
|
const droppedItemsUpdateFalse = Object.fromEntries(
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +186,6 @@ 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
|
||||||
|
|
@ -177,6 +201,8 @@ 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 = {
|
||||||
|
|
@ -185,26 +211,28 @@ 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": 0,
|
"minLength": "minLength" in dropTag ? dropTag.minLength : 0,
|
||||||
"maxLength": 0,
|
"maxLength": "maxLength" in dropTag ? dropTag.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": "Great Vibes",
|
"font": "",
|
||||||
"italic": false,
|
"italic": false,
|
||||||
"bold": false,
|
"bold": false,
|
||||||
"underline": false,
|
"underline": false,
|
||||||
"color": "black",
|
"color": "black",
|
||||||
"textSize": 14.0
|
"textSize": 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": false,
|
"required": "required" in dropTag ? dropTag.required : true,
|
||||||
|
"update": "update" in dropTag ? dropTag.update : false,
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,9 +274,13 @@ 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
|
||||||
|
|
||||||
|
|
@ -258,6 +290,7 @@ 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;
|
||||||
|
|
@ -279,6 +312,7 @@ 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] : '',
|
||||||
|
|
@ -301,6 +335,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,7 +422,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 } } : item
|
i === index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : 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
|
||||||
});
|
});
|
||||||
|
|
@ -407,6 +442,7 @@ 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;
|
||||||
|
|
@ -443,7 +479,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 } } : item
|
i === index ? { ...item, dimensions: { ...item.dimensions, width: newWidth, height: newHeight }, update: true } : 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
|
||||||
});
|
});
|
||||||
|
|
@ -471,11 +507,11 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
|
||||||
if ("id" in tag) {
|
if ("id" in tag) {
|
||||||
try {
|
try {
|
||||||
const response = await api.delete(`/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipientId}/tags/${tag.id}`)
|
const response = await api.delete(`/api/accounts/${accountId}/transactions/${transactionId}/recipients/${recipientId}/tags/${tag.id}`)
|
||||||
if(response.status !== 200) throw "Tag Deletion error"
|
if (response.status !== 200) throw "Tag Deletion error"
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Tag Deletion error")
|
console.error("Tag Deletion error")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSelectedTag(null)
|
setSelectedTag(null)
|
||||||
setDroppedItems(prevState => ({
|
setDroppedItems(prevState => ({
|
||||||
|
|
@ -485,109 +521,152 @@ 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 handleAddRadio = (e: React.MouseEvent<HTMLElement>, key: string, index: number) => {
|
// // const lastRadioElemRect = e.currentTarget?.parentElement.previousSibling.getBoundingClientRect();
|
||||||
// const dataToSet: DroppedItemsObj = {
|
// // console.log(lastRadioElemRect, "lastRadioElemRect")
|
||||||
// ...droppedItems,
|
|
||||||
// [key]: [...(droppedItems[key] || []),
|
|
||||||
// {
|
|
||||||
// ...prepData,
|
|
||||||
// radioGroups: dragElemData.label === "radio" ? [{ ...prepData }] : []
|
|
||||||
// }],
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const lastRadioElemRect = e.currentTarget?.parentElement.previousSibling.getBoundingClientRect();
|
// const setNewData = droppedItems;
|
||||||
// console.log(lastRadioElemRect, "lastRadioElemRect")
|
|
||||||
|
|
||||||
const setNewData = droppedItems;
|
// if (Array.isArray(setNewData[key][index].radioGroups) && setNewData[key][index].radioGroups?.length > 0) {
|
||||||
|
// setNewData[key][index].radioGroups?.push(setNewData[key][index].radioGroups[0])
|
||||||
|
// }
|
||||||
|
|
||||||
if (Array.isArray(setNewData[key][index].radioGroups) && setNewData[key][index].radioGroups?.length > 0) {
|
// setDroppedItems(setNewData);
|
||||||
setNewData[key][index].radioGroups?.push(setNewData[key][index].radioGroups[0])
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// 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(setNewData);
|
setDroppedItems({
|
||||||
|
...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') => {
|
||||||
const handleRadioTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number, radioIndex: number) => {
|
handleDirectionalMove(direction, 5); // Use default stepSize of 5
|
||||||
|
};
|
||||||
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 (
|
||||||
|
|
@ -596,7 +675,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'>
|
||||||
|
|
@ -720,13 +799,18 @@ 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} />}
|
{selectedTag && <RightSideBar selectedTag={droppedItems[selectedTag.key][selectedTag.index]} setDroppedItems={setDroppedItems} selTagKey={selectedTag.key} selTagIndx={selectedTag.index} DirectionalControlsProps={{
|
||||||
|
onMove: directionalControlsHandler,
|
||||||
|
disabled: false,
|
||||||
|
stepSize: 5,
|
||||||
|
className: ''
|
||||||
|
}} />}
|
||||||
{/* Center Doc Container - END */}
|
{/* Center Doc Container - END */}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
const handlePointerDown = (event: React.DragEvent<HTMLDivElement>, label: string, recipientId: number, recipientIndx: number, tagsProperties: TagsProperties) => {
|
||||||
|
|
||||||
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,7 +27,8 @@ 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);
|
||||||
|
|
@ -41,7 +42,7 @@ interface FieldListProps {
|
||||||
|
|
||||||
const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx }) => {
|
const FieldList: React.FC<FieldListProps> = ({ color, recipientId, recipientIndx }) => {
|
||||||
|
|
||||||
const [tagsData, setTagsData] = useState([]);
|
const [tagsData, setTagsData] = useState<TagsProperties[]>([]);
|
||||||
|
|
||||||
const { accountId } = useParams<{ accountId: string }>();
|
const { accountId } = useParams<{ accountId: string }>();
|
||||||
if (!accountId) {
|
if (!accountId) {
|
||||||
|
|
@ -91,7 +92,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)}
|
onPointerDown={(e) => handlePointerDown(e, field.type.toLowerCase(), recipientId, recipientIndx, field)}
|
||||||
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`}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import { 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';
|
||||||
|
|
@ -25,28 +24,6 @@ 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'>
|
||||||
|
|
|
||||||
|
|
@ -1,653 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
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;
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
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;
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,472 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
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
|
||||||
|
|
@ -18,7 +18,9 @@ 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 = () => {
|
||||||
|
|
@ -155,7 +157,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:image/png;base64,${signature.signatureBlob}`}
|
src={`data:${signature.signatureMimeType};base64,${signature.signatureBlob}`}
|
||||||
className="h-[50px] w-[250px] object-contain"
|
className="h-[50px] w-[250px] object-contain"
|
||||||
alt="Signature"
|
alt="Signature"
|
||||||
/>
|
/>
|
||||||
|
|
@ -179,7 +181,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:image/png;base64,${signature.initialsBlob}`}
|
src={`data:${signature.initialsMimeType};base64,${signature.initialsBlob}`}
|
||||||
className="h-[50px] object-contain"
|
className="h-[50px] object-contain"
|
||||||
alt="DS Signature"
|
alt="DS Signature"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ 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 {
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,6 @@ 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) => {
|
||||||
|
|
|
||||||
|
|
@ -180,10 +180,9 @@ 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) => ({
|
||||||
|
|
@ -209,7 +208,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);
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,41 @@ 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",
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ 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: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue