marco.pms.web/src/pages/Expense/ExpensePage.jsx

275 lines
9.0 KiB
JavaScript

import React, {
createContext,
useContext,
useState,
useEffect,
useRef,
} from "react";
import { useForm, useFormContext } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useSelector } from "react-redux";
import Breadcrumb from "../../components/common/Breadcrumb";
import GlobalModel from "../../components/common/GlobalModel";
import ExpenseList from "../../components/Expenses/ExpenseList";
import ViewExpense from "../../components/Expenses/ViewExpense";
import ManageExpense from "../../components/Expenses/ManageExpense";
import ExpenseFilterPanel from "../../components/Expenses/ExpenseFilterPanel";
import ExpenseFilterChips from "../../components/Expenses/ExpenseFilterChips";
import { useFab } from "../../Context/FabContext";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import {
CREATE_EXEPENSE,
VIEW_ALL_EXPNESE,
VIEW_SELF_EXPENSE,
} from "../../utils/constants";
import {
defaultFilter,
SearchSchema,
} from "../../components/Expenses/ExpenseSchema";
import PreviewDocument from "../../components/Expenses/PreviewDocument";
import handleExpenseExport from "../../components/PaymentRequest/handleExpenseExport";
// Context
export const ExpenseContext = createContext();
export const useExpenseContext = () => {
const context = useContext(ExpenseContext);
if (!context) {
throw new Error("useExpenseContext must be used within an ExpenseProvider");
}
return context;
};
const ExpensePage = () => {
const selectedProjectId = useSelector(
(store) => store.localVariables.projectId
);
const [filters, setFilters] = useState(defaultFilter);
const [groupBy, setGroupBy] = useState("transactionDate");
const [searchText, setSearchText] = useState("");
const filterPanelRef = useRef();
const [ManageExpenseModal, setManageExpenseModal] = useState({
IsOpen: null,
expenseId: null,
});
const [viewExpense, setViewExpense] = useState({
expenseId: null,
view: false,
});
const [ViewDocument, setDocumentView] = useState({
IsOpen: false,
Images: null,
});
const IsCreatedAble = useHasUserPermission(CREATE_EXEPENSE);
const IsViewAll = useHasUserPermission(VIEW_ALL_EXPNESE);
const IsViewSelf = useHasUserPermission(VIEW_SELF_EXPENSE);
const { setOffcanvasContent, setShowTrigger } = useFab();
const [filterData, setFilterdata] = useState(defaultFilter);
const tableRef = useRef(null);
const [filteredData, setFilteredData] = useState([]);
const removeFilterChip = (key, id) => {
setFilters((prev) => {
const updated = { ...prev };
if (Array.isArray(updated[key])) {
updated[key] = updated[key].filter((v) => v !== id);
filterPanelRef.current?.resetFieldValue(key, updated[key]);
} else if (key === "dateRange") {
updated.startDate = null;
updated.endDate = null;
filterPanelRef.current?.resetFieldValue("startDate", null);
filterPanelRef.current?.resetFieldValue("endDate", null);
}
return updated;
});
};
useEffect(() => {
if (IsViewAll || IsViewSelf || IsCreatedAble) {
setShowTrigger(true);
setOffcanvasContent(
"Expense Filters",
<ExpenseFilterPanel
ref={filterPanelRef}
onApply={setFilters}
handleGroupBy={setGroupBy}
setFilterdata={setFilterdata}
/>
);
}
return () => {
setShowTrigger(false);
setOffcanvasContent("", null);
};
}, [IsViewAll, IsViewSelf, IsCreatedAble]);
const contextValue = {
setViewExpense,
setManageExpenseModal,
setDocumentView,
filterData,
removeFilterChip,
};
const handleExport = (type) => {
handleExpenseExport(type, filteredData, tableRef); // <-- corrected
};
return (
<ExpenseContext.Provider value={contextValue}>
<div className="container-fluid">
<Breadcrumb
data={[{ label: "Home", link: "/dashboard" }, { label: "Expense" }]}
/>
{IsViewAll || IsViewSelf || IsCreatedAble ? (
<>
<div className="card my-3 px-sm-4 px-0">
<div className="card-body py-2 px-3 me-n1">
<div className="row align-items-center">
<div className="col-md-8 col-sm-12 mb-2 mb-md-0">
<div className="d-flex align-items-center flex-wrap gap-0">
<input
type="search"
className="form-control form-control-sm w-auto"
placeholder="Search Expense"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
</div>
</div>
<div className="col-md-4 col-sm-12 text-md-end text-end d-flex justify-content-end align-items-center gap-0">
{IsCreatedAble && (
<button
className="btn btn-sm btn-primary"
type="button"
onClick={() =>
setManageExpenseModal({
IsOpen: true,
expenseId: null,
})
}
>
<i className="bx bx-plus-circle me-2"></i>
<span className="d-none d-md-inline-block">
Add New Expense
</span>
</button>
)}
{/* 3-Dots Dropdown */}
<div className="dropdown">
<button
className="btn btn-icon"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="bx bx-dots-vertical-rounded bx-md"></i>
</button>
<ul className="dropdown-menu dropdown-menu-end shadow-sm" style={{ minWidth: "220px" }}>
<li>
<button className="dropdown-item" onClick={() => handleExport("print")}>
<i className="bx bx-printer me-2"></i> Print
</button>
</li>
<li><hr className="dropdown-divider" /></li>
<li>
<button className="dropdown-item" onClick={() => handleExport("csv")}>
<i className="bx bx-file me-2"></i> CSV
</button>
</li>
<li>
<button className="dropdown-item" onClick={() => handleExport("excel")}>
<i className="bx bxs-file-export me-2"></i> Excel
</button>
</li>
<li>
<button className="dropdown-item" onClick={() => handleExport("pdf")}>
<i className="bx bxs-file-pdf me-2"></i> PDF
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<ExpenseList
filters={filters}
groupBy={groupBy}
searchText={searchText}
tableRef={tableRef}
onDataFiltered={setFilteredData}
/>
</>
) : (
<div className="card text-center py-1">
<i className="fa-solid fa-triangle-exclamation fs-5" />
<p>
Access Denied: You don't have permission to perform this action!
</p>
</div>
)}
{/* Modals */}
{ManageExpenseModal.IsOpen && (
<GlobalModel
isOpen
size="lg"
closeModal={() =>
setManageExpenseModal({ IsOpen: null, expenseId: null })
}
>
<ManageExpense
key={ManageExpenseModal.expenseId ?? "new"}
expenseToEdit={ManageExpenseModal.expenseId}
closeModal={() =>
setManageExpenseModal({ IsOpen: null, expenseId: null })
}
/>
</GlobalModel>
)}
{viewExpense.view && (
<GlobalModel
isOpen
size="xl"
modalType="top"
closeModal={() => setViewExpense({ expenseId: null, view: false })}
>
<ViewExpense ExpenseId={viewExpense.expenseId} />
</GlobalModel>
)}
{ViewDocument.IsOpen && (
<GlobalModel
isOpen
size="md"
key={ViewDocument.Images ?? "doc"}
closeModal={() => setDocumentView({ IsOpen: false, Images: null })}
>
<PreviewDocument files={ViewDocument.Images} />
</GlobalModel>
)}
</div>
</ExpenseContext.Provider>
);
};
export default ExpensePage;