partially added advance filter

This commit is contained in:
pramod.mahajan 2025-12-08 00:47:46 +05:30
parent 102a88aee6
commit e5fa6bb6ea
4 changed files with 227 additions and 174 deletions

View File

@ -95,11 +95,13 @@ const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => {
reorder: true,
columnVisibility: true,
pageSizeSelector: true,
// groupByKey: "clientSubmitedDate",
aggregation: true,
pinning: true,
IsNumbering: true,
grouping: true,
groupByKey: "clientSubmitedDate",
aggregation: true,
IsNumbering: true,
actions: [
{
label: "Edit",
@ -111,10 +113,10 @@ const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => {
icon: "bx-trash text-danger",
onClick: (row) => console.log("Delete", row),
},
]
],
}}
/>
);
};

View File

@ -57,6 +57,8 @@ export default function PmsGrid({
expanded,
toggleExpand,
setColState,
onAdvanceFilters,
setAdanceFilter,
} = grid;
// --- Pin / Unpin helpers ---
@ -254,14 +256,10 @@ export default function PmsGrid({
style={{ position: "sticky", top: 0, zIndex: 10 }}
>
<tr className="p-2">
{features.IsNumbering && (
<th>
#
</th>
)}
{features.IsNumbering && <th>#</th>}
{features.expand && (
<th className="text-center ticky-action-column">
<i className='bx bx-collapse-vertical'></i>
<i className="bx bx-collapse-vertical"></i>
</th>
)}
{features.selection && (
@ -333,10 +331,12 @@ export default function PmsGrid({
<div className="d-flex align-items-center gap-1">
{features.pinning && (
<PmsHeaderOption
column={col.key}
pinned={col.pinned}
onPinLeft={() => pinColumn(col.key, "left")}
onPinRight={() => pinColumn(col.key, "right")}
onUnpin={() => unpinColumn(col.key)}
onAdvancedFilter={setAdanceFilter}
/>
)}
{features.resizing && (
@ -478,8 +478,6 @@ export default function PmsGrid({
</td>
)}
{/* Data columns */}
{visibleColumns.map((col) => {
const style = {

View File

@ -1,101 +1,142 @@
// 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>
import { useState } from "react";
import { useEffect } from "react";
const OPERATORS = {
number: [
{ key: "eq", label: "Equals" },
{ key: "neq", label: "Not Equal" },
{ key: "gt", label: "Greater Than" },
{ key: "gte", label: "Greater or Equal" },
{ key: "less than", label: "Less Than" },
{ key: "lte", label: "Less or Equal" },
{ key: "between", label: "Between" },
],
text: [
{ key: "contains", label: "Contains" },
{ key: "starts", label: "Starts With" },
{ key: "ends", label: "Ends With" },
{ key: "eq", label: "Equals" },
],
date: [
{ key: "before", label: "Before" },
{ key: "after", label: "After" },
{ key: "between", label: "Between" },
],
};
// <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 toggleSubmenu = (e) => {
e.stopPropagation();
// ----------- FILTER UI COMPONENT ----------
function AdvanceFilter({ type = "number", onApply, onClear }) {
const [operator, setOperator] = useState("");
const [value1, setValue1] = useState("");
const [value2, setValue2] = useState("");
const currentSubMenu = e.currentTarget.nextElementSibling;
if (!currentSubMenu) return;
const ops = OPERATORS[type];
const allSubMenus = document.querySelectorAll(
".dropdown-submenu > .dropdown-menu"
);
const apply = () => {
if (!operator) return;
allSubMenus.forEach((menu) => {
if (menu !== currentSubMenu) {
menu.classList.remove("show");
if (operator === "between") {
onApply({
operator,
from: value1,
to: value2,
});
} else {
onApply({
operator,
value: value1,
});
}
});
currentSubMenu.classList.toggle("show");
};
useEffect(() => {
const handleOutsideClick = () => {
const allSubMenus = document.querySelectorAll(
".dropdown-submenu > .dropdown-menu"
return (
<div
className="dropdown p-3"
onClick={(e) => e.stopPropagation()} // prevent closing menu
style={{ width: 240 }}
>
{/* Operator Dropdown */}
<div className="mb-2">
<label className="form-label">Condition</label>
<select
className="form-select form-select-sm"
value={operator}
onChange={(e) => setOperator(e.target.value)}
>
<option value="">Select</option>
{ops.map((o) => (
<option key={o.key} value={o.key}>
{o.label}
</option>
))}
</select>
</div>
{/* Values */}
{operator && (
<div>
<label className="form-label">Value</label>
{operator !== "between" ? (
<input
type={type === "date" ? "date" : "number"}
className="form-control form-control-sm"
value={value1}
onChange={(e) => setValue1(e.target.value)}
/>
) : (
<div className="d-flex gap-2">
<input
type={type === "date" ? "date" : "number"}
className="form-control form-control-sm"
placeholder="From"
value={value1}
onChange={(e) => setValue1(e.target.value)}
/>
<input
type={type === "date" ? "date" : "number"}
className="form-control form-control-sm"
placeholder="To"
value={value2}
onChange={(e) => setValue2(e.target.value)}
/>
</div>
)}
</div>
)}
{/* Buttons */}
<div className="d-flex justify-content-between mt-3">
<button className="btn btn-light btn-sm" onClick={onClear}>
Clear
</button>
<button className="btn btn-primary btn-sm" onClick={apply}>
Apply
</button>
</div>
</div>
);
}
allSubMenus.forEach((menu) => {
menu.classList.remove("show");
});
// -------- HEADER OPTION ---------------
const PmsHeaderOption = ({
column,
pinned,
onPinLeft,
onPinRight,
onUnpin,
onAdvancedFilter,
}) => {
const [openMenu, setOpenMenu] = useState(null);
const toggleMenu = (key, e) => {
e.stopPropagation();
setOpenMenu((prev) => (prev === key ? null : key));
};
document.addEventListener("click", handleOutsideClick);
return () => {
document.removeEventListener("click", handleOutsideClick);
};
}, []);
return (
<div className="dropdown" style={{ zIndex: 9999 }}>
<button
type="button"
className="btn btn-icon btn-text-secondary rounded-pill p-0"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
@ -103,72 +144,61 @@ const PmsHeaderOption = ({ pinned, onPinLeft, onPinRight, onUnpin }) => {
<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">
<ul className="dropdown-menu dropdown-menu-end shadow border rounded-3 py-2">
{/* ADVANCED FILTER */}
<li className="dropdown-submenu dropstart">
<button
type="button"
className="dropdown-item d-flex align-items-center justify-content-between"
onClick={toggleSubmenu}
className="dropdown-item d-flex justify-content-between"
onClick={(e) => toggleMenu("filter", e)}
>
<span>
<i className="bx bx-left-arrow-alt me-2"></i>
Move Column
<i className="bx bx-filter-alt me-2"></i> Filter
</span>
<i className="bx bx-chevron-left"></i>
</button>
<ul className="dropdown-menu border shadow rounded-3 py-2">
<li>
<button
className="dropdown-item d-flex align-items-center"
onClick={() => console.log("Move Left")}
<ul
className={`dropdown-menu shadow rounded-3 py-2 p-0 ${
openMenu === "filter" ? "show" : ""
}`}
>
<i className="bx bx-arrow-to-left me-2"></i>
Move Left
</button>
</li>
<li>
<button
className="dropdown-item d-flex align-items-center"
onClick={() => console.log("Move Right")}
>
<i className="bx bx-arrow-to-right me-2"></i>
Move Right
</button>
<li className="p-0">
<AdvanceFilter
type="number"
onApply={(f) => onAdvancedFilter({...f,column})}
onClear={() => onAdvancedFilter(null)}
/>
</li>
</ul>
</li>
{/* PIN COLUMN */}
<li className="dropdown-submenu dropstart">
<button
type="button"
className="dropdown-item d-flex align-items-center justify-content-between"
onClick={toggleSubmenu}
className="dropdown-item d-flex justify-content-between"
onClick={(e) => toggleMenu("pin", e)}
>
<span>
<i className="bx bx-pin me-2"></i> Pin Column
</span>
<i className="bx bx-chevron-right"></i>
<i className="bx bx-chevron-left"></i>
</button>
<ul className="dropdown-menu border shadow rounded-3 py-2">
<li>
<button
className="dropdown-item d-flex align-items-center"
onClick={onUnpin}
<ul
className={`dropdown-menu shadow rounded-3 py-2 ${
openMenu === "pin" ? "show" : ""
}`}
>
{pinned !== "left" && pinned !== "right" && (
<i className="bx bx-check me-2"></i>
)}
<li>
<button className="dropdown-item" onClick={onUnpin}>
{!pinned && <i className="bx bx-check me-2"></i>}
No Pin
</button>
</li>
<li>
<button
className={`dropdown-item d-flex align-items-center ${
pinned === "left" ? "active" : ""
}`}
className={`dropdown-item ${pinned === "left" ? "active" : ""}`}
onClick={onPinLeft}
>
{pinned === "left" && <i className="bx bx-check me-2"></i>}
@ -176,9 +206,9 @@ const PmsHeaderOption = ({ pinned, onPinLeft, onPinRight, onUnpin }) => {
</button>
</li>
<li className="d-flex flex-row gap-2">
<li>
<button
className={`dropdown-item d-flex align-items-center ${
className={`dropdown-item ${
pinned === "right" ? "active" : ""
}`}
onClick={onPinRight}

View File

@ -20,6 +20,7 @@ export function useGridCore({
const [debouncedSearch, setDebouncedSearch] = useState("");
const [groupBy, setGroupBy] = useState(null);
const [serverGroupRows, setServerGroupRows] = useState(null);
const [onAdvanceFilter, setAdanceFilter] = useState(null);
const [sortBy, setSortBy] = useState({ key: null, dir: "asc" });
const [selected, setSelected] = useState(new Set());
@ -74,6 +75,7 @@ export function useGridCore({
// server-side fetch
const fetchServer = useCallback(async () => {
// sorting column wise
const sortFilters = sortBy.key
? [
{
@ -83,9 +85,19 @@ export function useGridCore({
]
: [];
// -----------------------------
// 3. ADVANCED FILTERS (NUMERIC FILTER POPOVER)
// The grid will pass "filter" already shaped like:
// filter = { totalAmount: { type: "gt", value: 200 } }
// -----------------------------
let advanceFilters = [];
if (onAdvanceFilter) {
advanceFilters.push(onAdvanceFilter);
}
const filterPayload = JSON.stringify({
sortFilters,
groupByColumn: groupBy || null,
advanceFilters,
});
if (!serverMode || typeof fetcher !== "function") return;
@ -104,7 +116,16 @@ export function useGridCore({
} finally {
setLoading(false);
}
}, [serverMode, fetcher, page, pageSize, sortBy, debouncedSearch, groupBy]);
}, [
serverMode,
fetcher,
page,
pageSize,
sortBy,
debouncedSearch,
groupBy,
onAdvanceFilter,
]);
useEffect(() => {
if (serverMode) fetchServer();
@ -209,6 +230,8 @@ export function useGridCore({
setColState,
groupBy,
setGroupBy,
onAdvanceFilter,
setAdanceFilter,
// data
rows,