added filter - sorting, and groupby

This commit is contained in:
pramod.mahajan 2025-11-22 19:17:47 +05:30
parent b8861fbf41
commit ee1cf01743
7 changed files with 251 additions and 85 deletions

View File

@ -29,7 +29,7 @@ const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => {
{ key: "isActive", title: "Status" }, { key: "isActive", title: "Status" },
]; ];
const fetcher = async ({ page, pageSize, search }) => { const fetcher = async ({ page, pageSize, search, filter }) => {
const response = await CollectionRepository.getCollections( const response = await CollectionRepository.getCollections(
selectedProject, selectedProject,
search || "", search || "",
@ -38,7 +38,8 @@ const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => {
pageSize, pageSize,
page, page,
true, // isActive true, // isActive
isPending isPending,
filter
); );
const api = response.data; const api = response.data;

View File

@ -1,4 +1,4 @@
import React from 'react' import React from "react";
export const EmployeeChip = ({ handleRemove, employee }) => { export const EmployeeChip = ({ handleRemove, employee }) => {
return ( return (
@ -40,6 +40,29 @@ export const EmployeeChip = ({handleRemove,employee}) => {
title="Remove" title="Remove"
/> />
</span> </span>
) );
} };
export const Chip = ({ handleRemove, chipObj }) => {
return (
<span
key={chipObj?.id}
className="tagify__tag d-inline-flex align-items-center me-1 mb-1"
role="listitem"
>
<div className="d-flex align-items-center">
<div className="d-flex flex-colemployeemn">
<span className="tagify__tag-text">{chipObj?.name}</span>
</div>
</div>
<bemployeetton
type="bemployeetton"
className="tagify__tag__removeBtn border-none"
onClick={() => handleRemove(chipObj?.id)}
aria-label={`Remove ${chipObj.name}`}
title="Remove"
/>
</span>
);
};

View File

@ -5,7 +5,7 @@ export const CollectionRepository = {
createNewCollection: (data) => createNewCollection: (data) =>
api.post(`/api/Collection/invoice/create`, data), api.post(`/api/Collection/invoice/create`, data),
updateCollection: (id, data) => { updateCollection: (id, data) => {
api.put(`/api/Collection/invoice/edit/${id}`, data) api.put(`/api/Collection/invoice/edit/${id}`, data);
}, },
// getCollections: (pageSize, pageNumber,fromDate,toDate, isPending,isActive,projectId, searchString) => { // getCollections: (pageSize, pageNumber,fromDate,toDate, isPending,isActive,projectId, searchString) => {
// let url = `/api/Collection/invoice/list?pageSize=${pageSize}&pageNumber=${pageNumber}&isPending=${isPending}&isActive=${isActive}&searchString=${searchString}`; // let url = `/api/Collection/invoice/list?pageSize=${pageSize}&pageNumber=${pageNumber}&isPending=${isPending}&isActive=${isActive}&searchString=${searchString}`;
@ -21,7 +21,17 @@ export const CollectionRepository = {
// return api.get(url); // return api.get(url);
// }, // },
getCollections: (projectId, searchString, fromDate, toDate, pageSize, pageNumber, isActive, isPending) => { getCollections: (
projectId,
searchString,
fromDate,
toDate,
pageSize,
pageNumber,
isActive,
isPending,
filter
) => {
let url = `/api/Collection/invoice/list`; let url = `/api/Collection/invoice/list`;
const params = []; const params = [];
@ -33,6 +43,7 @@ export const CollectionRepository = {
if (pageNumber) params.push(`pageNumber=${pageNumber}`); if (pageNumber) params.push(`pageNumber=${pageNumber}`);
if (isActive) params.push(`isActive=${isActive}`); if (isActive) params.push(`isActive=${isActive}`);
if (isPending) params.push(`isPending=${isPending}`); if (isPending) params.push(`isPending=${isPending}`);
if (filter) params.push(`filter=${filter}`);
if (params.length > 0) { if (params.length > 0) {
url += "?" + params.join("&"); url += "?" + params.join("&");
@ -41,9 +52,10 @@ export const CollectionRepository = {
return api.get(url); return api.get(url);
}, },
makeReceivePayment: (data) => api.post(`/api/Collection/invoice/payment/received`, data), makeReceivePayment: (data) =>
markPaymentReceived: (invoiceId) => api.put(`/api/Collection/invoice/marked/completed/${invoiceId}`), api.post(`/api/Collection/invoice/payment/received`, data),
markPaymentReceived: (invoiceId) =>
api.put(`/api/Collection/invoice/marked/completed/${invoiceId}`),
getCollection: (id) => api.get(`/api/Collection/invoice/details/${id}`), getCollection: (id) => api.get(`/api/Collection/invoice/details/${id}`),
addComment: (data) => api.post(`/api/Collection/invoice/add/comment`, data) addComment: (data) => api.post(`/api/Collection/invoice/add/comment`, data),
}; };

View File

@ -38,8 +38,7 @@ export default function PmsGrid({
pageSize, pageSize,
setPage, setPage,
setPageSize, setPageSize,
setGroupBy setGroupBy,
,
search, search,
setSearch, setSearch,
selected, selected,
@ -181,27 +180,26 @@ export default function PmsGrid({
Group By Group By
</button> </button>
<div className="dropdown-menu p-2"> <div className="dropdown-menu px-1">
{visibleColumns {visibleColumns
.filter((c) => c.groupable) .filter((c) => c.groupable)
.map((c) => ( .map((c) => (
<div <li
key={c.key} key={c.key}
className="dropdown-item" className="dropdown-item rounded py-1 cursor-pointer"
onClick={() => grid.setGroupBy(c.key)} onClick={() => setGroupBy(c.key)}
style={{ cursor: "pointer" }}
> >
{c.title} {c.title}
</div> </li>
))} ))}
{grid.groupBy && ( {grid.groupBy && (
<div <li
className="dropdown-item text-danger" className="dropdown-item text-danger py-1 cursor-pointer rounded"
onClick={() => grid.setGroupBy(null)} onClick={() => setGroupBy(null)}
> >
Clear Grouping Clear Grouping
</div> </li>
)} )}
</div> </div>
</div> </div>

View File

@ -1,17 +1,86 @@
// const PmsHeaderOption = ({ pinned, onPinLeft, onPinRight, onUnpin }) => {
// return (
// <div className="dropdown z-100">
// <button
// type="button"
// className="btn btn-icon btn-text-secondary rounded-pill p-0"
// data-bs-toggle="dropdown"
// data-bs-auto-close="outside"
// aria-expanded="true"
// >
// <i className="bx bx-dots-vertical-rounded bx-sm text-muted"></i>
// </button>
// <ul className="dropdown-menu dropdown-menu-end border shadow rounded-3 py-2">
// <li>
// <button
// className={`dropdown-item d-flex align-items-center ${
// pinned === "left" ? "active" : ""
// }`}
// onClick={onPinLeft}
// >
// <i className="bx bx-pin me-2"></i> Pin Left
// </button>
// </li>
// <li>
// <button
// className={`dropdown-item d-flex align-items-center ${
// pinned === "right" ? "active" : ""
// }`}
// onClick={onPinRight}
// >
// <i className="bx bx-pin me-2"></i> Pin Right
// </button>
// </li>
// {pinned && (
// <>
// <li>
// <hr className="dropdown-divider" />
// </li>
// <li>
// <button
// className="dropdown-item text-danger d-flex align-items-center"
// onClick={onUnpin}
// >
// <i className="bx bx-x me-2"></i> Unpin Column
// </button>
// </li>
// </>
// )}
// </ul>
// </div>
// );
// };
// export default PmsHeaderOption;
const PmsHeaderOption = ({ pinned, onPinLeft, onPinRight, onUnpin }) => { const PmsHeaderOption = ({ pinned, onPinLeft, onPinRight, onUnpin }) => {
return ( return (
<div className="dropdown z-100"> <div className="dropdown" style={{ zIndex: 9999 }}>
<button <button
type="button" type="button"
className="btn btn-icon btn-text-secondary rounded-pill p-0" className="btn btn-icon btn-text-secondary rounded-pill p-0"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
data-bs-auto-close="outside" data-bs-auto-close="outside"
aria-expanded="false"
> >
<i className="bx bx-dots-vertical-rounded bx-sm text-muted"></i> <i className="bx bx-dots-vertical-rounded bx-sm text-muted"></i>
</button> </button>
<ul className="dropdown-menu dropdown-menu-end shadow-sm border-0 rounded-3 py-2"> <ul className="dropdown-menu dropdown-menu-end border shadow rounded-3 py-2">
<li className="dropdown-submenu dropstart">
<button
type="button"
className="dropdown-item d-flex align-items-center justify-content-between"
onClick={(e) => {
e.stopPropagation();
e.currentTarget.nextElementSibling.classList.toggle("show");
}}
>
<span>
<i className="bx bx-pin me-2"></i> Pin
</span>
<i className="bx bx-chevron-right"></i>
</button>
<ul className="dropdown-menu border shadow rounded-3 py-2">
<li> <li>
<button <button
className={`dropdown-item d-flex align-items-center ${ className={`dropdown-item d-flex align-items-center ${
@ -19,9 +88,11 @@ const PmsHeaderOption = ({ pinned, onPinLeft, onPinRight, onUnpin }) => {
}`} }`}
onClick={onPinLeft} onClick={onPinLeft}
> >
<i className="bx bx-pin me-2"></i> Pin Left <i className="bx bx-left-arrow-alt me-2"></i>
Left
</button> </button>
</li> </li>
<li> <li>
<button <button
className={`dropdown-item d-flex align-items-center ${ className={`dropdown-item d-flex align-items-center ${
@ -29,18 +100,34 @@ const PmsHeaderOption = ({ pinned, onPinLeft, onPinRight, onUnpin }) => {
}`} }`}
onClick={onPinRight} onClick={onPinRight}
> >
<i className="bx bx-pin me-2"></i> Pin Right <i className="bx bx-right-arrow-alt me-2"></i>
Right
</button> </button>
</li> </li>
</ul>
</li>
<li>
<button
className={`dropdown-item d-flex align-items-center ${
pinned === "left" ? "active" : ""
}`}
>
<i className="bx bx-left-arrow-alt me-2"></i>
</button>
</li>
{pinned && ( {pinned && (
<> <>
<li><hr className="dropdown-divider" /></li> <li>
<hr className="dropdown-divider" />
</li>
<li> <li>
<button <button
className="dropdown-item text-danger d-flex align-items-center" className="dropdown-item text-danger d-flex align-items-center"
onClick={onUnpin} onClick={onUnpin}
> >
<i className="bx bx-x me-2"></i> Unpin Column <i className="bx bx-x me-2"></i>
Unpin Column
</button> </button>
</li> </li>
</> </>
@ -49,4 +136,5 @@ const PmsHeaderOption = ({ pinned, onPinLeft, onPinRight, onUnpin }) => {
</div> </div>
); );
}; };
export default PmsHeaderOption; export default PmsHeaderOption;

View File

@ -230,3 +230,20 @@
margin-left: 0; margin-left: 0;
} }
} }
.dropdown-submenu {
position: relative;
}
.dropdown-submenu > .dropdown-menu {
top: 0;
left: 100%;
margin-left: 0.1rem;
display: none;
}
.dropdown-submenu > .dropdown-menu.show {
display: block;
}

View File

@ -19,6 +19,8 @@ export function useGridCore({
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [debouncedSearch, setDebouncedSearch] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState("");
const [groupBy, setGroupBy] = useState(null); const [groupBy, setGroupBy] = useState(null);
const [serverGroupRows, setServerGroupRows] = useState(null);
const [sortBy, setSortBy] = useState({ key: null, dir: "asc" }); const [sortBy, setSortBy] = useState({ key: null, dir: "asc" });
const [selected, setSelected] = useState(new Set()); const [selected, setSelected] = useState(new Set());
const [expanded, setExpanded] = useState(new Set()); const [expanded, setExpanded] = useState(new Set());
@ -72,6 +74,20 @@ export function useGridCore({
// server-side fetch // server-side fetch
const fetchServer = useCallback(async () => { const fetchServer = useCallback(async () => {
const sortFilters = sortBy.key
? [
{
column: sortBy.key,
sortDescending: sortBy.dir === "desc",
},
]
: [];
const filterPayload = JSON.stringify({
sortFilters,
groupByColumn: groupBy || null,
});
if (!serverMode || typeof fetcher !== "function") return; if (!serverMode || typeof fetcher !== "function") return;
setLoading(true); setLoading(true);
try { try {
@ -80,6 +96,7 @@ export function useGridCore({
pageSize, pageSize,
sortBy, sortBy,
search: debouncedSearch, search: debouncedSearch,
filter: filterPayload,
}); });
// expected: { rows: [], total } // expected: { rows: [], total }
setServerRows(resp.rows || []); setServerRows(resp.rows || []);
@ -87,7 +104,7 @@ export function useGridCore({
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [serverMode, fetcher, page, pageSize, sortBy, debouncedSearch]); }, [serverMode, fetcher, page, pageSize, sortBy, debouncedSearch, groupBy]);
useEffect(() => { useEffect(() => {
if (serverMode) fetchServer(); if (serverMode) fetchServer();
@ -133,11 +150,21 @@ export function useGridCore({
}, []); }, []);
const changeSort = useCallback((key) => { const changeSort = useCallback((key) => {
setSortBy((prev) => setSortBy((prev) => {
prev.key === key // first click = asc
? { key, dir: prev.dir === "asc" ? "desc" : "asc" } if (prev.key !== key) {
: { key, dir: "asc" } return { key, dir: "asc" };
); }
// second click = desc
if (prev.dir === "asc") {
return { key, dir: "desc" };
}
// third click = remove sort
return { key: null, dir: "asc" };
});
setPage(1); setPage(1);
}, []); }, []);