diff --git a/src/components/Expenses/ExpenseFilterChips.jsx b/src/components/Expenses/ExpenseFilterChips.jsx new file mode 100644 index 00000000..9287e38e --- /dev/null +++ b/src/components/Expenses/ExpenseFilterChips.jsx @@ -0,0 +1,222 @@ +import React, { useMemo } from "react"; + +const ExpenseFilterChips = ({ filters, filterData, removeFilterChip }) => { + // Build chips from filters + const filterChips = useMemo(() => { + const chips = []; + + const buildGroup = (ids, list, label, key) => { + if (!ids?.length) return; + const items = ids.map((id) => ({ + id, + name: list.find((item) => item.id === id)?.name || id, + })); + chips.push({ key, label, items }); + }; + + buildGroup(filters.projectIds, filterData.projects, "Project", "projectIds"); + buildGroup(filters.createdByIds, filterData.createdBy, "Submitted By", "createdByIds"); + buildGroup(filters.paidById, filterData.paidBy, "Paid By", "paidById"); + buildGroup(filters.statusIds, filterData.status, "Status", "statusIds"); + buildGroup(filters.ExpenseTypeIds, filterData.expensesType, "Category", "ExpenseTypeIds"); + + if (filters.startDate || filters.endDate) { + const start = filters.startDate + ? new Date(filters.startDate).toLocaleDateString() + : ""; + const end = filters.endDate + ? new Date(filters.endDate).toLocaleDateString() + : ""; + chips.push({ + key: "dateRange", + label: "Date Range", + items: [{ id: "dateRange", name: `${start} - ${end}` }], + }); + } + + return chips; + }, [filters, filterData]); + + if (!filterChips.length) return null; + + return ( + +
+
+ Filter: +
+
+ {filterChips.map((chip) => ( + + {chip.label}: +
+ {chip.items.map((item) => ( + + {item.name} +
+
+ ))} +
+
+ + + ); +}; + +export default ExpenseFilterChips; + + +// import React, { useMemo } from "react"; + +// const ExpenseFilterChips = ({ filters, filterData, removeFilterChip, groupBy }) => { +// // Build filter chips +// const filterChips = useMemo(() => { +// const chips = []; + +// const buildGroup = (ids, list, label, key) => { +// if (!ids?.length) return; +// const items = ids.map((id) => ({ +// id, +// name: list.find((item) => item.id === id)?.name || id, +// })); +// chips.push({ key, label, items }); +// }; + +// buildGroup(filters.projectIds, filterData.projects, "Project", "projectIds"); +// buildGroup(filters.createdByIds, filterData.createdBy, "Submitted By", "createdByIds"); +// buildGroup(filters.paidById, filterData.paidBy, "Paid By", "paidById"); +// buildGroup(filters.statusIds, filterData.status, "Status", "statusIds"); +// buildGroup(filters.ExpenseTypeIds, filterData.expensesType, "Category", "ExpenseTypeIds"); + +// if (filters.startDate || filters.endDate) { +// const start = filters.startDate +// ? new Date(filters.startDate).toLocaleDateString("en-GB", { +// day: "2-digit", +// month: "short", +// year: "numeric", +// }) +// : ""; +// const end = filters.endDate +// ? new Date(filters.endDate).toLocaleDateString("en-GB", { +// day: "2-digit", +// month: "short", +// year: "numeric", +// }) +// : ""; +// chips.push({ +// key: "dateRange", +// label: "Date Range", +// items: [{ id: "dateRange", name: `${start} - ${end}` }], +// }); +// } + +// return chips; +// }, [filters, filterData]); + +// // Prepare groupBy chip +// const groupByChip = useMemo(() => { +// if (!groupBy) return null; + +// const formattedGroup = +// groupBy === "transactionDate" +// ? "Transaction Date" +// : groupBy === "status" +// ? "Status" +// : groupBy === "submittedBy" +// ? "Submitted By" +// : groupBy === "project" +// ? "Project" +// : groupBy === "paymentMode" +// ? "Payment Mode" +// : groupBy === "expensesType" +// ? "Expense Type" +// : groupBy === "createdAt" +// ? "Submitted Date" +// : "Others"; + +// return { +// key: "groupBy", +// label: "Group By", +// items: [{ id: groupBy, name: formattedGroup }], +// }; +// }, [groupBy]); + +// if (!filterChips.length && !groupByChip) return null; + +// return ( +//
+// {/* Filters */} +// {filterChips.length > 0 && ( +// <> +// Filters: +// {filterChips.map((chip) => ( +// +// {chip.label}: +//
+// {chip.items.map((item) => ( +// +// {item.name} +//
+//
+// ))} +// +// )} + +// {/* Group By */} +// {groupByChip && ( +// <> +// Group By: +// {groupByChip.items.map((item) => ( +// +// {item.name} +// +// ))} +// +// )} +//
+// ); +// }; + +// export default ExpenseFilterChips; diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index 92baab2a..5225a5fd 100644 --- a/src/components/Expenses/ExpenseFilterPanel.jsx +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -15,8 +15,8 @@ import { useExpenseFilter } from "../../hooks/useExpense"; import { ExpenseFilterSkeleton } from "./ExpenseSkeleton"; import { useLocation, useNavigate, useParams } from "react-router-dom"; -const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { - const { status, project } = useParams(); +const ExpenseFilterPanel = ({ onApply, handleGroupBy, setFilterdata }) => { + const { status } = useParams(); const navigate = useNavigate(); const selectedProjectId = useSelector( (store) => store.localVariables.projectId @@ -43,7 +43,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { return { ...defaultFilter, statusIds: status ? [status] : defaultFilter.statusIds || [], - projectIds: project ? [project] : defaultFilter.projectIds || [], + projectIds: defaultFilter.projectIds || [], createdByIds: defaultFilter.createdByIds || [], paidById: defaultFilter.paidById || [], ExpenseTypeIds: defaultFilter.ExpenseTypeIds || [], @@ -65,6 +65,13 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { document.querySelector(".offcanvas.show .btn-close")?.click(); }; + // Change here + useEffect(() => { + if (data && setFilterdata) { + setFilterdata(data); + } + }, [data, setFilterdata]); + const handleGroupChange = (e) => { const group = groupByList.find((g) => g.id === e.target.value); if (group) setSelectedGroup(group); @@ -100,7 +107,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { const [appliedStatusId, setAppliedStatusId] = useState(null); useEffect(() => { - if (!status && !project) return; + if (!status) return; if (status !== appliedStatusId && data) { const filterWithStatus = { @@ -122,7 +129,6 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { } }, [ status, - project, data, dynamicDefaultFilter, onApply, @@ -134,6 +140,9 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { if (isLoading || isFetching) return ; if (isError && isFetched) return
Something went wrong Here- {error.message}
; + + + return ( <> @@ -273,4 +282,4 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { ); }; -export default ExpenseFilterPanel; +export default ExpenseFilterPanel; \ No newline at end of file diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index affb44a0..37db2a55 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -15,16 +15,19 @@ import { ExpenseTableSkeleton } from "./ExpenseSkeleton"; import ConfirmModal from "../common/ConfirmModal"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useSelector } from "react-redux"; +import ExpenseFilterChips from "./ExpenseFilterChips"; +import { defaultFilter } from "./ExpenseSchema"; const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { const [deletingId, setDeletingId] = useState(null); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const { setViewExpense, setManageExpenseModal } = useExpenseContext(); + const { setViewExpense, setManageExpenseModal, filterData, removeFilterChip } = useExpenseContext(); const IsExpenseEditable = useHasUserPermission(); const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE); const [currentPage, setCurrentPage] = useState(1); const debouncedSearch = useDebounce(searchText, 500); + const { mutate: DeleteExpense, isPending } = useDeleteExpense(); const { data, isLoading, isError, isInitialLoading, error } = useExpenseList( ITEMS_PER_PAGE, @@ -67,9 +70,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { key = item?.status?.displayName || "Unknown"; break; case "submittedBy": - key = `${item?.createdBy?.firstName ?? ""} ${ - item.createdBy?.lastName ?? "" - }`.trim(); + key = `${item?.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? "" + }`.trim(); break; case "project": key = item?.project?.name || "Unknown Project"; @@ -110,9 +112,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { label: "Submitted By", align: "text-start", getValue: (e) => - `${e.createdBy?.firstName ?? ""} ${ - e.createdBy?.lastName ?? "" - }`.trim() || "N/A", + `${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" + }`.trim() || "N/A", customRender: (e) => (
{ lastName={e.createdBy?.lastName} /> - {`${e.createdBy?.firstName ?? ""} ${ - e.createdBy?.lastName ?? "" - }`.trim() || "N/A"} + {`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? "" + }`.trim() || "N/A"}
), @@ -140,7 +140,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { label: "Amount", getValue: (e) => ( <> - {formatCurrency(e?.amount)} + {formatCurrency(e?.amount)} ), isAlwaysVisible: true, @@ -152,9 +152,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { align: "text-center", getValue: (e) => ( {e.status?.name || "Unknown"} @@ -183,6 +182,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { ); }; + return ( <> {IsDeleteModalOpen && ( @@ -199,10 +199,20 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { )}
+ {/* Filter Chips */} +
+ + +
@@ -293,9 +303,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { ) : ( )} diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index 1206e237..f6d9be16 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -3,15 +3,14 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useSelector } from "react-redux"; -import ExpenseList from "../../components/Expenses/ExpenseList"; -import ViewExpense from "../../components/Expenses/ViewExpense"; import Breadcrumb from "../../components/common/Breadcrumb"; import GlobalModel from "../../components/common/GlobalModel"; -import PreviewDocument from "../../components/Expenses/PreviewDocument"; +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"; -// Context & Hooks import { useFab } from "../../Context/FabContext"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { @@ -20,11 +19,7 @@ import { VIEW_SELF_EXPENSE, } from "../../utils/constants"; -// Schema & Defaults -import { - defaultFilter, - SearchSchema, -} from "../../components/Expenses/ExpenseSchema"; +import { defaultFilter, SearchSchema } from "../../components/Expenses/ExpenseSchema"; // Context export const ExpenseContext = createContext(); @@ -41,7 +36,7 @@ const ExpensePage = () => { (store) => store.localVariables.projectId ); - const [filters, setFilter] = useState(); + const [filters, setFilters] = useState(defaultFilter); const [groupBy, setGroupBy] = useState("transactionDate"); const [searchText, setSearchText] = useState(""); @@ -70,23 +65,38 @@ const ExpensePage = () => { resolver: zodResolver(SearchSchema), defaultValues: defaultFilter, }); - const { reset } = methods; + const [filterData, setFilterdata] = useState(defaultFilter); + const clearFilter = () => { - setFilter(defaultFilter); + setFilters(defaultFilter); reset(); }; + + const removeFilterChip = (key, id) => { + setFilters((prev) => { + const updated = { ...prev }; + if (Array.isArray(updated[key])) { + updated[key] = updated[key].filter((v) => v !== id); + } else if (key === "dateRange") { + updated.startDate = null; + updated.endDate = null; + } + return updated; + }); + }; + useEffect(() => { if (IsViewAll || IsViewSelf || IsCreatedAble) { setShowTrigger(true); setOffcanvasContent( "Expense Filters", ); } @@ -101,6 +111,8 @@ const ExpensePage = () => { setViewExpense, setManageExpenseModal, setDocumentView, + filterData, + removeFilterChip }; return ( @@ -115,21 +127,20 @@ const ExpensePage = () => {
-
-
- setSearchText(e.target.value)} - /> -
+
+ setSearchText(e.target.value)} + />
-
- +
+ {IsCreatedAble && (
+ + {

- Access Denied: You don't have permission to perform this action ! + Access Denied: You don't have permission to perform this action!

)}
-
+

No Expense Found

-
+