UNPKG

advanced-react-datatable

Version:

A comprehensive React data grid component with advanced features like pinned columns, inline editing, grouping, filtering, and export capabilities

1,253 lines 88 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; import { jsx, jsxs, Fragment } from "react/jsx-runtime"; import * as React from "react"; import { useState, useMemo, useEffect, useCallback } from "react"; import { X, ChevronDown, Pin, ArrowUpDown, ArrowUp, ArrowDown, Check, ChevronRight, Download, Filter, Settings, Maximize2, GripVertical, EyeOff, Eye, ChevronsLeft, ChevronLeft, ChevronsRight } from "lucide-react"; import { cva } from "class-variance-authority"; import { clsx } from "clsx"; import { twMerge } from "tailwind-merge"; import { Transition, Dialog as Dialog$1 } from "@headlessui/react"; function cn(...inputs) { return twMerge(clsx(inputs)); } const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10" } }, defaultVariants: { variant: "default", size: "default" } } ); const Button = React.forwardRef( (_a, ref) => { var _b = _a, { className, variant, size } = _b, props = __objRest(_b, ["className", "variant", "size"]); return /* @__PURE__ */ jsx( "button", __spreadValues({ className: cn(buttonVariants({ variant, size, className })), ref }, props) ); } ); Button.displayName = "Button"; const Input = React.forwardRef( (_c, ref) => { var _d = _c, { className, type } = _d, props = __objRest(_d, ["className", "type"]); return /* @__PURE__ */ jsx( "input", __spreadValues({ type, className: cn( "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className ), ref }, props) ); } ); Input.displayName = "Input"; const PopoverContext = React.createContext(null); const Popover = ({ children }) => { const [isOpen, setIsOpen] = React.useState(false); return /* @__PURE__ */ jsx(PopoverContext.Provider, { value: { isOpen, setIsOpen }, children: /* @__PURE__ */ jsx("div", { className: "relative inline-block w-full", children }) }); }; const PopoverTrigger = ({ children, asChild }) => { const context = React.useContext(PopoverContext); if (asChild) { return React.cloneElement(children, { onClick: () => context == null ? void 0 : context.setIsOpen(!context.isOpen) }); } return /* @__PURE__ */ jsx("div", { onClick: () => context == null ? void 0 : context.setIsOpen(!context.isOpen), children }); }; const PopoverContent = React.forwardRef((_e, ref) => { var _f = _e, { className, children, align = "center", side = "bottom", sideOffset = 4 } = _f, props = __objRest(_f, ["className", "children", "align", "side", "sideOffset"]); const context = React.useContext(PopoverContext); const contentRef = React.useRef(null); React.useEffect(() => { const handleClickOutside = (event) => { if (contentRef.current && !contentRef.current.contains(event.target)) { context == null ? void 0 : context.setIsOpen(false); } }; if (context == null ? void 0 : context.isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [context == null ? void 0 : context.isOpen, context]); if (!(context == null ? void 0 : context.isOpen)) return null; const sideClass = side === "top" ? "bottom-full mb-1" : side === "left" ? "right-full mr-1" : side === "right" ? "left-full ml-1" : "top-full mt-1"; const alignClass = align === "start" ? "left-0" : align === "end" ? "right-0" : "left-1/2 -translate-x-1/2"; return /* @__PURE__ */ jsx( "div", __spreadProps(__spreadValues({ ref: contentRef, className: cn( "absolute z-50 w-72 rounded-md bg-popover p-4 text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95", sideClass, alignClass, className ) }, props), { children }) ); }); PopoverContent.displayName = "PopoverContent"; const badgeVariants = cva( "inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ring-1 ring-inset", { variants: { variant: { default: "bg-blue-50 text-blue-700 ring-blue-700/10", secondary: "bg-gray-50 text-gray-600 ring-gray-500/10", destructive: "bg-green-50 text-green-700 ring-green-600/20", outline: "bg-gray-50 text-gray-600 ring-gray-500/10" } }, defaultVariants: { variant: "default" } } ); function Badge(_g) { var _h = _g, { className, variant } = _h, props = __objRest(_h, ["className", "variant"]); return /* @__PURE__ */ jsx("div", __spreadValues({ className: cn(badgeVariants({ variant }), className) }, props)); } function getUniqueValues(data, field) { const values = /* @__PURE__ */ new Set(); data.forEach((row) => { const value = row[field]; if (value != null) { values.add(String(value)); } }); return Array.from(values).sort(); } function filterData(data, filters) { return data.filter((row) => { return filters.every((filter) => { const value = row[filter.field]; const filterValue = filter.value; if (value == null) return false; switch (filter.operator) { case "contains": return String(value).toLowerCase().includes(String(filterValue).toLowerCase()); case "equals": return value === filterValue; case "startsWith": return String(value).toLowerCase().startsWith(String(filterValue).toLowerCase()); case "endsWith": return String(value).toLowerCase().endsWith(String(filterValue).toLowerCase()); case "gt": return Number(value) > Number(filterValue); case "lt": return Number(value) < Number(filterValue); case "gte": if (typeof filterValue === "object" && filterValue.min !== void 0) { const numValue = Number(value); const minValue = Number(filterValue.min); const maxValue = Number(filterValue.max); return numValue >= minValue && (filterValue.max === "" || numValue <= maxValue); } return Number(value) >= Number(filterValue); case "lte": if (typeof filterValue === "object" && filterValue.max !== void 0) { const numValue = Number(value); const minValue = Number(filterValue.min); const maxValue = Number(filterValue.max); return numValue <= maxValue && (filterValue.min === "" || numValue >= minValue); } return Number(value) <= Number(filterValue); case "in": if (Array.isArray(filterValue)) { return filterValue.includes(String(value)); } return filterValue === String(value); default: return true; } }); }); } function sortData(data, sorts) { if (sorts.length === 0) return data; return [...data].sort((a, b) => { for (const sort of sorts) { const aValue = a[sort.field]; const bValue = b[sort.field]; if (aValue == null && bValue == null) continue; if (aValue == null) return 1; if (bValue == null) return -1; let comparison = 0; const aNum = Number(aValue); const bNum = Number(bValue); if (!isNaN(aNum) && !isNaN(bNum)) { comparison = aNum - bNum; } else { const aStr = String(aValue).toLowerCase(); const bStr = String(bValue).toLowerCase(); comparison = aStr < bStr ? -1 : aStr > bStr ? 1 : 0; } if (comparison !== 0) { return sort.direction === "asc" ? comparison : -comparison; } } return 0; }); } function groupData(data, groupFields, expandedGroups, columns) { const fields = Array.isArray(groupFields) ? groupFields : [groupFields]; if (fields.length === 0 || fields[0] === "") return data; function buildHierarchy(items, fieldIndex, parentPath = "") { if (fieldIndex >= fields.length) { return items; } const currentField = fields[fieldIndex]; const groups = /* @__PURE__ */ new Map(); items.forEach((item) => { const value = String(item[currentField] || "Ungrouped"); if (!groups.has(value)) { groups.set(value, []); } groups.get(value).push(item); }); const result = []; groups.forEach((groupItems, groupValue) => { const groupPath = parentPath ? `${parentPath}|${groupValue}` : groupValue; const isExpanded = expandedGroups.has(groupPath); function getTotalCount(items2) { return items2.length; } const summaries = /* @__PURE__ */ new Map(); columns.forEach((column) => { if (column.type === "number") { const sum = groupItems.reduce((acc, item) => { const value = Number(item[column.field]); return acc + (isNaN(value) ? 0 : value); }, 0); summaries.set(String(column.field), sum); } }); const groupHeader = { __isGroupHeader: true, __groupFields: [currentField], __groupValue: groupValue, __groupKey: groupPath, __itemCount: getTotalCount(groupItems), __expanded: isExpanded, __summaries: summaries, __level: fieldIndex, __field: currentField }; result.push(groupHeader); if (isExpanded) { if (fieldIndex === fields.length - 1) { result.push(...groupItems.map((groupItem) => __spreadProps(__spreadValues({}, groupItem), { __level: fields.length + 1 }))); } else { const nestedGroups = buildHierarchy(groupItems, fieldIndex + 1, groupPath); result.push(...nestedGroups); } } }); return result; } return buildHierarchy(data, 0); } function DataTableColumnFilter({ column, filter, data, onFilterChange }) { var _a, _b; const [filterValue, setFilterValue] = useState((filter == null ? void 0 : filter.value) || ""); const [rangeMin, setRangeMin] = useState( (filter == null ? void 0 : filter.operator) === "gte" ? filter.value : ((_a = filter == null ? void 0 : filter.value) == null ? void 0 : _a.min) || "" ); const [rangeMax, setRangeMax] = useState( (filter == null ? void 0 : filter.operator) === "lte" ? filter.value : ((_b = filter == null ? void 0 : filter.value) == null ? void 0 : _b.max) || "" ); const [selectedValues, setSelectedValues] = useState( (filter == null ? void 0 : filter.operator) === "in" && Array.isArray(filter.value) ? filter.value : [] ); const filterOptions = useMemo(() => { if (column.type === "select" || column.type === "boolean") { return getUniqueValues(data, column.field); } return column.options || []; }, [data, column.field, column.type, column.options]); useEffect(() => { if (filter) { setFilterValue(filter.value || ""); if (filter.operator === "gte") { if (typeof filter.value === "object" && filter.value.min !== void 0) { setRangeMin(filter.value.min); setRangeMax(filter.value.max || ""); } else { setRangeMin(filter.value); } } else if (filter.operator === "lte") { if (typeof filter.value === "object" && filter.value.max !== void 0) { setRangeMax(filter.value.max); setRangeMin(filter.value.min || ""); } else { setRangeMax(filter.value); } } if (filter.operator === "in" && Array.isArray(filter.value)) { setSelectedValues(filter.value); } } else { setFilterValue(""); setRangeMin(""); setRangeMax(""); setSelectedValues([]); } }, [filter]); const handleFilterChange = useCallback((value, operator = "contains") => { if (value === "" || value === null || value === void 0 || value === "__all__") { onFilterChange(null); return; } const newFilter = { field: String(column.field), operator, value, type: column.type || "text" }; onFilterChange(newFilter); }, [column.field, column.type, onFilterChange]); const handleMultiSelectChange = useCallback((value, checked) => { setSelectedValues((prev) => { const newValues = checked ? [...prev, value] : prev.filter((v) => v !== value); if (newValues.length === 0) { onFilterChange(null); return []; } const newFilter = { field: String(column.field), operator: "in", value: newValues, type: column.type || "text" }; onFilterChange(newFilter); return newValues; }); }, [column.field, column.type, onFilterChange]); const clearMultiSelect = useCallback(() => { setSelectedValues([]); onFilterChange(null); }, [onFilterChange]); const handleRangeChange = useCallback(() => { if (rangeMin !== "" && rangeMax !== "") { const newFilter = { field: String(column.field), operator: "gte", value: { min: rangeMin, max: rangeMax }, type: column.type || "number" }; onFilterChange(newFilter); } else if (rangeMin !== "") { handleFilterChange(rangeMin, "gte"); } else if (rangeMax !== "") { handleFilterChange(rangeMax, "lte"); } else { onFilterChange(null); } }, [rangeMin, rangeMax, handleFilterChange]); const clearFilter = useCallback(() => { setFilterValue(""); setRangeMin(""); setRangeMax(""); onFilterChange(null); }, [onFilterChange]); if (!column.filterable) { return /* @__PURE__ */ jsx("div", { className: "h-8" }); } const renderFilter = () => { switch (column.type) { case "select": return /* @__PURE__ */ jsxs(Popover, { children: [ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs( Button, { variant: "outline", size: "sm", className: "h-8 w-full border-0 bg-gray-200 rounded-sm justify-between text-sm font-light min-w-0", children: [ /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedValues.length === 0 ? "All" : selectedValues.length === 1 ? selectedValues[0] : `${selectedValues.length} selected` }), /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 opacity-50 flex-shrink-0" }) ] } ) }), /* @__PURE__ */ jsx(PopoverContent, { className: "w-56 p-2 bg-white ring-1 ring-gray-900/5", align: "start", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: "Filter options" }), selectedValues.length > 0 && /* @__PURE__ */ jsx( Button, { variant: "ghost", size: "sm", onClick: clearMultiSelect, className: "h-6 px-2 text-xs", children: "Clear" } ) ] }), /* @__PURE__ */ jsx("div", { className: "max-h-48 overflow-y-auto", children: filterOptions.map((option) => /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2 py-1", children: [ /* @__PURE__ */ jsx( "input", { type: "checkbox", id: `${String(column.field)}-${option}`, checked: selectedValues.includes(option), onChange: (e) => handleMultiSelectChange(option, e.target.checked), className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" } ), /* @__PURE__ */ jsx( "label", { htmlFor: `${String(column.field)}-${option}`, className: "text-sm font-normal cursor-pointer flex-1", children: option } ) ] }, option)) }), selectedValues.length > 0 && /* @__PURE__ */ jsx("div", { className: "border-t pt-2", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: selectedValues.map((value) => /* @__PURE__ */ jsxs( Badge, { variant: "secondary", className: "text-xs", children: [ value, /* @__PURE__ */ jsx( X, { className: "h-3 w-3 ml-1 cursor-pointer", onClick: () => handleMultiSelectChange(value, false) } ) ] }, value )) }) }) ] }) }) ] }); case "number": return /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [ /* @__PURE__ */ jsx( Input, { type: "number", placeholder: "Min", value: rangeMin, onChange: (e) => setRangeMin(e.target.value), onBlur: handleRangeChange, className: "h-8 text-sm font-light border-0 bg-gray-200 rounded-sm" } ), /* @__PURE__ */ jsx( Input, { type: "number", placeholder: "Max", value: rangeMax, onChange: (e) => setRangeMax(e.target.value), onBlur: handleRangeChange, className: "h-8 text-sm font-light border-0 bg-gray-200 rounded-sm" } ) ] }); case "boolean": return /* @__PURE__ */ jsxs(Popover, { children: [ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs( Button, { variant: "outline", size: "sm", className: "h-8 w-full justify-between text-sm font-light border-0 bg-gray-200 rounded-sm min-w-0", children: [ /* @__PURE__ */ jsx("span", { className: "truncate", children: selectedValues.length === 0 ? "All" : selectedValues.length === 1 ? selectedValues[0] : `${selectedValues.length} selected` }), /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 opacity-50 flex-shrink-0" }) ] } ) }), /* @__PURE__ */ jsx(PopoverContent, { className: "w-40 p-2", align: "start", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: "Status" }), selectedValues.length > 0 && /* @__PURE__ */ jsx( Button, { variant: "ghost", size: "sm", onClick: clearMultiSelect, className: "h-6 px-2 text-xs", children: "Clear" } ) ] }), /* @__PURE__ */ jsx("div", { className: "space-y-1", children: filterOptions.map((option) => /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [ /* @__PURE__ */ jsx( "input", { type: "checkbox", id: `${String(column.field)}-${option}`, checked: selectedValues.includes(option), onChange: (e) => handleMultiSelectChange(option, e.target.checked), className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" } ), /* @__PURE__ */ jsx( "label", { htmlFor: `${String(column.field)}-${option}`, className: "text-xs font-normal cursor-pointer flex-1", children: option } ) ] }, option)) }) ] }) }) ] }); default: return /* @__PURE__ */ jsx( Input, { type: "text", placeholder: "Filter...", value: filterValue, onChange: (e) => setFilterValue(e.target.value), onBlur: () => handleFilterChange(filterValue), onKeyDown: (e) => e.key === "Enter" && handleFilterChange(filterValue), className: "h-8 text-sm font-light w-full min-w-0 border-0 bg-gray-200 rounded-sm" } ); } }; return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 w-full", children: [ /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: renderFilter() }), filter && /* @__PURE__ */ jsx( Button, { variant: "ghost", size: "sm", className: "h-6 w-6 p-0 flex-shrink-0", onClick: clearFilter, children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" }) } ) ] }); } function DataTableHeader({ columns, sorts, filters, selectedRows, totalRows, data, onSort, onSelectAll, onFilterChange, // showSelection, showFilters = true }) { const getSortIcon = (field) => { const sort = sorts == null ? void 0 : sorts.find((s) => s.field === field); if (!sort) return /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-4 w-4" }); return sort.direction === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4 text-primary-500" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "h-4 w-4 text-primary-500" }); }; const visibleColumns = columns.filter((col) => !col.hidden); const pinnedLeftColumns = visibleColumns.filter((col) => col.pinned === "left"); const unpinnedColumns = visibleColumns.filter((col) => !col.pinned); const pinnedRightColumns = visibleColumns.filter((col) => col.pinned === "right"); const renderHeaderCell = (column, isPinned = false) => { const currentFilter = filters == null ? void 0 : filters.find((f) => f.field === String(column.field)); if (column.useSelection) { return /* @__PURE__ */ jsx( "div", { className: cn( "px-4 py-3 text-left bg-gray-50", isPinned && "sticky z-20 bg-gray-50", column.pinned === "left" && "left-0 border-r border-gray-800/10", column.pinned === "right" && "right-0 border-l border-gray-800/10" ), style: { minWidth: column.minWidth || (column.filterable ? "180px" : "120px"), maxWidth: column.maxWidth, width: column.width }, children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [ column.headerRenderer ? column.headerRenderer(column, { getSortIcon, onSort }, { selectedRows, onSelectAll, totalRows }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [ /* @__PURE__ */ jsx( "input", { type: "checkbox", checked: selectedRows.length > 0 && selectedRows.length === totalRows, onClick: (e) => { e.stopPropagation(); }, onChange: () => { onSelectAll(); }, className: cn( "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", "transition-all duration-150 ease-in-out", "hover:scale-110 focus:scale-110" ) } ), /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: column.header }), column.pinned && /* @__PURE__ */ jsx(Pin, { className: "h-3 w-3 text-primary-500" }), column.sortable && /* @__PURE__ */ jsx( Button, { variant: "ghost", size: "sm", className: cn( "p-0 h-4 w-4", "transition-all duration-200 ease-in-out", "hover:scale-110 hover:bg-gray-100 hover:shadow-sm", "active:scale-95", "focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1" ), onClick: () => onSort(String(column.field)), children: /* @__PURE__ */ jsx("div", { className: "transition-transform duration-200 ease-in-out hover:rotate-[5deg]", children: getSortIcon(String(column.field)) }) } ) ] }) ] }), showFilters && column.filterable && /* @__PURE__ */ jsx("div", { className: "w-full min-w-0", children: /* @__PURE__ */ jsx( DataTableColumnFilter, { column, filter: currentFilter, data, onFilterChange: (filter) => onFilterChange(String(column.field), filter) } ) }) ] }) }, String(column.field) ); } return /* @__PURE__ */ jsx( "div", { className: cn( "px-4 py-3 text-left bg-gray-50", isPinned && "sticky z-20 bg-gray-50", column.pinned === "left" && "left-0 border-r border-gray-800/10", column.pinned === "right" && "right-0 border-l border-gray-800/10" ), style: { minWidth: column.minWidth || (column.filterable ? "180px" : "120px"), maxWidth: column.maxWidth, width: column.width }, children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: column.headerRenderer ? column.headerRenderer(column, { getSortIcon, onSort }) : /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: column.header }), column.pinned && /* @__PURE__ */ jsx(Pin, { className: "h-3 w-3 text-primary-500" }), column.sortable && /* @__PURE__ */ jsx( Button, { variant: "ghost", size: "sm", className: cn( "p-0 h-4 w-4", "transition-all duration-200 ease-in-out", "hover:scale-110 hover:bg-gray-100 hover:shadow-sm", "active:scale-95", "focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1" ), onClick: () => onSort(String(column.field)), children: /* @__PURE__ */ jsx("div", { className: "transition-transform duration-200 ease-in-out hover:rotate-[5deg]", children: getSortIcon(String(column.field)) }) } ) ] }) }), showFilters && column.filterable && /* @__PURE__ */ jsx("div", { className: "w-full min-w-0", children: /* @__PURE__ */ jsx( DataTableColumnFilter, { column, filter: currentFilter, data, onFilterChange: (filter) => onFilterChange(String(column.field), filter) } ) }) ] }) }, String(column.field) ); }; const generateGridColumns = () => { const allColumns = [...pinnedLeftColumns, ...unpinnedColumns, ...pinnedRightColumns]; return allColumns.map((column, index) => { if (index === 0) { return "minmax(200px, 1fr)"; } const width = column.width || column.minWidth || (column.filterable ? 180 : 120); return `${width}px`; }).join(" "); }; return /* @__PURE__ */ jsxs( "div", { className: "bg-gray-50 sticky top-0 z-30 grid border-b border-gray-200", style: { gridTemplateColumns: generateGridColumns(), minWidth: "fit-content" }, children: [ pinnedLeftColumns.map((column) => renderHeaderCell(column, true)), unpinnedColumns.map((column) => renderHeaderCell(column, false)), pinnedRightColumns.map((column) => renderHeaderCell(column, true)) ] } ); } const SelectContext = React.createContext(null); const Select = ({ children, value, onValueChange, defaultValue = "" }) => { const [isOpen, setIsOpen] = React.useState(false); const [internalValue, setInternalValue] = React.useState(defaultValue); const currentValue = value !== void 0 ? value : internalValue; const handleValueChange = (newValue) => { if (value === void 0) { setInternalValue(newValue); } onValueChange == null ? void 0 : onValueChange(newValue); setIsOpen(false); }; return /* @__PURE__ */ jsx(SelectContext.Provider, { value: { isOpen, setIsOpen, value: currentValue, onValueChange: handleValueChange }, children: /* @__PURE__ */ jsx("div", { className: "relative w-full", children }) }); }; const SelectValue = ({ placeholder = "Select..." }) => { const context = React.useContext(SelectContext); return /* @__PURE__ */ jsx("span", { children: (context == null ? void 0 : context.value) || placeholder }); }; const SelectTrigger = React.forwardRef((_i, ref) => { var _j = _i, { className, children } = _j, props = __objRest(_j, ["className", "children"]); const context = React.useContext(SelectContext); return /* @__PURE__ */ jsxs( "button", __spreadProps(__spreadValues({ ref, type: "button", className: cn( "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className ), onClick: () => context == null ? void 0 : context.setIsOpen(!context.isOpen) }, props), { children: [ children, /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 opacity-50" }) ] }) ); }); SelectTrigger.displayName = "SelectTrigger"; const SelectContent = React.forwardRef((_k, ref) => { var _l = _k, { className, children, position = "popper" } = _l, props = __objRest(_l, ["className", "children", "position"]); const context = React.useContext(SelectContext); const contentRef = React.useRef(null); React.useEffect(() => { const handleClickOutside = (event) => { if (contentRef.current && !contentRef.current.contains(event.target)) { context == null ? void 0 : context.setIsOpen(false); } }; if (context == null ? void 0 : context.isOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [context == null ? void 0 : context.isOpen, context]); if (!(context == null ? void 0 : context.isOpen)) return null; return /* @__PURE__ */ jsx( "div", __spreadProps(__spreadValues({ ref: contentRef, className: cn( "absolute z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md top-full mt-1 animate-in fade-in-0 zoom-in-95", className ) }, props), { children: /* @__PURE__ */ jsx("div", { className: "p-1", children }) }) ); }); SelectContent.displayName = "SelectContent"; const SelectLabel = React.forwardRef((_m, ref) => { var _n = _m, { className, children } = _n, props = __objRest(_n, ["className", "children"]); return /* @__PURE__ */ jsx( "div", __spreadProps(__spreadValues({ ref, className: cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className) }, props), { children }) ); }); SelectLabel.displayName = "SelectLabel"; const SelectItem = React.forwardRef((_o, ref) => { var _p = _o, { className, children, value } = _p, props = __objRest(_p, ["className", "children", "value"]); const context = React.useContext(SelectContext); const isSelected = (context == null ? void 0 : context.value) === value; return /* @__PURE__ */ jsxs( "div", __spreadProps(__spreadValues({ ref, className: cn( "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className ), onClick: () => context == null ? void 0 : context.onValueChange(value) }, props), { children: [ /* @__PURE__ */ jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: isSelected && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }), /* @__PURE__ */ jsx("span", { children }) ] }) ); }); SelectItem.displayName = "SelectItem"; const SelectSeparator = React.forwardRef((_q, ref) => { var _r = _q, { className } = _r, props = __objRest(_r, ["className"]); return /* @__PURE__ */ jsx( "div", __spreadValues({ ref, className: cn("-mx-1 my-1 h-px bg-muted", className) }, props) ); }); SelectSeparator.displayName = "SelectSeparator"; function DataTableEditableCell({ value, // row, column, onSave, onCancel }) { const [editValue, setEditValue] = useState(value); const handleSave = useCallback(() => { onSave(editValue); }, [editValue, onSave]); const handleKeyDown = useCallback((e) => { if (e.key === "Enter") { handleSave(); } else if (e.key === "Escape") { onCancel(); } }, [handleSave, onCancel]); const renderEditor = () => { var _a; switch (column.type) { case "select": return /* @__PURE__ */ jsxs(Select, { value: editValue, onValueChange: setEditValue, children: [ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 text-sm", children: /* @__PURE__ */ jsx(SelectValue, {}) }), /* @__PURE__ */ jsx(SelectContent, { children: (_a = column.options) == null ? void 0 : _a.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option, children: option }, option)) }) ] }); case "number": return /* @__PURE__ */ jsx( Input, { type: "number", value: editValue, onChange: (e) => setEditValue(e.target.value), onKeyDown: handleKeyDown, className: "h-8 text-sm", autoFocus: true } ); case "boolean": return /* @__PURE__ */ jsxs(Select, { value: editValue ? "true" : "false", onValueChange: (val) => setEditValue(val === "true"), children: [ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 text-sm", children: /* @__PURE__ */ jsx(SelectValue, {}) }), /* @__PURE__ */ jsxs(SelectContent, { children: [ /* @__PURE__ */ jsx(SelectItem, { value: "true", children: "True" }), /* @__PURE__ */ jsx(SelectItem, { value: "false", children: "False" }) ] }) ] }); default: return /* @__PURE__ */ jsx( Input, { type: "text", value: editValue, onChange: (e) => setEditValue(e.target.value), onKeyDown: handleKeyDown, className: "h-8 text-sm", autoFocus: true } ); } }; return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [ /* @__PURE__ */ jsx("div", { className: "flex-1", children: renderEditor() }), /* @__PURE__ */ jsx( Button, { variant: "ghost", size: "sm", className: "h-6 w-6 p-0", onClick: handleSave, children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) } ), /* @__PURE__ */ jsx( Button, { variant: "ghost", size: "sm", className: "h-6 w-6 p-0", onClick: onCancel, children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" }) } ) ] }); } const INDENT_SIZE = 15; const DEFAULT_ROW_HEIGHT = 44; function DataTableRow({ row, level, columns, isSelected, onRowSelect, // onRowEdit, // onRowDelete, onCellEdit }) { const [editingCell, setEditingCell] = useState(null); const visibleColumns = columns.filter((col) => !col.hidden); const firstColumn = visibleColumns[0]; const pinnedLeftColumns = visibleColumns.filter((col) => col.pinned === "left"); const unpinnedColumns = visibleColumns.filter((col) => !col.pinned); const pinnedRightColumns = visibleColumns.filter((col) => col.pinned === "right"); const handleCellDoubleClick = (column) => { if (column.editable && onCellEdit) { setEditingCell(column.field); } }; const handleCellSave = (field, value) => { if (onCellEdit) { onCellEdit(row, field, value); } setEditingCell(null); }; const handleCellCancel = () => { setEditingCell(null); }; const renderCell = (column, isPinned = false) => { const value = column.valueGetter ? column.valueGetter(row) : row[column.field]; const isEditing = editingCell === column.field; const isFirstColumn = firstColumn.field === column.field; if (column.useSelection) { return /* @__PURE__ */ jsxs( "div", { className: cn( `min-h-[${DEFAULT_ROW_HEIGHT}px]`, "px-4 py-2 flex items-center justify-start gap-3", "transition-all duration-200 ease-in-out", isPinned && "bg-white z-10 sticky border-gray-800/10 overflow-x-hidden", column.pinned === "left" && "left-0 border-r", column.pinned === "right" && "right-0 border-l" ), style: { paddingLeft: isFirstColumn && level ? `${level * INDENT_SIZE}px` : void 0, minWidth: column.minWidth || 180, maxWidth: column.maxWidth, width: column.width }, children: [ /* @__PURE__ */ jsx( "input", { type: "checkbox", checked: isSelected, onClick: (e) => { e.stopPropagation(); }, onChange: () => { onRowSelect(row); }, className: cn( "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", "transition-all duration-150 ease-in-out" ) } ), /* @__PURE__ */ jsx("span", { className: cn("flex-1", isPinned ? "truncate" : ""), children: column.cellRenderer ? column.cellRenderer(value, row) : value }) ] }, String(column.field) ); } return /* @__PURE__ */ jsx( "div", { className: cn( `min-h-[${DEFAULT_ROW_HEIGHT}px]`, "px-4 py-2 flex items-center justify-start", "transition-all duration-200 ease-in-out", isPinned && "bg-white sticky z-10 border-gray-800/10", column.pinned === "left" && "left-0 border-r", column.pinned === "right" && "right-0 border-l", column.editable && "cursor-pointer hover:bg-gray-50 hover:scale-[1.02]", editingCell === column.field && "bg-blue-50 ring-2 ring-indigo-500 ring-inset" ), style: { paddingLeft: isFirstColumn && level ? `${level * INDENT_SIZE}px` : void 0, minWidth: column.minWidth, maxWidth: column.maxWidth, width: column.width }, onDoubleClick: () => handleCellDoubleClick(column), children: isEditing ? /* @__PURE__ */ jsx( DataTableEditableCell, { value, row, column, onSave: (newValue) => handleCellSave(column.field, newValue), onCancel: handleCellCancel } ) : column.cellRenderer ? column.cellRenderer(value, row) : value }, String(column.field) ); }; const generateGridColumns = () => { const allColumns = [...pinnedLeftColumns, ...unpinnedColumns, ...pinnedRightColumns]; return allColumns.map((column, index) => { if (index === 0) { return "minmax(200px, 1fr)"; } const width = column.width || column.minWidth || (column.filterable ? 180 : 120); return `${width}px`; }).join(" "); }; return /* @__PURE__ */ jsxs( "div", { className: cn( "group relative grid", "transition-all duration-200 ease-in-out", isSelected && "*:bg-blue-50 transform" ), style: { gridTemplateColumns: generateGridColumns(), minWidth: "fit-content" }, onClick: () => onRowSelect(row), children: [ pinnedLeftColumns.map((column) => renderCell(column, true)), unpinnedColumns.map((column) => renderCell(column, false)), pinnedRightColumns.map((column) => renderCell(column, true)) ] } ); } function DataTableGroupHeader({ groupValue, itemCount, expanded, summaries, columns, onToggle, level = 0, field }) { const visibleColumns = columns.filter((col) => !col.hidden); const generateGridColumns = () => { return visibleColumns.map((column, index) => { if (index === 0) { return "minmax(200px, 1fr)"; } const width = column.width || column.minWidth || (column.filterable ? 180 : 120); return `${width}px`; }).join(" "); }; return /* @__PURE__ */ jsx( "div", { className: "transition-colors grid", style: { gridTemplateColumns: generateGridColumns(), minWidth: "fit-content" }, children: visibleColumns.map((column, index) => { const isCheckboxColumn = String(column.field) === "__select__"; const isFirstDataColumn = !isCheckboxColumn && index === (visibleColumns.some((col) => String(col.field) === "__select__") ? 1 : 0); if (isCheckboxColumn || isFirstDataColumn) { const indentSize = level * INDENT_SIZE; return /* @__PURE__ */ jsx( "div", { className: cn( `min-h-[${DEFAULT_ROW_HEIGHT}px]`, "px-4 py-2 transition-colors bg-white flex items-center justify-start", column.pinned === "left" ? "left-0 border-r z-10 sticky border-gray-800/10 shadow-md overflow-x-hidden" : "" ), children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", style: { paddingLeft: `${indentSize}px` }, children: [ /* @__PURE__ */ jsx( Button, { variant: "ghost", size: "sm", onClick: onToggle, className: cn( "h-6 w-6 p-0 flex-shrink-0", "transition-all duration-200 ease-in-out", "hover:scale-110 active:scale-95", "hover:bg-gray-200 focus:ring-2 focus:ring-indigo-500" ), children: /* @__PURE__ */ jsx("div", { className: "transition-transform duration-200 ease-in-out", children: expanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) }) } ), /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ jsxs("span", { className: "font-medium text-gray-900 text-sm", children: [ groupValue, " (", itemCount, ")" ] }), field && /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500 ml-1", children: [ "(", field, ")" ] }) ] }) ] }) }, String(column.field) ); } else { const fieldName = String(column.field); const summary = summaries == null ? void 0 : summaries.get(fieldName); return /* @__PURE__ */ jsx( "div", { className: cn( `min-w-[${column.minWidth || "120px"}]`, `min-h-[${DEFAULT_ROW_HEIGHT}px]`, "px-4 py-2 transition-colors flex items-center", column.pinned === "left" ? "left-0 border-r sticky z-10 border-gray-800/10 shadow-md overflow-x-hidden" : "", column.pinned === "right" ? "right-0 bg-white border-l sticky z-10 border-gray-800/10 shadow-md overflow-x-hidden" : "" ), children: summary && /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs font-medium", children: [ "$", summary.toLocaleString() ] }) }, String(column.field) ); } }) } ); } function DataTableActionBar({ filters, onExport, onClearFilters, onOpenColumnConfig, onToggleFullscreen, isFullscreen, enablePdfExport = false }) { const hasActiveFilters = filters.length > 0; return /* @__PURE__ */ jsx("div", { className: "bg-gray-900 text-white p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ jsxs( Button, { variant: "outline", size: "sm", onClick: () => onExport == null ? void 0 : onExport("csv"), className: "flex items-center gap-2 bg-gray-800/50 hover:bg-gray-800 text-white border-gray-400 hover:border-gray-400/10", children: [ /* @__PURE__ */ jsx(Download, { className: "h-4 w-4 text-white" }), "Export CSV" ] } ), enablePdfExport && /* @__PURE__ */ jsxs( Button, { variant: "outline", size: "sm", onClick: () => onExport == null ? void 0 : onExport("pdf"), className: "flex items-center gap-2 bg-gray-800/50 hover:bg-gray-800 text-white border-gray-400 hover:border-gray-400/10", children: [ /* @__PURE__ */ jsx(Download, { className: "h-4 w-4 text-white" }), "Export PDF" ] } ) ] }), hasActiveFilters && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "bg-indigo-500 text-white border-indigo-400", children: [ /* @__PURE__ */ jsx(Filter, { className: "h-3 w-3 mr-1 text-white" }), filters.length, " filter", filters.length > 1 ? "s" : ""