295 lines
8.1 KiB
JavaScript
295 lines
8.1 KiB
JavaScript
import { useState, useRef, useEffect } from "react";
|
|
import { useDropdownPosition } from "./useGridCore";
|
|
|
|
const OPERATORS = {
|
|
number: [
|
|
{ key: "eq", label: "Equals" },
|
|
{ key: "neq", label: "Not Equal" },
|
|
{ key: "gt", label: "Greater Than" },
|
|
{ key: "gte", label: "Greater or Equal" },
|
|
{ key: "lt", 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" },
|
|
],
|
|
};
|
|
|
|
// ----------- FILTER UI COMPONENT ----------
|
|
function AdvanceFilter({ type = "number", onApply, onClear }) {
|
|
const [operation, setOperator] = useState("");
|
|
const [value1, setValue1] = useState("");
|
|
const [value2, setValue2] = useState("");
|
|
|
|
const ops = OPERATORS[type];
|
|
|
|
const apply = () => {
|
|
if (!operation) return;
|
|
|
|
if (operation === "between") {
|
|
onApply({
|
|
operation,
|
|
from: value1,
|
|
to: value2,
|
|
});
|
|
} else {
|
|
onApply({
|
|
operation,
|
|
value: value1,
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className=""
|
|
onClick={(e) => e.stopPropagation()}
|
|
style={{ width: 240 }}
|
|
>
|
|
<div className="mb-2">
|
|
<label className="form-label text-decoration-none">Condition</label>
|
|
<select
|
|
className="form-select form-select-sm"
|
|
value={operation}
|
|
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 */}
|
|
{operation && (
|
|
<div className="text-decoration-none">
|
|
<label className="form-label">Value</label>
|
|
|
|
{operation !== "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 flex-row mt-3">
|
|
<button className="btn btn-primary btn-xs" onClick={apply}>
|
|
Apply
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// -------- HEADER OPTION ---------------
|
|
const PmsHeaderOption = ({
|
|
column,
|
|
pinned,
|
|
onPinLeft,
|
|
onPinRight,
|
|
onUnpin,
|
|
onAdvancedFilter,
|
|
}) => {
|
|
const { key: columnKey, enableAdvancedFilter } = column;
|
|
const rootRef = useRef(null);
|
|
|
|
const btnRef = useRef(null);
|
|
const menuRef = useRef(null);
|
|
|
|
const filterBtnRef = useRef(null);
|
|
const filterMenuRef = useRef(null);
|
|
|
|
const pinBtnRef = useRef(null);
|
|
const pinMenuRef = useRef(null);
|
|
|
|
const [open, setOpen] = useState(false);
|
|
const [openMenu, setOpenMenu] = useState(null);
|
|
|
|
// Main dropdown position
|
|
const mainStyle = useDropdownPosition(btnRef, menuRef, open, 0);
|
|
|
|
// Submenus
|
|
const filterStyle = useDropdownPosition(
|
|
filterBtnRef,
|
|
filterMenuRef,
|
|
openMenu === "filter",
|
|
1
|
|
);
|
|
|
|
const pinStyle = useDropdownPosition(
|
|
pinBtnRef,
|
|
pinMenuRef,
|
|
openMenu === "pin",
|
|
1
|
|
);
|
|
|
|
const toggleMenu = (name, e) => {
|
|
e.stopPropagation();
|
|
setOpenMenu((prev) => (prev === name ? null : name));
|
|
};
|
|
|
|
// ----------------------------
|
|
// CLOSE WHEN CLICKING OUTSIDE
|
|
// ----------------------------
|
|
useEffect(() => {
|
|
const handleOutside = (e) => {
|
|
if (!rootRef.current) return;
|
|
|
|
if (!rootRef.current.contains(e.target)) {
|
|
setOpen(false);
|
|
setOpenMenu(null);
|
|
}
|
|
};
|
|
|
|
if (open) document.addEventListener("mousedown", handleOutside);
|
|
return () => document.removeEventListener("mousedown", handleOutside);
|
|
}, [open]);
|
|
|
|
return (
|
|
<div
|
|
ref={rootRef}
|
|
className="dropdown "
|
|
style={{ zIndex: 9999, position: "relative" }}
|
|
>
|
|
{/* BUTTON */}
|
|
<button
|
|
ref={btnRef}
|
|
className="btn btn-icon btn-text-secondary rounded-pill p-0"
|
|
onClick={() => setOpen((p) => !p)}
|
|
>
|
|
<i className="bx bx-dots-vertical-rounded bx-sm text-muted"></i>
|
|
</button>
|
|
|
|
{/* MAIN MENU */}
|
|
{open && (
|
|
<ul
|
|
ref={menuRef}
|
|
className="dropdown-menu dropdown-menu-end shadow border rounded-3 py-2 show"
|
|
style={mainStyle}
|
|
>
|
|
{/* ADVANCED FILTER */}
|
|
{enableAdvancedFilter && (
|
|
<li className="dropdown-submenu dropstart position-relative">
|
|
<button
|
|
ref={filterBtnRef}
|
|
className="dropdown-item d-flex justify-content-between py-1 px-2 "
|
|
onClick={(e) => toggleMenu("filter", e)}
|
|
>
|
|
<span>
|
|
<i className="bx bx-filter-alt me-2"></i> Filter
|
|
</span>
|
|
<i className="bx bx-chevron-right"></i>
|
|
</button>
|
|
|
|
{openMenu === "filter" && (
|
|
<ul
|
|
ref={filterMenuRef}
|
|
className="dropdown-menu shadow rounded-3 py-2 p-0 show"
|
|
style={filterStyle}
|
|
>
|
|
<li className="p-2">
|
|
<AdvanceFilter
|
|
type="number"
|
|
onApply={(f) => onAdvancedFilter({ ...f, columnKey })}
|
|
onClear={() => onAdvancedFilter(null)}
|
|
/>
|
|
</li>
|
|
</ul>
|
|
)}
|
|
</li>
|
|
)}
|
|
|
|
{/* PIN COLUMN */}
|
|
<li className="dropdown-submenu dropstart position-relative">
|
|
<button
|
|
ref={pinBtnRef}
|
|
className="dropdown-item d-flex justify-content-between py-1 px-2 "
|
|
onClick={(e) => toggleMenu("pin", e)}
|
|
>
|
|
<span>
|
|
<i className="bx bx-pin me-2"></i> Pin Column
|
|
</span>
|
|
<i className="bx bx-chevron-right"></i>
|
|
</button>
|
|
|
|
{openMenu === "pin" && (
|
|
<ul
|
|
ref={pinMenuRef}
|
|
className=" dropdown-menu dropdown-menu-end shadow border rounded-3 py-2 show"
|
|
style={pinStyle}
|
|
>
|
|
<li>
|
|
<button className="dropdown-item py-1 px-2" onClick={onUnpin}>
|
|
{!pinned && <i className="bx bx-check me-2"></i>}
|
|
No Pin
|
|
</button>
|
|
</li>
|
|
|
|
<li>
|
|
<button
|
|
className={`dropdown-item py-1 px-2 ${
|
|
pinned === "left" ? "active" : ""
|
|
}`}
|
|
onClick={onPinLeft}
|
|
>
|
|
{pinned === "left" && <i className="bx bx-check me-2"></i>}
|
|
Pin Left
|
|
</button>
|
|
</li>
|
|
|
|
<li>
|
|
<button
|
|
className={`dropdown-item py-1 px-2 ${
|
|
pinned === "right" ? "active" : ""
|
|
}`}
|
|
onClick={onPinRight}
|
|
>
|
|
{pinned === "right" && <i className="bx bx-check me-2"></i>}
|
|
Pin Right
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
)}
|
|
</li>
|
|
</ul>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PmsHeaderOption;
|