321 lines
12 KiB
JavaScript
321 lines
12 KiB
JavaScript
import React, { useState } from "react";
|
|
import { useDeleteExpense, useExpenseList } from "../../hooks/useExpense";
|
|
import Avatar from "../common/Avatar";
|
|
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
|
|
import { formatDate, formatUTCToLocalTime } from "../../utils/dateUtils";
|
|
import Pagination from "../common/Pagination";
|
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
|
import { AppColorconfig, getColorNameFromHex } from "../../utils/appUtils";
|
|
import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
|
|
import ConfirmModal from "../common/ConfirmModal";
|
|
import { useProfile } from "../../hooks/useProfile";
|
|
|
|
const ExpenseList = ({filters}) => {
|
|
const [deletingId, setDeletingId] = useState(null);
|
|
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
|
const { setViewExpense, setManageExpenseModal } = useExpenseContext();
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const pageSize = 10;
|
|
const { profile } = useProfile();
|
|
|
|
|
|
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
|
|
const { data, isLoading, isError, isInitialLoading, error, isFetching } =
|
|
useExpenseList(10, currentPage, filters);
|
|
|
|
const handleDelete = (id) => {
|
|
setDeletingId(id);
|
|
DeleteExpense(
|
|
{ id },
|
|
{
|
|
onSettled: () => {
|
|
setDeletingId(null);
|
|
setIsDeleteModalOpen(false);
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
if (isInitialLoading) return <ExpenseTableSkeleton />;
|
|
if (isError) return <div>{error}</div>;
|
|
const items = data?.data ?? [];
|
|
const totalPages = data?.totalPages ?? 1;
|
|
const hasMore = currentPage < totalPages;
|
|
|
|
const paginate = (page) => {
|
|
if (page >= 1 && page <= totalPages) {
|
|
setCurrentPage(page);
|
|
}
|
|
};
|
|
const STATUS_ORDER = [
|
|
"Draft",
|
|
"Review Pending",
|
|
"Approval Pending",
|
|
"Process Pending",
|
|
"Processed",
|
|
"Paid",
|
|
"Rejected",
|
|
];
|
|
|
|
const groupExpensesByDateAndStatus = (expenses) => {
|
|
const grouped = {};
|
|
|
|
expenses.forEach((expense) => {
|
|
const dateKey = expense.transactionDate.split("T")[0];
|
|
if (!grouped[dateKey]) grouped[dateKey] = [];
|
|
grouped[dateKey].push(expense);
|
|
});
|
|
|
|
const sortedDates = Object.keys(grouped).sort(
|
|
(a, b) => new Date(b) - new Date(a)
|
|
);
|
|
|
|
return sortedDates.map((date) => ({
|
|
date,
|
|
expenses: grouped[date].sort((a, b) => {
|
|
return (
|
|
STATUS_ORDER.indexOf(a.status.name) -
|
|
STATUS_ORDER.indexOf(b.status.name)
|
|
);
|
|
}),
|
|
}));
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{IsDeleteModalOpen && (
|
|
<div
|
|
className={`modal fade ${IsDeleteModalOpen ? "show" : ""}`}
|
|
tabIndex="-1"
|
|
role="dialog"
|
|
style={{
|
|
display: IsDeleteModalOpen ? "block" : "none",
|
|
backgroundColor: IsDeleteModalOpen
|
|
? "rgba(0,0,0,0.5)"
|
|
: "transparent",
|
|
}}
|
|
aria-hidden="false"
|
|
>
|
|
<ConfirmModal
|
|
type={"delete"}
|
|
header={"Delete Expense"}
|
|
message={"Are you sure you want delete?"}
|
|
onSubmit={handleDelete}
|
|
onClose={() => setIsDeleteModalOpen(false)}
|
|
loading={isPending}
|
|
paramData={deletingId}
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="card ">
|
|
<div className="card-datatable table-responsive">
|
|
<div
|
|
id="DataTables_Table_0_wrapper"
|
|
className="dataTables_wrapper no-footer px-2"
|
|
>
|
|
<table
|
|
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap px-3"
|
|
aria-describedby="DataTables_Table_0_info"
|
|
id="horizontal-example"
|
|
>
|
|
<thead>
|
|
<tr>
|
|
{/* <th
|
|
className="sorting sorting_desc"
|
|
tabIndex="0"
|
|
aria-controls="DataTables_Table_0"
|
|
rowSpan="1"
|
|
colSpan="2"
|
|
aria-label="Date: activate to sort column ascending"
|
|
aria-sort="descending"
|
|
>
|
|
<div className="text-start ms-6">Date Time</div>
|
|
</th> */}
|
|
<th
|
|
className="sorting sorting_desc d-none d-sm-table-cell"
|
|
tabIndex="0"
|
|
aria-controls="DataTables_Table_0"
|
|
rowSpan="1"
|
|
colSpan="1"
|
|
aria-label="Expense Type: activate to sort column ascending"
|
|
aria-sort="descending"
|
|
>
|
|
<div className="text-start ms-5">Expense Type</div>
|
|
</th>
|
|
<th
|
|
className="sorting sorting_desc d-table-cell"
|
|
tabIndex="0"
|
|
aria-controls="DataTables_Table_0"
|
|
rowSpan="1"
|
|
colSpan="1"
|
|
aria-label="Payment Mode: activate to sort column ascending"
|
|
aria-sort="descending"
|
|
>
|
|
<div className="text-start ">Payment Mode</div>
|
|
</th>
|
|
<th
|
|
className="sorting sorting_desc d-table-cell"
|
|
tabIndex="0"
|
|
aria-controls="DataTables_Table_0"
|
|
rowSpan="1"
|
|
colSpan="1"
|
|
aria-label="Paid By: activate to sort column ascending"
|
|
aria-sort="descending"
|
|
>
|
|
<div className="text-start ms-5">Paid By</div>
|
|
</th>
|
|
<th
|
|
className="sorting d-table-cell"
|
|
tabIndex="0"
|
|
aria-controls="DataTables_Table_0"
|
|
rowSpan="1"
|
|
colSpan="1"
|
|
aria-label="Amount: activate to sort column ascending"
|
|
>
|
|
Amount
|
|
</th>
|
|
<th
|
|
className="sorting"
|
|
tabIndex="0"
|
|
aria-controls="DataTables_Table_0"
|
|
rowSpan="1"
|
|
colSpan="1"
|
|
aria-label="Status: activate to sort column ascending"
|
|
>
|
|
Status
|
|
</th>
|
|
<th
|
|
className="sorting"
|
|
tabIndex="0"
|
|
aria-controls="DataTables_Table_0"
|
|
rowSpan="1"
|
|
colSpan="1"
|
|
aria-label="Status: activate to sort column ascending"
|
|
>
|
|
Action
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody >
|
|
{!isInitialLoading &&
|
|
groupExpensesByDateAndStatus(items).map(
|
|
({ date, expenses }) => (
|
|
<>
|
|
<tr key={`date-${date}`} className="tr-group text-dark">
|
|
<td colSpan={7} className="text-start">
|
|
<strong>{formatUTCToLocalTime(date)}</strong>
|
|
</td>
|
|
</tr> {expenses.map((expense) => (
|
|
<tr key={expense.id} >
|
|
<td className="text-start d-table-cell ms-5">
|
|
{expense.expensesType?.name || "N/A"}
|
|
</td>
|
|
<td className="text-start d-table-cell ms-5">
|
|
{expense.paymentMode?.name || "N/A"}
|
|
</td>
|
|
<td className="text-start d-table-cell ms-5">
|
|
<div className="d-flex align-items-center">
|
|
<Avatar
|
|
size="xs"
|
|
classAvatar="m-0"
|
|
firstName={expense.paidBy?.firstName}
|
|
lastName={expense.paidBy?.lastName}
|
|
/>
|
|
<span>
|
|
{`${expense.paidBy?.firstName ?? ""} ${
|
|
expense.paidBy?.lastName ?? ""
|
|
}`.trim() || "N/A"}
|
|
</span>
|
|
</div>
|
|
</td>
|
|
<td className="d-table-cell text-end">
|
|
<i className="bx bx-rupee b-xs"></i>
|
|
{expense?.amount}
|
|
</td>
|
|
<td>
|
|
<span
|
|
className={`badge bg-label-${
|
|
getColorNameFromHex(expense?.status?.color) ||
|
|
"secondary"
|
|
}`}
|
|
>
|
|
{expense.status?.displayName || "Unknown"}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div className="d-flex justify-content-center align-items-center gap-2">
|
|
<span className="cursor-pointer">
|
|
<i
|
|
className="bx bx-show text-primary"
|
|
onClick={() =>
|
|
setViewExpense({
|
|
expenseId: expense.id,
|
|
view: true,
|
|
})
|
|
}
|
|
></i>
|
|
</span>
|
|
|
|
<span className="cursor-pointer">
|
|
{(expense.status.name === "Draft" ||
|
|
expense.status.name === "Rejected") &&
|
|
expense.createdBy.id ===
|
|
profile?.employeeInfo.id ? (
|
|
<i
|
|
className="bx bx-edit bx-sm text-secondary"
|
|
onClick={() =>
|
|
setManageExpenseModal({
|
|
IsOpen: true,
|
|
expenseId: expense.id,
|
|
})
|
|
}
|
|
></i>
|
|
) : (
|
|
<i
|
|
className="bx bx-edit bx-sm"
|
|
style={{ visibility: "hidden" }}
|
|
></i>
|
|
)}
|
|
</span>
|
|
|
|
<span className="cursor-pointer">
|
|
{expense.status.name === "Draft" ? (
|
|
<i
|
|
className="bx bx-trash bx-sm text-danger"
|
|
onClick={() => {
|
|
setIsDeleteModalOpen(true);
|
|
setDeletingId(expense.id);
|
|
}}
|
|
></i>
|
|
) : (
|
|
<i
|
|
className="bx bx-trash bx-sm"
|
|
style={{ visibility: "hidden" }}
|
|
></i>
|
|
)}
|
|
</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</>
|
|
)
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{!isInitialLoading && items.length > 0 && (
|
|
<Pagination
|
|
currentPage={currentPage}
|
|
totalPages={totalPages}
|
|
onPageChange={paginate}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ExpenseList;
|