We are creating filter chips in the Payment Request filter panel.

This commit is contained in:
Kartik Sharma 2025-11-14 10:22:27 +05:30
parent 297e0712bc
commit 598601c515
4 changed files with 156 additions and 68 deletions

View File

@ -0,0 +1,60 @@
import React, { useMemo } from "react";
const PaymentRequestFilterChips = ({ filters, filterData, removeFilterChip, clearFilter }) => {
const data = filterData?.data || filterData || {};
const filterChips = useMemo(() => {
const chips = [];
const addGroup = (ids, list, label, key) => {
if (!ids?.length) return;
const items = ids.map((id) => ({
id,
name: list?.find((i) => i.id === id)?.name || id,
}));
chips.push({ key, label, items });
};
addGroup(filters.createdByIds, data.createdBy, "Created By", "createdByIds");
addGroup(filters.currencyIds, data.currency, "Currency", "currencyIds");
addGroup(filters.expenseCategoryIds, data.expenseCategory, "Expense Category", "expenseCategoryIds");
addGroup(filters.payees, data.payees, "Payees", "payees");
addGroup(filters.projectIds, data.projects, "Projects", "projectIds");
addGroup(filters.statusIds, data.status, "Status", "statusIds");
return chips;
}, [filters, filterData]);
if (!filterChips.length) return null;
return (
<div className="d-flex flex-wrap align-items-center gap-2">
{filterChips.map((chipGroup) => (
<div key={chipGroup.key} className="d-flex align-items-center flex-wrap">
<span className="fw-semibold me-2">{chipGroup.label}:</span>
{chipGroup.items.map((item) => (
<span
key={item.id}
className="d-flex align-items-center bg-light rounded px-2 py-1 me-1"
>
<span>{item.name}</span>
<button
type="button"
className="btn-close btn-close-white btn-sm ms-2"
style={{
filter: "invert(1) grayscale(1)",
opacity: 0.7,
fontSize: "0.6rem",
}}
onClick={() => removeFilterChip(chipGroup.key, item.id)}
/>
</span>
))}
</div>
))}
</div>
);
};
export default PaymentRequestFilterChips;

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState, useMemo } from "react";
import React, { useEffect, useState, useMemo, forwardRef, useImperativeHandle } from "react";
import { FormProvider, useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { defaultPaymentRequestFilter,SearchPaymentRequestSchema } from "./PaymentRequestSchema";
import { defaultPaymentRequestFilter, SearchPaymentRequestSchema } from "./PaymentRequestSchema";
import DateRangePicker, { DateRangePicker1 } from "../common/DateRangePicker";
import SelectMultiple from "../common/SelectMultiple";
@ -13,7 +13,7 @@ import moment from "moment";
import { usePaymentRequestFilter } from "../../hooks/useExpense";
import { useLocation, useNavigate, useParams } from "react-router-dom";
const PaymentRequestFilterPanel = ({ onApply, handleGroupBy }) => {
const PaymentRequestFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata, clearFilter }, ref) => {
const { status } = useParams();
const navigate = useNavigate();
const selectedProjectId = useSelector(
@ -38,10 +38,23 @@ const PaymentRequestFilterPanel = ({ onApply, handleGroupBy }) => {
const [selectedGroup, setSelectedGroup] = useState(groupByList[6]);
const [resetKey, setResetKey] = useState(0);
const dynamicDefaultFilter = useMemo(() => {
return {
...defaultPaymentRequestFilter,
projectIds: defaultPaymentRequestFilter.projectIds || [],
statusIds: status ? [status] : defaultPaymentRequestFilter.statusIds || [],
createdByIds: defaultPaymentRequestFilter.createdByIds || [],
currencyIds: defaultPaymentRequestFilter.currencyIds || [],
expenseCategoryIds: defaultPaymentRequestFilter.expenseCategoryIds || [],
payees: defaultPaymentRequestFilter.payees || [],
startDate: defaultPaymentRequestFilter.startDate,
endDate: defaultPaymentRequestFilter.endDate,
};
}, [status, selectedProjectId]);
const methods = useForm({
resolver: zodResolver(SearchPaymentRequestSchema),
defaultValues: defaultPaymentRequestFilter,
defaultValues: dynamicDefaultFilter,
});
const { control, handleSubmit, reset, setValue, watch } = methods;
@ -52,12 +65,28 @@ const PaymentRequestFilterPanel = ({ onApply, handleGroupBy }) => {
};
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
}));
useEffect(() => {
if (data && setFilterdata) {
setFilterdata(data);
}
}, [data, setFilterdata]);
const onSubmit = (formData) => {
onApply({
@ -179,7 +208,7 @@ const PaymentRequestFilterPanel = ({ onApply, handleGroupBy }) => {
))}
</div>
</div>
</div>
</div>
<div className="d-flex justify-content-end py-3 gap-2">
<button
@ -197,6 +226,6 @@ const PaymentRequestFilterPanel = ({ onApply, handleGroupBy }) => {
</FormProvider>
</>
);
};
});
export default PaymentRequestFilterPanel;

