@chainsys/sab-react-grid 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/cjs/SabReactTable.js +1192 -0
- package/dist/cjs/SabReactTable.js.map +1 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/utils/exportUtils.js +85 -0
- package/dist/cjs/utils/exportUtils.js.map +1 -0
- package/dist/cjs/utils/tableColumnResizeUtils.js +153 -0
- package/dist/cjs/utils/tableColumnResizeUtils.js.map +1 -0
- package/dist/cjs/utils/tableFormatters.js +239 -0
- package/dist/cjs/utils/tableFormatters.js.map +1 -0
- package/dist/esm/SabReactTable.d.ts +46 -0
- package/dist/esm/SabReactTable.d.ts.map +1 -0
- package/dist/esm/SabReactTable.js +1163 -0
- package/dist/esm/SabReactTable.js.map +1 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/utils/exportUtils.d.ts +17 -0
- package/dist/esm/utils/exportUtils.d.ts.map +1 -0
- package/dist/esm/utils/exportUtils.js +78 -0
- package/dist/esm/utils/exportUtils.js.map +1 -0
- package/dist/esm/utils/tableColumnResizeUtils.d.ts +21 -0
- package/dist/esm/utils/tableColumnResizeUtils.d.ts.map +1 -0
- package/dist/esm/utils/tableColumnResizeUtils.js +148 -0
- package/dist/esm/utils/tableColumnResizeUtils.js.map +1 -0
- package/dist/esm/utils/tableFormatters.d.ts +57 -0
- package/dist/esm/utils/tableFormatters.d.ts.map +1 -0
- package/dist/esm/utils/tableFormatters.js +231 -0
- package/dist/esm/utils/tableFormatters.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,1163 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useReactTable, getCoreRowModel, getSortedRowModel, getGroupedRowModel, getExpandedRowModel, getPaginationRowModel, getFilteredRowModel, flexRender } from '@tanstack/react-table';
|
|
3
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
4
|
+
import React, { useState, useEffect, useLayoutEffect, useRef, useCallback, memo, useMemo } from 'react';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
6
|
+
import { DataFormatter, ActionFormatter, formatDateValue, renderDateCell, isIsoDateLike } from './utils/tableFormatters';
|
|
7
|
+
import { exportTableToExcel, exportTableToCSV } from './utils/exportUtils';
|
|
8
|
+
import { resizeColumnsByCellContent } from './utils/tableColumnResizeUtils';
|
|
9
|
+
import Flatpickr from "react-flatpickr";
|
|
10
|
+
import "flatpickr/dist/themes/material_green.css";
|
|
11
|
+
/** Resolve column header label at render time from table + columns so grouping zone and group rows always show header (Name, Email) not id (name, email). */
|
|
12
|
+
function resolveGroupingLabel(colId, table, columns, labelMap) {
|
|
13
|
+
if (colId == null || typeof colId !== 'string')
|
|
14
|
+
return String(colId ?? '');
|
|
15
|
+
const s = String(colId).trim();
|
|
16
|
+
if (!s)
|
|
17
|
+
return colId;
|
|
18
|
+
const fromMap = labelMap[s];
|
|
19
|
+
if (fromMap != null && fromMap !== '' && fromMap !== s)
|
|
20
|
+
return fromMap;
|
|
21
|
+
if (Array.isArray(columns)) {
|
|
22
|
+
const fromProp = columns.find((c) => String(c?.id ?? c?.accessorKey ?? '') === s);
|
|
23
|
+
const propHeader = fromProp && fromProp.header;
|
|
24
|
+
if (typeof propHeader === 'string')
|
|
25
|
+
return propHeader;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const col = table.getColumn(s);
|
|
29
|
+
if (col) {
|
|
30
|
+
const h = col.columnDef.header;
|
|
31
|
+
if (typeof h === 'string')
|
|
32
|
+
return h;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (_) { /* ignore */ }
|
|
36
|
+
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Filter Operands Configuration
|
|
40
|
+
*/
|
|
41
|
+
const STRING_OPERANDS = [
|
|
42
|
+
{ label: 'Contains', value: 'contains', symbol: '*a*' },
|
|
43
|
+
{ label: 'Not Contains', value: 'notContains', symbol: '<>' },
|
|
44
|
+
{ label: 'Equals', value: 'equals', symbol: '=' },
|
|
45
|
+
{ label: 'Not Equal to', value: 'notEquals', symbol: '!=' },
|
|
46
|
+
{ label: 'Starts With', value: 'startsWith', symbol: 'a*' },
|
|
47
|
+
{ label: 'Ends With', value: 'endsWith', symbol: '*z' },
|
|
48
|
+
];
|
|
49
|
+
const NUMBER_OPERANDS = [
|
|
50
|
+
{ label: 'Equal to', value: 'equals', symbol: '=' },
|
|
51
|
+
{ label: 'LESS_THAN', value: 'lt', symbol: '<' },
|
|
52
|
+
{ label: 'Greater than', value: 'gt', symbol: '>' },
|
|
53
|
+
{ label: 'LESS_THAN_OR_EQUAL_TO', value: 'lte', symbol: '<=' },
|
|
54
|
+
{ label: 'GREATER_THAN_OR_EQUAL_TO', value: 'gte', symbol: '>=' },
|
|
55
|
+
{ label: 'NOT_EQUAL_TO', value: 'notEquals', symbol: '!=' },
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Advanced Filter Function
|
|
59
|
+
* Handles both object payload {value, operand} and simple string payload
|
|
60
|
+
*/
|
|
61
|
+
const parseCustomDate = (val) => {
|
|
62
|
+
if (val === null || val === undefined || val === '')
|
|
63
|
+
return NaN;
|
|
64
|
+
if (typeof val === 'number')
|
|
65
|
+
return val;
|
|
66
|
+
const str = String(val).trim();
|
|
67
|
+
// Handle numbers with commas first to prevent "1,000" -> NaN
|
|
68
|
+
const cleanNum = str.replace(/,/g, '');
|
|
69
|
+
if (/^-?\d+(\.\d+)?$/.test(cleanNum))
|
|
70
|
+
return Number(cleanNum);
|
|
71
|
+
// Handle DD/MM/YYYY format
|
|
72
|
+
if (str.includes('/')) {
|
|
73
|
+
const parts = str.split(/[\s/:]/);
|
|
74
|
+
if (parts.length >= 3) {
|
|
75
|
+
const d = Number(parts[0]);
|
|
76
|
+
const m = Number(parts[1]);
|
|
77
|
+
const y = Number(parts[2]);
|
|
78
|
+
if (!isNaN(d) && !isNaN(m) && !isNaN(y)) {
|
|
79
|
+
let h = 0, min = 0;
|
|
80
|
+
if (parts.length >= 5) {
|
|
81
|
+
h = Number(parts[3]);
|
|
82
|
+
min = Number(parts[4]);
|
|
83
|
+
const ampm = str.toLowerCase();
|
|
84
|
+
if (ampm.includes('pm') && h < 12)
|
|
85
|
+
h += 12;
|
|
86
|
+
if (ampm.includes('am') && h === 12)
|
|
87
|
+
h = 0;
|
|
88
|
+
}
|
|
89
|
+
const date = new Date(y, m - 1, d, h, min);
|
|
90
|
+
if (!isNaN(date.getTime()))
|
|
91
|
+
return date.getTime();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const fallback = new Date(str).getTime();
|
|
96
|
+
return isNaN(fallback) ? Number(str) : fallback;
|
|
97
|
+
};
|
|
98
|
+
const advancedFilterFn = (row, columnId, filterValue) => {
|
|
99
|
+
// 1. Handle Array (MultiSelect / Checkbox)
|
|
100
|
+
if (Array.isArray(filterValue)) {
|
|
101
|
+
if (filterValue.length === 0)
|
|
102
|
+
return true;
|
|
103
|
+
const cellValue = row.getValue(columnId);
|
|
104
|
+
if (Array.isArray(cellValue))
|
|
105
|
+
return filterValue.some(v => cellValue.includes(v));
|
|
106
|
+
return filterValue.includes(cellValue);
|
|
107
|
+
}
|
|
108
|
+
// 2. Handle Object { value, operand }
|
|
109
|
+
let value = filterValue;
|
|
110
|
+
let operand = 'contains';
|
|
111
|
+
if (filterValue && typeof filterValue === 'object' && 'value' in filterValue) {
|
|
112
|
+
value = filterValue.value;
|
|
113
|
+
operand = filterValue.operand || 'contains';
|
|
114
|
+
}
|
|
115
|
+
if (value === undefined || value === null || value === '')
|
|
116
|
+
return true;
|
|
117
|
+
const cellValue = row.getValue(columnId);
|
|
118
|
+
// Boolean Check
|
|
119
|
+
if (typeof value === 'boolean') {
|
|
120
|
+
return cellValue === value;
|
|
121
|
+
}
|
|
122
|
+
if (cellValue === undefined || cellValue === null)
|
|
123
|
+
return false;
|
|
124
|
+
const sVal = String(cellValue).toLowerCase();
|
|
125
|
+
const fVal = String(value).toLowerCase();
|
|
126
|
+
// Date/Number Parsing
|
|
127
|
+
const nCell = parseCustomDate(cellValue);
|
|
128
|
+
const nFilt = parseCustomDate(value);
|
|
129
|
+
// Helper checks
|
|
130
|
+
const isDateCompare = !isNaN(nCell) && !isNaN(nFilt) && (String(cellValue).includes('/') || String(value).includes('/'));
|
|
131
|
+
switch (operand) {
|
|
132
|
+
case 'contains': return sVal.includes(fVal);
|
|
133
|
+
case 'notContains': return !sVal.includes(fVal);
|
|
134
|
+
case 'equals': return isNaN(nCell) || isNaN(nFilt) ? sVal === fVal : (isDateCompare ? Math.abs(nCell - nFilt) < 60000 : nCell === nFilt); // 1 min tolerance for dates? or exact.
|
|
135
|
+
case 'notEquals': return isNaN(nCell) || isNaN(nFilt) ? sVal !== fVal : nCell !== nFilt;
|
|
136
|
+
case 'startsWith': return sVal.startsWith(fVal);
|
|
137
|
+
case 'endsWith': return sVal.endsWith(fVal);
|
|
138
|
+
case 'gt': return (isDateCompare || !isNaN(nCell)) && nCell > nFilt;
|
|
139
|
+
case 'lt': return (isDateCompare || !isNaN(nCell)) && nCell < nFilt;
|
|
140
|
+
case 'lte': return (isDateCompare || !isNaN(nCell)) && nCell <= nFilt;
|
|
141
|
+
case 'gte': return (isDateCompare || !isNaN(nCell)) && nCell >= nFilt;
|
|
142
|
+
default: return sVal.includes(fVal);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
// --- OPTIMIZATION: Memoized Header Cell ---
|
|
146
|
+
const MemoizedHeaderCell = memo(({ header, draggedColumnId, onDragStart, onDragOver, onDragEnd, onDrop, layoutMode, totalSize }) => {
|
|
147
|
+
const [showMenu, setShowMenu] = useState(false);
|
|
148
|
+
const menuRef = useRef(null);
|
|
149
|
+
const isSorted = header.column.getIsSorted();
|
|
150
|
+
const handleSort = (e, dir) => {
|
|
151
|
+
e.stopPropagation();
|
|
152
|
+
if (dir === 'clear')
|
|
153
|
+
header.column.clearSorting();
|
|
154
|
+
else
|
|
155
|
+
header.column.toggleSorting(dir === 'desc', true);
|
|
156
|
+
setShowMenu(false);
|
|
157
|
+
};
|
|
158
|
+
const handleRemoveFilter = (e) => {
|
|
159
|
+
e.stopPropagation();
|
|
160
|
+
header.column.setFilterValue(undefined);
|
|
161
|
+
setShowMenu(false);
|
|
162
|
+
};
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
const hide = (e) => {
|
|
165
|
+
if (menuRef.current && !menuRef.current.contains(e.target))
|
|
166
|
+
setShowMenu(false);
|
|
167
|
+
};
|
|
168
|
+
if (showMenu)
|
|
169
|
+
document.addEventListener('mousedown', hide);
|
|
170
|
+
return () => document.removeEventListener('mousedown', hide);
|
|
171
|
+
}, [showMenu]);
|
|
172
|
+
const widthStyle = (layoutMode === 'fit-window' && totalSize > 0)
|
|
173
|
+
? `${(header.getSize() / totalSize) * 100}%`
|
|
174
|
+
: header.getSize();
|
|
175
|
+
const [menuStyle, setMenuStyle] = useState({});
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (showMenu && menuRef.current) {
|
|
178
|
+
const rect = menuRef.current.parentElement?.getBoundingClientRect();
|
|
179
|
+
if (rect) {
|
|
180
|
+
// If cell is near left edge, align menu to left
|
|
181
|
+
if (rect.left < 200) {
|
|
182
|
+
setMenuStyle({ left: 0, right: 'auto' });
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
setMenuStyle({ right: 0, left: 'auto' });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}, [showMenu]);
|
|
190
|
+
return (_jsxs("th", { colSpan: header.colSpan, onDragOver: (e) => onDragOver(e, header.column.id), onDrop: (e) => onDrop(e, header.column.id), className: `slick-header-th ${draggedColumnId === header.column.id ? 'dragging' : ''}`, style: {
|
|
191
|
+
width: widthStyle,
|
|
192
|
+
position: 'sticky',
|
|
193
|
+
top: 0,
|
|
194
|
+
zIndex: 35,
|
|
195
|
+
background: '#f1f5f9'
|
|
196
|
+
}, children: [_jsxs("div", { className: "slick-header-cell-inner", children: [_jsxs("div", { className: "slick-header-label", draggable: true, onDragStart: (e) => {
|
|
197
|
+
if (e.dataTransfer) {
|
|
198
|
+
e.dataTransfer.setData('text/plain', header.column.id);
|
|
199
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
200
|
+
}
|
|
201
|
+
onDragStart(header.column.id);
|
|
202
|
+
}, onDragEnd: onDragEnd, onClick: header.column.getCanSort() ? header.column.getToggleSortingHandler() : undefined, style: { cursor: header.column.getCanSort() ? 'pointer' : 'default' }, title: typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : undefined, children: [flexRender(header.column.columnDef.header, header.getContext()), header.column.getCanSort() && (isSorted === 'asc' ? ' ▴' : isSorted === 'desc' ? ' ▾' : '')] }), !header.column.columnDef.meta?.hideMenu && (_jsx("div", { className: `slick-header-actions ${showMenu ? 'visible' : ''}`, onClick: (e) => { e.stopPropagation(); setShowMenu(!showMenu); }, children: _jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "6 9 12 15 18 9" }) }) })), showMenu && (_jsxs("div", { ref: menuRef, className: "slick-header-dropdown-menu", style: menuStyle, children: [header.column.getCanSort() && isSorted !== 'asc' && (_jsxs("div", { className: "dropdown-item", onClick: (e) => handleSort(e, 'asc'), children: [_jsx("div", { className: "round-icon-bg action-icon mini-icon", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M11 5h10" }), _jsx("path", { d: "M11 9h7" }), _jsx("path", { d: "M11 13h4" }), _jsx("path", { d: "M3 17l3 3 3-3" }), _jsx("path", { d: "M6 18V4" })] }) }), _jsx("span", { children: "Sort Ascending" })] })), header.column.getCanSort() && isSorted !== 'desc' && (_jsxs("div", { className: "dropdown-item", onClick: (e) => handleSort(e, 'desc'), children: [_jsx("div", { className: "round-icon-bg action-icon mini-icon", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M11 5h10" }), _jsx("path", { d: "M11 9h7" }), _jsx("path", { d: "M11 13h4" }), _jsx("path", { d: "M3 7l3-3 3 3" }), _jsx("path", { d: "M6 20V6" })] }) }), _jsx("span", { children: "Sort Descending" })] })), _jsx("div", { className: "dropdown-divider" }), _jsxs("div", { className: "dropdown-item", onClick: handleRemoveFilter, children: [_jsx("div", { className: "round-icon-bg action-icon mini-icon", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M22 3H2l8 9.46V19l4 2v-8.54L22 3z" }), _jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21" })] }) }), _jsx("span", { children: "Remove Filter" })] }), header.column.getCanSort() && (_jsxs("div", { className: "dropdown-item", onClick: (e) => handleSort(e, 'clear'), children: [_jsx("div", { className: "round-icon-bg action-icon mini-icon", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M7 15l5 5 5-5" }), _jsx("path", { d: "M7 9l5-5 5 5" })] }) }), _jsx("span", { children: "Remove Sort" })] }))] }))] }), header.column.getCanResize() && layoutMode !== 'fit-window' && (_jsx("div", { className: "slick-resizer", onMouseDown: header.getResizeHandler(), onTouchStart: header.getResizeHandler(), onClick: (e) => e.stopPropagation() }))] }, header.id));
|
|
203
|
+
}, (prevProps, nextProps) => {
|
|
204
|
+
// Re-render when header changes (important for column reordering to work)
|
|
205
|
+
if (prevProps.header !== nextProps.header)
|
|
206
|
+
return false;
|
|
207
|
+
return (prevProps.draggedColumnId === nextProps.draggedColumnId &&
|
|
208
|
+
prevProps.layoutMode === nextProps.layoutMode &&
|
|
209
|
+
prevProps.totalSize === nextProps.totalSize &&
|
|
210
|
+
prevProps.onDragStart === nextProps.onDragStart &&
|
|
211
|
+
prevProps.onDragOver === nextProps.onDragOver &&
|
|
212
|
+
prevProps.onDragEnd === nextProps.onDragEnd &&
|
|
213
|
+
prevProps.onDrop === nextProps.onDrop);
|
|
214
|
+
});
|
|
215
|
+
// Placeholder for text/number and dropdown filter fields
|
|
216
|
+
const FILTER_PLACEHOLDER = '🔎︎';
|
|
217
|
+
// Placeholder for date, datetime, and time filter fields only (used as Flatpickr placeholder text)
|
|
218
|
+
// const FILTER_PLACEHOLDER_DATE = '📅'
|
|
219
|
+
// Calendar icon SVG for date/datetime/time filter placeholder (light gray, shown when empty)
|
|
220
|
+
const FilterCalendarIcon = () => (_jsx("span", { className: "filter-date-placeholder-icon", "aria-hidden": true, children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2", ry: "2" }), _jsx("line", { x1: "16", y1: "2", x2: "16", y2: "6" }), _jsx("line", { x1: "8", y1: "2", x2: "8", y2: "6" }), _jsx("line", { x1: "3", y1: "10", x2: "21", y2: "10" })] }) }));
|
|
221
|
+
// --- OPTIMIZATION: Memoized Filter Cell ---
|
|
222
|
+
const MemoizedFilterCell = memo(({ header, filterValue, draggedId, layoutMode, totalSize, columnMeta }) => {
|
|
223
|
+
const meta = (columnMeta ?? header.column.columnDef.meta);
|
|
224
|
+
const colId = String(header.column.id || header.column.columnDef?.accessorKey || '');
|
|
225
|
+
const inferredDateFromId = /^(created|updated|date|time|dob|birth)/i.test(colId);
|
|
226
|
+
const filterType = meta?.filterType || (inferredDateFromId ? 'date' : 'text');
|
|
227
|
+
const filterOptions = meta?.filterOptions || [];
|
|
228
|
+
// State for all types
|
|
229
|
+
const [showPopover, setShowPopover] = useState(false);
|
|
230
|
+
const [dropdownPosition, setDropdownPosition] = useState(null);
|
|
231
|
+
const containerRef = useRef(null);
|
|
232
|
+
const operandSelectorRef = useRef(null);
|
|
233
|
+
const operandDropdownRef = useRef(null);
|
|
234
|
+
const textInputRef = useRef(null);
|
|
235
|
+
const flatpickrInstanceRef = useRef(null);
|
|
236
|
+
const isNumber = meta?.dataType === 'number' || meta?.dataType === 'decimal' || meta?.dataType === 'currency' || filterType === 'number';
|
|
237
|
+
const isDate = ['date', 'datetime', 'time'].includes(meta?.dataType || '') || ['date', 'datetime', 'time'].includes(filterType || '') || inferredDateFromId;
|
|
238
|
+
const operands = (isNumber || isDate) ? NUMBER_OPERANDS : STRING_OPERANDS;
|
|
239
|
+
// Stable options for Flatpickr to prevent re-init on re-render
|
|
240
|
+
const flatpickrOptions = useMemo(() => ({
|
|
241
|
+
enableTime: true,
|
|
242
|
+
dateFormat: "d/m/Y h:i K",
|
|
243
|
+
time_24hr: false,
|
|
244
|
+
closeOnSelect: false,
|
|
245
|
+
allowInput: true,
|
|
246
|
+
clickOpens: true,
|
|
247
|
+
position: 'auto',
|
|
248
|
+
static: false,
|
|
249
|
+
monthSelectorType: 'static',
|
|
250
|
+
onReady: (_d, _str, instance) => {
|
|
251
|
+
if (instance)
|
|
252
|
+
flatpickrInstanceRef.current = instance;
|
|
253
|
+
if (instance?.calendarContainer) {
|
|
254
|
+
instance.calendarContainer.addEventListener('mousedown', (e) => e.stopPropagation());
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}), []);
|
|
258
|
+
// Raw value from table state
|
|
259
|
+
const rawValue = filterValue;
|
|
260
|
+
// Derived external value based on type
|
|
261
|
+
const getSafeValue = () => {
|
|
262
|
+
if (['multiselect', 'checkbox'].includes(filterType)) {
|
|
263
|
+
return Array.isArray(rawValue) ? rawValue : [];
|
|
264
|
+
}
|
|
265
|
+
if (filterType === 'boolean') {
|
|
266
|
+
return rawValue; // boolean | undefined
|
|
267
|
+
}
|
|
268
|
+
// Text/Number: { value, operand }
|
|
269
|
+
if (typeof rawValue === 'object' && rawValue !== null && 'value' in rawValue) {
|
|
270
|
+
return rawValue;
|
|
271
|
+
}
|
|
272
|
+
return { value: rawValue || '', operand: (isNumber || isDate) ? (isDate ? 'equals' : 'equals') : 'contains' };
|
|
273
|
+
};
|
|
274
|
+
const extValue = getSafeValue();
|
|
275
|
+
// Local state for persistence (Text/Number uses object, others use direct value)
|
|
276
|
+
// We initialize from extValue.
|
|
277
|
+
const [localValue, setLocalValue] = useState(extValue);
|
|
278
|
+
// SYNC LOGIC: Always sync to support external clear
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
setLocalValue(getSafeValue());
|
|
281
|
+
}, [rawValue]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
282
|
+
// Compute dropdown position when opening (useLayoutEffect so portal has position in same frame - dropdown opens reliably)
|
|
283
|
+
useLayoutEffect(() => {
|
|
284
|
+
if (showPopover && operandSelectorRef.current) {
|
|
285
|
+
const rect = operandSelectorRef.current.getBoundingClientRect();
|
|
286
|
+
setDropdownPosition({ left: rect.left, top: rect.bottom + 4 });
|
|
287
|
+
}
|
|
288
|
+
else if (!showPopover) {
|
|
289
|
+
setDropdownPosition(null);
|
|
290
|
+
}
|
|
291
|
+
}, [showPopover]);
|
|
292
|
+
// Close popover on outside click (ignore clicks inside filter wrapper and portaled operand dropdown). Use capture + slight delay so same mousedown that opened doesn't close.
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
if (!showPopover)
|
|
295
|
+
return;
|
|
296
|
+
const hide = (e) => {
|
|
297
|
+
const target = e.target;
|
|
298
|
+
const inContainer = containerRef.current?.contains(target);
|
|
299
|
+
const inDropdown = operandDropdownRef.current?.contains(target);
|
|
300
|
+
if (!inContainer && !inDropdown)
|
|
301
|
+
setShowPopover(false);
|
|
302
|
+
};
|
|
303
|
+
const t = setTimeout(() => document.addEventListener('mousedown', hide), 0);
|
|
304
|
+
return () => { clearTimeout(t); document.removeEventListener('mousedown', hide); };
|
|
305
|
+
}, [showPopover]);
|
|
306
|
+
// Handlers
|
|
307
|
+
const handleClear = (e) => {
|
|
308
|
+
e?.stopPropagation();
|
|
309
|
+
if (['multiselect', 'checkbox'].includes(filterType)) {
|
|
310
|
+
setLocalValue([]);
|
|
311
|
+
header.column.setFilterValue(undefined);
|
|
312
|
+
}
|
|
313
|
+
else if (filterType === 'boolean') {
|
|
314
|
+
setLocalValue(undefined); // or null
|
|
315
|
+
header.column.setFilterValue(undefined);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
// Text/Number
|
|
319
|
+
const resetVal = { ...localValue, value: '' };
|
|
320
|
+
setLocalValue(resetVal);
|
|
321
|
+
header.column.setFilterValue(undefined);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
const updateTextNumber = (val) => {
|
|
325
|
+
const next = { ...localValue, value: val };
|
|
326
|
+
setLocalValue(next);
|
|
327
|
+
if (!val) {
|
|
328
|
+
// We don't clear local operand, just value
|
|
329
|
+
header.column.setFilterValue(undefined);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
header.column.setFilterValue(next);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
const updateOperand = (op) => {
|
|
336
|
+
const next = { ...localValue, operand: op };
|
|
337
|
+
setLocalValue(next);
|
|
338
|
+
if (next.value)
|
|
339
|
+
header.column.setFilterValue(next);
|
|
340
|
+
};
|
|
341
|
+
// Render Helpers
|
|
342
|
+
const renderTextNumber = () => {
|
|
343
|
+
const currentOp = operands.find(o => o.value === localValue.operand);
|
|
344
|
+
const operandDropdownContent = showPopover && dropdownPosition && (_jsx("div", { ref: operandDropdownRef, role: "listbox", className: "operand-dropdown operand-dropdown-portal", style: {
|
|
345
|
+
position: 'fixed',
|
|
346
|
+
left: dropdownPosition.left,
|
|
347
|
+
top: dropdownPosition.top,
|
|
348
|
+
zIndex: 10002,
|
|
349
|
+
}, children: operands.map(op => (_jsxs("div", { role: "option", className: `operand-item ${localValue.operand === op.value ? 'active' : ''}`, onMouseDown: (e) => { e.preventDefault(); e.stopPropagation(); updateOperand(op.value); setShowPopover(false); }, children: [_jsx("span", { className: "op-symbol", children: op.symbol }), _jsx("span", { className: "op-label", children: op.label })] }, op.value))) }));
|
|
350
|
+
return (_jsxs("div", { className: "slick-premium-filter-container slick-premium-filter-container-operand-visible", children: [_jsxs("div", { ref: operandSelectorRef, role: "button", tabIndex: 0, "aria-haspopup": "listbox", "aria-expanded": showPopover, className: "slick-filter-prefix operand-selector", onMouseDown: (e) => {
|
|
351
|
+
e.preventDefault();
|
|
352
|
+
e.stopPropagation();
|
|
353
|
+
setShowPopover(prev => !prev);
|
|
354
|
+
}, onClick: (e) => e.stopPropagation(), children: [currentOp?.symbol ? (_jsx("span", { className: "prefix-operand-symbol", children: currentOp.symbol })) : _jsx("span", { className: "prefix-operand-placeholder" }), _jsx("span", { className: "operand-selector-chevron", "aria-hidden": true, children: _jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: _jsx("path", { d: "M6 9l6 6 6-6" }) }) })] }), typeof document !== 'undefined' && operandDropdownContent && createPortal(operandDropdownContent, document.body), _jsxs("div", { className: "slick-filter-search-area", onClick: (e) => {
|
|
355
|
+
if (e.target !== e.currentTarget)
|
|
356
|
+
return;
|
|
357
|
+
if (isDate)
|
|
358
|
+
flatpickrInstanceRef.current?.open();
|
|
359
|
+
else
|
|
360
|
+
textInputRef.current?.focus();
|
|
361
|
+
}, children: [isDate ? (_jsxs(_Fragment, { children: [!localValue.value && _jsx(FilterCalendarIcon, {}), _jsx(Flatpickr, { className: "slick-premium-filter-input", value: localValue.value || '', onChange: ([date]) => {
|
|
362
|
+
if (date) {
|
|
363
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
364
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
365
|
+
const year = date.getFullYear();
|
|
366
|
+
let hours = date.getHours();
|
|
367
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
368
|
+
const ampm = hours >= 12 ? 'PM' : 'AM';
|
|
369
|
+
hours = hours % 12;
|
|
370
|
+
hours = hours ? hours : 12;
|
|
371
|
+
const strHours = String(hours).padStart(2, '0');
|
|
372
|
+
const formatted = `${day}/${month}/${year} ${strHours}:${minutes} ${ampm}`;
|
|
373
|
+
updateTextNumber(formatted);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
updateTextNumber('');
|
|
377
|
+
}
|
|
378
|
+
}, options: flatpickrOptions }, header.id + "_flatpickr")] })) : (_jsxs(_Fragment, { children: [_jsx("input", { ref: textInputRef, className: "slick-premium-filter-input", value: localValue.value || '', onChange: e => updateTextNumber(e.target.value), placeholder: FILTER_PLACEHOLDER }), localValue.value && (_jsx("div", { className: "filter-clear-btn", onClick: handleClear, title: "Clear filter", children: _jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", children: [_jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), _jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) }))] })), localValue.value && isDate && (_jsx("div", { className: "filter-clear-btn", onClick: handleClear, children: _jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", children: [_jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), _jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) }))] }), _jsx("style", { children: `
|
|
379
|
+
.slick-premium-filter-container .slick-filter-prefix.operand-selector,
|
|
380
|
+
.slick-premium-filter-container .slick-filter-prefix.search-selector { width: 36px; min-width: 36px; justify-content: center; background: #f1f5f9; border-right: 1px solid #e2e8f0; display: flex; align-items: center; border-radius: 6px 0 0 6px; }
|
|
381
|
+
.slick-premium-filter-container .operand-selector { width: 42px; min-width: 42px; }
|
|
382
|
+
.operand-selector-chevron { display: flex; align-items: center; margin-left: 1px; color: #64748b; flex-shrink: 0; }
|
|
383
|
+
.operand-selector:hover .operand-selector-chevron { color: #3b82f6; }
|
|
384
|
+
.slick-filter-dropdown-btn { flex: 1; display: flex; align-items: center; justify-content: space-between; padding: 0 8px; overflow: hidden; }
|
|
385
|
+
.dropdown-text-container { flex: 1; overflow: hidden; display: flex; align-items: center; }
|
|
386
|
+
.dropdown-text { font-size: 12.5px; color: #334155; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%; }
|
|
387
|
+
.slick-premium-filter-input { padding-right: 24px !important; }
|
|
388
|
+
.slick-premium-filter-input::placeholder { color: #94a3b8; opacity: 0.9; }
|
|
389
|
+
.filter-date-placeholder-icon { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); color: #94a3b8 !important; pointer-events: none; display: flex; align-items: center; z-index: 1; }
|
|
390
|
+
.filter-date-placeholder-icon svg { color: #94a3b8 !important; stroke: #94a3b8 !important; }
|
|
391
|
+
.slick-filter-search-area { position: relative; }
|
|
392
|
+
.flatpickr-input { width: 100%; min-width: 0; background: transparent; border: none; font-size: 12px; height: 100%; outline: none; color: #334155; font-weight: 500; font-family: inherit; overflow: hidden; text-overflow: ellipsis; padding-left: 28px !important; box-sizing: border-box; }
|
|
393
|
+
.flatpickr-wrapper { width: 100%; }
|
|
394
|
+
.filter-clear-btn { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); color: #94a3b8; cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 10; width: 16px; height: 16px; border-radius: 50%; transition: all 0.2s; background: rgba(255,255,255,0.8); }
|
|
395
|
+
.filter-clear-btn:hover { color: #ef4444; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
396
|
+
|
|
397
|
+
/* Flatpickr Overrides - Plain white/blue theme; hide dropdown arrow so only grey calendar placeholder shows in filter */
|
|
398
|
+
.flatpickr-calendar.arrowBottom:before,
|
|
399
|
+
.flatpickr-calendar.arrowBottom:after,
|
|
400
|
+
.flatpickr-calendar.arrowTop:before,
|
|
401
|
+
.flatpickr-calendar.arrowTop:after { display: none !important; }
|
|
402
|
+
.flatpickr-calendar { z-index: 10001 !important; box-shadow: 0 10px 40px rgba(0,0,0,0.2) !important; border: 1px solid #e2e8f0 !important; border-radius: 12px !important; font-family: 'Inter', sans-serif !important; }
|
|
403
|
+
.flatpickr-months { background: #fff !important; border-radius: 12px 12px 0 0 !important; }
|
|
404
|
+
.flatpickr-month { background: #fff !important; color: #334155 !important; }
|
|
405
|
+
.flatpickr-current-month { color: #334155 !important; }
|
|
406
|
+
.flatpickr-current-month .flatpickr-monthDropdown-months { background: #fff !important; color: #334155 !important; }
|
|
407
|
+
.flatpickr-weekdays { background: #fff !important; }
|
|
408
|
+
span.flatpickr-weekday { background: #fff !important; color: #64748b !important; font-weight: 600; }
|
|
409
|
+
.flatpickr-day.selected, .flatpickr-day.startRange, .flatpickr-day.endRange, .flatpickr-day.selected.inRange, .flatpickr-day.startRange.inRange, .flatpickr-day.endRange.inRange, .flatpickr-day.selected:focus, .flatpickr-day.startRange:focus, .flatpickr-day.endRange:focus, .flatpickr-day.selected:hover, .flatpickr-day.startRange:hover, .flatpickr-day.endRange:hover, .flatpickr-day.selected.prevMonthDay, .flatpickr-day.startRange.prevMonthDay, .flatpickr-day.endRange.prevMonthDay, .flatpickr-day.selected.nextMonthDay, .flatpickr-day.startRange.nextMonthDay, .flatpickr-day.endRange.nextMonthDay {
|
|
410
|
+
background: #3b82f6 !important;
|
|
411
|
+
border-color: #3b82f6 !important;
|
|
412
|
+
color: #fff !important;
|
|
413
|
+
}
|
|
414
|
+
.flatpickr-day:hover { background: #eff6ff !important; border-color: #eff6ff !important; color: #1e40af !important; }
|
|
415
|
+
.flatpickr-day.today { border-color: #3b82f6 !important; }
|
|
416
|
+
.flatpickr-day.today:hover { background: #eff6ff !important; }
|
|
417
|
+
.numInputWrapper:hover { background: #f8fafc !important; }
|
|
418
|
+
.flatpickr-time input:hover, .flatpickr-time .flatpickr-am-pm:hover, .flatpickr-time input:focus, .flatpickr-time .flatpickr-am-pm:focus { background: #f1f5f9 !important; }
|
|
419
|
+
.flatpickr-prev-month svg, .flatpickr-next-month svg { fill: #64748b !important; }
|
|
420
|
+
.flatpickr-prev-month:hover svg, .flatpickr-next-month:hover svg { fill: #3b82f6 !important; }
|
|
421
|
+
` })] }));
|
|
422
|
+
};
|
|
423
|
+
const renderCheckboxList = (isBoolean = false) => {
|
|
424
|
+
let options = filterOptions;
|
|
425
|
+
if (isBoolean) {
|
|
426
|
+
options = [{ label: 'True', value: true }, { label: 'False', value: false }];
|
|
427
|
+
if (filterOptions.length > 0)
|
|
428
|
+
options = filterOptions;
|
|
429
|
+
}
|
|
430
|
+
const selected = Array.isArray(localValue) ? localValue : [];
|
|
431
|
+
const count = selected.length;
|
|
432
|
+
const totalOpts = options.length;
|
|
433
|
+
// Display Text
|
|
434
|
+
const selectedLabels = options
|
|
435
|
+
.filter((o) => selected.includes(o.value))
|
|
436
|
+
.map((o) => o.label);
|
|
437
|
+
const displayText = selectedLabels.join(', ');
|
|
438
|
+
// Toggle Item
|
|
439
|
+
const toggleItem = (val) => {
|
|
440
|
+
const current = Array.isArray(localValue) ? localValue : [];
|
|
441
|
+
const exists = current.includes(val);
|
|
442
|
+
const next = exists ? current.filter(v => v !== val) : [...current, val];
|
|
443
|
+
setLocalValue(next);
|
|
444
|
+
header.column.setFilterValue(next.length ? next : undefined);
|
|
445
|
+
};
|
|
446
|
+
// Select All
|
|
447
|
+
const toggleAll = () => {
|
|
448
|
+
const current = Array.isArray(localValue) ? localValue : [];
|
|
449
|
+
if (current.length === totalOpts) {
|
|
450
|
+
setLocalValue([]);
|
|
451
|
+
header.column.setFilterValue(undefined);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
const all = options.map((o) => o.value);
|
|
455
|
+
setLocalValue(all);
|
|
456
|
+
header.column.setFilterValue(all);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
return (_jsxs("div", { className: `slick-premium-filter-container dropdown-trigger ${count > 0 ? 'has-selection' : ''}`, onClick: () => setShowPopover(!showPopover), children: [_jsx("div", { className: "slick-filter-prefix search-selector", role: "button", tabIndex: 0, title: "Click to select value(s)", onClick: (e) => { e.stopPropagation(); setShowPopover(true); }, onKeyDown: (e) => { if (e.key === 'Enter' || e.key === ' ') {
|
|
460
|
+
e.preventDefault();
|
|
461
|
+
setShowPopover(true);
|
|
462
|
+
} }, children: _jsx("span", { className: "filter-placeholder-char", "aria-hidden": true, children: FILTER_PLACEHOLDER }) }), _jsxs("div", { className: "slick-filter-dropdown-btn", children: [_jsx("div", { className: "dropdown-text-container", children: _jsx("span", { className: "dropdown-text", title: displayText, children: displayText }) }), _jsx("div", { className: "dropdown-icons", children: _jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M6 9l6 6 6-6" }) }) })] }), showPopover && (_jsxs("div", { className: "operand-dropdown check-list", children: [_jsxs("div", { className: "operand-item select-all", onClick: (e) => { e.stopPropagation(); toggleAll(); }, children: [_jsx("div", { className: `checkbox-box ${count === totalOpts ? 'checked' : ''}`, children: count === totalOpts && _jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) }) }), _jsx("span", { className: "op-label", children: "Select All" })] }), _jsx("div", { className: "list-scroll-area", children: options.map((opt) => {
|
|
463
|
+
const isSel = selected.includes(opt.value);
|
|
464
|
+
return (_jsxs("div", { className: `operand-item ${isSel ? 'active' : ''}`, onClick: (e) => { e.stopPropagation(); toggleItem(opt.value); }, children: [_jsx("div", { className: `checkbox-box ${isSel ? 'checked' : ''}`, children: isSel && _jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) }) }), _jsx("span", { className: "op-label", children: opt.label })] }, String(opt.value)));
|
|
465
|
+
}) }), _jsx("div", { className: "filter-popup-footer", children: _jsx("button", { className: "filter-ok-btn", onClick: (e) => { e.stopPropagation(); setShowPopover(false); }, children: "Ok" }) })] }))] }));
|
|
466
|
+
};
|
|
467
|
+
const widthStyle = (layoutMode === 'fit-window' && totalSize > 0)
|
|
468
|
+
? `${(header.getSize() / totalSize) * 100}%`
|
|
469
|
+
: header.getSize();
|
|
470
|
+
// Show filter for every column except Actions (by id or meta.actions)
|
|
471
|
+
const isActionColumn = header.column.id === 'actions' || !!(meta?.actions);
|
|
472
|
+
const canFilter = !isActionColumn;
|
|
473
|
+
return (_jsx("td", { className: `slick-filter-td ${draggedId === header.column.id ? 'dragging' : ''}`, style: {
|
|
474
|
+
width: widthStyle,
|
|
475
|
+
minWidth: 0,
|
|
476
|
+
maxWidth: widthStyle,
|
|
477
|
+
position: 'sticky',
|
|
478
|
+
top: 32, // Height of the header row (min-height 36px + 8px top + 8px bottom padding)
|
|
479
|
+
zIndex: 33,
|
|
480
|
+
background: canFilter ? '#f8fafc' : 'transparent',
|
|
481
|
+
padding: canFilter ? '6px 12px' : '0',
|
|
482
|
+
borderRight: canFilter ? '1px solid #f1f5f9' : 'none',
|
|
483
|
+
borderBottom: '1px solid #e2e8f0',
|
|
484
|
+
cursor: 'default',
|
|
485
|
+
overflow: 'visible'
|
|
486
|
+
}, children: canFilter && (_jsxs("div", { ref: containerRef, className: "filter-wrapper", style: { width: '100%', minWidth: 0, maxWidth: '100%', overflow: 'visible', boxSizing: 'border-box' }, children: [(filterType === 'text' || filterType === 'number' || filterType === 'date' || filterType === 'datetime' || filterType === 'time' || isDate) && renderTextNumber(), ['multiselect', 'checkbox'].includes(filterType) && renderCheckboxList(false), ['dropdown', 'select', 'radio', 'boolean'].includes(filterType) && renderCheckboxList(filterType === 'boolean'), !(filterType === 'text' || filterType === 'number' || filterType === 'date' || filterType === 'datetime' || filterType === 'time' || isDate) && !['multiselect', 'checkbox', 'dropdown', 'select', 'radio', 'boolean'].includes(filterType) && renderTextNumber()] })) }, `filter-${header.id}`));
|
|
487
|
+
}, (prevProps, nextProps) => {
|
|
488
|
+
// Re-render when header, filterValue, or columnMeta changes
|
|
489
|
+
if (prevProps.header !== nextProps.header || prevProps.filterValue !== nextProps.filterValue || prevProps.columnMeta !== nextProps.columnMeta)
|
|
490
|
+
return false;
|
|
491
|
+
return (prevProps.draggedId === nextProps.draggedId &&
|
|
492
|
+
prevProps.layoutMode === nextProps.layoutMode &&
|
|
493
|
+
prevProps.totalSize === nextProps.totalSize);
|
|
494
|
+
});
|
|
495
|
+
// --- OPTIMIZATION: Memoized Data Row ---
|
|
496
|
+
const MemoizedRow = memo(({ row, visibleCount, onRowClick, onActionClick, setContextMenu, getGroupingLabel, layoutMode, totalSize, columnMetaById }) => {
|
|
497
|
+
if (row.getIsGrouped()) {
|
|
498
|
+
const colId = row.groupingColumnId ?? '';
|
|
499
|
+
const colLabel = getGroupingLabel(colId);
|
|
500
|
+
const value = String(row.groupingValue);
|
|
501
|
+
return (_jsx("tr", { className: "slick-group-row", children: _jsx("td", { colSpan: visibleCount, children: _jsxs("div", { className: `slick-group-header-row ${row.getIsExpanded() ? 'expanded' : ''}`, onClick: row.getToggleExpandedHandler(), style: { paddingLeft: `${row.depth * 20 + 16}px` }, children: [_jsx("span", { className: "slick-group-chevron", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "9 18 15 12 9 6" }) }) }), _jsxs("span", { className: "slick-group-title", children: [_jsxs("strong", { className: "group-col-label", children: [colLabel, ":"] }), " ", value === 'undefined' || value === 'null' ? 'NULL' : value] }), _jsxs("span", { className: "slick-group-count", children: ["(", row.subRows.length, " items)"] })] }) }) }));
|
|
502
|
+
}
|
|
503
|
+
// Use table order: getVisibleCells() respects state.columnOrder and matches getHeaderGroups() order.
|
|
504
|
+
const cells = row.getVisibleCells();
|
|
505
|
+
return (_jsx("tr", { className: "slick-data-row", onClick: () => onRowClick && onRowClick(row.original), style: { cursor: onRowClick ? 'pointer' : 'default' }, children: cells.map(cell => (_jsx(MemoizedCell, { cell: cell, onActionClick: onActionClick, setContextMenu: setContextMenu, layoutMode: layoutMode, totalSize: totalSize, columnMetaById: columnMetaById }, cell.id))) }));
|
|
506
|
+
}, (prevProps, nextProps) => {
|
|
507
|
+
// Re-render when row or columnOrder changes (important for column reordering)
|
|
508
|
+
// columnOrder is accessed from props object, not destructured in component body
|
|
509
|
+
if (prevProps.row !== nextProps.row || prevProps.columnOrder !== nextProps.columnOrder)
|
|
510
|
+
return false;
|
|
511
|
+
return (prevProps.visibleCount === nextProps.visibleCount &&
|
|
512
|
+
prevProps.layoutMode === nextProps.layoutMode &&
|
|
513
|
+
prevProps.totalSize === nextProps.totalSize &&
|
|
514
|
+
prevProps.onRowClick === nextProps.onRowClick &&
|
|
515
|
+
prevProps.onActionClick === nextProps.onActionClick &&
|
|
516
|
+
prevProps.setContextMenu === nextProps.setContextMenu &&
|
|
517
|
+
prevProps.getGroupingLabel === nextProps.getGroupingLabel &&
|
|
518
|
+
prevProps.columnMetaById === nextProps.columnMetaById);
|
|
519
|
+
});
|
|
520
|
+
// Data types that should be right-aligned (numbers, dates); all others (text, textarea, email, etc.) left-aligned
|
|
521
|
+
const RIGHT_ALIGN_DATA_TYPES = ['number', 'decimal', 'currency', 'date', 'datetime', 'time', 'percentage'];
|
|
522
|
+
/** Infer dataType from column id/accessorKey when meta.dataType is not set (so alignment works without column meta) */
|
|
523
|
+
function inferDataTypeFromColumn(column) {
|
|
524
|
+
const def = column.columnDef;
|
|
525
|
+
const key = String(column.id || def?.accessorKey || '').toLowerCase();
|
|
526
|
+
if (!key)
|
|
527
|
+
return 'text';
|
|
528
|
+
if (key === 'id' || key === 'ids')
|
|
529
|
+
return 'number';
|
|
530
|
+
if (key === 'email')
|
|
531
|
+
return 'email';
|
|
532
|
+
const numericKeys = ['salary', 'amount', 'price', 'quantity', 'count', 'total', 'rate', 'number', 'num', 'qty', 'revenue', 'cost', 'balance'];
|
|
533
|
+
if (numericKeys.some(n => key.includes(n) || key === n))
|
|
534
|
+
return 'number';
|
|
535
|
+
const dateOnlyKeys = ['date', 'founded', 'dob', 'birth'];
|
|
536
|
+
if (dateOnlyKeys.some(d => key.includes(d) || key === d))
|
|
537
|
+
return 'date';
|
|
538
|
+
const dateTimeKeys = ['created', 'updated', 'time', 'open', 'close', 'willopen', 'willclose'];
|
|
539
|
+
if (dateTimeKeys.some(d => key.includes(d) || key === d))
|
|
540
|
+
return 'datetime';
|
|
541
|
+
return 'text';
|
|
542
|
+
}
|
|
543
|
+
// --- OPTIMIZATION: Memoized Cell Rendering ---
|
|
544
|
+
const MemoizedCell = memo(({ cell, onActionClick, setContextMenu, layoutMode, totalSize, columnMetaById }) => {
|
|
545
|
+
const widthStyle = (layoutMode === 'fit-window' && totalSize > 0)
|
|
546
|
+
? `${(cell.column.getSize() / totalSize) * 100}%`
|
|
547
|
+
: cell.column.getSize();
|
|
548
|
+
const colId = cell.column.id;
|
|
549
|
+
const accessorKey = cell.column.columnDef?.accessorKey;
|
|
550
|
+
const originalMeta = columnMetaById?.[colId] ?? (accessorKey ? columnMetaById?.[accessorKey] : undefined);
|
|
551
|
+
const meta = (originalMeta ?? cell.column.columnDef.meta);
|
|
552
|
+
const dataType = meta?.dataType ?? inferDataTypeFromColumn(cell.column);
|
|
553
|
+
const rawValue = cell.getValue();
|
|
554
|
+
const inferredDataType = inferDataTypeFromColumn(cell.column);
|
|
555
|
+
const isDateColumn = dataType === 'date' || dataType === 'datetime' || dataType === 'time';
|
|
556
|
+
const isInferredDateColumn = inferredDataType === 'date' || inferredDataType === 'datetime' || inferredDataType === 'time';
|
|
557
|
+
const valueLooksLikeIso = isIsoDateLike(rawValue);
|
|
558
|
+
const dateMode = isDateColumn
|
|
559
|
+
? dataType
|
|
560
|
+
: valueLooksLikeIso && isInferredDateColumn
|
|
561
|
+
? inferredDataType
|
|
562
|
+
: null;
|
|
563
|
+
const dateFormatOpts = meta?.formatOptions ? { dateFormat: meta.formatOptions.dateFormat, datetimeFormat: meta.formatOptions.datetimeFormat, timeFormat: meta.formatOptions.timeFormat } : undefined;
|
|
564
|
+
const cellTitle = dateMode ? (formatDateValue(rawValue, dateMode, dateFormatOpts) ?? String(rawValue ?? '')) : String(rawValue ?? '');
|
|
565
|
+
const formatterMeta = originalMeta ?? cell.column.columnDef.meta;
|
|
566
|
+
const useDateCell = dateMode !== null;
|
|
567
|
+
const textAlign = (RIGHT_ALIGN_DATA_TYPES.includes(dataType) || useDateCell) ? 'right' : 'left';
|
|
568
|
+
return (_jsx("td", { onContextMenu: (e) => {
|
|
569
|
+
e.preventDefault();
|
|
570
|
+
setContextMenu({
|
|
571
|
+
x: e.clientX,
|
|
572
|
+
y: e.clientY,
|
|
573
|
+
content: String(cell.getValue() ?? ''),
|
|
574
|
+
colId: cell.column.id,
|
|
575
|
+
isGroupable: !!cell.column.columnDef.enableGrouping
|
|
576
|
+
});
|
|
577
|
+
}, style: {
|
|
578
|
+
width: widthStyle,
|
|
579
|
+
textAlign,
|
|
580
|
+
}, children: _jsx("div", { className: "slick-cell-content", title: cellTitle, style: { textAlign }, children: cell.column.columnDef.cell
|
|
581
|
+
? flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
582
|
+
: (formatterMeta?.actions ?? cell.column.columnDef.meta?.actions)
|
|
583
|
+
? ActionFormatter(cell.getContext(), onActionClick)
|
|
584
|
+
: useDateCell
|
|
585
|
+
? renderDateCell(rawValue, dateMode, dateFormatOpts)
|
|
586
|
+
: DataFormatter(cell.getContext(), formatterMeta) }) }));
|
|
587
|
+
});
|
|
588
|
+
export function SabReactTable({ data, columns, title = 'Table_List', onSortedDataChange, defaultPageSize = 50, pageSizeOptions = [10, 20, 50, 100, 500, 1000], groupingLabels = {}, onRowClick, onActionClick, initialLayoutMode = 'fit-content' }) {
|
|
589
|
+
// UI States
|
|
590
|
+
const [sorting, setSorting] = useState([]);
|
|
591
|
+
const [columnSizing, setColumnSizing] = useState({});
|
|
592
|
+
const [grouping, setGrouping] = useState([]);
|
|
593
|
+
const [expanded, setExpanded] = useState({});
|
|
594
|
+
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: defaultPageSize });
|
|
595
|
+
const [columnFilters, setColumnFilters] = useState([]);
|
|
596
|
+
const [columnVisibility, setColumnVisibility] = useState(() => {
|
|
597
|
+
const initialMap = {};
|
|
598
|
+
columns.forEach(col => {
|
|
599
|
+
const id = (col.id || col.accessorKey);
|
|
600
|
+
if (id && col.meta?.initialHidden) {
|
|
601
|
+
initialMap[id] = false;
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
return initialMap;
|
|
605
|
+
});
|
|
606
|
+
// Initialize with column ids so header + filter + row cells all use same order from first render
|
|
607
|
+
const [columnOrder, setColumnOrder] = useState(() => columns.map(c => (c.id || c.accessorKey)).filter(Boolean));
|
|
608
|
+
// Sync column order when column set changes (add/remove) but preserve user reorder
|
|
609
|
+
useEffect(() => {
|
|
610
|
+
const currentIds = columns.map(c => (c.id || c.accessorKey)).filter(Boolean);
|
|
611
|
+
setColumnOrder(prev => {
|
|
612
|
+
const prevSet = new Set(prev);
|
|
613
|
+
if (prev.length !== currentIds.length || !currentIds.every(id => prevSet.has(id)))
|
|
614
|
+
return currentIds;
|
|
615
|
+
return prev;
|
|
616
|
+
});
|
|
617
|
+
}, [columns]);
|
|
618
|
+
const [layoutMode, setLayoutMode] = useState(initialLayoutMode);
|
|
619
|
+
// UI Feedback States
|
|
620
|
+
const [showCommands, setShowCommands] = useState(false);
|
|
621
|
+
const [commandMenuPos, setCommandMenuPos] = useState(null);
|
|
622
|
+
const [showFilters, setShowFilters] = useState(false);
|
|
623
|
+
const [showPaginationState] = useState(true);
|
|
624
|
+
const [showGroupingZone, setShowGroupingZone] = useState(false);
|
|
625
|
+
const [contextMenu, setContextMenu] = useState(null);
|
|
626
|
+
const [expandedSections, setExpandedSections] = useState({
|
|
627
|
+
quickActions: false, layoutMode: false, dataOptions: false, columns: false, groupingOptions: false, dataExport: false
|
|
628
|
+
});
|
|
629
|
+
const menuRef = useRef(null);
|
|
630
|
+
const triggerRef = useRef(null);
|
|
631
|
+
const contextMenuRef = useRef(null);
|
|
632
|
+
const tableContainerRef = useRef(null);
|
|
633
|
+
const tableRef = useRef(null);
|
|
634
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
635
|
+
const table = useReactTable({
|
|
636
|
+
data, columns,
|
|
637
|
+
state: { sorting, columnSizing, grouping, expanded, pagination, columnFilters, columnVisibility, columnOrder },
|
|
638
|
+
onSortingChange: setSorting,
|
|
639
|
+
onColumnSizingChange: setColumnSizing,
|
|
640
|
+
onGroupingChange: setGrouping,
|
|
641
|
+
onExpandedChange: setExpanded,
|
|
642
|
+
onPaginationChange: setPagination,
|
|
643
|
+
onColumnFiltersChange: setColumnFilters,
|
|
644
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
645
|
+
onColumnOrderChange: setColumnOrder,
|
|
646
|
+
getCoreRowModel: getCoreRowModel(),
|
|
647
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
648
|
+
getSortedRowModel: getSortedRowModel(),
|
|
649
|
+
getGroupedRowModel: getGroupedRowModel(),
|
|
650
|
+
getExpandedRowModel: getExpandedRowModel(),
|
|
651
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
652
|
+
filterFns: { advanced: advancedFilterFn },
|
|
653
|
+
defaultColumn: {
|
|
654
|
+
filterFn: advancedFilterFn,
|
|
655
|
+
enableGrouping: true,
|
|
656
|
+
enableColumnFilter: true,
|
|
657
|
+
size: 80,
|
|
658
|
+
minSize: 40
|
|
659
|
+
},
|
|
660
|
+
columnResizeMode: 'onChange',
|
|
661
|
+
enableColumnResizing: true,
|
|
662
|
+
enableGrouping: true,
|
|
663
|
+
enableExpanding: true,
|
|
664
|
+
enableColumnFilters: true,
|
|
665
|
+
enableMultiSort: true,
|
|
666
|
+
isMultiSortEvent: () => true,
|
|
667
|
+
autoResetPageIndex: true,
|
|
668
|
+
});
|
|
669
|
+
tableRef.current = table;
|
|
670
|
+
const { rows } = table.getRowModel();
|
|
671
|
+
const totalSize = table.getTotalSize();
|
|
672
|
+
const filteredTotalCount = table.getFilteredRowModel().rows.length;
|
|
673
|
+
const pageCount = table.getPageCount();
|
|
674
|
+
// Use table as single source of truth. Filter to visible columns only so when user hides a column, entire column (header + filter + cells) hides.
|
|
675
|
+
const headerGroups = useMemo(() => table.getHeaderGroups(), [table, columnOrder, columnVisibility]);
|
|
676
|
+
const visibleHeaderGroups = useMemo(() => headerGroups.map(hg => ({
|
|
677
|
+
...hg,
|
|
678
|
+
headers: hg.headers.filter(h => h.column.getIsVisible())
|
|
679
|
+
})), [headerGroups]);
|
|
680
|
+
const visibleCount = visibleHeaderGroups[0]?.headers.length ?? 0;
|
|
681
|
+
// Resolve grouping labels so chips and group rows show column header name (e.g. "Name", "Email") not column id (e.g. "name", "email")
|
|
682
|
+
const resolvedGroupingLabels = useMemo(() => {
|
|
683
|
+
const map = {};
|
|
684
|
+
const idStr = (x) => (x != null ? String(x) : '');
|
|
685
|
+
// 1) Build from columns prop first (original header string before any table normalization)
|
|
686
|
+
columns.forEach((col) => {
|
|
687
|
+
const id = col.id ?? col.accessorKey;
|
|
688
|
+
if (id == null)
|
|
689
|
+
return;
|
|
690
|
+
const header = col.header;
|
|
691
|
+
const label = typeof header === 'string' ? header : idStr(id);
|
|
692
|
+
map[idStr(id)] = label;
|
|
693
|
+
const acc = col.accessorKey;
|
|
694
|
+
if (acc != null && idStr(acc) !== idStr(id))
|
|
695
|
+
map[idStr(acc)] = label;
|
|
696
|
+
});
|
|
697
|
+
// 2) Fill or overwrite from table columns (e.g. generated ids, or when prop gave fallback id)
|
|
698
|
+
table.getAllLeafColumns().forEach((col) => {
|
|
699
|
+
const id = col.id;
|
|
700
|
+
const sid = idStr(id);
|
|
701
|
+
const def = col.columnDef;
|
|
702
|
+
const header = def.header;
|
|
703
|
+
const tableLabel = typeof header === 'string' ? header : idStr(id);
|
|
704
|
+
const existing = map[sid];
|
|
705
|
+
if (existing != null && existing !== sid)
|
|
706
|
+
return;
|
|
707
|
+
map[sid] = tableLabel;
|
|
708
|
+
const acc = def.accessorKey;
|
|
709
|
+
if (acc != null && idStr(acc) !== sid)
|
|
710
|
+
map[idStr(acc)] = tableLabel;
|
|
711
|
+
});
|
|
712
|
+
return { ...map, ...groupingLabels };
|
|
713
|
+
}, [table, columns, groupingLabels]);
|
|
714
|
+
const getGroupingLabel = useCallback((colId) => resolveGroupingLabel(colId, table, columns, resolvedGroupingLabels), [table, columns, resolvedGroupingLabels]);
|
|
715
|
+
// Column meta from columns prop keyed by both id and accessorKey so date/datetime/time filter is always found
|
|
716
|
+
const columnMetaById = useMemo(() => {
|
|
717
|
+
const map = {};
|
|
718
|
+
columns.forEach((col) => {
|
|
719
|
+
if (!col.meta)
|
|
720
|
+
return;
|
|
721
|
+
const id = col.id ?? col.accessorKey;
|
|
722
|
+
const accessorKey = col.accessorKey;
|
|
723
|
+
if (id)
|
|
724
|
+
map[String(id)] = col.meta;
|
|
725
|
+
if (accessorKey && String(accessorKey) !== String(id))
|
|
726
|
+
map[String(accessorKey)] = col.meta;
|
|
727
|
+
});
|
|
728
|
+
return map;
|
|
729
|
+
}, [columns]);
|
|
730
|
+
// --- Virtualization ---
|
|
731
|
+
const rowVirtualizer = useVirtualizer({
|
|
732
|
+
count: rows.length,
|
|
733
|
+
getScrollElement: () => tableContainerRef.current,
|
|
734
|
+
estimateSize: useCallback(() => 40, []),
|
|
735
|
+
overscan: 10,
|
|
736
|
+
});
|
|
737
|
+
const virtualRows = rowVirtualizer.getVirtualItems();
|
|
738
|
+
const rowTotalHeight = rowVirtualizer.getTotalSize();
|
|
739
|
+
const paddingTop = virtualRows.length > 0 ? virtualRows[0]?.start || 0 : 0;
|
|
740
|
+
const paddingBottom = virtualRows.length > 0 ? rowTotalHeight - (virtualRows[virtualRows.length - 1]?.end || 0) : 0;
|
|
741
|
+
// --- ResizeObserver: track container width for fit-to-content (debounced for large resize) ---
|
|
742
|
+
useEffect(() => {
|
|
743
|
+
const el = tableContainerRef.current;
|
|
744
|
+
if (!el)
|
|
745
|
+
return;
|
|
746
|
+
let rafId;
|
|
747
|
+
let timeoutId;
|
|
748
|
+
const scheduleUpdate = () => {
|
|
749
|
+
if (timeoutId)
|
|
750
|
+
clearTimeout(timeoutId);
|
|
751
|
+
timeoutId = setTimeout(() => {
|
|
752
|
+
rafId = requestAnimationFrame(() => {
|
|
753
|
+
setContainerWidth(el.clientWidth);
|
|
754
|
+
});
|
|
755
|
+
}, 100);
|
|
756
|
+
};
|
|
757
|
+
const ro = new ResizeObserver(scheduleUpdate);
|
|
758
|
+
ro.observe(el);
|
|
759
|
+
setContainerWidth(el.clientWidth);
|
|
760
|
+
return () => {
|
|
761
|
+
ro.disconnect();
|
|
762
|
+
clearTimeout(timeoutId);
|
|
763
|
+
if (rafId)
|
|
764
|
+
cancelAnimationFrame(rafId);
|
|
765
|
+
};
|
|
766
|
+
}, []);
|
|
767
|
+
// --- Shared helper: build resize defs and getCellDisplayText for layout effects (avoids duplication) ---
|
|
768
|
+
const getResizeParams = useCallback((t) => {
|
|
769
|
+
const visibleColumns = t.getVisibleLeafColumns();
|
|
770
|
+
const resizeDefs = visibleColumns.map(col => ({
|
|
771
|
+
id: col.id,
|
|
772
|
+
header: typeof col.columnDef.header === 'string' ? col.columnDef.header : col.id,
|
|
773
|
+
isCheckboxOrSelector: col.id === 'cspfm_assignment_checkbox' || String(col.id).endsWith('_checkbox_selector') || col.id === 'cspfm_sno',
|
|
774
|
+
isAction: col.id === 'cspfm_multi_line_action' || !!col.columnDef.meta?.actions
|
|
775
|
+
}));
|
|
776
|
+
const rowsForResize = t.getRowModel().rows
|
|
777
|
+
.filter(r => !r.getIsGrouped())
|
|
778
|
+
.map(r => r.original);
|
|
779
|
+
const getCellDisplayText = (row, columnId) => {
|
|
780
|
+
const col = columns.find(c => (c.id || c.accessorKey) === columnId);
|
|
781
|
+
if (!col)
|
|
782
|
+
return '';
|
|
783
|
+
const key = col.accessorKey ?? col.id;
|
|
784
|
+
return String((key ? row[key] : undefined) ?? '');
|
|
785
|
+
};
|
|
786
|
+
return { resizeDefs, rowsForResize, getCellDisplayText };
|
|
787
|
+
}, [columns]);
|
|
788
|
+
// --- Fit-to-content: compute column sizes from header + cell content (current page only for performance), then distribute extra space ---
|
|
789
|
+
useEffect(() => {
|
|
790
|
+
if (layoutMode !== 'fit-content' || containerWidth <= 0)
|
|
791
|
+
return;
|
|
792
|
+
const t = tableRef.current;
|
|
793
|
+
if (!t)
|
|
794
|
+
return;
|
|
795
|
+
const { resizeDefs, rowsForResize, getCellDisplayText } = getResizeParams(t);
|
|
796
|
+
const sizing = resizeColumnsByCellContent({
|
|
797
|
+
containerWidth,
|
|
798
|
+
columns: resizeDefs,
|
|
799
|
+
rows: rowsForResize,
|
|
800
|
+
getCellDisplayText,
|
|
801
|
+
distributeExtraSpace: true
|
|
802
|
+
});
|
|
803
|
+
setColumnSizing(sizing);
|
|
804
|
+
}, [layoutMode, containerWidth, data, columns, grouping, getResizeParams]);
|
|
805
|
+
// --- Fit-to-default: same as Angular - with data use content-based widths (no space distribution); with no data use default column widths ---
|
|
806
|
+
useEffect(() => {
|
|
807
|
+
if (layoutMode !== 'fit-default')
|
|
808
|
+
return;
|
|
809
|
+
const t = tableRef.current;
|
|
810
|
+
if (!t)
|
|
811
|
+
return;
|
|
812
|
+
const { resizeDefs, rowsForResize, getCellDisplayText } = getResizeParams(t);
|
|
813
|
+
if (rowsForResize.length > 0) {
|
|
814
|
+
const sizing = resizeColumnsByCellContent({
|
|
815
|
+
containerWidth: containerWidth || 1,
|
|
816
|
+
columns: resizeDefs,
|
|
817
|
+
rows: rowsForResize,
|
|
818
|
+
getCellDisplayText,
|
|
819
|
+
distributeExtraSpace: false
|
|
820
|
+
});
|
|
821
|
+
setColumnSizing(sizing);
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
setColumnSizing({});
|
|
825
|
+
}
|
|
826
|
+
}, [layoutMode, containerWidth, data, columns, grouping, getResizeParams]);
|
|
827
|
+
// --- Handlers ---
|
|
828
|
+
useEffect(() => {
|
|
829
|
+
if (!onSortedDataChange)
|
|
830
|
+
return;
|
|
831
|
+
const sortedRows = table.getPrePaginationRowModel().rows.filter(r => !r.getIsGrouped()).map(r => r.original);
|
|
832
|
+
onSortedDataChange(sortedRows);
|
|
833
|
+
}, [data, sorting, grouping, expanded, columnFilters, onSortedDataChange, table]);
|
|
834
|
+
useEffect(() => {
|
|
835
|
+
const handleClickOutside = (e) => {
|
|
836
|
+
if (menuRef.current && !menuRef.current.contains(e.target) && triggerRef.current && !triggerRef.current.contains(e.target))
|
|
837
|
+
setShowCommands(false);
|
|
838
|
+
if (contextMenuRef.current && !contextMenuRef.current.contains(e.target))
|
|
839
|
+
setContextMenu(null);
|
|
840
|
+
};
|
|
841
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
842
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
843
|
+
}, []);
|
|
844
|
+
const toggleCommands = useCallback((e) => {
|
|
845
|
+
e.stopPropagation();
|
|
846
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
847
|
+
const menuWidth = 310;
|
|
848
|
+
const menuHeight = 450;
|
|
849
|
+
const padding = 10;
|
|
850
|
+
let left = rect.left;
|
|
851
|
+
if (left + menuWidth > window.innerWidth) {
|
|
852
|
+
left = window.innerWidth - menuWidth - padding;
|
|
853
|
+
}
|
|
854
|
+
if (left < padding) {
|
|
855
|
+
left = padding;
|
|
856
|
+
}
|
|
857
|
+
let top;
|
|
858
|
+
const spaceBelow = window.innerHeight - rect.bottom - padding;
|
|
859
|
+
const spaceAbove = rect.top - padding;
|
|
860
|
+
if (spaceBelow >= menuHeight) {
|
|
861
|
+
top = rect.bottom + 8;
|
|
862
|
+
}
|
|
863
|
+
else if (spaceAbove >= menuHeight) {
|
|
864
|
+
top = rect.top - menuHeight - 8;
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
top = Math.max(padding, window.innerHeight - menuHeight - padding);
|
|
868
|
+
}
|
|
869
|
+
top = Math.max(padding, Math.min(top, window.innerHeight - menuHeight - padding));
|
|
870
|
+
setCommandMenuPos({ top, left });
|
|
871
|
+
setShowCommands(s => !s);
|
|
872
|
+
}, []);
|
|
873
|
+
const handleExport = useCallback((format) => {
|
|
874
|
+
const visibleColumns = table.getVisibleLeafColumns();
|
|
875
|
+
// Get filtered and sorted rows, excluding group header rows
|
|
876
|
+
const allRows = table.getFilteredRowModel().flatRows;
|
|
877
|
+
const leafRows = allRows
|
|
878
|
+
.filter(r => !r.getIsGrouped()) // Exclude group header rows
|
|
879
|
+
.map(r => {
|
|
880
|
+
const rowData = {};
|
|
881
|
+
visibleColumns.forEach(col => {
|
|
882
|
+
const header = typeof col.columnDef.header === 'string' ? col.columnDef.header : col.id;
|
|
883
|
+
rowData[header] = r.getValue(col.id);
|
|
884
|
+
});
|
|
885
|
+
return rowData;
|
|
886
|
+
});
|
|
887
|
+
const fileName = (title || 'Export').toLowerCase().replace(/\s+/g, '_');
|
|
888
|
+
if (format === 'excel')
|
|
889
|
+
exportTableToExcel(leafRows, fileName, title);
|
|
890
|
+
else
|
|
891
|
+
exportTableToCSV(leafRows, fileName);
|
|
892
|
+
setShowCommands(false);
|
|
893
|
+
}, [table, title]);
|
|
894
|
+
const toggleGrouping = useCallback((colId) => setGrouping(prev => prev.includes(colId) ? prev.filter(g => g !== colId) : [...prev, colId]), []);
|
|
895
|
+
const clearGrouping = useCallback(() => { setGrouping([]); setExpanded({}); }, []);
|
|
896
|
+
const handleCopy = useCallback(() => { if (contextMenu?.content)
|
|
897
|
+
navigator.clipboard.writeText(contextMenu.content); setContextMenu(null); }, [contextMenu]);
|
|
898
|
+
const expandAllGroups = useCallback(() => { table.toggleAllRowsExpanded(true); setContextMenu(null); }, [table]);
|
|
899
|
+
const collapseAllGroups = useCallback(() => { table.toggleAllRowsExpanded(false); setContextMenu(null); }, [table]);
|
|
900
|
+
const clearAllFilters = useCallback(() => table.setColumnFilters([]), [table]);
|
|
901
|
+
const clearAllSorting = useCallback(() => table.setSorting([]), [table]);
|
|
902
|
+
const toggleMenuSection = useCallback((section) => {
|
|
903
|
+
setExpandedSections(prev => {
|
|
904
|
+
const isExpanding = !prev[section];
|
|
905
|
+
if (isExpanding) {
|
|
906
|
+
const next = {};
|
|
907
|
+
Object.keys(prev).forEach(k => { next[k] = k === section; });
|
|
908
|
+
return next;
|
|
909
|
+
}
|
|
910
|
+
return { ...prev, [section]: false };
|
|
911
|
+
});
|
|
912
|
+
}, []);
|
|
913
|
+
// Drag-Drop Columns
|
|
914
|
+
const [draggedColumnId, setDraggedColumnId] = useState(null);
|
|
915
|
+
const handleDragStart = useCallback((id) => setDraggedColumnId(id), []);
|
|
916
|
+
const handleDragEnd = useCallback(() => setDraggedColumnId(null), []);
|
|
917
|
+
const handleDragOver = useCallback((e, _targetColumnId) => {
|
|
918
|
+
e.preventDefault();
|
|
919
|
+
if (e.dataTransfer) {
|
|
920
|
+
e.dataTransfer.dropEffect = 'move';
|
|
921
|
+
}
|
|
922
|
+
}, []);
|
|
923
|
+
const handleDrop = useCallback((e, targetColumnId) => {
|
|
924
|
+
e.preventDefault();
|
|
925
|
+
const sourceId = e.dataTransfer.getData('text/plain') || draggedColumnId;
|
|
926
|
+
if (sourceId && sourceId !== targetColumnId) {
|
|
927
|
+
setColumnOrder(prev => {
|
|
928
|
+
// Use current table column order when prev is empty so drop always has effect
|
|
929
|
+
const order = prev.length > 0 ? prev : (tableRef.current ? tableRef.current.getVisibleLeafColumns().map(c => c.id) : []);
|
|
930
|
+
const newOrder = [...order];
|
|
931
|
+
const draggedIdx = newOrder.indexOf(sourceId);
|
|
932
|
+
const targetIdx = newOrder.indexOf(targetColumnId);
|
|
933
|
+
if (draggedIdx !== -1 && targetIdx !== -1) {
|
|
934
|
+
newOrder.splice(draggedIdx, 1);
|
|
935
|
+
newOrder.splice(targetIdx, 0, sourceId);
|
|
936
|
+
return newOrder;
|
|
937
|
+
}
|
|
938
|
+
return prev.length > 0 ? prev : order;
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
setDraggedColumnId(null);
|
|
942
|
+
}, [draggedColumnId]);
|
|
943
|
+
const handleGroupDrop = useCallback((e) => {
|
|
944
|
+
e.preventDefault();
|
|
945
|
+
const colId = e.dataTransfer.getData('text/plain') || draggedColumnId;
|
|
946
|
+
if (colId && !grouping.includes(colId)) {
|
|
947
|
+
toggleGrouping(colId);
|
|
948
|
+
}
|
|
949
|
+
setDraggedColumnId(null);
|
|
950
|
+
}, [draggedColumnId, grouping, toggleGrouping]);
|
|
951
|
+
const isGrouped = grouping.length > 0;
|
|
952
|
+
const isGroupingZoneVisible = isGrouped || showGroupingZone;
|
|
953
|
+
const isFloatingDrag = !!draggedColumnId && !isGrouped && !showGroupingZone;
|
|
954
|
+
return (_jsxs("div", { className: `slick-enterprise-table ${layoutMode}`, "data-sab-grid": "true", onContextMenu: (e) => e.preventDefault(), children: [_jsx("div", { className: `slick-grouping-zone-wrapper ${isGroupingZoneVisible ? 'expanded' : 'collapsed'} ${isFloatingDrag ? 'floating' : ''}`, children: isGroupingZoneVisible && (_jsxs("div", { className: `slick-grouping-zone ${draggedColumnId && isGroupingZoneVisible ? 'dropping-target' : ''} ${isGrouped ? 'active' : ''}`, onDragOver: (e) => { e.preventDefault(); if (e.dataTransfer)
|
|
955
|
+
e.dataTransfer.dropEffect = 'move'; }, onDragEnter: (e) => e.preventDefault(), onDrop: handleGroupDrop, children: [!isGrouped && showGroupingZone && _jsx("div", { className: "group-drop-hint", children: "Drop column here to group" }), _jsx("div", { className: "slick-group-chips-container", children: grouping.map(colId => (_jsxs("div", { className: "slick-group-chip", children: [_jsx("span", { className: "chip-label", children: resolveGroupingLabel(colId, table, columns, resolvedGroupingLabels) }), _jsx("button", { className: "chip-remove", onClick: () => toggleGrouping(colId), children: "\u00D7" })] }, colId))) }), isGrouped && _jsx("button", { className: "clear-all-groups-btn-premium", onClick: clearGrouping, children: "Clear All" })] })) }), _jsx("div", { className: "slick-toolbar", children: _jsxs("div", { className: "slick-toolbar-content", children: [_jsxs("div", { className: "slick-toolbar-left-group", children: [_jsx("div", { className: "slick-title-icon", children: _jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), _jsx("line", { x1: "9", y1: "3", x2: "9", y2: "21" }), _jsx("line", { x1: "15", y1: "3", x2: "15", y2: "21" }), _jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }), _jsx("line", { x1: "3", y1: "15", x2: "21", y2: "15" })] }) }), _jsx("span", { className: "slick-table-title", children: title }), _jsx("div", { className: "slick-command-separator", children: "|" }), _jsx("div", { className: "hamburger-trigger-area", ref: triggerRef, children: _jsx("button", { className: "slick-command-btn circular", onClick: toggleCommands, title: "Context menu", children: _jsx("span", { className: "slick-icon-touch-target", "aria-hidden": true, children: _jsxs("span", { className: "slick-icon-css slick-icon-hamburger", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }) }) }) })] }), _jsxs("div", { className: "slick-toolbar-right-group", children: [showPaginationState && (_jsxs("div", { className: "slick-pagination-controls-integrated", children: [_jsx("span", { className: "slick-items-label", children: "Items per page :" }), _jsx("select", { value: pagination.pageSize, onChange: e => { table.setPageSize(Number(e.target.value)); setPagination(prev => ({ ...prev, pageSize: Number(e.target.value) })); }, className: "slick-pagesize-select compact", children: pageSizeOptions.map(n => _jsx("option", { value: n, children: n }, n)) }), _jsxs("span", { className: "slick-stat-range", children: [pagination.pageIndex * pagination.pageSize + 1, " - ", Math.min((pagination.pageIndex + 1) * pagination.pageSize, filteredTotalCount), " of ", filteredTotalCount] }), _jsxs("span", { className: "slick-page-info", children: [pagination.pageIndex + 1, "/", pageCount || 1, " Page"] }), _jsxs("div", { className: "slick-nav-buttons", children: [_jsx("button", { className: "slick-icon-action mini nav-arrow", onClick: () => table.previousPage(), disabled: !table.getCanPreviousPage(), children: "\u2039" }), _jsx("button", { className: "slick-icon-action mini nav-arrow", onClick: () => table.nextPage(), disabled: !table.getCanNextPage(), children: "\u203A" })] })] })), _jsx("button", { className: `slick-icon-action filter-toggle ${showFilters ? 'active' : ''}`, onClick: () => setShowFilters(s => !s), title: "Toggle filters", children: _jsx("span", { className: "slick-icon-touch-target", "aria-hidden": true, children: _jsxs("span", { className: "slick-icon-css slick-icon-filter", "aria-hidden": true, children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }) }) })] })] }) }), _jsx("div", { className: `slick-grid-container ${visibleCount === 1 ? 'single-column-mode' : ''}`, ref: tableContainerRef, children: _jsxs("table", { className: "slick-table", style: {
|
|
956
|
+
width: layoutMode === 'fit-content' ? 'max-content' : '100%',
|
|
957
|
+
minWidth: '100%',
|
|
958
|
+
// fixed layout so column widths come only from header + row data (resizeColumnsByCellContent); filter panel content is excluded
|
|
959
|
+
tableLayout: 'fixed'
|
|
960
|
+
}, children: [_jsx("thead", { children: visibleHeaderGroups.map(hg => (_jsxs(React.Fragment, { children: [_jsx("tr", { className: `slick-header-row ${draggedColumnId ? 'dragging-active' : ''}`, children: hg.headers.map(h => (_jsx(MemoizedHeaderCell, { header: h, draggedColumnId: draggedColumnId, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragEnd: handleDragEnd, onDrop: handleDrop, layoutMode: layoutMode, totalSize: totalSize }, h.id))) }), showFilters && (_jsx("tr", { className: "slick-filter-row", children: hg.headers.map(h => (_jsx(MemoizedFilterCell, { header: h, filterValue: h.column.getFilterValue(), draggedId: draggedColumnId, layoutMode: layoutMode, totalSize: totalSize, columnMeta: columnMetaById[h.column.id] ?? columnMetaById[h.column.columnDef?.accessorKey] }, h.id))) }))] }, hg.id))) }), _jsxs("tbody", { children: [paddingTop > 0 && _jsx("tr", { children: _jsx("td", { style: { height: `${paddingTop}px` } }) }), virtualRows.map(vr => (_jsx(MemoizedRow, { row: rows[vr.index], visibleCount: visibleCount, onRowClick: onRowClick, onActionClick: onActionClick, setContextMenu: setContextMenu, getGroupingLabel: getGroupingLabel, layoutMode: layoutMode, totalSize: totalSize, columnOrder: columnOrder, columnMetaById: columnMetaById }, rows[vr.index].id))), paddingBottom > 0 && _jsx("tr", { children: _jsx("td", { style: { height: `${paddingBottom}px` } }) })] })] }) }), showCommands && commandMenuPos && (_jsxs("div", { ref: menuRef, className: "slick-fixed-cmd-menu consolidated-menu", style: {
|
|
961
|
+
top: commandMenuPos.top,
|
|
962
|
+
left: commandMenuPos.left,
|
|
963
|
+
position: 'fixed',
|
|
964
|
+
maxHeight: `calc(100vh - ${commandMenuPos.top + 16}px)`,
|
|
965
|
+
overflowY: 'auto'
|
|
966
|
+
}, children: [_jsxs("div", { className: `menu-collapsible-section ${expandedSections.quickActions ? 'expanded' : ''}`, children: [_jsxs("div", { className: "menu-header clickable", onClick: () => toggleMenuSection('quickActions'), children: [_jsx("span", { children: "QUICK ACTIONS" }), _jsx("span", { className: "expand-icon", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", style: { transform: expandedSections.quickActions ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }, children: _jsx("polyline", { points: "9 18 15 12 9 6" }) }) })] }), expandedSections.quickActions && (_jsxs("div", { className: "section-content", children: [_jsxs("div", { className: "menu-action-item", onClick: () => { clearAllFilters(); setShowCommands(false); }, children: [_jsx("div", { className: "round-icon-bg action-icon", children: _jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: _jsx("path", { d: "M22 3H2l8 9.46V19l4 2v-8.54L22 3z" }) }) }), _jsx("span", { children: "Clear All Filters" })] }), _jsxs("div", { className: "menu-action-item", onClick: () => { clearAllSorting(); setShowCommands(false); }, children: [_jsx("div", { className: "round-icon-bg action-icon", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("circle", { cx: "12", cy: "12", r: "3" }), _jsx("path", { d: "M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" })] }) }), _jsx("span", { children: "Clear All Sorting" })] })] }))] }), _jsx("div", { className: "menu-divider" }), _jsxs("div", { className: `menu-collapsible-section ${expandedSections.dataExport ? 'expanded' : ''}`, children: [_jsxs("div", { className: "menu-header clickable", onClick: () => toggleMenuSection('dataExport'), children: [_jsx("span", { children: "DATA EXPORT" }), _jsx("span", { className: "expand-icon", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", style: { transform: expandedSections.dataExport ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }, children: _jsx("polyline", { points: "9 18 15 12 9 6" }) }) })] }), expandedSections.dataExport && (_jsxs("div", { className: "section-content", children: [_jsxs("div", { className: "menu-action-item", onClick: () => handleExport('excel'), children: [_jsx("div", { className: "round-icon-bg action-icon", style: { background: '#10b981', color: '#fff' }, children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }), _jsx("polyline", { points: "14 2 14 8 20 8" }), _jsx("line", { x1: "16", y1: "13", x2: "8", y2: "13" }), _jsx("line", { x1: "16", y1: "17", x2: "8", y2: "17" }), _jsx("polyline", { points: "10 9 9 9 8 9" })] }) }), _jsx("span", { children: "Export to Excel" })] }), _jsxs("div", { className: "menu-action-item", onClick: () => handleExport('csv'), children: [_jsx("div", { className: "round-icon-bg action-icon", style: { background: '#f59e0b', color: '#fff' }, children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" }), _jsx("polyline", { points: "14 2 14 8 20 8" }), _jsx("line", { x1: "16", y1: "13", x2: "8", y2: "13" }), _jsx("line", { x1: "16", y1: "17", x2: "8", y2: "17" })] }) }), _jsx("span", { children: "Export to CSV" })] })] }))] }), _jsx("div", { className: "menu-divider" }), _jsxs("div", { className: `menu-collapsible-section ${expandedSections.layoutMode ? 'expanded' : ''}`, children: [_jsxs("div", { className: "menu-header clickable", onClick: () => toggleMenuSection('layoutMode'), children: [_jsx("span", { children: "LAYOUT MODE" }), _jsx("span", { className: "expand-icon", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", style: { transform: expandedSections.layoutMode ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }, children: _jsx("polyline", { points: "9 18 15 12 9 6" }) }) })] }), expandedSections.layoutMode && (_jsxs("div", { className: "section-content", children: [_jsxs("div", { className: "menu-action-item", onClick: () => { setLayoutMode('fit-default'); setShowCommands(false); }, children: [_jsx("div", { className: `radio-circle ${layoutMode === 'fit-default' ? 'checked' : ''}`, children: _jsx("div", { className: "dot" }) }), _jsx("span", { children: "Fit to Default" })] }), _jsxs("div", { className: "menu-action-item", onClick: () => { setLayoutMode('fit-window'); setShowCommands(false); }, children: [_jsx("div", { className: `radio-circle ${layoutMode === 'fit-window' ? 'checked' : ''}`, children: _jsx("div", { className: "dot" }) }), _jsx("span", { children: "Fit to Window" })] }), _jsxs("div", { className: "menu-action-item", onClick: () => { setLayoutMode('fit-content'); setShowCommands(false); }, children: [_jsx("div", { className: `radio-circle ${layoutMode === 'fit-content' ? 'checked' : ''}`, children: _jsx("div", { className: "dot" }) }), _jsx("span", { children: "Fit to Content" })] })] }))] }), _jsx("div", { className: "menu-divider" }), _jsxs("div", { className: `menu-collapsible-section ${expandedSections.columns ? 'expanded' : ''}`, children: [_jsxs("div", { className: "menu-header clickable", onClick: () => toggleMenuSection('columns'), children: [_jsx("span", { children: "COLUMNS" }), _jsx("span", { className: "expand-icon", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", style: { transform: expandedSections.columns ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }, children: _jsx("polyline", { points: "9 18 15 12 9 6" }) }) })] }), expandedSections.columns && (_jsx("div", { className: "section-content", children: _jsx("div", { className: "menu-columns-scroll consolidated", children: (() => {
|
|
967
|
+
const allCols = table.getAllLeafColumns();
|
|
968
|
+
const visibleCols = allCols.filter(c => c.getIsVisible());
|
|
969
|
+
const visibleCount = visibleCols.length;
|
|
970
|
+
return allCols.map(column => {
|
|
971
|
+
const isVisible = column.getIsVisible();
|
|
972
|
+
const isLastVisible = isVisible && visibleCount === 1;
|
|
973
|
+
return (_jsx("div", { className: "menu-column-list-item-pro", children: _jsxs("div", { className: "column-checkbox-part", onClick: (e) => {
|
|
974
|
+
e.stopPropagation();
|
|
975
|
+
if (isLastVisible)
|
|
976
|
+
return;
|
|
977
|
+
column.toggleVisibility();
|
|
978
|
+
}, style: { cursor: 'pointer' }, children: [_jsx("input", { type: "checkbox", checked: isVisible, readOnly: true }), _jsx("span", { className: "col-label-text", children: typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id })] }) }, column.id));
|
|
979
|
+
});
|
|
980
|
+
})() }) }))] }), _jsx("div", { className: "menu-divider" }), _jsxs("div", { className: `menu-collapsible-section ${expandedSections.groupingOptions ? 'expanded' : ''}`, children: [_jsxs("div", { className: "menu-header clickable", onClick: () => toggleMenuSection('groupingOptions'), children: [_jsx("span", { children: "GROUPING OPTIONS" }), _jsx("span", { className: "expand-icon", children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", style: { transform: expandedSections.groupingOptions ? 'rotate(90deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }, children: _jsx("polyline", { points: "9 18 15 12 9 6" }) }) })] }), expandedSections.groupingOptions && (_jsxs("div", { className: "section-content", children: [_jsxs("div", { className: "menu-action-item", onClick: () => { setShowGroupingZone(!showGroupingZone); setShowCommands(false); }, children: [_jsx("div", { className: "round-icon-bg action-icon", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), _jsx("line", { x1: "9", y1: "3", x2: "9", y2: "21" }), _jsx("line", { x1: "15", y1: "3", x2: "15", y2: "21" })] }) }), _jsxs("span", { children: [showGroupingZone ? 'Hide' : 'Enable', " Group By"] })] }), _jsxs("div", { className: "menu-action-item", onClick: () => { clearGrouping(); setShowCommands(false); }, children: [_jsx("div", { className: "round-icon-bg action-icon", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M21 4H8l-7 8 7 8h13a2 2 0 002-2V6a2 2 0 00-2-2z" }), _jsx("line", { x1: "18", y1: "9", x2: "12", y2: "15" }), _jsx("line", { x1: "12", y1: "9", x2: "18", y2: "15" })] }) }), _jsx("span", { children: "Clear All Grouping" })] }), _jsxs("div", { className: "menu-action-item", onClick: () => { expandAllGroups(); setShowCommands(false); }, children: [_jsx("div", { className: "round-icon-bg action-icon", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M15 9l6-6" }), _jsx("path", { d: "M9 15l-6 6" }), _jsx("path", { d: "M3 15h6v6" }), _jsx("path", { d: "M21 9h-6V3" })] }) }), _jsx("span", { children: "Expand All Groups" })] }), _jsxs("div", { className: "menu-action-item", onClick: () => { collapseAllGroups(); setShowCommands(false); }, children: [_jsx("div", { className: "round-icon-bg action-icon", children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M4 14h6v6" }), _jsx("path", { d: "M20 10h-6V4" }), _jsx("path", { d: "M14 10l7-7" }), _jsx("path", { d: "M3 21l7-7" })] }) }), _jsx("span", { children: "Collapse All Groups" })] })] }))] })] })), contextMenu && (_jsxs("div", { ref: contextMenuRef, className: "slick-fixed-cmd-menu consolidated-menu context-premium", style: {
|
|
981
|
+
top: contextMenu.y + 350 > window.innerHeight ? contextMenu.y - 350 : contextMenu.y,
|
|
982
|
+
left: Math.min(contextMenu.x, window.innerWidth - 320),
|
|
983
|
+
position: 'fixed',
|
|
984
|
+
maxHeight: '350px',
|
|
985
|
+
overflowY: 'auto'
|
|
986
|
+
}, children: [_jsx("div", { className: "menu-header-main", children: "QUICK ACTIONS" }), _jsxs("div", { className: "menu-action-item", onClick: handleCopy, children: [_jsx("div", { className: "round-icon-bg action-icon", style: { background: '#f8fafc', color: '#64748b' }, children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" })] }) }), _jsx("span", { children: "Copy Selection" })] }), isGrouped && (_jsxs(_Fragment, { children: [_jsx("div", { className: "menu-divider" }), _jsxs("div", { className: "menu-action-item", onClick: () => { collapseAllGroups(); setContextMenu(null); }, children: [_jsx("div", { className: "round-icon-bg action-icon", style: { background: '#f8fafc', color: '#64748b' }, children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M4 14h6v6" }), _jsx("path", { d: "M20 10h-6V4" }), _jsx("path", { d: "M14 10l7-7" }), _jsx("path", { d: "M3 21l7-7" })] }) }), _jsx("span", { children: "Collapse All Groups" })] }), _jsxs("div", { className: "menu-action-item", onClick: () => { expandAllGroups(); setContextMenu(null); }, children: [_jsx("div", { className: "round-icon-bg action-icon", style: { background: '#f8fafc', color: '#64748b' }, children: _jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [_jsx("path", { d: "M15 9l6-6" }), _jsx("path", { d: "M9 15l-6 6" }), _jsx("path", { d: "M3 15h6v6" }), _jsx("path", { d: "M21 9h-6V3" })] }) }), _jsx("span", { children: "Expand All Groups" })] }), _jsxs("div", { className: "menu-action-item", onClick: () => { clearGrouping(); setContextMenu(null); }, children: [_jsx("div", { className: "round-icon-bg action-icon", style: { background: '#f8fafc', color: '#64748b' }, children: _jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: _jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }) }), _jsx("span", { children: "Clear All Grouping" })] })] }))] })), _jsx("style", { children: `
|
|
987
|
+
.slick-enterprise-table { width: 100%; font-family: 'Inter', system-ui, sans-serif; border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.06); }
|
|
988
|
+
.slick-grouping-zone { margin: 12px 20px 0; padding: 10px 18px; border: 1.5px dashed #cbd5e1; border-radius: 12px; display: flex; align-items: center; justify-content: space-between; background: #fdfdfd; transition: all 0.2s; min-height: 48px; position: relative; }
|
|
989
|
+
.slick-grouping-zone.dropping-target { border-color: #3b82f6; background: #f0f7ff; border-style: solid; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); }
|
|
990
|
+
.group-drop-hint { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: 13px; color: #3b82f6; font-weight: 600; pointer-events: none; }
|
|
991
|
+
.slick-group-chips-container { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
992
|
+
.slick-group-chip { background: #fff; border: 1px solid #3b82f6; border-radius: 20px; padding: 4px 12px; display: flex; align-items: center; gap: 8px; box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1); }
|
|
993
|
+
.chip-label { font-size: 12.5px; font-weight: 700; color: #1e40af; }
|
|
994
|
+
.chip-remove { background: transparent; border: none; color: #3b82f6; font-size: 18px; font-weight: 700; cursor: pointer; padding: 0; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: all 0.2s; }
|
|
995
|
+
.chip-remove:hover { background: #3b82f6; color: #fff; }
|
|
996
|
+
.clear-all-groups-btn-premium { background: #ef4444; color: #fff; border: none; padding: 6px 14px; border-radius: 6px; font-size: 12px; font-weight: 700; cursor: pointer; transition: all 0.2s; }
|
|
997
|
+
.clear-all-groups-btn-premium:hover { background: #dc2626; box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3); }
|
|
998
|
+
.slick-group-row td { padding: 0 !important; background: #fff !important; border-bottom: 1px solid #e2e8f0; }
|
|
999
|
+
.slick-group-header-row { display: flex; align-items: center; gap: 10px; padding: 10px 16px; cursor: pointer; background: #f8fafc; border-left: 4px solid transparent; transition: all 0.2s; font-size: 12.5px; font-weight: 700; color: #334155; }
|
|
1000
|
+
.slick-group-header-row.expanded { background: #f1f7ff; border-left-color: #3b82f6; border-bottom: 1px solid #dbeafe; }
|
|
1001
|
+
.slick-group-chevron { display: flex; align-items: center; color: #64748b; transition: transform 0.2s; }
|
|
1002
|
+
.slick-group-header-row.expanded .slick-group-chevron { transform: rotate(90deg); color: #3b82f6; }
|
|
1003
|
+
.slick-group-title { font-size: 12.5px; font-weight: 700; color: #334155; }
|
|
1004
|
+
.slick-group-title .group-col-label { font-size: 12.5px; font-weight: 700; color: #334155; }
|
|
1005
|
+
.slick-group-count { font-size: 12px; color: #3b82f6; font-weight: 600; background: #eff6ff; padding: 2px 8px; border-radius: 12px; }
|
|
1006
|
+
.slick-toolbar { border-bottom: 1px solid #f1f5f9; background: #f8fafc; padding: 0 16px; height: 48px; display: flex; align-items: center; }
|
|
1007
|
+
.slick-toolbar-content { width: 100%; display: flex; justify-content: space-between; align-items: center; }
|
|
1008
|
+
.slick-toolbar-left-group { display: flex; align-items: center; gap: 12px; }
|
|
1009
|
+
.slick-toolbar-right-group { display: flex; align-items: center; gap: 16px; }
|
|
1010
|
+
.slick-title-icon { color:black; background: #ecfdf5; padding: 6px; border-radius: 8px; display: flex; align-items: center; }
|
|
1011
|
+
.slick-table-title { font-weight: 700; color: #334155; font-size: 14.5px; }
|
|
1012
|
+
.slick-command-separator { color: #e2e8f0; font-weight: 300; margin: 0 4px; }
|
|
1013
|
+
.slick-command-btn.circular { width: 34px; height: 34px; border-radius: 50%; border: 1px solid #e2e8f0; background: #eff6ff; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #3b82f6; transition: all 0.2s; }
|
|
1014
|
+
.slick-command-btn.circular:hover { border-color: #3b82f6; color: #1d4ed8; background: #dbeafe; box-shadow: 0 2px 8px rgba(59, 130, 246, 0.12); }
|
|
1015
|
+
/* CSS-only toolbar/filter icons (span-based, no SVG) so consumers can target reliably */
|
|
1016
|
+
.slick-icon-touch-target { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; flex-shrink: 0; color: inherit; }
|
|
1017
|
+
.slick-icon-css.slick-icon-hamburger { display: flex; flex-direction: column; justify-content: space-between; width: 18px; height: 12px; color: currentColor; }
|
|
1018
|
+
.slick-icon-css.slick-icon-hamburger span { display: block; height: 2px; background: currentColor; border-radius: 1px; width: 100%; }
|
|
1019
|
+
/* Three-bar filter icon (top longest, middle shorter, bottom shortest) – CSS-only */
|
|
1020
|
+
.slick-icon-css.slick-icon-filter { display: flex; flex-direction: column; align-items: center; justify-content: space-between; width: 16px; height: 12px; color: currentColor; gap: 3px; }
|
|
1021
|
+
.slick-icon-css.slick-icon-filter span { display: block; height: 2px; background: currentColor; border-radius: 1px; }
|
|
1022
|
+
.slick-icon-css.slick-icon-filter span:nth-child(1) { width: 100%; }
|
|
1023
|
+
.slick-icon-css.slick-icon-filter span:nth-child(2) { width: 65%; }
|
|
1024
|
+
.slick-icon-css.slick-icon-filter span:nth-child(3) { width: 40%; }
|
|
1025
|
+
.slick-icon-css.slick-icon-search { width: 12px; height: 12px; border: 2px solid currentColor; border-radius: 50%; position: relative; box-sizing: border-box; color: currentColor; }
|
|
1026
|
+
.slick-icon-css.slick-icon-search::after { content: ''; position: absolute; bottom: -1px; right: -1px; width: 6px; height: 2px; background: currentColor; transform: rotate(45deg); transform-origin: right center; border-radius: 1px; }
|
|
1027
|
+
.slick-icon-css.slick-icon-calendar { width: 12px; height: 12px; border: 2px solid currentColor; border-radius: 2px; position: relative; box-sizing: border-box; color: currentColor; }
|
|
1028
|
+
.slick-icon-css.slick-icon-calendar::before { content: ''; position: absolute; top: -2px; left: 50%; transform: translateX(-50%); width: 4px; height: 2px; background: currentColor; border-radius: 1px; }
|
|
1029
|
+
.slick-icon-css.slick-icon-calendar::after { content: ''; position: absolute; top: 2px; left: 1px; right: 1px; height: 1px; background: currentColor; border-radius: 0; }
|
|
1030
|
+
|
|
1031
|
+
.slick-pagination-controls-integrated { display: flex; align-items: center; gap: 12px; font-size: 13px; color: #64748b; font-weight: 500; }
|
|
1032
|
+
.slick-pagesize-select { border: 1px solid #e2e8f0; border-radius: 6px; padding: 2px 8px; font-size: 12px; outline: none; background: #fff; cursor: pointer; }
|
|
1033
|
+
.slick-nav-buttons { display: flex; align-items: center; border-left: 1px solid #e2e8f0; padding-left: 12px; margin-left: 4px; gap: 4px; }
|
|
1034
|
+
.slick-icon-action { width: 32px; height: 32px; border-radius: 50%; border: 1px solid #e2e8f0; background: #eff6ff; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #3b82f6; transition: all 0.2s; }
|
|
1035
|
+
.slick-icon-action:hover:not(:disabled) { border-color: #3b82f6; color: #1d4ed8; background: #dbeafe; }
|
|
1036
|
+
.slick-icon-action:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
1037
|
+
.slick-icon-action.mini { width: 28px; height: 28px; font-size: 18px; }
|
|
1038
|
+
.slick-icon-action.filter-toggle { border-color: #e2e8f0; background: #eff6ff; color: #3b82f6; }
|
|
1039
|
+
.slick-icon-action.filter-toggle.active { background: #3b82f6; color: #fff; border-color: #3b82f6; }
|
|
1040
|
+
.slick-icon-action.filter-toggle:not(.active):hover { background: #dbeafe; color: #1d4ed8; border-color: #3b82f6; }
|
|
1041
|
+
.consolidated-menu { width: 310px; background: #fff; border: 1px solid #cbd5e1; border-radius: 14px; box-shadow: 0 12px 48px rgba(0,0,0,0.18); z-index: 10000; padding: 10px 0; max-height: 520px; overflow-y: auto; }
|
|
1042
|
+
.menu-header-main { padding: 10px 18px 12px; font-size: 14px; font-weight: 700; color: #334155; border-bottom: 1px solid #f1f5f9; margin-bottom: 8px; }
|
|
1043
|
+
.menu-action-item { padding: 8px 18px; display: flex; align-items: center; gap: 14px; cursor: pointer; }
|
|
1044
|
+
.menu-action-item:hover { background: #f8fafc; }
|
|
1045
|
+
.menu-action-item span { font-size: 14px; font-weight: 600; color: #1e293b; }
|
|
1046
|
+
.round-icon-bg.action-icon { width: 34px; height: 34px; border-radius: 50%; background: #eff6ff; display: flex; align-items: center; justify-content: center; color: #3b82f6; }
|
|
1047
|
+
.round-icon-bg.action-icon.mini-icon { width: 26px; height: 26px; }
|
|
1048
|
+
.menu-header.clickable { display: flex; justify-content: space-between; align-items: center; cursor: pointer; margin: 0 8px; padding: 10px 12px; border-radius: 8px; font-size: 11px; font-weight: 800; color: #94a3b8; text-transform: uppercase; }
|
|
1049
|
+
.menu-header.clickable:hover { background: #f8fafc; }
|
|
1050
|
+
.menu-collapsible-section.expanded .menu-header.clickable { color: #3b82f6; background: #f0f7ff; }
|
|
1051
|
+
.expand-icon { width: 20px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
1052
|
+
|
|
1053
|
+
.slick-grid-container { width: 100%; overflow: auto; height: calc(100vh - 420px); min-height: 420px; }
|
|
1054
|
+
.slick-table { border-collapse: separate; border-spacing: 0; min-width: 100%; table-layout: fixed; }
|
|
1055
|
+
/* Column headers fixed (sticky) by default - do not scroll with body */
|
|
1056
|
+
.slick-header-th { border-bottom: 2px solid #dde3ed; border-right: 1px solid #e8ecf2; background: #f0f4f8; position: sticky; top: 0; z-index: 35; }
|
|
1057
|
+
.slick-header-cell-inner { padding: 8px 10px; font-size: 12.5px; font-weight: 700; color: #334155; display: flex; align-items: center; justify-content: flex-start; gap: 4px; position: relative; min-height: 24px; }
|
|
1058
|
+
.slick-header-label { flex: 1; text-align: left; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; gap: 4px; }
|
|
1059
|
+
.slick-header-actions { display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 1px; border-radius: 4px; color: #94a3b8; width: 20px; height: 20px; opacity: 0; visibility: hidden; transition: opacity 0.2s; flex-shrink: 0; }
|
|
1060
|
+
.slick-header-th:hover .slick-header-actions, .slick-header-actions.visible { opacity: 1; visibility: visible; }
|
|
1061
|
+
.slick-header-actions:hover { background: #e2e8f0; color: #3b82f6; }
|
|
1062
|
+
.slick-header-actions svg, .slick-header-label svg { pointer-events: none; }
|
|
1063
|
+
|
|
1064
|
+
.slick-header-dropdown-menu { position: absolute; top: 100%; right: 0; width: 220px; background: #fff; border: 1px solid #e2e8f0; border-radius: 8px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); z-index: 1000; padding: 6px 0; margin-top: 4px; }
|
|
1065
|
+
.dropdown-item { padding: 8px 16px; font-size: 13px; font-weight: 500; color: #475569; display: flex; align-items: center; gap: 10px; cursor: pointer; }
|
|
1066
|
+
.dropdown-item:hover { background: #f8fafc; color: #3b82f6; }
|
|
1067
|
+
.dropdown-divider { height: 1px; background: #f1f5f9; margin: 4px 0; }
|
|
1068
|
+
|
|
1069
|
+
.slick-data-row:nth-child(even) { background: #fdfdfd; }
|
|
1070
|
+
.slick-data-row:hover { background: #e8f4fd; }
|
|
1071
|
+
.slick-data-row td { border-bottom: 1px solid #edf0f5; border-right: 1px solid #edf0f5; padding: 7px 10px; font-size: 13px; vertical-align: middle; }
|
|
1072
|
+
|
|
1073
|
+
/* Single Column Professional Mode */
|
|
1074
|
+
.single-column-mode .slick-table { border: 1px solid #e2e8f0; border-radius: 4px; overflow: visible; margin: 4px; width: calc(100% - 8px); }
|
|
1075
|
+
.single-column-mode .slick-header-th { background: #f1f5f9; border-bottom: 1px solid #e2e8f0; font-weight: 700; color: #334155; }
|
|
1076
|
+
.single-column-mode .slick-data-row td { border-bottom: 1px solid #f1f5f9; background: #fff; }
|
|
1077
|
+
.single-column-mode .slick-data-row:hover td { background: #f8fafc; }
|
|
1078
|
+
.single-column-mode .slick-header-cell-inner { padding: 8px 12px; }
|
|
1079
|
+
.slick-cell-content { font-size: 13px; color: #475569; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1080
|
+
|
|
1081
|
+
.slick-filter-prefix { width: 24px; display: flex; align-items: center; justify-content: center; background: #f1f5f9; border-right: 1px solid #e2e8f0; cursor: pointer; border-radius: 6px 0 0 6px; }
|
|
1082
|
+
.filter-placeholder-char { font-size: 14px; line-height: 1; display: flex; align-items: center; justify-content: center; color: #94a3b8; }
|
|
1083
|
+
.prefix-dot-trigger { width: 10px; height: 4px; border-radius: 50%; background: #94a3b8; }
|
|
1084
|
+
.prefix-operand-symbol { font-size: 13px; font-weight: 700; color: #000; display: flex; align-items: center; justify-content: center; width: 100%; }
|
|
1085
|
+
.prefix-operand-symbol.placeholder { color: #cbd5e1; }
|
|
1086
|
+
.prefix-operand-placeholder { display: block; width: 14px; height: 14px; }
|
|
1087
|
+
.slick-filter-search-area { flex: 1; min-width: 0; display: flex; align-items: center; position: relative; padding-left: 8px; overflow: hidden; cursor: text; }
|
|
1088
|
+
.active-operand-overlay { position: absolute; bottom: -2px; right: -4px; font-size: 8px; font-weight: 900; background: #3b82f6; color: #fff; border-radius: 3px; padding: 0 2px; line-height: 1; }
|
|
1089
|
+
.operand-selector { cursor: pointer; position: relative; user-select: none; }
|
|
1090
|
+
.operand-selector-chevron { display: flex; align-items: center; color: #64748b; }
|
|
1091
|
+
.operand-selector:hover .operand-selector-chevron { color: #3b82f6; }
|
|
1092
|
+
.operand-dropdown { position: absolute; top: 100%; left: 0; min-width: 140px; width: max-content; max-width: 280px; background: #fff; border: 1px solid #e2e8f0; border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); z-index: 2000; padding: 6px 0; margin-top: 4px; }
|
|
1093
|
+
.operand-item { padding: 8px 12px; display: flex; align-items: center; gap: 10px; cursor: pointer; transition: all 0.2s; white-space: nowrap; }
|
|
1094
|
+
.operand-item:hover { background: #f0f7ff; }
|
|
1095
|
+
.operand-item.active { background: #eff6ff; border-left: 3px solid #3b82f6; }
|
|
1096
|
+
.op-symbol { width: 14px; font-weight: 800; color: #3b82f6; font-size: 15px; text-align: center; }
|
|
1097
|
+
.op-label { font-size: 11px; color: #475569; font-weight: 700; text-transform: uppercase; letter-spacing: 0.2px; }
|
|
1098
|
+
|
|
1099
|
+
.slick-premium-filter-container { display: flex; height: 32px; min-width: 0; max-width: 100%; background: #fff; border: 1px solid #e2e8f0; border-radius: 6px; overflow: visible; transition: all 0.2s; position: relative; }
|
|
1100
|
+
.slick-premium-filter-container:focus-within { border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); }
|
|
1101
|
+
.slick-premium-filter-input { flex: 1; min-width: 0; border: none; outline: none; padding: 0; font-size: 12.5px; color: #334155; background: transparent; overflow: hidden; text-overflow: ellipsis; }
|
|
1102
|
+
|
|
1103
|
+
.menu-header-sec { padding: 8px 18px 4px; font-size: 11px; font-weight: 800; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
1104
|
+
.menu-context-groups-list { max-height: 200px; overflow-y: auto; padding: 4px 0; }
|
|
1105
|
+
.round-icon-bg.action-icon.active { background: #3b82f6; color: #fff; border-color: #3b82f6; }
|
|
1106
|
+
.slick-header-th.dragging { opacity: 0.35; background: #e0f2fe !important; border: 2px dashed #0ea5e9; }
|
|
1107
|
+
.slick-header-th .slick-header-label { user-select: none; pointer-events: auto !important; }
|
|
1108
|
+
.slick-header-th .slick-header-actions, .slick-header-th .slick-header-dropdown-menu, .filter-wrapper { pointer-events: auto; }
|
|
1109
|
+
/* Prevent child pointer interferences during reordering, but keep the TH accessible for dragover/drop */
|
|
1110
|
+
.slick-header-row.dragging-active .slick-header-th:not(.dragging) * { pointer-events: none !important; }
|
|
1111
|
+
.slick-grouping-zone-wrapper { transition: all 0.3s ease; overflow: hidden; position: relative; }
|
|
1112
|
+
.slick-grouping-zone-wrapper.collapsed { height: 0; margin-top: 0; }
|
|
1113
|
+
.slick-grouping-zone-wrapper.expanded { height: auto; min-height: 48px; }
|
|
1114
|
+
.slick-grouping-zone-wrapper.floating { position: absolute; top: 0; left: 0; right: 0; z-index: 1000; background: #fff; border-bottom: 2px solid #3b82f6; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
|
1115
|
+
.slick-resizer { position: absolute; right: 0; top: 0; height: 100%; width: 5px; background: rgba(0,0,0,0.05); cursor: col-resize; z-index: 40; }
|
|
1116
|
+
.slick-resizer:hover { background: #3b82f6; width: 3px; }
|
|
1117
|
+
.slick-resizer:hover { background: #3b82f6; width: 6px; }
|
|
1118
|
+
.muted-action { opacity: 0.4; pointer-events: none; }
|
|
1119
|
+
.slick-edit-input { width: 100%; padding: 4px 10px; border: 1px solid #3b82f6; border-radius: 4px; outline: none; }
|
|
1120
|
+
.menu-column-list-item-pro { padding: 8px 16px; border-bottom: 1px solid #f1f5f9; display: flex; align-items: center; }
|
|
1121
|
+
.column-checkbox-part { display: flex; align-items: center; gap: 10px; cursor: pointer; flex: 1; }
|
|
1122
|
+
.col-label-text { font-size: 13.5px; font-weight: 600; color: #334155; }
|
|
1123
|
+
.menu-divider { height: 1px; background: #f1f5f9; margin: 4px 0; }
|
|
1124
|
+
/* Toolbar icons: ensure visible when consumed from NPM (override host app button/global styles) */
|
|
1125
|
+
.slick-enterprise-table[data-sab-grid] .slick-toolbar .slick-command-btn.circular,
|
|
1126
|
+
.slick-enterprise-table[data-sab-grid] .slick-toolbar .slick-icon-action { background-color: #eff6ff !important; color: #3b82f6 !important; border-color: #e2e8f0 !important; }
|
|
1127
|
+
.slick-enterprise-table[data-sab-grid] .slick-toolbar .slick-command-btn.circular:hover,
|
|
1128
|
+
.slick-enterprise-table[data-sab-grid] .slick-toolbar .slick-icon-action:hover:not(:disabled) { background-color: #dbeafe !important; color: #1d4ed8 !important; border-color: #3b82f6 !important; }
|
|
1129
|
+
.slick-enterprise-table[data-sab-grid] .slick-toolbar .slick-icon-action.filter-toggle { background-color: #eff6ff !important; color: #3b82f6 !important; }
|
|
1130
|
+
.slick-enterprise-table[data-sab-grid] .slick-toolbar .slick-icon-action.filter-toggle:not(.active):hover { background-color: #dbeafe !important; color: #1d4ed8 !important; }
|
|
1131
|
+
.slick-enterprise-table[data-sab-grid] .slick-toolbar .slick-icon-action.filter-toggle.active { background-color: #3b82f6 !important; color: #fff !important; border-color: #3b82f6 !important; }
|
|
1132
|
+
/* Force toolbar icons to be visible (CSS span icons; override host app button styles) */
|
|
1133
|
+
.slick-enterprise-table[data-sab-grid] .slick-toolbar .slick-command-btn.circular .slick-icon-css,
|
|
1134
|
+
.slick-enterprise-table[data-sab-grid] .slick-toolbar .slick-icon-action .slick-icon-css { color: inherit !important; }
|
|
1135
|
+
|
|
1136
|
+
/* New Filter Styles */
|
|
1137
|
+
.filter-clear-btn { cursor: pointer; color: #94a3b8; width: 24px; display: flex; align-items: center; justify-content: center; transition: color 0.2s; }
|
|
1138
|
+
.filter-clear-btn:hover { color: #ef4444; }
|
|
1139
|
+
.slick-filter-dropdown-btn { width: 100%; height: 100%; display: flex; align-items: center; justify-content: space-between; padding: 0 8px; font-size: 12.5px; color: #334155; cursor: pointer; }
|
|
1140
|
+
.dropdown-text-container { min-width: 0; flex: 1; }
|
|
1141
|
+
.dropdown-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; max-width: 100%; }
|
|
1142
|
+
.dropdown-icons { display: flex; align-items: center; gap: 6px; color: #94a3b8; }
|
|
1143
|
+
.mini-clear-btn { cursor: pointer; display: flex; align-items: center; }
|
|
1144
|
+
.mini-clear-btn:hover { color: #ef4444; }
|
|
1145
|
+
/* Filter panel: fixed default size so column width is not affected */
|
|
1146
|
+
.slick-filter-row td { min-height: 40px; vertical-align: middle; }
|
|
1147
|
+
.operand-dropdown.check-list { width: 220px; min-width: 220px; max-width: 220px; max-height: 260px; overflow-y: auto; }
|
|
1148
|
+
|
|
1149
|
+
.checkbox-box { width: 16px; height: 16px; border: 1.5px solid #cbd5e1; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: #fff; transition: all 0.2s; background: #fff; }
|
|
1150
|
+
.checkbox-box.checked { background: #3b82f6; border-color: #3b82f6; }
|
|
1151
|
+
|
|
1152
|
+
.radio-circle { width: 16px; height: 16px; border: 1.5px solid #cbd5e1; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #fff; margin-right: 2px; }
|
|
1153
|
+
.radio-circle .dot { width: 8px; height: 8px; border-radius: 50%; background: #fff; transform: scale(0); transition: transform 0.2s; }
|
|
1154
|
+
.radio-circle.checked { border-color: #3b82f6; }
|
|
1155
|
+
.radio-circle.checked .dot { background: #3b82f6; transform: scale(1); }
|
|
1156
|
+
.operand-item.active .radio-circle { border-color: #3b82f6; }
|
|
1157
|
+
.operand-item.active .radio-circle .dot { background: #3b82f6; transform: scale(1); }
|
|
1158
|
+
.filter-popup-footer { padding: 8px 12px; border-top: 1px solid #f1f5f9; display: flex; justify-content: center; }
|
|
1159
|
+
.filter-ok-btn { font-size: 13px; font-weight: 600; color: #fff; background: #0ea5e9; border: none; padding: 6px 20px; border-radius: 6px; cursor: pointer; transition: background 0.2s; }
|
|
1160
|
+
.filter-ok-btn:hover { background: #0284c7; }
|
|
1161
|
+
` })] }));
|
|
1162
|
+
}
|
|
1163
|
+
//# sourceMappingURL=SabReactTable.js.map
|