diff --git a/src/components/Expenses/ExpenseFilterChips.jsx b/src/components/Expenses/ExpenseFilterChips.jsx new file mode 100644 index 00000000..c44dd7a5 --- /dev/null +++ b/src/components/Expenses/ExpenseFilterChips.jsx @@ -0,0 +1,89 @@ +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 ( +
+
+
+ {filterChips.map((chip) => ( +
+ {/* Chip Label */} + {chip.label}: + + {/* Chip Items */} +
+ {chip.items.map((item) => ( + + {item.name} +
+
+ ))} +
+
+
+ + + + ); +}; + +export default ExpenseFilterChips; + + diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index c81a5651..fca03329 100644 --- a/src/components/Expenses/ExpenseFilterPanel.jsx +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useMemo } from "react"; +import React, {forwardRef, useEffect,useImperativeHandle, useState, useMemo } from "react"; import { FormProvider, useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { defaultFilter, SearchSchema } from "./ExpenseSchema"; @@ -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 = forwardRef(({ onApply, handleGroupBy, setFilterdata }, ref) => { + 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,11 +65,30 @@ 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); }; + useImperativeHandle(ref, () => ({ + resetFieldValue: (name, value) => { + // Reset specific field + if (value !== undefined) { + setValue(name, value); + } else { + reset({ ...methods.getValues(), [name]: defaultFilter[name] }); + } + }, + getValues: methods.getValues, // optional, to read current filter state + })); + const onSubmit = (formData) => { onApply({ ...formData, @@ -100,7 +119,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 +141,6 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { } }, [ status, - project, data, dynamicDefaultFilter, onApply, @@ -134,6 +152,9 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => { if (isLoading || isFetching) return ; if (isError && isFetched) return
Something went wrong Here- {error.message}
; + + + return ( <> @@ -271,6 +292,6 @@ 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 c346a49c..642b31d0 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -19,16 +19,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, @@ -135,9 +138,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"}
), @@ -173,9 +174,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { align: "text-center", getValue: (e) => ( {e.status?.name || "Unknown"} @@ -183,7 +183,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { }, ]; - if (isInitialLoading) return ; + if (isInitialLoading && !data) return ; if (isError) return
{error?.message}
; const grouped = groupBy @@ -208,6 +208,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { ); }; + return ( <> {IsDeleteModalOpen && ( @@ -224,10 +225,20 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { )}
+ {/* Filter Chips */} +
+ + +
diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index 1206e237..f826c018 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -1,17 +1,16 @@ -import React, { createContext, useContext, useState, useEffect } from "react"; -import { useForm } from "react-hook-form"; +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 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,10 +36,10 @@ const ExpensePage = () => { (store) => store.localVariables.projectId ); - const [filters, setFilter] = useState(); + 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, @@ -64,18 +59,31 @@ const ExpensePage = () => { const IsViewAll = useHasUserPermission(VIEW_ALL_EXPNESE); const IsViewSelf = useHasUserPermission(VIEW_SELF_EXPENSE); + const { setOffcanvasContent, setShowTrigger } = useFab(); - const methods = useForm({ - resolver: zodResolver(SearchSchema), - defaultValues: defaultFilter, - }); - const { reset } = methods; - const clearFilter = () => { - setFilter(defaultFilter); - reset(); + const [filterData, setFilterdata] = useState(defaultFilter); + + + + + + 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(() => { @@ -84,9 +92,10 @@ const ExpensePage = () => { setOffcanvasContent( "Expense Filters", ); } @@ -101,6 +110,8 @@ const ExpensePage = () => { setViewExpense, setManageExpenseModal, setDocumentView, + filterData, + removeFilterChip }; return ( @@ -115,21 +126,18 @@ 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!

)}