partially added advance filter
This commit is contained in:
parent
102a88aee6
commit
e5fa6bb6ea
@ -81,40 +81,42 @@ const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<PmsGrid
|
||||
columns={columns}
|
||||
serverMode
|
||||
fetcher={fetcher}
|
||||
rowKey="id"
|
||||
features={{
|
||||
search: true,
|
||||
pagination: true,
|
||||
pinning: true,
|
||||
resizing: true,
|
||||
selection: false,
|
||||
reorder: true,
|
||||
columnVisibility: true,
|
||||
pageSizeSelector: true,
|
||||
// groupByKey: "clientSubmitedDate",
|
||||
aggregation: true,
|
||||
pinning: true,
|
||||
IsNumbering: true,
|
||||
grouping: true,
|
||||
actions: [
|
||||
{
|
||||
label: "Edit",
|
||||
icon: "bx-edit ",
|
||||
onClick: (row) => console.log("Edit", row),
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
icon: "bx-trash text-danger",
|
||||
onClick: (row) => console.log("Delete", row),
|
||||
},
|
||||
<PmsGrid
|
||||
columns={columns}
|
||||
serverMode
|
||||
fetcher={fetcher}
|
||||
rowKey="id"
|
||||
features={{
|
||||
search: true,
|
||||
pagination: true,
|
||||
pinning: true,
|
||||
resizing: true,
|
||||
selection: false,
|
||||
reorder: true,
|
||||
columnVisibility: true,
|
||||
pageSizeSelector: true,
|
||||
|
||||
grouping: true,
|
||||
groupByKey: "clientSubmitedDate",
|
||||
|
||||
aggregation: true,
|
||||
IsNumbering: true,
|
||||
|
||||
actions: [
|
||||
{
|
||||
label: "Edit",
|
||||
icon: "bx-edit ",
|
||||
onClick: (row) => console.log("Edit", row),
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
icon: "bx-trash text-danger",
|
||||
onClick: (row) => console.log("Delete", row),
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
|
||||
]
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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 && (
|
||||
@ -378,9 +378,9 @@ export default function PmsGrid({
|
||||
</tr>
|
||||
)}
|
||||
{!loading && groupBy && groupedRows && groupedRows.length > 0
|
||||
? groupedRows.map((g,indG) => (
|
||||
? groupedRows.map((g, indG) => (
|
||||
<React.Fragment key={g.key}>
|
||||
<tr className="bg-light-primary" >
|
||||
<tr className="bg-light-primary">
|
||||
<td
|
||||
colSpan={
|
||||
visibleColumns.length +
|
||||
@ -400,7 +400,7 @@ export default function PmsGrid({
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
{g.items.map((row,indG) => renderRow(row,indG))}
|
||||
{g.items.map((row, indG) => renderRow(row, indG))}
|
||||
</React.Fragment>
|
||||
))
|
||||
: currentRows.map((row, ind) => renderRow(row, ind))}
|
||||
@ -450,7 +450,7 @@ export default function PmsGrid({
|
||||
<small className="text-secondry">{ind + 1}</small>
|
||||
</td>
|
||||
)}
|
||||
{/* Expand toggle next to selection */}
|
||||
{/* Expand toggle next to selection */}
|
||||
{features.expand && (
|
||||
<td className="text-center align-middle ">
|
||||
<button
|
||||
@ -478,8 +478,6 @@ export default function PmsGrid({
|
||||
</td>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
{/* Data columns */}
|
||||
{visibleColumns.map((col) => {
|
||||
const style = {
|
||||
|
||||
@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
currentSubMenu.classList.toggle("show");
|
||||
if (operator === "between") {
|
||||
onApply({
|
||||
operator,
|
||||
from: value1,
|
||||
to: value2,
|
||||
});
|
||||
} else {
|
||||
onApply({
|
||||
operator,
|
||||
value: value1,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
allSubMenus.forEach((menu) => {
|
||||
menu.classList.remove("show");
|
||||
});
|
||||
};
|
||||
{/* Values */}
|
||||
{operator && (
|
||||
<div>
|
||||
<label className="form-label">Value</label>
|
||||
|
||||
document.addEventListener("click", handleOutsideClick);
|
||||
{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)}
|
||||
/>
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleOutsideClick);
|
||||
};
|
||||
}, []);
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
// -------- 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));
|
||||
};
|
||||
|
||||
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")}
|
||||
>
|
||||
<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>
|
||||
<ul
|
||||
className={`dropdown-menu shadow rounded-3 py-2 p-0 ${
|
||||
openMenu === "filter" ? "show" : ""
|
||||
}`}
|
||||
>
|
||||
<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">
|
||||
<ul
|
||||
className={`dropdown-menu shadow rounded-3 py-2 ${
|
||||
openMenu === "pin" ? "show" : ""
|
||||
}`}
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item d-flex align-items-center"
|
||||
onClick={onUnpin}
|
||||
>
|
||||
{pinned !== "left" && pinned !== "right" && (
|
||||
<i className="bx bx-check me-2"></i>
|
||||
)}
|
||||
<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}
|
||||
|
||||
@ -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,
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user