View File

@ -20,8 +20,9 @@ import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import Error from "../common/Error";
import Pagination from "../common/Pagination";
import PaymentRequestFilterChips from "./PaymentRequestFilterChips";
const PaymentRequestList = ({ filters, groupBy = "submittedBy", search }) => {
const PaymentRequestList = ({ filters, filterData, removeFilterChip, clearFilter, search, groupBy = "submittedBy" }) => {
const { setManageRequest, setVieRequest } = usePaymentRequestContext();
const navigate = useNavigate();
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
@ -92,33 +93,6 @@ const PaymentRequestList = ({ filters, groupBy = "submittedBy", search }) => {
align: "text-start",
getValue: (e) => e.title || "N/A",
},
// {
// key: "SubmittedBy",
// label: "Submitted By",
// align: "text-start",
// getValue: (e) =>
// `${e.createdBy?.firstName ?? ""} ${
// e.createdBy?.lastName ?? ""
// }`.trim() || "N/A",
// customRender: (e) => (
// <div
// className="d-flex align-items-center cursor-pointer"
// onClick={() => navigate(`/employee/${e.createdBy?.id}`)}
// >
// <Avatar
// size="xs"
// classAvatar="m-0"
// firstName={e.createdBy?.firstName}
// lastName={e.createdBy?.lastName}
// />
// <span className="text-truncate">
// {`${e.createdBy?.firstName ?? ""} ${
// e.createdBy?.lastName ?? ""
// }`.trim() || "N/A"}
// </span>
// </div>
// ),
// },
{
key: "createdAt",
label: "Created At",
@ -250,6 +224,14 @@ const PaymentRequestList = ({ filters, groupBy = "submittedBy", search }) => {
)}
<div className="card page-min-h table-responsive px-sm-4">
<div className="card-datatable mx-2" id="payment-request-table ">
<div className="col-12 mb-2 mt-2">
<PaymentRequestFilterChips
filters={filters}
filterData={filterData}
removeFilterChip={removeFilterChip}
clearFilter={clearFilter}
/>
</div>
<table className="table border-top dataTable text-nowrap align-middle">
<thead>
<tr>

View File

@ -1,12 +1,11 @@
import React, { createContext, useState, useEffect, useContext } from "react";
import React, { createContext, useState, useEffect, useContext, useRef } from "react";
import Breadcrumb from "../../components/common/Breadcrumb";
import GlobalModel from "../../components/common/GlobalModel";
import ManagePaymentRequest from "../../components/PaymentRequest/ManagePaymentRequest";
import ExpenseFilterPanel from "../../components/Expenses/ExpenseFilterPanel";
import { useFab } from "../../Context/FabContext";
import PaymentRequestList from "../../components/PaymentRequest/PaymentRequestList";
import PaymentRequestFilterPanel from "../../components/PaymentRequest/PaymentRequestFilterPanel";
import { defaultPaymentRequestFilter,SearchPaymentRequestSchema } from "../../components/PaymentRequest/PaymentRequestSchema";
import { defaultPaymentRequestFilter } from "../../components/PaymentRequest/PaymentRequestSchema";
import ViewPaymentRequest from "../../components/PaymentRequest/ViewPaymentRequest";
import PreviewDocument from "../../components/Expenses/PreviewDocument";
import MakeExpense from "../../components/PaymentRequest/MakeExpense";
@ -15,26 +14,22 @@ export const PaymentRequestContext = createContext();
export const usePaymentRequestContext = () => {
const context = useContext(PaymentRequestContext);
if (!context) {
throw new Error("usePaymentRequestContext must be used within an ExpenseProvider");
throw new Error("usePaymentRequestContext must be used within PaymentRequestContext.Provider");
}
return context;
};
const PaymentRequestPage = () => {
const [ManageRequest, setManageRequest] = useState({
IsOpen: null,
RequestId: null,
});
const [ViewRequest,setVieRequest] = useState({view:false,requestId:null})
const { setOffcanvasContent, setShowTrigger } = useFab();
const [ManageRequest, setManageRequest] = useState({ IsOpen: null, RequestId: null });
const [ViewRequest, setVieRequest] = useState({ view: false, requestId: null });
const [filters, setFilters] = useState(defaultPaymentRequestFilter);
const [ViewDocument, setDocumentView] = useState({
IsOpen: false,
Image: null,
});
const [isExpenseGenerate,setIsExpenseGenerate] = useState({IsOpen: null,
RequestId: null,})
const [modalSize,setModalSize] = useState("md")
const [filterData, setFilterdata] = useState(null);
const [ViewDocument, setDocumentView] = useState({ IsOpen: false, Image: null });
const [isExpenseGenerate, setIsExpenseGenerate] = useState({ IsOpen: null, RequestId: null });
const [modalSize, setModalSize] = useState("md");
const [search, setSearch] = useState("");
const updatedRef = useRef();
const { setOffcanvasContent, setShowTrigger } = useFab();
const contextValue = {
setManageRequest,
@ -42,15 +37,21 @@ const PaymentRequestPage = () => {
setDocumentView,
setModalSize,
setIsExpenseGenerate,
isExpenseGenerate
isExpenseGenerate,
};
useEffect(() => {
const clearFilter = () => setFilters(defaultPaymentRequestFilter);
useEffect(() => {
setShowTrigger(true);
setOffcanvasContent(
"Payment Request Filters",
<PaymentRequestFilterPanel onApply={setFilters} />
<PaymentRequestFilterPanel
onApply={setFilters}
ref={updatedRef}
clearFilter={clearFilter}
setFilterdata={setFilterdata}
/>
);
return () => {
@ -59,6 +60,22 @@ const PaymentRequestPage = () => {
};
}, []);
const handleRemoveChip = (key, id) => {
setFilters((prev) => {
const updated = { ...prev };
if (Array.isArray(updated[key])) {
updated[key] = updated[key].filter((v) => v !== id);
setTimeout(() => updatedRef.current?.resetFieldValue(key, updated[key]), 0);
} else {
updated[key] = null;
setTimeout(() => updatedRef.current?.resetFieldValue(key, null), 0);
}
return updated;
});
};
return (
<PaymentRequestContext.Provider value={contextValue}>
<div className="container-fluid">
@ -109,6 +126,9 @@ const PaymentRequestPage = () => {
<PaymentRequestList
search={search}
filters={filters}
filterData={filterData}
removeFilterChip={handleRemoveChip}
clearFilter={clearFilter}
/>
{/* Add/Edit Modal */}
@ -116,20 +136,16 @@ const PaymentRequestPage = () => {
<GlobalModel
isOpen
size="lg"
closeModal={() =>
setManageRequest({ IsOpen: null, expenseId: null })
}
closeModal={() => setManageRequest({ IsOpen: null, expenseId: null })}
>
<ManagePaymentRequest
key={ManageRequest.RequestId ?? "new"}
requestToEdit={ManageRequest.RequestId}
closeModal={() =>
setManageRequest({ IsOpen: null, RequestId: null })
}
closeModal={() => setManageRequest({ IsOpen: null, RequestId: null })}
/>
</GlobalModel>
)}
{ViewRequest.view && (
<GlobalModel
isOpen
@ -137,17 +153,19 @@ const PaymentRequestPage = () => {
modalType="top"
closeModal={() => setVieRequest({ requestId: null, view: false })}
>
<ViewPaymentRequest requestId={ViewRequest?.requestId} />
<ViewPaymentRequest requestId={ViewRequest?.requestId} />
</GlobalModel>
)}
{isExpenseGenerate.IsOpen && (
{isExpenseGenerate.IsOpen && (
<GlobalModel
isOpen
size="md"
closeModal={() => setIsExpenseGenerate({IsOpen:false, requestId: null})}
closeModal={() => setIsExpenseGenerate({ IsOpen: false, requestId: null })}
>
<MakeExpe
nse onClose={() => setIsExpenseGenerate({IsOpen:false, requestId: null})} />
<MakeExpense
onClose={() => setIsExpenseGenerate({ IsOpen: false, requestId: null })}
/>
</GlobalModel>
)}
@ -161,7 +179,6 @@ const PaymentRequestPage = () => {
<PreviewDocument imageUrl={ViewDocument.Image} />
</GlobalModel>
)}
</div>
</PaymentRequestContext.Provider>
);