From 8bb43d6ee57d262450d1d7e094d0b9968b2760ba Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Fri, 11 Jul 2025 12:23:11 +0530 Subject: [PATCH] Process step two , dashboard , navbar --- src/App.tsx | 1 + src/components/accounts/Account.tsx | 149 +++++++- src/components/accounts/AccountNav.tsx | 172 ++++++++- src/components/accounts/Accounts.tsx | 3 +- src/components/dashboard/SideBar.tsx | 7 +- src/components/home/Cards.tsx | 345 ++++++++++-------- src/components/home/Home.tsx | 211 +++++++++-- src/components/home/StartSection.tsx | 2 +- .../processStepTwo/DocumentContainer.tsx | 76 +++- .../rightSideBar/DirectionalControls.tsx | 120 ++++++ .../rightSideBar/MaxMinLenAttr.tsx | 61 +++- .../rightSideBar/RecipientsAttrCon.tsx | 2 +- .../rightSideBar/RightSideBar.tsx | 167 +++++++-- .../rightSideBar/SelectTagAttr.tsx | 40 +- .../setting/Signature/Signature.tsx | 8 +- 15 files changed, 1090 insertions(+), 274 deletions(-) create mode 100644 src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx diff --git a/src/App.tsx b/src/App.tsx index c7efee9..b8c8d23 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -47,6 +47,7 @@ const App = () => { }> } /> } /> + } /> } /> } /> diff --git a/src/components/accounts/Account.tsx b/src/components/accounts/Account.tsx index 0006088..fee10b2 100644 --- a/src/components/accounts/Account.tsx +++ b/src/components/accounts/Account.tsx @@ -7,6 +7,17 @@ import { FaCheck } from "react-icons/fa"; import Cards from "../home/Cards"; import AddInviteModal from "./AddInviteModal"; import { Bounce, toast } from "react-toastify"; +import { + FaCheckCircle, + FaClock, + FaHourglassHalf, + FaTasks, + FaChevronDown, + FaPlus, + FaUserPlus, + FaFilter, + FaCalendarAlt +} from 'react-icons/fa'; interface Transaction { id: number; @@ -21,31 +32,152 @@ const formatDate = (timestamp: number) => { return new Date(timestamp * 1000).toLocaleString(); }; + + const Account = () => { - const { accountId } = useParams<{ accountId: string }>(); + const mapApiDataToCards = (data: any): CardData[] => { + return [ + { + title: 'Action Needed', + count: data.actionNeeded || 0, + icon: , + 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: , + 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: , + 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: , + 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: , + 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: , + 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: , + 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([]); const [selectedTransactions, setSelectedTransactions] = useState([]); const [accountsData, setAccountsData] = useState([]) const [inviteLoading, setInviteLoading] = useState(false); + const [cards, setCards] = useState([]); + const [accountID, setAccountID] = useState() + const [isLoading, setIsLoading] = 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 = () => { setIsModalOpen(true); }; const fetchTransactions = async () => { + console.log("fetchTransactions") try { - const response = await api(`/api/accounts/${accountId}/transactions`); + const response = await api(`/api/accounts/${accountID}/transactions`); setTransactions(response.data || []); } catch (error) { console.error("Error fetching transactions:", error); } }; - useEffect(() => { - if (accountId) fetchTransactions(); - }, [accountId]); + // useEffect(() => { + // if (accountID) fetchTransactions(); + // }, [accountID]); const getAccountDetails = async () => { try { @@ -65,7 +197,7 @@ const Account = () => { const navigate = useNavigate(); 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 }) => { @@ -98,9 +230,12 @@ const Account = () => { } }; + + return ( <> - + {transactions.length > 0 && (
diff --git a/src/components/accounts/AccountNav.tsx b/src/components/accounts/AccountNav.tsx index 9239ca7..9e6ac4f 100644 --- a/src/components/accounts/AccountNav.tsx +++ b/src/components/accounts/AccountNav.tsx @@ -1,12 +1,15 @@ import React, { useEffect, useRef, useState } from 'react'; // import { useSelector } from 'react-redux'; 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 { FaUser, FaSignOutAlt, FaBuilding, FaChevronDown, FaCheck } from 'react-icons/fa'; // import { RootState } from '../../store/store'; // import { UserState } from '../../store/slices/userSlice'; + + interface AccountNavProps { accountName?: string | null; transactionName?: string | null; @@ -28,9 +31,20 @@ interface UserData { account: Account; } +interface AccountDataArray { + accountId: number, accountName: string, role: string, status: boolean +} + const AccountNav: React.FC = ({ accountName = null, transactionName = null }) => { // 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([]); + const [hasModalBeenClosed, setHasModalBeenClosed] = useState(false); // Track if modal was closed + const dropdownRef = useRef(null); + const [user, setUser] = useState({ id: "", firstName: "", @@ -44,6 +58,62 @@ const AccountNav: React.FC = ({ 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 getUserDetails = async () => { @@ -74,22 +144,26 @@ const AccountNav: React.FC = ({ accountName = null, transaction // Add more routes here if needed }; - const [open, setOpen] = useState(false); - const dropdownRef = useRef(null); + // const [open, setOpen] = useState(false); + // const dropdownRef = useRef(null); // Close dropdown when clicked outside useEffect(() => { function handleClickOutside(event: MouseEvent) { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + if (open) { + setHasModalBeenClosed(true); // Mark modal as closed when clicking outside + } setOpen(false); + setAccountsOpen(false); // Also close accounts dropdown } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); - }, []); + }, [open]); - const handleLogout = async() => { + const handleLogout = async () => { try { let res = await api.post('/api/auth/logout') if (res.status === 200) { @@ -101,12 +175,37 @@ const AccountNav: React.FC = ({ 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) => segment .replace(/[-_]/g, " ") .replace(/\b\w/g, (char) => char.toUpperCase()); + const ShimmerRow = () => ( +
+
+
+
+
+
+
+
+
+
+ ); + + return (
- - - -
{/* User Dropdown */} @@ -148,7 +243,7 @@ const AccountNav: React.FC = ({ accountName = null, transaction {/* Avatar Button */}
+ {/* Account List Section */} +
+ + + {/* Accounts Dropdown */} + {accountsOpen && ( +
+ {loadingAccounts ? ( +
+ + +
+ ) : ( +
+ {accounts.length > 0 ? (accounts?.map((account) => ( + + ))) : No Account...} + +
+ )} +
+ )} +
+ {/* Logout Section */}
diff --git a/src/components/dashboard/SideBar.tsx b/src/components/dashboard/SideBar.tsx index 31e99ae..b06c077 100644 --- a/src/components/dashboard/SideBar.tsx +++ b/src/components/dashboard/SideBar.tsx @@ -10,6 +10,7 @@ import { IoSettingsOutline } from "react-icons/io5"; import axios from 'axios'; import { MdManageAccounts } from 'react-icons/md'; +import { GrTransaction } from 'react-icons/gr'; type SideBarProps = { showSideBar: boolean; @@ -24,7 +25,8 @@ const menuPathMap: MenuPathMap = { '/': 'Home', '/templates': 'Templates', '/s/contact': 'Contact', - '/accounts': 'Accounts', + // '/accounts': 'Accounts', + 'transactions' : "Account" }; const SideBar: React.FC = ({ showSideBar, setShowSideBar }) => { @@ -61,7 +63,8 @@ const SideBar: React.FC = ({ showSideBar, setShowSideBar }) => { } label="Home" showSideBar={showSideBar} menuPathMap={menuPathMap} /> } label="Templates" showSideBar={showSideBar} menuPathMap={menuPathMap} /> - } label="Accounts" showSideBar={showSideBar} menuPathMap={menuPathMap} /> + {/* } label="Accounts" showSideBar={showSideBar} menuPathMap={menuPathMap} /> */} + } label="Transactions" showSideBar={showSideBar} menuPathMap={menuPathMap} /> diff --git a/src/components/home/Cards.tsx b/src/components/home/Cards.tsx index 3a8802a..06fa7d2 100644 --- a/src/components/home/Cards.tsx +++ b/src/components/home/Cards.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from 'react'; -import { - FaCheckCircle, - FaClock, - FaHourglassHalf, +import { + FaCheckCircle, + FaClock, + FaHourglassHalf, FaTasks, FaChevronDown, FaPlus, @@ -24,54 +24,30 @@ interface CardData { 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 { type?: 'dashboard' | 'account' | 'transactions'; 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: , - 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: , - 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: , - 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: , - 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[] = [ { @@ -94,7 +70,7 @@ const AccountData: CardData[] = [ textColor: 'text-purple-700', trend: 'stable' }, - + { title: 'Completed', count: 27, @@ -120,23 +96,53 @@ const AccountData: CardData[] = [ const filters = ['Last 7 Days', 'Last 30 Days', 'Last 6 Months', 'This Year']; const accounts = ['All Accounts', 'Account 1', 'Account 2', 'Account 3']; -const Cards: React.FC = ({ type = 'dashboard', onOpenModal }) => { +const Cards: React.FC = ({ type = 'dashboard', onOpenModal, onDateRangeChange, cards, isLoading }) => { const [selectedFilter, setSelectedFilter] = useState('Last 7 Days'); const [selectedAccount, setSelectedAccount] = useState('All Accounts'); const [showCards, setShowCards] = useState([]); - const [isLoading, setIsLoading] = useState(true); + // const [isLoading, setIsLoading] = useState(true); + + // useEffect(() => { + // setIsLoading(true); + // setTimeout(() => { + // if (type === 'account') { + // setShowCards(AccountData); + // } else { + // setShowCards(cards); + // } + // setIsLoading(false); + // }, 500); + // }, [type]); useEffect(() => { - setIsLoading(true); - setTimeout(() => { - if (type === 'account') { - setShowCards(AccountData); - } else { - setShowCards(cards); + + if (type === 'dashboard') { + const now = new Date(); + let fromDate = new Date(); + + 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); - }, [type]); + + const from = fromDate.toISOString().split('T')[0]; + const to = now.toISOString().split('T')[0]; + + onDateRangeChange({ from, to }); + } + }, [selectedFilter]); const getButtonConfig = () => { switch (type) { @@ -161,7 +167,7 @@ const Cards: React.FC = ({ type = 'dashboard', onOpenModal }) => { const renderTrendIndicator = (trend: string | undefined, percentage: number | undefined) => { if (!trend || !percentage) return null; - + const trendColors = { up: 'text-emerald-500', down: 'text-red-500', @@ -182,122 +188,141 @@ const Cards: React.FC = ({ type = 'dashboard', onOpenModal }) => { ); }; - if (isLoading) { - return ( -
+ // if (isLoading) { + // return ( + //
+ //
+ //
+ //
+ // {[...Array(7)].map((_, i) => ( + //
+ // ))} + //
+ //
+ //
+ // ); + // } + + return ( +
+ {/* Header */} +
+
+ {/*
+ +
*/} +
+

Overview

+ {/*

Monitor performance metrics

*/} +
+
+ + +
+ + {/* Cards */} + {isLoading ?
- {[...Array(4)].map((_, i) => ( + {[...Array(7)].map((_, i) => (
))}
-
- ); - } +
: +
+ {cards.map((card, index) => ( +
+ {/* Background Pattern */} +
+
+
+
- return ( -
- {/* Header */} -
-
-
- -
-
-

Overview

-

Monitor performance metrics

-
+ {/* Content */} +
+
+
+ {card.icon} +
+ {renderTrendIndicator(card.trend, card.percentage)} +
+ +
+

+ {card.title} +

+
+

+ {card.count} +

+ items +
+
+
+ + {/* Hover Effect */} +
+
+ ))}
-
- {/* Account Selector */} -
- - -
+ } +
+ ); +}; - {/* Date Filter */} -
- - -
+export default Cards; - {/* Action Button */} + + + +{/*
+ {type === 'dashboard' && <> + Account Selector +
+ + +
+ + Date Filter +
+ + +
+ } + + Action Button {buttonConfig && ( - )} -
-
- - {/* Cards */} -
- {showCards.map((card, index) => ( -
- {/* Background Pattern */} -
-
-
-
- - {/* Content */} -
-
-
- {card.icon} -
- {renderTrendIndicator(card.trend, card.percentage)} -
- -
-

- {card.title} -

-
-

- {card.count} -

- items -
-
-
- - {/* Hover Effect */} -
-
- ))} -
-
- ); -}; - -export default Cards; \ No newline at end of file +
*/} \ No newline at end of file diff --git a/src/components/home/Home.tsx b/src/components/home/Home.tsx index 8b7e70b..b772ece 100644 --- a/src/components/home/Home.tsx +++ b/src/components/home/Home.tsx @@ -1,24 +1,193 @@ -import React from 'react' -import HomeTopNav from './HomeTopNav' -import UploadDoc from './UploadDoc' -import FilterSort from './FilterSort' -import ShowDocs from './ShowDocs' -import Cards from './Cards' -import StartSection from './StartSection' -import AccountNav from '../accounts/AccountNav' +import React, { useEffect, useState, useCallback } from 'react'; +import AccountNav from '../accounts/AccountNav'; +import Cards from './Cards'; +import StartSection from './StartSection'; +import api from '../../config/api'; +import { + FaCheckCircle, + FaClock, + FaHourglassHalf, + FaTasks, + FaChevronDown, + FaPlus, + FaUserPlus, + FaFilter, + FaCalendarAlt +} from 'react-icons/fa'; -const Home = () => { - return ( - <> - {/* */} - - - - {/* */} - {/* */} - {/* */} - - ) +interface CardData { + title: string; + count: number; + icon: JSX.Element; + gradient: string; + shadowColor: string; + iconBg: string; + textColor: string; + percentage?: number; + trend?: 'up' | 'down' | 'stable'; } -export default Home \ No newline at end of file +const Home = () => { + const mapApiDataToCards = (data: any): CardData[] => { + return [ + { + title: 'Action Needed', + count: data.actionNeeded || 0, + icon: , + 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: , + 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: , + 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: , + 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: , + // 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: , + // 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: , + // 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(null); + const [fromDate, setFromDate] = useState(''); // ISO string + const [toDate, setToDate] = useState(''); // ISO string + const [cards, setCards] = useState([]); + const [accountID, setAccountID] = useState() + const [isLoading, setIsLoading] = useState(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 ( + <> + + + + + ); +}; + +export default Home; diff --git a/src/components/home/StartSection.tsx b/src/components/home/StartSection.tsx index 05c5ee7..70b989b 100644 --- a/src/components/home/StartSection.tsx +++ b/src/components/home/StartSection.tsx @@ -77,7 +77,7 @@ const StartSection = () => { return (
-

Get Started

+

Get Started

{cards.map((card, index) => ( diff --git a/src/components/process/processStepTwo/DocumentContainer.tsx b/src/components/process/processStepTwo/DocumentContainer.tsx index 03db2a8..99d6f4d 100644 --- a/src/components/process/processStepTwo/DocumentContainer.tsx +++ b/src/components/process/processStepTwo/DocumentContainer.tsx @@ -14,6 +14,7 @@ import { fetchRecipients } from '../../../store/slices/selectedRecipientsSlice'; import { fetchDocuments } from '../../../store/slices/documentSlice'; import { FaQuestionCircle } from 'react-icons/fa'; import typeToIconMap from '../../../helper/iconMap'; +import Loader from '../../dashboard/Loader'; export interface DroppedItemsObj { [key: string]: DroppedItem[] @@ -210,23 +211,24 @@ const DocumentContainer: React.FC = ({ isSend }) => { "dimensions": dropTag.dimensions, "type": dropTag.type.toUpperCase(), // "subType": (dropTag.subType ? dropTag.subType.toUpperCase() : null), - validation: { - "minLength": 0, - "maxLength": 0, - "error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error", - "regex": "regex" in dropTag ? dropTag.regex : [] - }, + // validation: { + "minLength": "minLength" in dropTag ? dropTag.minLength : 0, + "maxLength": "maxLength" in dropTag ? dropTag.maxLength : 0, + "error": ("error" in dropTag && dropTag.error) ? `${dropTag.error}`.toUpperCase() : "error", + "regex": "regex" in dropTag ? dropTag.regex : [] + // } + , "documentId": dropTag.documentId, "options": ("options" in dropTag) ? dropTag.options : [], "pageIndex": dropTag.pageIndex, "recipientId": dropTag.recipientId, "styles": "styles" in dropTag ? dropTag.styles : { - "font": "Great Vibes", + "font": "", "italic": false, "bold": false, "underline": false, "color": "black", - "textSize": 14.0 + "textSize": 0 }, // "required": (dropTag.type.toUpperCase() !== "SIGNATURE" && dropTag.type.toUpperCase() !== "INITIALS") ? dropTag.required : true, "required": "required" in dropTag ? dropTag.required : true, @@ -288,6 +290,7 @@ const DocumentContainer: React.FC = ({ isSend }) => { // HANDLE DROP - START const handleDrop = (event: React.DragEvent, docId: string, documentId: number, pageIndex: number) => { + console.log("// HANDLE DROP - START") event.preventDefault(); const data = window.dragElemData; @@ -439,6 +442,7 @@ const DocumentContainer: React.FC = ({ isSend }) => { // HANDLE EXPAND TAG - START const handleExpandTag = (event: React.MouseEvent, key: string, index: number) => { + console.log("HANDLE EXPAND TAG - START") event.preventDefault(); // Prevent text selection issues const expandingElemIcon = event.currentTarget; @@ -620,13 +624,58 @@ const DocumentContainer: React.FC = ({ isSend }) => { // } + // 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({ + ...droppedItems, + [key]: Array.isArray(droppedItems[key]) + ? droppedItems[key].map((item, i) => + i === index + ? { ...item, dimensions: { ...item.dimensions, left: newLeft, top: newTop }, update: true } + : item + ) + : [], + }); + }; + + // Create a wrapper that matches DirectionalControls' expected signature + const directionalControlsHandler = (direction: 'up' | 'down' | 'left' | 'right') => { + handleDirectionalMove(direction, 5); // Use default stepSize of 5 + }; + + return ( <> {/* Center Doc Container - START */}
{ - selectedDocuments.length > 0 && selectedDocuments.map((doc, indxDoc) => { + selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => { return (
@@ -750,13 +799,18 @@ const DocumentContainer: React.FC = ({ isSend }) => {
) - }) + }) : }
- {selectedTag && } + {selectedTag && } {/* Center Doc Container - END */} ) diff --git a/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx b/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx new file mode 100644 index 0000000..4644623 --- /dev/null +++ b/src/components/process/processStepTwo/rightSideBar/DirectionalControls.tsx @@ -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 = ({ + 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 ( +
+ {/* Top Row */} +
+
+ + Top: {Math.round(top)}px +
+
+ + {/* Middle Row */} +
+ + Left: {Math.round(left)}px +
+ +
+ +
+ + Right: {Math.round(right)}px +
+ + {/* Bottom Row */} +
+
+ + Bottom: {Math.round(bottom)}px +
+
+
+ ); +}; + +export default DirectionalControls; diff --git a/src/components/process/processStepTwo/rightSideBar/MaxMinLenAttr.tsx b/src/components/process/processStepTwo/rightSideBar/MaxMinLenAttr.tsx index 67f4196..6c2fbdc 100644 --- a/src/components/process/processStepTwo/rightSideBar/MaxMinLenAttr.tsx +++ b/src/components/process/processStepTwo/rightSideBar/MaxMinLenAttr.tsx @@ -1,29 +1,68 @@ -import React from 'react' +import React, { useRef } from 'react'; interface MaxMinLenAttrIntr { maxLength: number; minLength: number; + handlePropertyChange: (changeTo: string, changeWith: string | boolean | number) => void; } -const MaxMinLenAttr : React.FC = ({ maxLength, minLength }) => { +const MaxMinLenAttr: React.FC = ({ maxLength, minLength, handlePropertyChange }) => { + const firstChangedField = useRef(null); + + const handleMaxChange = (e: React.ChangeEvent) => { + 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) => { + 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 ( <>
- + + min={0} + className="outline-none border-1 rounded px-2 py-1 border-slate-200" + onChange={handleMaxChange} + />
- + + min={0} + className="outline-none border-1 rounded px-2 py-1 border-slate-200" + onChange={handleMinChange} + />
- ) -} + ); +}; -export default MaxMinLenAttr \ No newline at end of file +export default MaxMinLenAttr; diff --git a/src/components/process/processStepTwo/rightSideBar/RecipientsAttrCon.tsx b/src/components/process/processStepTwo/rightSideBar/RecipientsAttrCon.tsx index 7490575..78fc345 100644 --- a/src/components/process/processStepTwo/rightSideBar/RecipientsAttrCon.tsx +++ b/src/components/process/processStepTwo/rightSideBar/RecipientsAttrCon.tsx @@ -65,7 +65,7 @@ const RecipientsAttrCon: React.FC = ({ selectedRecipientI 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() + handlePropertyChange('recipientId', recipient.id); setTimeout(() => { setDropDownToggle(false); }, 0) diff --git a/src/components/process/processStepTwo/rightSideBar/RightSideBar.tsx b/src/components/process/processStepTwo/rightSideBar/RightSideBar.tsx index 6203764..5747f6e 100644 --- a/src/components/process/processStepTwo/rightSideBar/RightSideBar.tsx +++ b/src/components/process/processStepTwo/rightSideBar/RightSideBar.tsx @@ -16,12 +16,27 @@ 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> selTagKey: string, - selTagIndx: number + selTagIndx: number, + DirectionalControlsProps: { + onMove: (direction: 'up' | 'down' | 'left' | 'right', stepSize?: number) => void; + disabled?: boolean; + stepSize?: number; + className?: string; + } } export enum TEXT { @@ -77,15 +92,32 @@ export enum DROPDOWNTOGGLE { DDMMYY_FORMAT } -const RightSideBar: React.FC = ({ selectedTag, setDroppedItems, selTagKey, selTagIndx }) => { +const defaultStyles = { + font: "", + italic: false, + bold: false, + underline: false, + color: "#000000", + textSize: 0 +}; + + + +const RightSideBar: React.FC = ({ 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(null); const [decimalPlace, setDecimalPlace] = useState(2) const [styleOpen, setStyleOpen] = useState(false); - const handlePropertyChange = (changeTo: string, changeWith: string | boolean | number, is24Hrs=null) => { + const handlePropertyChange = (changeTo: string, changeWith: string | boolean | number, is24Hrs = null) => { console.log("Handle Property Change Called", "changeTo", changeTo, "changeWith", changeWith) if (changeTo === '' || changeWith === '') return @@ -150,16 +182,23 @@ const RightSideBar: React.FC = ({ selectedTag, setDroppedItems )); } else { - setDroppedItems((prev) => ( - { + 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]: Array.isArray(prev[selTagKey]) // Ensure it's an array - ? prev[selTagKey].map((item, i) => - i === selTagIndx ? { ...item, [changeTo]: changeWith, update: true } : item - ) - : [], - }// Provide a fallback in case it's undefined or not an array - )); + [selTagKey]: updatedItems, + }; + }); + } setDropDownToggle(null) } @@ -193,24 +232,67 @@ const RightSideBar: React.FC = ({ selectedTag, setDroppedItems handlePropertyChange("decimalPlace", val) } - useEffect(() => { - if ("styles" in selectedTag) { - const fontSize = selectedTag?.styles?.textSize; - const tagHeight = selectedTag?.dimensions?.height - 18; + // useEffect(() => { + // if ("styles" in selectedTag) { + // console.log("styles" in selectedTag) + // const fontSize = selectedTag?.styles?.textSize; + // const tagHeight = selectedTag?.dimensions?.height - 18; - 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) - } + // 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 }); } - }, [selectedTag]) + + // 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 */} -
+

Properties


@@ -220,11 +302,20 @@ const RightSideBar: React.FC = ({ selectedTag, setDroppedItems } + + + {selectedTag.label && } {(selectedTag.type !== TagsTypes.SIGNATURE && selectedTag.type !== TagsTypes.INITIALS) && - } + + <> + + + + + } {selectedTag.type.toLowerCase() === TagsTypes.TEMPORAL && } @@ -290,11 +381,13 @@ const RightSideBar: React.FC = ({ selectedTag, setDroppedItems } { - selectedTag.type === TagsTypes.TEXT && - + (selectedTag.type.toLowerCase() === TagsTypes.TEXT || selectedTag.type.toLowerCase() === TagsTypes.COMPANY) && + } + + {
= ({ selectedTag, setDroppedItems 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)} />
@@ -323,6 +418,10 @@ const RightSideBar: React.FC = ({ selectedTag, setDroppedItems // 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) + } />
@@ -332,21 +431,27 @@ const RightSideBar: React.FC = ({ selectedTag, setDroppedItems handleStyleChange("color", e.target.value)} />
{/* Toggles for Bold, Italic, Underline */}
diff --git a/src/components/process/processStepTwo/rightSideBar/SelectTagAttr.tsx b/src/components/process/processStepTwo/rightSideBar/SelectTagAttr.tsx index 306ec4a..0ec0d4b 100644 --- a/src/components/process/processStepTwo/rightSideBar/SelectTagAttr.tsx +++ b/src/components/process/processStepTwo/rightSideBar/SelectTagAttr.tsx @@ -15,22 +15,32 @@ const SelectTagAttr: React.FC = ({ options, selTagKey, selTagI const [selectedOption, setSelectedOption] = useState() 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], update: true }; - } - return item; - }) - : [], - } - )); - } + 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) => ( diff --git a/src/components/setting/Signature/Signature.tsx b/src/components/setting/Signature/Signature.tsx index e8a18e7..2b0b901 100644 --- a/src/components/setting/Signature/Signature.tsx +++ b/src/components/setting/Signature/Signature.tsx @@ -18,7 +18,9 @@ interface Signature { signatureHash: string; signatureBlob: string; signatureStampedBlob: string; - default: boolean + default: boolean; + signatureMimeType:string; + initialsMimeType:string } const SignatureManager: React.FC = () => { @@ -155,7 +157,7 @@ const SignatureManager: React.FC = () => { {/* Signature Image */}
Signature @@ -179,7 +181,7 @@ const SignatureManager: React.FC = () => { {/* Signature Image */}
DS Signature