SME-Web/src/components/accounts/Account.tsx

350 lines
14 KiB
TypeScript

import { useEffect, useState } from "react";
import { PiDotsThreeOutlineVerticalFill } from "react-icons/pi";
import AccountNav from "./AccountNav";
import api from "../../config/api";
import { useNavigate, useParams } from "react-router-dom";
import { FaCheck } from "react-icons/fa";
import 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;
status: string;
active: boolean;
subject: string | null;
createdAt: number;
expireAfter: number | null;
}
const formatDate = (timestamp: number) => {
return new Date(timestamp * 1000).toLocaleString();
};
const Account = () => {
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 [activeAccountId, setActiveAccountId] = useState<string | null>(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
const [currentEmailId, setCurrentEmailId] = useState<string | null>('null');
console.log("activeAccountId Trans.", activeAccountId)
// const { accountId } = useParams<{ accountId: string }>();
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [selectedTransactions, setSelectedTransactions] = useState<number[]>([]);
const [accountsData, setAccountsData] = useState([])
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 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()
// }, [])
useEffect(() => {
const userDetails = JSON.parse(sessionStorage.getItem('userDetails'));
setCurrentEmailId(userDetails?.email)
}, []);
// Listen for sessionStorage changes
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'activeAccountId') {
const newActiveAccountId = e.newValue ? JSON.parse(e.newValue) : null;
setActiveAccountId(newActiveAccountId);
}
};
// Listen for storage events (works for changes from other tabs)
window.addEventListener('storage', handleStorageChange);
// Custom event listener for same-tab changes
const handleCustomStorageChange = (e: CustomEvent) => {
if (e.detail.key === 'activeAccountId') {
setActiveAccountId(e.detail.newValue);
}
};
window.addEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
return () => {
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
};
}, []);
const getDashboardData = async () => {
setIsLoading(true)
const payload = {
account: activeAccountId
};
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)
}
};
const fetchTransactions = async () => {
console.log("fetchTransactions")
try {
const response = await api(`/api/accounts/${activeAccountId}/transactions`);
setTransactions(response.data || []);
} catch (error) {
console.error("Error fetching transactions:", error);
}
};
useEffect(() => {
if (activeAccountId) {
getDashboardData()
fetchTransactions();
}
}, [activeAccountId]);
const handleOpenModal = () => {
setIsModalOpen(true);
};
// useEffect(() => {
// if (accountID) fetchTransactions();
// }, [accountID]);
// const getAccountDetails = async () => {
// try {
// let res = await api.get('/api/user/accounts')
// let items = res?.data
// setAccountsData(items)
// } catch (error) {
// console.log(error)
// }
// }
// useEffect(() => {
// getAccountDetails()
// }, [])
const navigate = useNavigate();
const handleTransactionClick = (id: number) => {
navigate(`/accounts/${activeAccountId}/transactions/${id}`);
};
const handleInvite = async (data: { i_accountName: number; email: string; role: string }) => {
setInviteLoading(true);
try {
const payloadInvite = {
email: data.email,
role: data.role
}
let res = await api.post(`/api/accounts/${data.i_accountName}/invite`, payloadInvite)
if (res?.data) {
toast.success("Invite Sent Successfully...!", {
position: "top-center",
autoClose: 1500,
hideProgressBar: false,
closeOnClick: false,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
transition: Bounce,
});
await fetchTransactions()
setIsModalOpen(false);
}
} catch (err) {
console.error('Failed to invite', err);
} finally {
setInviteLoading(false);
}
};
return (
<>
<Cards type="transactions" onOpenModal={handleOpenModal} cards={cards}
isLoading={isLoading} />
{transactions.length > 0 && (
<div className="overflow-x-auto py-2">
<table className="min-w-full text-sm border border-gray-200 rounded bg-white shadow-sm">
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider">
<tr>
<th className="px-4 py-3 text-left"></th>
<th className="px-4 py-3 text-center">Name</th>
<th className="px-4 py-3 text-center">Status</th>
<th className="px-4 py-3 text-center">Active</th>
<th className="px-4 py-3 text-center">Last Change</th>
<th className="px-4 py-3 text-center">Expire After (days)</th>
<th className="px-4 py-3 text-center">Sign</th>
<th className="px-4 py-3 text-center">Action</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{transactions.map((tx) => {
const lastModifiedAt = new Date(tx.lastModifiedAt, "DD/MM/YYYY");
return (
<tr
key={tx.id}
onClick={() => handleTransactionClick(tx.id)}
className="hover:bg-gray-50 cursor-pointer"
>
<td className="px-4 py-3">
<div className="w-5 h-5 rounded border border-slate-300 flex justify-center items-center">
{selectedTransactions.includes(tx.id) && (
<FaCheck className="text-purple-600 text-sm" />
)}
</div>
</td>
<td className="px-4 py-3 text-center">
{tx.subject || "Untitled"}
</td>
<td className="px-4 py-3 text-center">
<span className="px-2 py-0.5 rounded-xl text-xs font-medium bg-blue-100 text-blue-700">
{tx.status}
</span>
</td>
<td className="px-4 py-3 text-center">
{tx.active ? "Yes" : "No"}
</td>
<td className="px-4 py-3 text-center">
{formatDate(tx.lastModifiedAt)}
</td>
<td className="px-4 py-3 text-center">
{tx.expireAfter ?? "-"}
</td>
<td className="px-4 py-3 text-center ">
{tx?.createdBy?.email === currentEmailId ? <button className="px-2 py-0.5 rounded-xl text-xs font-medium bg-green-100 text-green-700 cursor-pointer">Sign now</button> : "-"}
</td>
<td className="px-4 py-3 text-center">
<PiDotsThreeOutlineVerticalFill className="text-lg text-slate-300 hover:text-black mx-auto" />
</td>
</tr>
)
})}
</tbody>
</table>
</div>
)}
{isModalOpen && <AddInviteModal onClose={() => setIsModalOpen(false)} onInvite={handleInvite} inviteLoading={inviteLoading}
accountOptions={accountsData} />}
</>
);
};
export default Account;