marco.pms.web/src/services/pmsGrid/PmsHeaderOption.jsx

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;