@@ -221,6 +293,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 8d96e121..7cccb5d3 100644
--- a/src/components/Expenses/ExpenseList.jsx
+++ b/src/components/Expenses/ExpenseList.jsx
@@ -10,23 +10,30 @@ import {
EXPENSE_REJECTEDBY,
ITEMS_PER_PAGE,
} from "../../utils/constants";
-import { getColorNameFromHex, useDebounce } from "../../utils/appUtils";
+import {
+ formatCurrency,
+ getColorNameFromHex,
+ useDebounce,
+} from "../../utils/appUtils";
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";
import { useNavigate } from "react-router-dom";
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 navigate = useNavigate();
+
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
ITEMS_PER_PAGE,
@@ -61,39 +68,60 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
const groupByField = (items, field) => {
return items.reduce((acc, item) => {
let key;
+ let displayField;
+
switch (field) {
case "transactionDate":
- key = item.transactionDate?.split("T")[0];
+ key = item?.transactionDate?.split("T")[0];
+ displayField = "Transaction Date";
break;
case "status":
- key = item.status?.displayName || "Unknown";
+ key = item?.status?.displayName || "Unknown";
+ displayField = "Status";
break;
case "submittedBy":
- key = `${item.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? ""
+ key = `${item?.createdBy?.firstName ?? ""} ${item.createdBy?.lastName ?? ""
}`.trim();
+ displayField = "Submitted By";
break;
case "project":
- key = item.project?.name || "Unknown Project";
+ key = item?.project?.name || "Unknown Project";
+ displayField = "Project";
break;
case "paymentMode":
- key = item.paymentMode?.name || "Unknown Mode";
+ key = item?.paymentMode?.name || "Unknown Mode";
+ displayField = "Payment Mode";
break;
case "expensesType":
- key = item.expensesType?.name || "Unknown Type";
+ key = item?.expensesType?.name || "Unknown Type";
+ displayField = "Expense Category";
break;
case "createdAt":
- key = item.createdAt?.split("T")[0] || "Unknown Type";
+ key = item?.createdAt?.split("T")[0] || "Unknown Date";
+ displayField = "Created Date";
break;
default:
key = "Others";
+ displayField = "Others";
}
- if (!acc[key]) acc[key] = [];
- acc[key].push(item);
+
+ const groupKey = `${field}_${key}`; // unique key for object property
+ if (!acc[groupKey]) {
+ acc[groupKey] = { key, displayField, items: [] };
+ }
+
+ acc[groupKey].items.push(item);
return acc;
}, {});
};
const expenseColumns = [
+ {
+ key: "expenseUId",
+ label: "Expense Id",
+ getValue: (e) => e.expenseUId || "N/A",
+ align: "text-start mx-2",
+ },
{
key: "expensesType",
label: "Expense Type",
@@ -116,7 +144,6 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
customRender: (e) => (
navigate(`/employee/${e.createdBy?.id}`)}>
-
{
{
key: "amount",
label: "Amount",
- getValue: (e) => (
- <>
- {e?.amount}
- >
- ),
+ getValue: (e) => <>{formatCurrency(e?.amount)}>,
isAlwaysVisible: true,
align: "text-end",
},
@@ -162,27 +185,30 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
},
];
- if (isInitialLoading) return ;
- if (isError) return {error.message}
;
+ if (isInitialLoading && !data) return ;
+ if (isError) return {error?.message}
;
const grouped = groupBy
? groupByField(data?.data ?? [], groupBy)
: { All: data?.data ?? [] };
- const IsGroupedByDate = ["transactionDate", "createdAt"].includes(groupBy);
+ const IsGroupedByDate = [
+ { key: "transactionDate", displayField: "Transaction Date" },
+ { key: "createdAt", displayField: "created Date" },
+ ]?.includes(groupBy);
+
const canEditExpense = (expense) => {
return (
- (expense.status.id === EXPENSE_DRAFT ||
- EXPENSE_REJECTEDBY.includes(expense.status.id)) &&
- expense.createdBy?.id === SelfId
+ (expense?.status?.id === EXPENSE_DRAFT ||
+ EXPENSE_REJECTEDBY.includes(expense?.status?.id)) &&
+ expense?.createdBy?.id === SelfId
);
};
const canDetetExpense = (expense) => {
return (
- expense.status.id === EXPENSE_DRAFT && expense.createdBy.id === SelfId
+ expense?.status?.id === EXPENSE_DRAFT && expense?.createdBy?.id === SelfId
);
};
-
return (
<>
{IsDeleteModalOpen && (
@@ -198,7 +224,14 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
/>
)}
-
+
+ {/* Filter Chips */}
+
{
{Object.keys(grouped).length > 0 ? (
- Object.entries(grouped).map(([group, expenses]) => (
-
+ Object.values(grouped).map(({ key, displayField, items }) => (
+
-
- {IsGroupedByDate
- ? formatUTCToLocalTime(group)
- : group}
-
+
+ {" "}
+
+ {displayField} :{" "}
+ {" "}
+
+ {IsGroupedByDate
+ ? formatUTCToLocalTime(key)
+ : key}
+
+
|
- {expenses.map((expense) => (
+ {items?.map((expense) => (
{expenseColumns.map(
(col) =>
@@ -263,27 +302,61 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
})
}
>
- {canEditExpense(expense) && (
-
- setManageExpenseModal({
- IsOpen: true,
- expenseId: expense.id,
- })
- }
- >
- )}
+ {canDetetExpense(expense) &&
+ canEditExpense(expense) && (
+
+
+
+ {canDetetExpense(expense) && (
+ -
+ setManageExpenseModal({
+ IsOpen: true,
+ expenseId: expense.id,
+ })
+ }
+ >
+
+
+
+ Modify
+
+
+
+ )}
- {canDetetExpense(expense) && (
- {
- setIsDeleteModalOpen(true);
- setDeletingId(expense.id);
- }}
- >
- )}
+ {canDetetExpense(expense) && (
+ - {
+ setIsDeleteModalOpen(true);
+ setDeletingId(expense.id);
+ }}
+ >
+
+
+
+ Delete
+
+
+
+ )}
+
+
+ )}
@@ -292,13 +365,17 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
))
) : (
-
- No Expense Found
+ |
+
|
)}
+
+
{data?.data?.length > 0 && (
{
onPageChange={paginate}
/>
)}
-
-
>
);
diff --git a/src/pages/Directory/ContactFilterPanel.jsx b/src/pages/Directory/ContactFilterPanel.jsx
index 063a6268..e77c8469 100644
--- a/src/pages/Directory/ContactFilterPanel.jsx
+++ b/src/pages/Directory/ContactFilterPanel.jsx
@@ -1,5 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod";
-import React, { useEffect } from "react";
+import React, {
+ useEffect,
+ useImperativeHandle,
+ forwardRef,
+ useMemo,
+} from "react";
import { FormProvider, useForm } from "react-hook-form";
import {
contactsFilter,
@@ -8,83 +13,101 @@ import {
import { useContactFilter } from "../../hooks/useDirectory";
import { ExpenseFilterSkeleton } from "../../components/Expenses/ExpenseSkeleton";
import SelectMultiple from "../../components/common/SelectMultiple";
-import { useLocation } from "react-router-dom";
+import { useParams } from "react-router-dom";
-const ContactFilterPanel = ({ onApply, clearFilter }) => {
- const { data, isError, isLoading, error, isFetched, isFetching } =
- useContactFilter();
+const ContactFilterPanel = forwardRef(
+ ({ onApply, clearFilter, setFilterdata }, ref) => {
+ const { data, isError, isLoading, error, isFetched, isFetching } =
+ useContactFilter();
+ const { status } = useParams();
- const location = useLocation();
+ const dynamicdefaultContactFilter = useMemo(() => {
+ return {
+ ...defaultContactFilter,
+ bucketIds: defaultContactFilter.bucketIds || [],
+ categoryIds: defaultContactFilter.categoryIds || [],
+ };
+ }, [status]);
- const methods = useForm({
- resolver: zodResolver(contactsFilter),
- defaultValues: defaultContactFilter,
- });
+ const methods = useForm({
+ resolver: zodResolver(contactsFilter),
+ defaultValues: dynamicdefaultContactFilter,
+ });
- const closePanel = () => {
- document.querySelector(".offcanvas.show .btn-close")?.click();
- };
+ const { handleSubmit, reset, setValue, getValues } = methods;
- const { register, handleSubmit, reset, watch } = methods;
+ useImperativeHandle(ref, () => ({
+ resetFieldValue: (name, value) => {
+ if (value !== undefined) {
+ setValue(name, value);
+ } else {
+ reset({ ...getValues(), [name]: defaultContactFilter[name] });
+ }
+ },
+ getValues, // optional: allows parent to read current form values
+ }));
- const onSubmit = (formData) => {
- onApply(formData);
- closePanel();
- };
+ useEffect(() => {
+ if (data && setFilterdata) {
+ setFilterdata(data);
+ }
+ }, [data, setFilterdata]);
- const handleClose = () => {
- reset(defaultContactFilter);
- onApply(defaultContactFilter);
- closePanel();
- };
+ const closePanel = () => {
+ document.querySelector(".offcanvas.show .btn-close")?.click();
+ };
-
- useEffect(() => {
- return () => {
+ const onSubmit = (formData) => {
+ onApply(formData);
closePanel();
};
- }, []);
- if (isLoading || isFetching) return