@smallwebco/tinypivot-react 1.0.48 → 1.0.50
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/README.md +0 -1
- package/dist/index.cjs +1638 -1509
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -77
- package/dist/index.d.ts +77 -77
- package/dist/index.js +1638 -1509
- package/dist/index.js.map +1 -1
- package/package.json +39 -39
package/dist/index.cjs
CHANGED
|
@@ -29,10 +29,10 @@ __export(index_exports, {
|
|
|
29
29
|
enableDemoMode: () => enableDemoMode,
|
|
30
30
|
exportPivotToCSV: () => exportPivotToCSV,
|
|
31
31
|
exportToCSV: () => exportToCSV,
|
|
32
|
-
formatCellValue: () =>
|
|
32
|
+
formatCellValue: () => import_tinypivot_core2.formatCellValue,
|
|
33
33
|
formatSelectionForClipboard: () => formatSelectionForClipboard,
|
|
34
|
-
getAggregationLabel: () =>
|
|
35
|
-
getColumnUniqueValues: () =>
|
|
34
|
+
getAggregationLabel: () => import_tinypivot_core5.getAggregationLabel,
|
|
35
|
+
getColumnUniqueValues: () => import_tinypivot_core2.getColumnUniqueValues,
|
|
36
36
|
setLicenseKey: () => setLicenseKey,
|
|
37
37
|
useColumnResize: () => useColumnResize,
|
|
38
38
|
useExcelGrid: () => useExcelGrid,
|
|
@@ -44,591 +44,1058 @@ __export(index_exports, {
|
|
|
44
44
|
});
|
|
45
45
|
module.exports = __toCommonJS(index_exports);
|
|
46
46
|
|
|
47
|
-
// src/components/
|
|
48
|
-
var import_react10 = require("react");
|
|
49
|
-
var import_react_dom2 = require("react-dom");
|
|
50
|
-
|
|
51
|
-
// src/hooks/useExcelGrid.ts
|
|
52
|
-
var import_react = require("react");
|
|
53
|
-
var import_react_table = require("@tanstack/react-table");
|
|
47
|
+
// src/components/CalculatedFieldModal.tsx
|
|
54
48
|
var import_tinypivot_core = require("@smallwebco/tinypivot-core");
|
|
55
|
-
var
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
var import_react = require("react");
|
|
50
|
+
var import_react_dom = require("react-dom");
|
|
51
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
52
|
+
function CalculatedFieldModal({
|
|
53
|
+
show,
|
|
54
|
+
availableFields,
|
|
55
|
+
existingField,
|
|
56
|
+
onClose,
|
|
57
|
+
onSave
|
|
58
|
+
}) {
|
|
59
|
+
const [name, setName] = (0, import_react.useState)("");
|
|
60
|
+
const [formula, setFormula] = (0, import_react.useState)("");
|
|
61
|
+
const [formatAs, setFormatAs] = (0, import_react.useState)("number");
|
|
62
|
+
const [decimals, setDecimals] = (0, import_react.useState)(2);
|
|
63
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
64
|
+
(0, import_react.useEffect)(() => {
|
|
65
|
+
if (show) {
|
|
66
|
+
if (existingField) {
|
|
67
|
+
setName(existingField.name);
|
|
68
|
+
setFormula(existingField.formula);
|
|
69
|
+
setFormatAs(existingField.formatAs || "number");
|
|
70
|
+
setDecimals(existingField.decimals ?? 2);
|
|
71
|
+
} else {
|
|
72
|
+
setName("");
|
|
73
|
+
setFormula("");
|
|
74
|
+
setFormatAs("number");
|
|
75
|
+
setDecimals(2);
|
|
76
|
+
}
|
|
77
|
+
setError(null);
|
|
61
78
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return filterValue.includes(cellString);
|
|
73
|
-
}
|
|
74
|
-
return true;
|
|
75
|
-
};
|
|
76
|
-
function useExcelGrid(options) {
|
|
77
|
-
const { data, enableSorting = true, enableFiltering = true } = options;
|
|
78
|
-
const [sorting, setSorting] = (0, import_react.useState)([]);
|
|
79
|
-
const [columnFilters, setColumnFilters] = (0, import_react.useState)([]);
|
|
80
|
-
const [columnVisibility, setColumnVisibility] = (0, import_react.useState)({});
|
|
81
|
-
const [globalFilter, setGlobalFilter] = (0, import_react.useState)("");
|
|
82
|
-
const [columnStatsCache, setColumnStatsCache] = (0, import_react.useState)({});
|
|
83
|
-
const dataSignature = (0, import_react.useMemo)(
|
|
84
|
-
() => `${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
85
|
-
[data]
|
|
86
|
-
);
|
|
87
|
-
const columnKeys = (0, import_react.useMemo)(() => {
|
|
88
|
-
if (data.length === 0) return [];
|
|
89
|
-
return Object.keys(data[0]);
|
|
90
|
-
}, [data]);
|
|
91
|
-
const getColumnStats = (0, import_react.useCallback)(
|
|
92
|
-
(columnKey) => {
|
|
93
|
-
const cacheKey = `${columnKey}-${dataSignature}`;
|
|
94
|
-
if (!columnStatsCache[cacheKey]) {
|
|
95
|
-
const stats = (0, import_tinypivot_core.getColumnUniqueValues)(data, columnKey);
|
|
96
|
-
setColumnStatsCache((prev) => ({ ...prev, [cacheKey]: stats }));
|
|
97
|
-
return stats;
|
|
79
|
+
}, [show, existingField]);
|
|
80
|
+
const validationError = (0, import_react.useMemo)(() => {
|
|
81
|
+
if (!formula.trim())
|
|
82
|
+
return null;
|
|
83
|
+
return (0, import_tinypivot_core.validateSimpleFormula)(formula, availableFields);
|
|
84
|
+
}, [formula, availableFields]);
|
|
85
|
+
const insertField = (0, import_react.useCallback)((field) => {
|
|
86
|
+
setFormula((prev) => {
|
|
87
|
+
if (prev.trim() && !prev.endsWith(" ")) {
|
|
88
|
+
return `${prev} ${field}`;
|
|
98
89
|
}
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
[data, columnStatsCache, dataSignature]
|
|
102
|
-
);
|
|
103
|
-
const clearStatsCache = (0, import_react.useCallback)(() => {
|
|
104
|
-
setColumnStatsCache({});
|
|
90
|
+
return prev + field;
|
|
91
|
+
});
|
|
105
92
|
}, []);
|
|
106
|
-
(0, import_react.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
id: key,
|
|
114
|
-
accessorKey: key,
|
|
115
|
-
header: key,
|
|
116
|
-
cell: (info) => (0, import_tinypivot_core.formatCellValue)(info.getValue(), stats.type),
|
|
117
|
-
filterFn: multiSelectFilter,
|
|
118
|
-
meta: {
|
|
119
|
-
type: stats.type,
|
|
120
|
-
uniqueCount: stats.uniqueValues.length
|
|
121
|
-
}
|
|
122
|
-
};
|
|
93
|
+
const insertOperator = (0, import_react.useCallback)((op) => {
|
|
94
|
+
setFormula((prev) => {
|
|
95
|
+
if (prev.trim() && !prev.endsWith(" ")) {
|
|
96
|
+
return `${prev} ${op} `;
|
|
97
|
+
}
|
|
98
|
+
return `${prev + op} `;
|
|
123
99
|
});
|
|
124
|
-
}, [
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
100
|
+
}, []);
|
|
101
|
+
const handleSave = (0, import_react.useCallback)(() => {
|
|
102
|
+
if (!name.trim()) {
|
|
103
|
+
setError("Name is required");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const validationResult = (0, import_tinypivot_core.validateSimpleFormula)(formula, availableFields);
|
|
107
|
+
if (validationResult) {
|
|
108
|
+
setError(validationResult);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const field = {
|
|
112
|
+
id: existingField?.id || `calc_${Date.now()}`,
|
|
113
|
+
name: name.trim(),
|
|
114
|
+
formula: formula.trim(),
|
|
115
|
+
formatAs,
|
|
116
|
+
decimals
|
|
117
|
+
};
|
|
118
|
+
onSave(field);
|
|
119
|
+
onClose();
|
|
120
|
+
}, [name, formula, formatAs, decimals, existingField, availableFields, onSave, onClose]);
|
|
121
|
+
const handleOverlayClick = (0, import_react.useCallback)((e) => {
|
|
122
|
+
if (e.target === e.currentTarget) {
|
|
123
|
+
onClose();
|
|
124
|
+
}
|
|
125
|
+
}, [onClose]);
|
|
126
|
+
if (!show)
|
|
127
|
+
return null;
|
|
128
|
+
const modalContent = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-modal-overlay", onClick: handleOverlayClick, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-modal", children: [
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-modal-header", children: [
|
|
130
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: [
|
|
131
|
+
existingField ? "Edit" : "Create",
|
|
132
|
+
" ",
|
|
133
|
+
"Calculated Field"
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "vpg-modal-close", onClick: onClose, children: "\xD7" })
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-modal-body", children: [
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-form-group", children: [
|
|
139
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "vpg-label", children: "Name" }),
|
|
140
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
141
|
+
"input",
|
|
142
|
+
{
|
|
143
|
+
type: "text",
|
|
144
|
+
className: "vpg-input",
|
|
145
|
+
placeholder: "e.g., Profit Margin %",
|
|
146
|
+
value: name,
|
|
147
|
+
onChange: (e) => setName(e.target.value)
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
] }),
|
|
151
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-form-group", children: [
|
|
152
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "vpg-label", children: "Formula" }),
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
154
|
+
"textarea",
|
|
155
|
+
{
|
|
156
|
+
className: "vpg-textarea",
|
|
157
|
+
placeholder: "e.g., revenue / units",
|
|
158
|
+
rows: 2,
|
|
159
|
+
value: formula,
|
|
160
|
+
onChange: (e) => setFormula(e.target.value)
|
|
161
|
+
}
|
|
162
|
+
),
|
|
163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-formula-hint", children: "Use field names with math operators: + - * / ( )" }),
|
|
164
|
+
validationError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-error", children: validationError })
|
|
165
|
+
] }),
|
|
166
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-form-group", children: [
|
|
167
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "vpg-label-small", children: "Operators" }),
|
|
168
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-button-group", children: [
|
|
169
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("+"), children: "+" }),
|
|
170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("-"), children: "\u2212" }),
|
|
171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("*"), children: "\xD7" }),
|
|
172
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("/"), children: "\xF7" }),
|
|
173
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("("), children: "(" }),
|
|
174
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator(")"), children: ")" })
|
|
175
|
+
] })
|
|
176
|
+
] }),
|
|
177
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-form-group", children: [
|
|
178
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "vpg-label-small", children: "Insert Field" }),
|
|
179
|
+
availableFields.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-button-group vpg-field-buttons", children: availableFields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
180
|
+
"button",
|
|
181
|
+
{
|
|
182
|
+
className: "vpg-insert-btn vpg-field-btn",
|
|
183
|
+
onClick: () => insertField(field),
|
|
184
|
+
children: field
|
|
185
|
+
},
|
|
186
|
+
field
|
|
187
|
+
)) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-no-fields", children: "No numeric fields available" })
|
|
188
|
+
] }),
|
|
189
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-form-row", children: [
|
|
190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-form-group vpg-form-group-half", children: [
|
|
191
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "vpg-label", children: "Format As" }),
|
|
192
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
193
|
+
"select",
|
|
194
|
+
{
|
|
195
|
+
className: "vpg-select",
|
|
196
|
+
value: formatAs,
|
|
197
|
+
onChange: (e) => setFormatAs(e.target.value),
|
|
198
|
+
children: [
|
|
199
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "number", children: "Number" }),
|
|
200
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "percent", children: "Percentage" }),
|
|
201
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "currency", children: "Currency ($)" })
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
] }),
|
|
206
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-form-group vpg-form-group-half", children: [
|
|
207
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "vpg-label", children: "Decimals" }),
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
209
|
+
"input",
|
|
210
|
+
{
|
|
211
|
+
type: "number",
|
|
212
|
+
className: "vpg-input",
|
|
213
|
+
min: 0,
|
|
214
|
+
max: 6,
|
|
215
|
+
value: decimals,
|
|
216
|
+
onChange: (e) => setDecimals(Number(e.target.value))
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
] })
|
|
220
|
+
] }),
|
|
221
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-error vpg-error-box", children: error })
|
|
222
|
+
] }),
|
|
223
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-modal-footer", children: [
|
|
224
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "vpg-btn vpg-btn-secondary", onClick: onClose, children: "Cancel" }),
|
|
225
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "vpg-btn vpg-btn-primary", onClick: handleSave, children: [
|
|
226
|
+
existingField ? "Update" : "Add",
|
|
227
|
+
" ",
|
|
228
|
+
"Field"
|
|
229
|
+
] })
|
|
230
|
+
] })
|
|
231
|
+
] }) });
|
|
232
|
+
if (typeof document === "undefined")
|
|
233
|
+
return null;
|
|
234
|
+
return (0, import_react_dom.createPortal)(modalContent, document.body);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/components/ColumnFilter.tsx
|
|
238
|
+
var import_react3 = require("react");
|
|
239
|
+
|
|
240
|
+
// src/components/NumericRangeFilter.tsx
|
|
241
|
+
var import_react2 = require("react");
|
|
242
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
243
|
+
function NumericRangeFilter({
|
|
244
|
+
dataMin,
|
|
245
|
+
dataMax,
|
|
246
|
+
currentRange,
|
|
247
|
+
onChange
|
|
248
|
+
}) {
|
|
249
|
+
const [localMin, setLocalMin] = (0, import_react2.useState)(currentRange?.min ?? null);
|
|
250
|
+
const [localMax, setLocalMax] = (0, import_react2.useState)(currentRange?.max ?? null);
|
|
251
|
+
const step = (0, import_react2.useMemo)(() => {
|
|
252
|
+
const range = dataMax - dataMin;
|
|
253
|
+
if (range === 0)
|
|
254
|
+
return 1;
|
|
255
|
+
if (range <= 1)
|
|
256
|
+
return 0.01;
|
|
257
|
+
if (range <= 10)
|
|
258
|
+
return 0.1;
|
|
259
|
+
if (range <= 100)
|
|
260
|
+
return 1;
|
|
261
|
+
if (range <= 1e3)
|
|
262
|
+
return 10;
|
|
263
|
+
return 10 ** (Math.floor(Math.log10(range)) - 2);
|
|
264
|
+
}, [dataMin, dataMax]);
|
|
265
|
+
const formatValue = (0, import_react2.useCallback)((val) => {
|
|
266
|
+
if (val === null)
|
|
267
|
+
return "";
|
|
268
|
+
if (Number.isInteger(val))
|
|
269
|
+
return val.toLocaleString();
|
|
270
|
+
return val.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
271
|
+
}, []);
|
|
272
|
+
const isFilterActive = localMin !== null || localMax !== null;
|
|
273
|
+
const minPercent = (0, import_react2.useMemo)(() => {
|
|
274
|
+
if (localMin === null || dataMax === dataMin)
|
|
275
|
+
return 0;
|
|
276
|
+
return (localMin - dataMin) / (dataMax - dataMin) * 100;
|
|
277
|
+
}, [localMin, dataMin, dataMax]);
|
|
278
|
+
const maxPercent = (0, import_react2.useMemo)(() => {
|
|
279
|
+
if (localMax === null || dataMax === dataMin)
|
|
280
|
+
return 100;
|
|
281
|
+
return (localMax - dataMin) / (dataMax - dataMin) * 100;
|
|
282
|
+
}, [localMax, dataMin, dataMax]);
|
|
283
|
+
const handleMinSlider = (0, import_react2.useCallback)((event) => {
|
|
284
|
+
const value = Number.parseFloat(event.target.value);
|
|
285
|
+
setLocalMin(() => {
|
|
286
|
+
if (localMax !== null && value > localMax) {
|
|
287
|
+
return localMax;
|
|
159
288
|
}
|
|
160
|
-
return
|
|
161
|
-
column: f.id,
|
|
162
|
-
type: "values",
|
|
163
|
-
values: Array.isArray(filterValue) ? filterValue : [],
|
|
164
|
-
range: null
|
|
165
|
-
};
|
|
289
|
+
return value;
|
|
166
290
|
});
|
|
167
|
-
}, [
|
|
168
|
-
const
|
|
169
|
-
(
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
if (!filterValue) return false;
|
|
174
|
-
if ((0, import_tinypivot_core.isNumericRange)(filterValue)) {
|
|
175
|
-
return filterValue.min !== null || filterValue.max !== null;
|
|
291
|
+
}, [localMax]);
|
|
292
|
+
const handleMaxSlider = (0, import_react2.useCallback)((event) => {
|
|
293
|
+
const value = Number.parseFloat(event.target.value);
|
|
294
|
+
setLocalMax(() => {
|
|
295
|
+
if (localMin !== null && value < localMin) {
|
|
296
|
+
return localMin;
|
|
176
297
|
}
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
298
|
+
return value;
|
|
299
|
+
});
|
|
300
|
+
}, [localMin]);
|
|
301
|
+
const handleSliderChange = (0, import_react2.useCallback)(() => {
|
|
302
|
+
if (localMin === null && localMax === null) {
|
|
303
|
+
onChange(null);
|
|
304
|
+
} else {
|
|
305
|
+
onChange({ min: localMin, max: localMax });
|
|
306
|
+
}
|
|
307
|
+
}, [localMin, localMax, onChange]);
|
|
308
|
+
const handleMinInput = (0, import_react2.useCallback)((event) => {
|
|
309
|
+
const value = event.target.value === "" ? null : Number.parseFloat(event.target.value);
|
|
310
|
+
if (value !== null && !Number.isNaN(value)) {
|
|
311
|
+
setLocalMin(Math.max(dataMin, Math.min(value, localMax ?? dataMax)));
|
|
312
|
+
} else if (value === null) {
|
|
313
|
+
setLocalMin(null);
|
|
314
|
+
}
|
|
315
|
+
}, [dataMin, dataMax, localMax]);
|
|
316
|
+
const handleMaxInput = (0, import_react2.useCallback)((event) => {
|
|
317
|
+
const value = event.target.value === "" ? null : Number.parseFloat(event.target.value);
|
|
318
|
+
if (value !== null && !Number.isNaN(value)) {
|
|
319
|
+
setLocalMax(Math.min(dataMax, Math.max(value, localMin ?? dataMin)));
|
|
320
|
+
} else if (value === null) {
|
|
321
|
+
setLocalMax(null);
|
|
322
|
+
}
|
|
323
|
+
}, [dataMin, dataMax, localMin]);
|
|
324
|
+
const handleInputBlur = (0, import_react2.useCallback)(() => {
|
|
325
|
+
if (localMin === null && localMax === null) {
|
|
326
|
+
onChange(null);
|
|
327
|
+
} else {
|
|
328
|
+
onChange({ min: localMin, max: localMax });
|
|
329
|
+
}
|
|
330
|
+
}, [localMin, localMax, onChange]);
|
|
331
|
+
const clearFilter = (0, import_react2.useCallback)(() => {
|
|
332
|
+
setLocalMin(null);
|
|
333
|
+
setLocalMax(null);
|
|
334
|
+
onChange(null);
|
|
335
|
+
}, [onChange]);
|
|
336
|
+
const setFullRange = (0, import_react2.useCallback)(() => {
|
|
337
|
+
setLocalMin(dataMin);
|
|
338
|
+
setLocalMax(dataMax);
|
|
339
|
+
onChange({ min: dataMin, max: dataMax });
|
|
340
|
+
}, [dataMin, dataMax, onChange]);
|
|
341
|
+
(0, import_react2.useEffect)(() => {
|
|
342
|
+
setLocalMin(currentRange?.min ?? null);
|
|
343
|
+
setLocalMax(currentRange?.max ?? null);
|
|
344
|
+
}, [currentRange]);
|
|
345
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-range-filter", children: [
|
|
346
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-range-info", children: [
|
|
347
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "vpg-range-label", children: "Data range:" }),
|
|
348
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "vpg-range-bounds", children: [
|
|
349
|
+
formatValue(dataMin),
|
|
350
|
+
" ",
|
|
351
|
+
"\u2013",
|
|
352
|
+
formatValue(dataMax)
|
|
353
|
+
] })
|
|
354
|
+
] }),
|
|
355
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-slider-container", children: [
|
|
356
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "vpg-slider-track", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
357
|
+
"div",
|
|
358
|
+
{
|
|
359
|
+
className: "vpg-slider-fill",
|
|
360
|
+
style: {
|
|
361
|
+
left: `${minPercent}%`,
|
|
362
|
+
right: `${100 - maxPercent}%`
|
|
363
|
+
}
|
|
198
364
|
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
365
|
+
) }),
|
|
366
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
367
|
+
"input",
|
|
368
|
+
{
|
|
369
|
+
type: "range",
|
|
370
|
+
className: "vpg-slider vpg-slider-min",
|
|
371
|
+
min: dataMin,
|
|
372
|
+
max: dataMax,
|
|
373
|
+
step,
|
|
374
|
+
value: localMin ?? dataMin,
|
|
375
|
+
onChange: handleMinSlider,
|
|
376
|
+
onMouseUp: handleSliderChange,
|
|
377
|
+
onTouchEnd: handleSliderChange
|
|
378
|
+
}
|
|
379
|
+
),
|
|
380
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
381
|
+
"input",
|
|
382
|
+
{
|
|
383
|
+
type: "range",
|
|
384
|
+
className: "vpg-slider vpg-slider-max",
|
|
385
|
+
min: dataMin,
|
|
386
|
+
max: dataMax,
|
|
387
|
+
step,
|
|
388
|
+
value: localMax ?? dataMax,
|
|
389
|
+
onChange: handleMaxSlider,
|
|
390
|
+
onMouseUp: handleSliderChange,
|
|
391
|
+
onTouchEnd: handleSliderChange
|
|
392
|
+
}
|
|
393
|
+
)
|
|
394
|
+
] }),
|
|
395
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-range-inputs", children: [
|
|
396
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-input-group", children: [
|
|
397
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "vpg-input-label", children: "Min" }),
|
|
398
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
399
|
+
"input",
|
|
400
|
+
{
|
|
401
|
+
type: "number",
|
|
402
|
+
className: "vpg-range-input",
|
|
403
|
+
placeholder: formatValue(dataMin),
|
|
404
|
+
value: localMin ?? "",
|
|
405
|
+
step,
|
|
406
|
+
onChange: handleMinInput,
|
|
407
|
+
onBlur: handleInputBlur
|
|
408
|
+
}
|
|
409
|
+
)
|
|
410
|
+
] }),
|
|
411
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "vpg-input-separator", children: "to" }),
|
|
412
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-input-group", children: [
|
|
413
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "vpg-input-label", children: "Max" }),
|
|
414
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
415
|
+
"input",
|
|
416
|
+
{
|
|
417
|
+
type: "number",
|
|
418
|
+
className: "vpg-range-input",
|
|
419
|
+
placeholder: formatValue(dataMax),
|
|
420
|
+
value: localMax ?? "",
|
|
421
|
+
step,
|
|
422
|
+
onChange: handleMaxInput,
|
|
423
|
+
onBlur: handleInputBlur
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
] })
|
|
427
|
+
] }),
|
|
428
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-range-actions", children: [
|
|
429
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
430
|
+
"button",
|
|
431
|
+
{
|
|
432
|
+
className: "vpg-range-btn",
|
|
433
|
+
disabled: !isFilterActive,
|
|
434
|
+
onClick: clearFilter,
|
|
435
|
+
children: [
|
|
436
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
437
|
+
"path",
|
|
438
|
+
{
|
|
439
|
+
strokeLinecap: "round",
|
|
440
|
+
strokeLinejoin: "round",
|
|
441
|
+
strokeWidth: 2,
|
|
442
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
443
|
+
}
|
|
444
|
+
) }),
|
|
445
|
+
"Clear"
|
|
446
|
+
]
|
|
447
|
+
}
|
|
448
|
+
),
|
|
449
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { className: "vpg-range-btn", onClick: setFullRange, children: [
|
|
450
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
451
|
+
"path",
|
|
452
|
+
{
|
|
453
|
+
strokeLinecap: "round",
|
|
454
|
+
strokeLinejoin: "round",
|
|
455
|
+
strokeWidth: 2,
|
|
456
|
+
d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"
|
|
457
|
+
}
|
|
458
|
+
) }),
|
|
459
|
+
"Full Range"
|
|
460
|
+
] })
|
|
461
|
+
] }),
|
|
462
|
+
isFilterActive && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-filter-summary", children: [
|
|
463
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
464
|
+
"path",
|
|
465
|
+
{
|
|
466
|
+
strokeLinecap: "round",
|
|
467
|
+
strokeLinejoin: "round",
|
|
468
|
+
strokeWidth: 2,
|
|
469
|
+
d: "M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
|
|
470
|
+
}
|
|
471
|
+
) }),
|
|
472
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
|
|
473
|
+
"Showing values",
|
|
474
|
+
" ",
|
|
475
|
+
localMin !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("strong", { children: [
|
|
476
|
+
"\u2265",
|
|
477
|
+
formatValue(localMin)
|
|
478
|
+
] }),
|
|
479
|
+
localMin !== null && localMax !== null && " and ",
|
|
480
|
+
localMax !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("strong", { children: [
|
|
481
|
+
"\u2264",
|
|
482
|
+
formatValue(localMax)
|
|
483
|
+
] })
|
|
484
|
+
] })
|
|
485
|
+
] })
|
|
486
|
+
] });
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/components/ColumnFilter.tsx
|
|
490
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
491
|
+
function ColumnFilter({
|
|
492
|
+
columnName,
|
|
493
|
+
stats,
|
|
494
|
+
selectedValues,
|
|
495
|
+
sortDirection,
|
|
496
|
+
numericRange,
|
|
497
|
+
onFilter,
|
|
498
|
+
onSort,
|
|
499
|
+
onClose,
|
|
500
|
+
onRangeFilter
|
|
501
|
+
}) {
|
|
502
|
+
const [searchQuery, setSearchQuery] = (0, import_react3.useState)("");
|
|
503
|
+
const [localSelected, setLocalSelected] = (0, import_react3.useState)(new Set(selectedValues));
|
|
504
|
+
const dropdownRef = (0, import_react3.useRef)(null);
|
|
505
|
+
const searchInputRef = (0, import_react3.useRef)(null);
|
|
506
|
+
const isNumericColumn = stats.type === "number" && stats.numericMin !== void 0 && stats.numericMax !== void 0;
|
|
507
|
+
const [filterMode, setFilterMode] = (0, import_react3.useState)(numericRange ? "range" : "values");
|
|
508
|
+
const [localRange, setLocalRange] = (0, import_react3.useState)(numericRange ?? null);
|
|
509
|
+
const hasBlankValues = stats.nullCount > 0;
|
|
510
|
+
const filteredValues = (0, import_react3.useMemo)(() => {
|
|
511
|
+
const values = stats.uniqueValues;
|
|
512
|
+
if (!searchQuery)
|
|
513
|
+
return values;
|
|
514
|
+
const query = searchQuery.toLowerCase();
|
|
515
|
+
return values.filter((v) => v.toLowerCase().includes(query));
|
|
516
|
+
}, [stats.uniqueValues, searchQuery]);
|
|
517
|
+
const allValues = (0, import_react3.useMemo)(() => {
|
|
518
|
+
const values = [...filteredValues];
|
|
519
|
+
if (hasBlankValues && (!searchQuery || "(blank)".includes(searchQuery.toLowerCase()))) {
|
|
520
|
+
values.unshift("(blank)");
|
|
521
|
+
}
|
|
522
|
+
return values;
|
|
523
|
+
}, [filteredValues, hasBlankValues, searchQuery]);
|
|
524
|
+
const _isAllSelected = (0, import_react3.useMemo)(
|
|
525
|
+
() => allValues.every((v) => localSelected.has(v)),
|
|
526
|
+
[allValues, localSelected]
|
|
228
527
|
);
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
} else if (!current.desc) {
|
|
235
|
-
return [{ id: columnId, desc: true }];
|
|
528
|
+
const toggleValue = (0, import_react3.useCallback)((value) => {
|
|
529
|
+
setLocalSelected((prev) => {
|
|
530
|
+
const next = new Set(prev);
|
|
531
|
+
if (next.has(value)) {
|
|
532
|
+
next.delete(value);
|
|
236
533
|
} else {
|
|
237
|
-
|
|
534
|
+
next.add(value);
|
|
238
535
|
}
|
|
536
|
+
return next;
|
|
239
537
|
});
|
|
240
538
|
}, []);
|
|
241
|
-
const
|
|
242
|
-
(
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
},
|
|
247
|
-
[sorting]
|
|
248
|
-
);
|
|
249
|
-
return {
|
|
250
|
-
// Table instance
|
|
251
|
-
table,
|
|
252
|
-
// State
|
|
253
|
-
sorting,
|
|
254
|
-
columnFilters,
|
|
255
|
-
columnVisibility,
|
|
256
|
-
globalFilter,
|
|
257
|
-
columnKeys,
|
|
258
|
-
setSorting,
|
|
259
|
-
setColumnFilters,
|
|
260
|
-
setGlobalFilter,
|
|
261
|
-
// Computed
|
|
262
|
-
filteredRowCount,
|
|
263
|
-
totalRowCount,
|
|
264
|
-
activeFilters,
|
|
265
|
-
// Methods
|
|
266
|
-
getColumnStats,
|
|
267
|
-
clearStatsCache,
|
|
268
|
-
hasActiveFilter,
|
|
269
|
-
setColumnFilter,
|
|
270
|
-
getColumnFilterValues,
|
|
271
|
-
clearAllFilters,
|
|
272
|
-
toggleSort,
|
|
273
|
-
getSortDirection,
|
|
274
|
-
// Numeric range filters
|
|
275
|
-
setNumericRangeFilter,
|
|
276
|
-
getNumericRangeFilter
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// src/hooks/usePivotTable.ts
|
|
281
|
-
var import_react3 = require("react");
|
|
282
|
-
var import_tinypivot_core3 = require("@smallwebco/tinypivot-core");
|
|
283
|
-
|
|
284
|
-
// src/hooks/useLicense.ts
|
|
285
|
-
var import_react2 = require("react");
|
|
286
|
-
var import_tinypivot_core2 = require("@smallwebco/tinypivot-core");
|
|
287
|
-
var globalLicenseInfo = (0, import_tinypivot_core2.getFreeLicenseInfo)();
|
|
288
|
-
var globalDemoMode = false;
|
|
289
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
290
|
-
function notifyListeners() {
|
|
291
|
-
listeners.forEach((listener) => listener());
|
|
292
|
-
}
|
|
293
|
-
async function setLicenseKey(key) {
|
|
294
|
-
globalLicenseInfo = await (0, import_tinypivot_core2.validateLicenseKey)(key);
|
|
295
|
-
if (!globalLicenseInfo.isValid) {
|
|
296
|
-
console.warn("[TinyPivot] Invalid or expired license key. Running in free mode.");
|
|
297
|
-
} else if (globalLicenseInfo.type !== "free") {
|
|
298
|
-
console.info(`[TinyPivot] Pro license activated (${globalLicenseInfo.type})`);
|
|
299
|
-
}
|
|
300
|
-
notifyListeners();
|
|
301
|
-
}
|
|
302
|
-
async function enableDemoMode(secret) {
|
|
303
|
-
const demoLicense = await (0, import_tinypivot_core2.getDemoLicenseInfo)(secret);
|
|
304
|
-
if (!demoLicense) {
|
|
305
|
-
console.warn("[TinyPivot] Demo mode activation failed - invalid secret");
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
globalDemoMode = true;
|
|
309
|
-
globalLicenseInfo = demoLicense;
|
|
310
|
-
console.info("[TinyPivot] Demo mode enabled - all Pro features unlocked for evaluation");
|
|
311
|
-
notifyListeners();
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
function configureLicenseSecret(secret) {
|
|
315
|
-
(0, import_tinypivot_core2.configureLicenseSecret)(secret);
|
|
316
|
-
}
|
|
317
|
-
function useLicense() {
|
|
318
|
-
const [, forceUpdate] = (0, import_react2.useState)({});
|
|
319
|
-
(0, import_react2.useState)(() => {
|
|
320
|
-
const update = () => forceUpdate({});
|
|
321
|
-
listeners.add(update);
|
|
322
|
-
return () => listeners.delete(update);
|
|
323
|
-
});
|
|
324
|
-
const isDemo = globalDemoMode;
|
|
325
|
-
const licenseInfo = globalLicenseInfo;
|
|
326
|
-
const isPro = (0, import_react2.useMemo)(
|
|
327
|
-
() => globalDemoMode || (0, import_tinypivot_core2.isPro)(licenseInfo),
|
|
328
|
-
[licenseInfo]
|
|
329
|
-
);
|
|
330
|
-
const canUsePivot = (0, import_react2.useMemo)(
|
|
331
|
-
() => globalDemoMode || (0, import_tinypivot_core2.canUsePivot)(licenseInfo),
|
|
332
|
-
[licenseInfo]
|
|
333
|
-
);
|
|
334
|
-
const canUseAdvancedAggregations = (0, import_react2.useMemo)(
|
|
335
|
-
() => globalDemoMode || licenseInfo.features.advancedAggregations,
|
|
336
|
-
[licenseInfo]
|
|
337
|
-
);
|
|
338
|
-
const canUsePercentageMode = (0, import_react2.useMemo)(
|
|
339
|
-
() => globalDemoMode || licenseInfo.features.percentageMode,
|
|
340
|
-
[licenseInfo]
|
|
341
|
-
);
|
|
342
|
-
const showWatermark = (0, import_react2.useMemo)(
|
|
343
|
-
() => (0, import_tinypivot_core2.shouldShowWatermark)(licenseInfo, globalDemoMode),
|
|
344
|
-
[licenseInfo]
|
|
345
|
-
);
|
|
346
|
-
const requirePro = (0, import_react2.useCallback)((feature) => {
|
|
347
|
-
if (!isPro) {
|
|
348
|
-
(0, import_tinypivot_core2.logProRequired)(feature);
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
return true;
|
|
352
|
-
}, [isPro]);
|
|
353
|
-
return {
|
|
354
|
-
licenseInfo,
|
|
355
|
-
isDemo,
|
|
356
|
-
isPro,
|
|
357
|
-
canUsePivot,
|
|
358
|
-
canUseAdvancedAggregations,
|
|
359
|
-
canUsePercentageMode,
|
|
360
|
-
showWatermark,
|
|
361
|
-
requirePro
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// src/hooks/usePivotTable.ts
|
|
366
|
-
function usePivotTable(data) {
|
|
367
|
-
const { canUsePivot, requirePro } = useLicense();
|
|
368
|
-
const [rowFields, setRowFieldsState] = (0, import_react3.useState)([]);
|
|
369
|
-
const [columnFields, setColumnFieldsState] = (0, import_react3.useState)([]);
|
|
370
|
-
const [valueFields, setValueFields] = (0, import_react3.useState)([]);
|
|
371
|
-
const [showRowTotals, setShowRowTotals] = (0, import_react3.useState)(true);
|
|
372
|
-
const [showColumnTotals, setShowColumnTotals] = (0, import_react3.useState)(true);
|
|
373
|
-
const [calculatedFields, setCalculatedFields] = (0, import_react3.useState)(() => (0, import_tinypivot_core3.loadCalculatedFields)());
|
|
374
|
-
const [currentStorageKey, setCurrentStorageKey] = (0, import_react3.useState)(null);
|
|
375
|
-
const availableFields = (0, import_react3.useMemo)(() => {
|
|
376
|
-
return (0, import_tinypivot_core3.computeAvailableFields)(data);
|
|
377
|
-
}, [data]);
|
|
378
|
-
const unassignedFields = (0, import_react3.useMemo)(() => {
|
|
379
|
-
return (0, import_tinypivot_core3.getUnassignedFields)(availableFields, rowFields, columnFields, valueFields);
|
|
380
|
-
}, [availableFields, rowFields, columnFields, valueFields]);
|
|
381
|
-
const isConfigured = (0, import_react3.useMemo)(() => {
|
|
382
|
-
return (0, import_tinypivot_core3.isPivotConfigured)({
|
|
383
|
-
rowFields,
|
|
384
|
-
columnFields,
|
|
385
|
-
valueFields,
|
|
386
|
-
showRowTotals,
|
|
387
|
-
showColumnTotals
|
|
388
|
-
});
|
|
389
|
-
}, [rowFields, columnFields, valueFields, showRowTotals, showColumnTotals]);
|
|
390
|
-
const pivotResult = (0, import_react3.useMemo)(() => {
|
|
391
|
-
if (!isConfigured) return null;
|
|
392
|
-
if (!canUsePivot) return null;
|
|
393
|
-
return (0, import_tinypivot_core3.computePivotResult)(data, {
|
|
394
|
-
rowFields,
|
|
395
|
-
columnFields,
|
|
396
|
-
valueFields,
|
|
397
|
-
showRowTotals,
|
|
398
|
-
showColumnTotals,
|
|
399
|
-
calculatedFields
|
|
400
|
-
});
|
|
401
|
-
}, [data, isConfigured, canUsePivot, rowFields, columnFields, valueFields, showRowTotals, showColumnTotals, calculatedFields]);
|
|
402
|
-
(0, import_react3.useEffect)(() => {
|
|
403
|
-
if (data.length === 0) return;
|
|
404
|
-
const newKeys = Object.keys(data[0]);
|
|
405
|
-
const storageKey = (0, import_tinypivot_core3.generateStorageKey)(newKeys);
|
|
406
|
-
if (storageKey !== currentStorageKey) {
|
|
407
|
-
setCurrentStorageKey(storageKey);
|
|
408
|
-
const savedConfig = (0, import_tinypivot_core3.loadPivotConfig)(storageKey);
|
|
409
|
-
if (savedConfig && (0, import_tinypivot_core3.isConfigValidForFields)(savedConfig, newKeys)) {
|
|
410
|
-
setRowFieldsState(savedConfig.rowFields);
|
|
411
|
-
setColumnFieldsState(savedConfig.columnFields);
|
|
412
|
-
setValueFields(savedConfig.valueFields);
|
|
413
|
-
setShowRowTotals(savedConfig.showRowTotals);
|
|
414
|
-
setShowColumnTotals(savedConfig.showColumnTotals);
|
|
415
|
-
if (savedConfig.calculatedFields) {
|
|
416
|
-
setCalculatedFields(savedConfig.calculatedFields);
|
|
417
|
-
}
|
|
418
|
-
} else {
|
|
419
|
-
const currentConfig = {
|
|
420
|
-
rowFields,
|
|
421
|
-
columnFields,
|
|
422
|
-
valueFields,
|
|
423
|
-
showRowTotals,
|
|
424
|
-
showColumnTotals
|
|
425
|
-
};
|
|
426
|
-
if (!(0, import_tinypivot_core3.isConfigValidForFields)(currentConfig, newKeys)) {
|
|
427
|
-
setRowFieldsState([]);
|
|
428
|
-
setColumnFieldsState([]);
|
|
429
|
-
setValueFields([]);
|
|
430
|
-
}
|
|
539
|
+
const selectAll = (0, import_react3.useCallback)(() => {
|
|
540
|
+
setLocalSelected((prev) => {
|
|
541
|
+
const next = new Set(prev);
|
|
542
|
+
for (const value of allValues) {
|
|
543
|
+
next.add(value);
|
|
431
544
|
}
|
|
545
|
+
return next;
|
|
546
|
+
});
|
|
547
|
+
}, [allValues]);
|
|
548
|
+
const clearAll = (0, import_react3.useCallback)(() => {
|
|
549
|
+
setLocalSelected(/* @__PURE__ */ new Set());
|
|
550
|
+
}, []);
|
|
551
|
+
const applyFilter = (0, import_react3.useCallback)(() => {
|
|
552
|
+
if (localSelected.size === 0) {
|
|
553
|
+
onFilter([]);
|
|
554
|
+
} else {
|
|
555
|
+
onFilter(Array.from(localSelected));
|
|
432
556
|
}
|
|
433
|
-
|
|
557
|
+
onClose();
|
|
558
|
+
}, [localSelected, onFilter, onClose]);
|
|
559
|
+
const sortAscending = (0, import_react3.useCallback)(() => {
|
|
560
|
+
onSort(sortDirection === "asc" ? null : "asc");
|
|
561
|
+
}, [sortDirection, onSort]);
|
|
562
|
+
const sortDescending = (0, import_react3.useCallback)(() => {
|
|
563
|
+
onSort(sortDirection === "desc" ? null : "desc");
|
|
564
|
+
}, [sortDirection, onSort]);
|
|
565
|
+
const clearFilter = (0, import_react3.useCallback)(() => {
|
|
566
|
+
setLocalSelected(/* @__PURE__ */ new Set());
|
|
567
|
+
onFilter([]);
|
|
568
|
+
onClose();
|
|
569
|
+
}, [onFilter, onClose]);
|
|
570
|
+
const handleRangeChange = (0, import_react3.useCallback)((range) => {
|
|
571
|
+
setLocalRange(range);
|
|
572
|
+
}, []);
|
|
573
|
+
const applyRangeFilter = (0, import_react3.useCallback)(() => {
|
|
574
|
+
onRangeFilter?.(localRange);
|
|
575
|
+
onClose();
|
|
576
|
+
}, [localRange, onRangeFilter, onClose]);
|
|
577
|
+
const clearRangeFilter = (0, import_react3.useCallback)(() => {
|
|
578
|
+
setLocalRange(null);
|
|
579
|
+
onRangeFilter?.(null);
|
|
580
|
+
onClose();
|
|
581
|
+
}, [onRangeFilter, onClose]);
|
|
434
582
|
(0, import_react3.useEffect)(() => {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
columnFields,
|
|
439
|
-
valueFields,
|
|
440
|
-
showRowTotals,
|
|
441
|
-
showColumnTotals,
|
|
442
|
-
calculatedFields
|
|
443
|
-
};
|
|
444
|
-
(0, import_tinypivot_core3.savePivotConfig)(currentStorageKey, config);
|
|
445
|
-
}, [currentStorageKey, rowFields, columnFields, valueFields, showRowTotals, showColumnTotals, calculatedFields]);
|
|
446
|
-
const addRowField = (0, import_react3.useCallback)(
|
|
447
|
-
(field) => {
|
|
448
|
-
if (!rowFields.includes(field)) {
|
|
449
|
-
setRowFieldsState((prev) => [...prev, field]);
|
|
583
|
+
const handleClickOutside = (event) => {
|
|
584
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
585
|
+
onClose();
|
|
450
586
|
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
(field) => {
|
|
462
|
-
if (!columnFields.includes(field)) {
|
|
463
|
-
setColumnFieldsState((prev) => [...prev, field]);
|
|
587
|
+
};
|
|
588
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
589
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
590
|
+
}, [onClose]);
|
|
591
|
+
(0, import_react3.useEffect)(() => {
|
|
592
|
+
const handleKeydown = (event) => {
|
|
593
|
+
if (event.key === "Escape") {
|
|
594
|
+
onClose();
|
|
595
|
+
} else if (event.key === "Enter" && event.ctrlKey) {
|
|
596
|
+
applyFilter();
|
|
464
597
|
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const setColumnFields = (0, import_react3.useCallback)((fields) => {
|
|
472
|
-
setColumnFieldsState(fields);
|
|
598
|
+
};
|
|
599
|
+
document.addEventListener("keydown", handleKeydown);
|
|
600
|
+
return () => document.removeEventListener("keydown", handleKeydown);
|
|
601
|
+
}, [onClose, applyFilter]);
|
|
602
|
+
(0, import_react3.useEffect)(() => {
|
|
603
|
+
searchInputRef.current?.focus();
|
|
473
604
|
}, []);
|
|
474
|
-
|
|
475
|
-
(
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
605
|
+
(0, import_react3.useEffect)(() => {
|
|
606
|
+
setLocalSelected(new Set(selectedValues));
|
|
607
|
+
}, [selectedValues]);
|
|
608
|
+
(0, import_react3.useEffect)(() => {
|
|
609
|
+
setLocalRange(numericRange ?? null);
|
|
610
|
+
if (numericRange) {
|
|
611
|
+
setFilterMode("range");
|
|
612
|
+
}
|
|
613
|
+
}, [numericRange]);
|
|
614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ref: dropdownRef, className: "vpg-filter-dropdown", children: [
|
|
615
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-filter-header", children: [
|
|
616
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "vpg-filter-title", children: columnName }),
|
|
617
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "vpg-filter-count", children: [
|
|
618
|
+
stats.uniqueValues.length.toLocaleString(),
|
|
619
|
+
" ",
|
|
620
|
+
"unique"
|
|
621
|
+
] })
|
|
622
|
+
] }),
|
|
623
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-sort-controls", children: [
|
|
624
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
625
|
+
"button",
|
|
626
|
+
{
|
|
627
|
+
className: `vpg-sort-btn ${sortDirection === "asc" ? "active" : ""}`,
|
|
628
|
+
title: isNumericColumn ? "Sort Low to High" : "Sort A to Z",
|
|
629
|
+
onClick: sortAscending,
|
|
630
|
+
children: [
|
|
631
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
632
|
+
"path",
|
|
633
|
+
{
|
|
634
|
+
strokeLinecap: "round",
|
|
635
|
+
strokeLinejoin: "round",
|
|
636
|
+
strokeWidth: 2,
|
|
637
|
+
d: "M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12"
|
|
638
|
+
}
|
|
639
|
+
) }),
|
|
640
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: isNumericColumn ? "1\u21929" : "A\u2192Z" })
|
|
641
|
+
]
|
|
482
642
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
643
|
+
),
|
|
644
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
645
|
+
"button",
|
|
646
|
+
{
|
|
647
|
+
className: `vpg-sort-btn ${sortDirection === "desc" ? "active" : ""}`,
|
|
648
|
+
title: isNumericColumn ? "Sort High to Low" : "Sort Z to A",
|
|
649
|
+
onClick: sortDescending,
|
|
650
|
+
children: [
|
|
651
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
652
|
+
"path",
|
|
653
|
+
{
|
|
654
|
+
strokeLinecap: "round",
|
|
655
|
+
strokeLinejoin: "round",
|
|
656
|
+
strokeWidth: 2,
|
|
657
|
+
d: "M3 4h13M3 8h9m-9 4h9m5-4v12m0 0l-4-4m4 4l4-4"
|
|
658
|
+
}
|
|
659
|
+
) }),
|
|
660
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: isNumericColumn ? "9\u21921" : "Z\u2192A" })
|
|
661
|
+
]
|
|
662
|
+
}
|
|
663
|
+
)
|
|
664
|
+
] }),
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "vpg-divider" }),
|
|
666
|
+
isNumericColumn && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-filter-tabs", children: [
|
|
667
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
668
|
+
"button",
|
|
669
|
+
{
|
|
670
|
+
className: `vpg-tab-btn ${filterMode === "values" ? "active" : ""}`,
|
|
671
|
+
onClick: () => setFilterMode("values"),
|
|
672
|
+
children: [
|
|
673
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
674
|
+
"path",
|
|
675
|
+
{
|
|
676
|
+
strokeLinecap: "round",
|
|
677
|
+
strokeLinejoin: "round",
|
|
678
|
+
strokeWidth: 2,
|
|
679
|
+
d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
|
|
680
|
+
}
|
|
681
|
+
) }),
|
|
682
|
+
"Values"
|
|
683
|
+
]
|
|
684
|
+
}
|
|
685
|
+
),
|
|
686
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
687
|
+
"button",
|
|
688
|
+
{
|
|
689
|
+
className: `vpg-tab-btn ${filterMode === "range" ? "active" : ""}`,
|
|
690
|
+
onClick: () => setFilterMode("range"),
|
|
691
|
+
children: [
|
|
692
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
693
|
+
"path",
|
|
694
|
+
{
|
|
695
|
+
strokeLinecap: "round",
|
|
696
|
+
strokeLinejoin: "round",
|
|
697
|
+
strokeWidth: 2,
|
|
698
|
+
d: "M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
|
|
699
|
+
}
|
|
700
|
+
) }),
|
|
701
|
+
"Range"
|
|
702
|
+
]
|
|
703
|
+
}
|
|
704
|
+
)
|
|
705
|
+
] }),
|
|
706
|
+
(!isNumericColumn || filterMode === "values") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
707
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-search-container", children: [
|
|
708
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "vpg-search-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
709
|
+
"path",
|
|
710
|
+
{
|
|
711
|
+
strokeLinecap: "round",
|
|
712
|
+
strokeLinejoin: "round",
|
|
713
|
+
strokeWidth: 2,
|
|
714
|
+
d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
502
715
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
716
|
+
) }),
|
|
717
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
718
|
+
"input",
|
|
719
|
+
{
|
|
720
|
+
ref: searchInputRef,
|
|
721
|
+
type: "text",
|
|
722
|
+
value: searchQuery,
|
|
723
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
724
|
+
placeholder: "Search values...",
|
|
725
|
+
className: "vpg-search-input"
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
searchQuery && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-clear-search", onClick: () => setSearchQuery(""), children: "\xD7" })
|
|
729
|
+
] }),
|
|
730
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-bulk-actions", children: [
|
|
731
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { className: "vpg-bulk-btn", onClick: selectAll, children: [
|
|
732
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
733
|
+
"path",
|
|
734
|
+
{
|
|
735
|
+
strokeLinecap: "round",
|
|
736
|
+
strokeLinejoin: "round",
|
|
737
|
+
strokeWidth: 2,
|
|
738
|
+
d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
739
|
+
}
|
|
740
|
+
) }),
|
|
741
|
+
"Select All"
|
|
742
|
+
] }),
|
|
743
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { className: "vpg-bulk-btn", onClick: clearAll, children: [
|
|
744
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
745
|
+
"path",
|
|
746
|
+
{
|
|
747
|
+
strokeLinecap: "round",
|
|
748
|
+
strokeLinejoin: "round",
|
|
749
|
+
strokeWidth: 2,
|
|
750
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
751
|
+
}
|
|
752
|
+
) }),
|
|
753
|
+
"Clear All"
|
|
754
|
+
] })
|
|
755
|
+
] }),
|
|
756
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-values-list", children: [
|
|
757
|
+
allValues.map((value) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
758
|
+
"label",
|
|
759
|
+
{
|
|
760
|
+
className: `vpg-value-item ${localSelected.has(value) ? "selected" : ""}`,
|
|
761
|
+
children: [
|
|
762
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
763
|
+
"input",
|
|
764
|
+
{
|
|
765
|
+
type: "checkbox",
|
|
766
|
+
checked: localSelected.has(value),
|
|
767
|
+
onChange: () => toggleValue(value),
|
|
768
|
+
className: "vpg-value-checkbox"
|
|
769
|
+
}
|
|
770
|
+
),
|
|
771
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `vpg-value-text ${value === "(blank)" ? "vpg-blank" : ""}`, children: value })
|
|
772
|
+
]
|
|
773
|
+
},
|
|
774
|
+
value
|
|
775
|
+
)),
|
|
776
|
+
allValues.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "vpg-no-results", children: "No matching values" })
|
|
777
|
+
] }),
|
|
778
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-filter-footer", children: [
|
|
779
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-btn-clear", onClick: clearFilter, children: "Clear Filter" }),
|
|
780
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-btn-apply", onClick: applyFilter, children: "Apply" })
|
|
781
|
+
] })
|
|
782
|
+
] }),
|
|
783
|
+
isNumericColumn && filterMode === "range" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
784
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
785
|
+
NumericRangeFilter,
|
|
786
|
+
{
|
|
787
|
+
dataMin: stats.numericMin,
|
|
788
|
+
dataMax: stats.numericMax,
|
|
789
|
+
currentRange: localRange,
|
|
790
|
+
onChange: handleRangeChange
|
|
791
|
+
}
|
|
792
|
+
),
|
|
793
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-filter-footer", children: [
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-btn-clear", onClick: clearRangeFilter, children: "Clear Filter" }),
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-btn-apply", onClick: applyRangeFilter, children: "Apply" })
|
|
796
|
+
] })
|
|
797
|
+
] })
|
|
798
|
+
] });
|
|
575
799
|
}
|
|
576
800
|
|
|
577
|
-
// src/
|
|
801
|
+
// src/components/DataGrid.tsx
|
|
802
|
+
var import_react10 = require("react");
|
|
803
|
+
var import_react_dom2 = require("react-dom");
|
|
804
|
+
|
|
805
|
+
// src/hooks/useExcelGrid.ts
|
|
806
|
+
var import_tinypivot_core2 = require("@smallwebco/tinypivot-core");
|
|
807
|
+
var import_react_table = require("@tanstack/react-table");
|
|
578
808
|
var import_react4 = require("react");
|
|
579
|
-
var
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
809
|
+
var multiSelectFilter = (row, columnId, filterValue) => {
|
|
810
|
+
if (!filterValue)
|
|
811
|
+
return true;
|
|
812
|
+
if ((0, import_tinypivot_core2.isNumericRange)(filterValue)) {
|
|
813
|
+
const cellValue = row.getValue(columnId);
|
|
814
|
+
if (cellValue === null || cellValue === void 0 || cellValue === "") {
|
|
815
|
+
return false;
|
|
816
|
+
}
|
|
817
|
+
const num = typeof cellValue === "number" ? cellValue : Number.parseFloat(String(cellValue));
|
|
818
|
+
if (Number.isNaN(num))
|
|
819
|
+
return false;
|
|
820
|
+
const { min, max } = filterValue;
|
|
821
|
+
if (min !== null && num < min)
|
|
822
|
+
return false;
|
|
823
|
+
if (max !== null && num > max)
|
|
824
|
+
return false;
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
if (Array.isArray(filterValue) && filterValue.length > 0) {
|
|
828
|
+
const cellValue = row.getValue(columnId);
|
|
829
|
+
const cellString = cellValue === null || cellValue === void 0 || cellValue === "" ? "(blank)" : String(cellValue);
|
|
830
|
+
return filterValue.includes(cellString);
|
|
831
|
+
}
|
|
832
|
+
return true;
|
|
833
|
+
};
|
|
834
|
+
function useExcelGrid(options) {
|
|
835
|
+
const { data, enableSorting = true, enableFiltering = true } = options;
|
|
836
|
+
const [sorting, setSorting] = (0, import_react4.useState)([]);
|
|
837
|
+
const [columnFilters, setColumnFilters] = (0, import_react4.useState)([]);
|
|
838
|
+
const [columnVisibility, setColumnVisibility] = (0, import_react4.useState)({});
|
|
839
|
+
const [globalFilter, setGlobalFilter] = (0, import_react4.useState)("");
|
|
840
|
+
const [columnStatsCache, setColumnStatsCache] = (0, import_react4.useState)({});
|
|
841
|
+
const dataSignature = (0, import_react4.useMemo)(
|
|
842
|
+
() => `${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
843
|
+
[data]
|
|
598
844
|
);
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
return
|
|
603
|
-
}, [data
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
845
|
+
const columnKeys = (0, import_react4.useMemo)(() => {
|
|
846
|
+
if (data.length === 0)
|
|
847
|
+
return [];
|
|
848
|
+
return Object.keys(data[0]);
|
|
849
|
+
}, [data]);
|
|
850
|
+
const getColumnStats = (0, import_react4.useCallback)(
|
|
851
|
+
(columnKey) => {
|
|
852
|
+
const cacheKey = `${columnKey}-${dataSignature}`;
|
|
853
|
+
if (!columnStatsCache[cacheKey]) {
|
|
854
|
+
const stats = (0, import_tinypivot_core2.getColumnUniqueValues)(data, columnKey);
|
|
855
|
+
setColumnStatsCache((prev) => ({ ...prev, [cacheKey]: stats }));
|
|
856
|
+
return stats;
|
|
857
|
+
}
|
|
858
|
+
return columnStatsCache[cacheKey];
|
|
859
|
+
},
|
|
860
|
+
[data, columnStatsCache, dataSignature]
|
|
608
861
|
);
|
|
609
|
-
const
|
|
862
|
+
const clearStatsCache = (0, import_react4.useCallback)(() => {
|
|
863
|
+
setColumnStatsCache({});
|
|
864
|
+
}, []);
|
|
865
|
+
(0, import_react4.useEffect)(() => {
|
|
866
|
+
clearStatsCache();
|
|
867
|
+
}, [dataSignature, clearStatsCache]);
|
|
868
|
+
const columnDefs = (0, import_react4.useMemo)(() => {
|
|
869
|
+
return columnKeys.map((key) => {
|
|
870
|
+
const stats = getColumnStats(key);
|
|
871
|
+
return {
|
|
872
|
+
id: key,
|
|
873
|
+
accessorKey: key,
|
|
874
|
+
header: key,
|
|
875
|
+
cell: (info) => (0, import_tinypivot_core2.formatCellValue)(info.getValue(), stats.type),
|
|
876
|
+
filterFn: multiSelectFilter,
|
|
877
|
+
meta: {
|
|
878
|
+
type: stats.type,
|
|
879
|
+
uniqueCount: stats.uniqueValues.length
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
});
|
|
883
|
+
}, [columnKeys, getColumnStats]);
|
|
884
|
+
const table = (0, import_react_table.useReactTable)({
|
|
885
|
+
data,
|
|
886
|
+
columns: columnDefs,
|
|
887
|
+
state: {
|
|
888
|
+
sorting,
|
|
889
|
+
columnFilters,
|
|
890
|
+
columnVisibility,
|
|
891
|
+
globalFilter
|
|
892
|
+
},
|
|
893
|
+
onSortingChange: setSorting,
|
|
894
|
+
onColumnFiltersChange: setColumnFilters,
|
|
895
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
896
|
+
onGlobalFilterChange: setGlobalFilter,
|
|
897
|
+
getCoreRowModel: (0, import_react_table.getCoreRowModel)(),
|
|
898
|
+
getSortedRowModel: enableSorting ? (0, import_react_table.getSortedRowModel)() : void 0,
|
|
899
|
+
getFilteredRowModel: enableFiltering ? (0, import_react_table.getFilteredRowModel)() : void 0,
|
|
900
|
+
filterFns: {
|
|
901
|
+
multiSelect: multiSelectFilter
|
|
902
|
+
},
|
|
903
|
+
enableSorting,
|
|
904
|
+
enableFilters: enableFiltering
|
|
905
|
+
});
|
|
906
|
+
const filteredRowCount = table.getFilteredRowModel().rows.length;
|
|
907
|
+
const totalRowCount = data.length;
|
|
908
|
+
const activeFilters = (0, import_react4.useMemo)(() => {
|
|
909
|
+
return columnFilters.map((f) => {
|
|
910
|
+
const filterValue = f.value;
|
|
911
|
+
if (filterValue && (0, import_tinypivot_core2.isNumericRange)(filterValue)) {
|
|
912
|
+
return {
|
|
913
|
+
column: f.id,
|
|
914
|
+
type: "range",
|
|
915
|
+
range: filterValue,
|
|
916
|
+
values: []
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
return {
|
|
920
|
+
column: f.id,
|
|
921
|
+
type: "values",
|
|
922
|
+
values: Array.isArray(filterValue) ? filterValue : [],
|
|
923
|
+
range: null
|
|
924
|
+
};
|
|
925
|
+
});
|
|
926
|
+
}, [columnFilters]);
|
|
927
|
+
const hasActiveFilter = (0, import_react4.useCallback)(
|
|
928
|
+
(columnId) => {
|
|
929
|
+
const column = table.getColumn(columnId);
|
|
930
|
+
if (!column)
|
|
931
|
+
return false;
|
|
932
|
+
const filterValue = column.getFilterValue();
|
|
933
|
+
if (!filterValue)
|
|
934
|
+
return false;
|
|
935
|
+
if ((0, import_tinypivot_core2.isNumericRange)(filterValue)) {
|
|
936
|
+
return filterValue.min !== null || filterValue.max !== null;
|
|
937
|
+
}
|
|
938
|
+
return Array.isArray(filterValue) && filterValue.length > 0;
|
|
939
|
+
},
|
|
940
|
+
[table]
|
|
941
|
+
);
|
|
942
|
+
const setColumnFilter = (0, import_react4.useCallback)(
|
|
943
|
+
(columnId, values) => {
|
|
944
|
+
const column = table.getColumn(columnId);
|
|
945
|
+
if (column) {
|
|
946
|
+
column.setFilterValue(values.length === 0 ? void 0 : values);
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
[table]
|
|
950
|
+
);
|
|
951
|
+
const setNumericRangeFilter = (0, import_react4.useCallback)(
|
|
952
|
+
(columnId, range) => {
|
|
953
|
+
const column = table.getColumn(columnId);
|
|
954
|
+
if (column) {
|
|
955
|
+
if (!range || range.min === null && range.max === null) {
|
|
956
|
+
column.setFilterValue(void 0);
|
|
957
|
+
} else {
|
|
958
|
+
column.setFilterValue(range);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
[table]
|
|
963
|
+
);
|
|
964
|
+
const getNumericRangeFilter = (0, import_react4.useCallback)(
|
|
965
|
+
(columnId) => {
|
|
966
|
+
const column = table.getColumn(columnId);
|
|
967
|
+
if (!column)
|
|
968
|
+
return null;
|
|
969
|
+
const filterValue = column.getFilterValue();
|
|
970
|
+
if (filterValue && (0, import_tinypivot_core2.isNumericRange)(filterValue)) {
|
|
971
|
+
return filterValue;
|
|
972
|
+
}
|
|
973
|
+
return null;
|
|
974
|
+
},
|
|
975
|
+
[table]
|
|
976
|
+
);
|
|
977
|
+
const clearAllFilters = (0, import_react4.useCallback)(() => {
|
|
978
|
+
table.resetColumnFilters();
|
|
979
|
+
setGlobalFilter("");
|
|
980
|
+
setColumnFilters([]);
|
|
981
|
+
}, [table]);
|
|
982
|
+
const getColumnFilterValues = (0, import_react4.useCallback)(
|
|
983
|
+
(columnId) => {
|
|
984
|
+
const column = table.getColumn(columnId);
|
|
985
|
+
if (!column)
|
|
986
|
+
return [];
|
|
987
|
+
const filterValue = column.getFilterValue();
|
|
988
|
+
return Array.isArray(filterValue) ? filterValue : [];
|
|
989
|
+
},
|
|
990
|
+
[table]
|
|
991
|
+
);
|
|
992
|
+
const toggleSort = (0, import_react4.useCallback)((columnId) => {
|
|
993
|
+
setSorting((prev) => {
|
|
994
|
+
const current = prev.find((s) => s.id === columnId);
|
|
995
|
+
if (!current) {
|
|
996
|
+
return [{ id: columnId, desc: false }];
|
|
997
|
+
} else if (!current.desc) {
|
|
998
|
+
return [{ id: columnId, desc: true }];
|
|
999
|
+
} else {
|
|
1000
|
+
return [];
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
}, []);
|
|
1004
|
+
const getSortDirection = (0, import_react4.useCallback)(
|
|
1005
|
+
(columnId) => {
|
|
1006
|
+
const sort = sorting.find((s) => s.id === columnId);
|
|
1007
|
+
if (!sort)
|
|
1008
|
+
return null;
|
|
1009
|
+
return sort.desc ? "desc" : "asc";
|
|
1010
|
+
},
|
|
1011
|
+
[sorting]
|
|
1012
|
+
);
|
|
1013
|
+
return {
|
|
1014
|
+
// Table instance
|
|
1015
|
+
table,
|
|
1016
|
+
// State
|
|
1017
|
+
sorting,
|
|
1018
|
+
columnFilters,
|
|
1019
|
+
columnVisibility,
|
|
1020
|
+
globalFilter,
|
|
1021
|
+
columnKeys,
|
|
1022
|
+
setSorting,
|
|
1023
|
+
setColumnFilters,
|
|
1024
|
+
setGlobalFilter,
|
|
1025
|
+
// Computed
|
|
1026
|
+
filteredRowCount,
|
|
1027
|
+
totalRowCount,
|
|
1028
|
+
activeFilters,
|
|
1029
|
+
// Methods
|
|
1030
|
+
getColumnStats,
|
|
1031
|
+
clearStatsCache,
|
|
1032
|
+
hasActiveFilter,
|
|
1033
|
+
setColumnFilter,
|
|
1034
|
+
getColumnFilterValues,
|
|
1035
|
+
clearAllFilters,
|
|
1036
|
+
toggleSort,
|
|
1037
|
+
getSortDirection,
|
|
1038
|
+
// Numeric range filters
|
|
1039
|
+
setNumericRangeFilter,
|
|
1040
|
+
getNumericRangeFilter
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// src/hooks/useGridFeatures.ts
|
|
1045
|
+
var import_tinypivot_core3 = require("@smallwebco/tinypivot-core");
|
|
1046
|
+
var import_react5 = require("react");
|
|
1047
|
+
function exportToCSV(data, columns, options) {
|
|
1048
|
+
(0, import_tinypivot_core3.exportToCSV)(data, columns, options);
|
|
1049
|
+
}
|
|
1050
|
+
function exportPivotToCSV(pivotData, rowFields, columnFields, valueFields, options) {
|
|
1051
|
+
(0, import_tinypivot_core3.exportPivotToCSV)(pivotData, rowFields, columnFields, valueFields, options);
|
|
1052
|
+
}
|
|
1053
|
+
function copyToClipboard(text, onSuccess, onError) {
|
|
1054
|
+
(0, import_tinypivot_core3.copyToClipboard)(text, onSuccess, onError);
|
|
1055
|
+
}
|
|
1056
|
+
function formatSelectionForClipboard(rows, columns, selectionBounds) {
|
|
1057
|
+
return (0, import_tinypivot_core3.formatSelectionForClipboard)(rows, columns, selectionBounds);
|
|
1058
|
+
}
|
|
1059
|
+
function usePagination(data, options = {}) {
|
|
1060
|
+
const [pageSize, setPageSize] = (0, import_react5.useState)(options.pageSize ?? 50);
|
|
1061
|
+
const [currentPage, setCurrentPage] = (0, import_react5.useState)(options.currentPage ?? 1);
|
|
1062
|
+
const totalPages = (0, import_react5.useMemo)(
|
|
1063
|
+
() => Math.max(1, Math.ceil(data.length / pageSize)),
|
|
1064
|
+
[data.length, pageSize]
|
|
1065
|
+
);
|
|
1066
|
+
const paginatedData = (0, import_react5.useMemo)(() => {
|
|
1067
|
+
const start = (currentPage - 1) * pageSize;
|
|
1068
|
+
const end = start + pageSize;
|
|
1069
|
+
return data.slice(start, end);
|
|
1070
|
+
}, [data, currentPage, pageSize]);
|
|
1071
|
+
const startIndex = (0, import_react5.useMemo)(() => (currentPage - 1) * pageSize + 1, [currentPage, pageSize]);
|
|
1072
|
+
const endIndex = (0, import_react5.useMemo)(
|
|
1073
|
+
() => Math.min(currentPage * pageSize, data.length),
|
|
1074
|
+
[currentPage, pageSize, data.length]
|
|
1075
|
+
);
|
|
1076
|
+
const goToPage = (0, import_react5.useCallback)(
|
|
610
1077
|
(page) => {
|
|
611
1078
|
setCurrentPage(Math.max(1, Math.min(page, totalPages)));
|
|
612
1079
|
},
|
|
613
1080
|
[totalPages]
|
|
614
1081
|
);
|
|
615
|
-
const nextPage = (0,
|
|
1082
|
+
const nextPage = (0, import_react5.useCallback)(() => {
|
|
616
1083
|
if (currentPage < totalPages) {
|
|
617
1084
|
setCurrentPage((prev) => prev + 1);
|
|
618
1085
|
}
|
|
619
1086
|
}, [currentPage, totalPages]);
|
|
620
|
-
const prevPage = (0,
|
|
1087
|
+
const prevPage = (0, import_react5.useCallback)(() => {
|
|
621
1088
|
if (currentPage > 1) {
|
|
622
1089
|
setCurrentPage((prev) => prev - 1);
|
|
623
1090
|
}
|
|
624
1091
|
}, [currentPage]);
|
|
625
|
-
const firstPage = (0,
|
|
1092
|
+
const firstPage = (0, import_react5.useCallback)(() => {
|
|
626
1093
|
setCurrentPage(1);
|
|
627
1094
|
}, []);
|
|
628
|
-
const lastPage = (0,
|
|
1095
|
+
const lastPage = (0, import_react5.useCallback)(() => {
|
|
629
1096
|
setCurrentPage(totalPages);
|
|
630
1097
|
}, [totalPages]);
|
|
631
|
-
const updatePageSize = (0,
|
|
1098
|
+
const updatePageSize = (0, import_react5.useCallback)((size) => {
|
|
632
1099
|
setPageSize(size);
|
|
633
1100
|
setCurrentPage(1);
|
|
634
1101
|
}, []);
|
|
@@ -648,9 +1115,9 @@ function usePagination(data, options = {}) {
|
|
|
648
1115
|
};
|
|
649
1116
|
}
|
|
650
1117
|
function useGlobalSearch(data, columns) {
|
|
651
|
-
const [searchTerm, setSearchTerm] = (0,
|
|
652
|
-
const [caseSensitive, setCaseSensitive] = (0,
|
|
653
|
-
const filteredData = (0,
|
|
1118
|
+
const [searchTerm, setSearchTerm] = (0, import_react5.useState)("");
|
|
1119
|
+
const [caseSensitive, setCaseSensitive] = (0, import_react5.useState)(false);
|
|
1120
|
+
const filteredData = (0, import_react5.useMemo)(() => {
|
|
654
1121
|
if (!searchTerm.trim()) {
|
|
655
1122
|
return data;
|
|
656
1123
|
}
|
|
@@ -658,906 +1125,470 @@ function useGlobalSearch(data, columns) {
|
|
|
658
1125
|
return data.filter((row) => {
|
|
659
1126
|
for (const col of columns) {
|
|
660
1127
|
const value = row[col];
|
|
661
|
-
if (value === null || value === void 0)
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
return false;
|
|
668
|
-
});
|
|
669
|
-
}, [data, columns, searchTerm, caseSensitive]);
|
|
670
|
-
const clearSearch = (0, import_react4.useCallback)(() => {
|
|
671
|
-
setSearchTerm("");
|
|
672
|
-
}, []);
|
|
673
|
-
return {
|
|
674
|
-
searchTerm,
|
|
675
|
-
setSearchTerm,
|
|
676
|
-
caseSensitive,
|
|
677
|
-
setCaseSensitive,
|
|
678
|
-
filteredData,
|
|
679
|
-
clearSearch
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
function useRowSelection(data) {
|
|
683
|
-
const [selectedRowIndices, setSelectedRowIndices] = (0, import_react4.useState)(/* @__PURE__ */ new Set());
|
|
684
|
-
const selectedRows = (0, import_react4.useMemo)(() => {
|
|
685
|
-
return Array.from(selectedRowIndices).sort((a, b) => a - b).map((idx) => data[idx]).filter(Boolean);
|
|
686
|
-
}, [data, selectedRowIndices]);
|
|
687
|
-
const allSelected = (0, import_react4.useMemo)(() => {
|
|
688
|
-
return data.length > 0 && selectedRowIndices.size === data.length;
|
|
689
|
-
}, [data.length, selectedRowIndices.size]);
|
|
690
|
-
const someSelected = (0, import_react4.useMemo)(() => {
|
|
691
|
-
return selectedRowIndices.size > 0 && selectedRowIndices.size < data.length;
|
|
692
|
-
}, [data.length, selectedRowIndices.size]);
|
|
693
|
-
const toggleRow = (0, import_react4.useCallback)((index) => {
|
|
694
|
-
setSelectedRowIndices((prev) => {
|
|
695
|
-
const next = new Set(prev);
|
|
696
|
-
if (next.has(index)) {
|
|
697
|
-
next.delete(index);
|
|
698
|
-
} else {
|
|
699
|
-
next.add(index);
|
|
700
|
-
}
|
|
701
|
-
return next;
|
|
702
|
-
});
|
|
703
|
-
}, []);
|
|
704
|
-
const selectRow = (0, import_react4.useCallback)((index) => {
|
|
705
|
-
setSelectedRowIndices((prev) => /* @__PURE__ */ new Set([...prev, index]));
|
|
706
|
-
}, []);
|
|
707
|
-
const deselectRow = (0, import_react4.useCallback)((index) => {
|
|
708
|
-
setSelectedRowIndices((prev) => {
|
|
709
|
-
const next = new Set(prev);
|
|
710
|
-
next.delete(index);
|
|
711
|
-
return next;
|
|
712
|
-
});
|
|
713
|
-
}, []);
|
|
714
|
-
const selectAll = (0, import_react4.useCallback)(() => {
|
|
715
|
-
setSelectedRowIndices(new Set(data.map((_, idx) => idx)));
|
|
716
|
-
}, [data]);
|
|
717
|
-
const deselectAll = (0, import_react4.useCallback)(() => {
|
|
718
|
-
setSelectedRowIndices(/* @__PURE__ */ new Set());
|
|
719
|
-
}, []);
|
|
720
|
-
const toggleAll = (0, import_react4.useCallback)(() => {
|
|
721
|
-
if (allSelected) {
|
|
722
|
-
deselectAll();
|
|
723
|
-
} else {
|
|
724
|
-
selectAll();
|
|
725
|
-
}
|
|
726
|
-
}, [allSelected, selectAll, deselectAll]);
|
|
727
|
-
const isSelected = (0, import_react4.useCallback)(
|
|
728
|
-
(index) => {
|
|
729
|
-
return selectedRowIndices.has(index);
|
|
730
|
-
},
|
|
731
|
-
[selectedRowIndices]
|
|
732
|
-
);
|
|
733
|
-
const selectRange = (0, import_react4.useCallback)((startIndex, endIndex) => {
|
|
734
|
-
const min = Math.min(startIndex, endIndex);
|
|
735
|
-
const max = Math.max(startIndex, endIndex);
|
|
736
|
-
setSelectedRowIndices((prev) => {
|
|
737
|
-
const next = new Set(prev);
|
|
738
|
-
for (let i = min; i <= max; i++) {
|
|
739
|
-
next.add(i);
|
|
740
|
-
}
|
|
741
|
-
return next;
|
|
742
|
-
});
|
|
743
|
-
}, []);
|
|
744
|
-
return {
|
|
745
|
-
selectedRowIndices,
|
|
746
|
-
selectedRows,
|
|
747
|
-
allSelected,
|
|
748
|
-
someSelected,
|
|
749
|
-
toggleRow,
|
|
750
|
-
selectRow,
|
|
751
|
-
deselectRow,
|
|
752
|
-
selectAll,
|
|
753
|
-
deselectAll,
|
|
754
|
-
toggleAll,
|
|
755
|
-
isSelected,
|
|
756
|
-
selectRange
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
function useColumnResize(initialWidths, minWidth = 60, maxWidth = 600) {
|
|
760
|
-
const [columnWidths, setColumnWidths] = (0, import_react4.useState)({ ...initialWidths });
|
|
761
|
-
const [isResizing, setIsResizing] = (0, import_react4.useState)(false);
|
|
762
|
-
const [resizingColumn, setResizingColumn] = (0, import_react4.useState)(null);
|
|
763
|
-
const startResize = (0, import_react4.useCallback)(
|
|
764
|
-
(columnId, event) => {
|
|
765
|
-
setIsResizing(true);
|
|
766
|
-
setResizingColumn(columnId);
|
|
767
|
-
const startX = event.clientX;
|
|
768
|
-
const startWidth = columnWidths[columnId] || 150;
|
|
769
|
-
const handleMouseMove = (e) => {
|
|
770
|
-
const diff = e.clientX - startX;
|
|
771
|
-
const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + diff));
|
|
772
|
-
setColumnWidths((prev) => ({
|
|
773
|
-
...prev,
|
|
774
|
-
[columnId]: newWidth
|
|
775
|
-
}));
|
|
776
|
-
};
|
|
777
|
-
const handleMouseUp = () => {
|
|
778
|
-
setIsResizing(false);
|
|
779
|
-
setResizingColumn(null);
|
|
780
|
-
document.removeEventListener("mousemove", handleMouseMove);
|
|
781
|
-
document.removeEventListener("mouseup", handleMouseUp);
|
|
782
|
-
};
|
|
783
|
-
document.addEventListener("mousemove", handleMouseMove);
|
|
784
|
-
document.addEventListener("mouseup", handleMouseUp);
|
|
785
|
-
},
|
|
786
|
-
[columnWidths, minWidth, maxWidth]
|
|
787
|
-
);
|
|
788
|
-
const resetColumnWidth = (0, import_react4.useCallback)(
|
|
789
|
-
(columnId) => {
|
|
790
|
-
if (initialWidths[columnId]) {
|
|
791
|
-
setColumnWidths((prev) => ({
|
|
792
|
-
...prev,
|
|
793
|
-
[columnId]: initialWidths[columnId]
|
|
794
|
-
}));
|
|
795
|
-
}
|
|
796
|
-
},
|
|
797
|
-
[initialWidths]
|
|
798
|
-
);
|
|
799
|
-
const resetAllWidths = (0, import_react4.useCallback)(() => {
|
|
800
|
-
setColumnWidths({ ...initialWidths });
|
|
801
|
-
}, [initialWidths]);
|
|
802
|
-
return {
|
|
803
|
-
columnWidths,
|
|
804
|
-
setColumnWidths,
|
|
805
|
-
isResizing,
|
|
806
|
-
resizingColumn,
|
|
807
|
-
startResize,
|
|
808
|
-
resetColumnWidth,
|
|
809
|
-
resetAllWidths
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// src/components/ColumnFilter.tsx
|
|
814
|
-
var import_react6 = require("react");
|
|
815
|
-
|
|
816
|
-
// src/components/NumericRangeFilter.tsx
|
|
817
|
-
var import_react5 = require("react");
|
|
818
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
819
|
-
function NumericRangeFilter({
|
|
820
|
-
dataMin,
|
|
821
|
-
dataMax,
|
|
822
|
-
currentRange,
|
|
823
|
-
onChange
|
|
824
|
-
}) {
|
|
825
|
-
const [localMin, setLocalMin] = (0, import_react5.useState)(currentRange?.min ?? null);
|
|
826
|
-
const [localMax, setLocalMax] = (0, import_react5.useState)(currentRange?.max ?? null);
|
|
827
|
-
const step = (0, import_react5.useMemo)(() => {
|
|
828
|
-
const range = dataMax - dataMin;
|
|
829
|
-
if (range === 0) return 1;
|
|
830
|
-
if (range <= 1) return 0.01;
|
|
831
|
-
if (range <= 10) return 0.1;
|
|
832
|
-
if (range <= 100) return 1;
|
|
833
|
-
if (range <= 1e3) return 10;
|
|
834
|
-
return Math.pow(10, Math.floor(Math.log10(range)) - 2);
|
|
835
|
-
}, [dataMin, dataMax]);
|
|
836
|
-
const formatValue = (0, import_react5.useCallback)((val) => {
|
|
837
|
-
if (val === null) return "";
|
|
838
|
-
if (Number.isInteger(val)) return val.toLocaleString();
|
|
839
|
-
return val.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
840
|
-
}, []);
|
|
841
|
-
const isFilterActive = localMin !== null || localMax !== null;
|
|
842
|
-
const minPercent = (0, import_react5.useMemo)(() => {
|
|
843
|
-
if (localMin === null || dataMax === dataMin) return 0;
|
|
844
|
-
return (localMin - dataMin) / (dataMax - dataMin) * 100;
|
|
845
|
-
}, [localMin, dataMin, dataMax]);
|
|
846
|
-
const maxPercent = (0, import_react5.useMemo)(() => {
|
|
847
|
-
if (localMax === null || dataMax === dataMin) return 100;
|
|
848
|
-
return (localMax - dataMin) / (dataMax - dataMin) * 100;
|
|
849
|
-
}, [localMax, dataMin, dataMax]);
|
|
850
|
-
const handleMinSlider = (0, import_react5.useCallback)((event) => {
|
|
851
|
-
const value = Number.parseFloat(event.target.value);
|
|
852
|
-
setLocalMin((prev) => {
|
|
853
|
-
if (localMax !== null && value > localMax) {
|
|
854
|
-
return localMax;
|
|
855
|
-
}
|
|
856
|
-
return value;
|
|
857
|
-
});
|
|
858
|
-
}, [localMax]);
|
|
859
|
-
const handleMaxSlider = (0, import_react5.useCallback)((event) => {
|
|
860
|
-
const value = Number.parseFloat(event.target.value);
|
|
861
|
-
setLocalMax((prev) => {
|
|
862
|
-
if (localMin !== null && value < localMin) {
|
|
863
|
-
return localMin;
|
|
864
|
-
}
|
|
865
|
-
return value;
|
|
866
|
-
});
|
|
867
|
-
}, [localMin]);
|
|
868
|
-
const handleSliderChange = (0, import_react5.useCallback)(() => {
|
|
869
|
-
if (localMin === null && localMax === null) {
|
|
870
|
-
onChange(null);
|
|
871
|
-
} else {
|
|
872
|
-
onChange({ min: localMin, max: localMax });
|
|
873
|
-
}
|
|
874
|
-
}, [localMin, localMax, onChange]);
|
|
875
|
-
const handleMinInput = (0, import_react5.useCallback)((event) => {
|
|
876
|
-
const value = event.target.value === "" ? null : Number.parseFloat(event.target.value);
|
|
877
|
-
if (value !== null && !Number.isNaN(value)) {
|
|
878
|
-
setLocalMin(Math.max(dataMin, Math.min(value, localMax ?? dataMax)));
|
|
879
|
-
} else if (value === null) {
|
|
880
|
-
setLocalMin(null);
|
|
881
|
-
}
|
|
882
|
-
}, [dataMin, dataMax, localMax]);
|
|
883
|
-
const handleMaxInput = (0, import_react5.useCallback)((event) => {
|
|
884
|
-
const value = event.target.value === "" ? null : Number.parseFloat(event.target.value);
|
|
885
|
-
if (value !== null && !Number.isNaN(value)) {
|
|
886
|
-
setLocalMax(Math.min(dataMax, Math.max(value, localMin ?? dataMin)));
|
|
887
|
-
} else if (value === null) {
|
|
888
|
-
setLocalMax(null);
|
|
889
|
-
}
|
|
890
|
-
}, [dataMin, dataMax, localMin]);
|
|
891
|
-
const handleInputBlur = (0, import_react5.useCallback)(() => {
|
|
892
|
-
if (localMin === null && localMax === null) {
|
|
893
|
-
onChange(null);
|
|
894
|
-
} else {
|
|
895
|
-
onChange({ min: localMin, max: localMax });
|
|
896
|
-
}
|
|
897
|
-
}, [localMin, localMax, onChange]);
|
|
898
|
-
const clearFilter = (0, import_react5.useCallback)(() => {
|
|
899
|
-
setLocalMin(null);
|
|
900
|
-
setLocalMax(null);
|
|
901
|
-
onChange(null);
|
|
902
|
-
}, [onChange]);
|
|
903
|
-
const setFullRange = (0, import_react5.useCallback)(() => {
|
|
904
|
-
setLocalMin(dataMin);
|
|
905
|
-
setLocalMax(dataMax);
|
|
906
|
-
onChange({ min: dataMin, max: dataMax });
|
|
907
|
-
}, [dataMin, dataMax, onChange]);
|
|
908
|
-
(0, import_react5.useEffect)(() => {
|
|
909
|
-
setLocalMin(currentRange?.min ?? null);
|
|
910
|
-
setLocalMax(currentRange?.max ?? null);
|
|
911
|
-
}, [currentRange]);
|
|
912
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-range-filter", children: [
|
|
913
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-range-info", children: [
|
|
914
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "vpg-range-label", children: "Data range:" }),
|
|
915
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "vpg-range-bounds", children: [
|
|
916
|
-
formatValue(dataMin),
|
|
917
|
-
" \u2013 ",
|
|
918
|
-
formatValue(dataMax)
|
|
919
|
-
] })
|
|
920
|
-
] }),
|
|
921
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-slider-container", children: [
|
|
922
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vpg-slider-track", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
923
|
-
"div",
|
|
924
|
-
{
|
|
925
|
-
className: "vpg-slider-fill",
|
|
926
|
-
style: {
|
|
927
|
-
left: `${minPercent}%`,
|
|
928
|
-
right: `${100 - maxPercent}%`
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
) }),
|
|
932
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
933
|
-
"input",
|
|
934
|
-
{
|
|
935
|
-
type: "range",
|
|
936
|
-
className: "vpg-slider vpg-slider-min",
|
|
937
|
-
min: dataMin,
|
|
938
|
-
max: dataMax,
|
|
939
|
-
step,
|
|
940
|
-
value: localMin ?? dataMin,
|
|
941
|
-
onChange: handleMinSlider,
|
|
942
|
-
onMouseUp: handleSliderChange,
|
|
943
|
-
onTouchEnd: handleSliderChange
|
|
944
|
-
}
|
|
945
|
-
),
|
|
946
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
947
|
-
"input",
|
|
948
|
-
{
|
|
949
|
-
type: "range",
|
|
950
|
-
className: "vpg-slider vpg-slider-max",
|
|
951
|
-
min: dataMin,
|
|
952
|
-
max: dataMax,
|
|
953
|
-
step,
|
|
954
|
-
value: localMax ?? dataMax,
|
|
955
|
-
onChange: handleMaxSlider,
|
|
956
|
-
onMouseUp: handleSliderChange,
|
|
957
|
-
onTouchEnd: handleSliderChange
|
|
958
|
-
}
|
|
959
|
-
)
|
|
960
|
-
] }),
|
|
961
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-range-inputs", children: [
|
|
962
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-input-group", children: [
|
|
963
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "vpg-input-label", children: "Min" }),
|
|
964
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
965
|
-
"input",
|
|
966
|
-
{
|
|
967
|
-
type: "number",
|
|
968
|
-
className: "vpg-range-input",
|
|
969
|
-
placeholder: formatValue(dataMin),
|
|
970
|
-
value: localMin ?? "",
|
|
971
|
-
step,
|
|
972
|
-
onChange: handleMinInput,
|
|
973
|
-
onBlur: handleInputBlur
|
|
974
|
-
}
|
|
975
|
-
)
|
|
976
|
-
] }),
|
|
977
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "vpg-input-separator", children: "to" }),
|
|
978
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-input-group", children: [
|
|
979
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "vpg-input-label", children: "Max" }),
|
|
980
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
981
|
-
"input",
|
|
982
|
-
{
|
|
983
|
-
type: "number",
|
|
984
|
-
className: "vpg-range-input",
|
|
985
|
-
placeholder: formatValue(dataMax),
|
|
986
|
-
value: localMax ?? "",
|
|
987
|
-
step,
|
|
988
|
-
onChange: handleMaxInput,
|
|
989
|
-
onBlur: handleInputBlur
|
|
990
|
-
}
|
|
991
|
-
)
|
|
992
|
-
] })
|
|
993
|
-
] }),
|
|
994
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-range-actions", children: [
|
|
995
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
996
|
-
"button",
|
|
997
|
-
{
|
|
998
|
-
className: "vpg-range-btn",
|
|
999
|
-
disabled: !isFilterActive,
|
|
1000
|
-
onClick: clearFilter,
|
|
1001
|
-
children: [
|
|
1002
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1003
|
-
"path",
|
|
1004
|
-
{
|
|
1005
|
-
strokeLinecap: "round",
|
|
1006
|
-
strokeLinejoin: "round",
|
|
1007
|
-
strokeWidth: 2,
|
|
1008
|
-
d: "M6 18L18 6M6 6l12 12"
|
|
1009
|
-
}
|
|
1010
|
-
) }),
|
|
1011
|
-
"Clear"
|
|
1012
|
-
]
|
|
1013
|
-
}
|
|
1014
|
-
),
|
|
1015
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "vpg-range-btn", onClick: setFullRange, children: [
|
|
1016
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1017
|
-
"path",
|
|
1018
|
-
{
|
|
1019
|
-
strokeLinecap: "round",
|
|
1020
|
-
strokeLinejoin: "round",
|
|
1021
|
-
strokeWidth: 2,
|
|
1022
|
-
d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"
|
|
1023
|
-
}
|
|
1024
|
-
) }),
|
|
1025
|
-
"Full Range"
|
|
1026
|
-
] })
|
|
1027
|
-
] }),
|
|
1028
|
-
isFilterActive && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vpg-filter-summary", children: [
|
|
1029
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1030
|
-
"path",
|
|
1031
|
-
{
|
|
1032
|
-
strokeLinecap: "round",
|
|
1033
|
-
strokeLinejoin: "round",
|
|
1034
|
-
strokeWidth: 2,
|
|
1035
|
-
d: "M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
|
|
1036
|
-
}
|
|
1037
|
-
) }),
|
|
1038
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
1039
|
-
"Showing values",
|
|
1040
|
-
" ",
|
|
1041
|
-
localMin !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
|
|
1042
|
-
"\u2265 ",
|
|
1043
|
-
formatValue(localMin)
|
|
1044
|
-
] }),
|
|
1045
|
-
localMin !== null && localMax !== null && " and ",
|
|
1046
|
-
localMax !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
|
|
1047
|
-
"\u2264 ",
|
|
1048
|
-
formatValue(localMax)
|
|
1049
|
-
] })
|
|
1050
|
-
] })
|
|
1051
|
-
] })
|
|
1052
|
-
] });
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
// src/components/ColumnFilter.tsx
|
|
1056
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1057
|
-
function ColumnFilter({
|
|
1058
|
-
columnName,
|
|
1059
|
-
stats,
|
|
1060
|
-
selectedValues,
|
|
1061
|
-
sortDirection,
|
|
1062
|
-
numericRange,
|
|
1063
|
-
onFilter,
|
|
1064
|
-
onSort,
|
|
1065
|
-
onClose,
|
|
1066
|
-
onRangeFilter
|
|
1067
|
-
}) {
|
|
1068
|
-
const [searchQuery, setSearchQuery] = (0, import_react6.useState)("");
|
|
1069
|
-
const [localSelected, setLocalSelected] = (0, import_react6.useState)(new Set(selectedValues));
|
|
1070
|
-
const dropdownRef = (0, import_react6.useRef)(null);
|
|
1071
|
-
const searchInputRef = (0, import_react6.useRef)(null);
|
|
1072
|
-
const isNumericColumn = stats.type === "number" && stats.numericMin !== void 0 && stats.numericMax !== void 0;
|
|
1073
|
-
const [filterMode, setFilterMode] = (0, import_react6.useState)(numericRange ? "range" : "values");
|
|
1074
|
-
const [localRange, setLocalRange] = (0, import_react6.useState)(numericRange ?? null);
|
|
1075
|
-
const hasBlankValues = stats.nullCount > 0;
|
|
1076
|
-
const filteredValues = (0, import_react6.useMemo)(() => {
|
|
1077
|
-
const values = stats.uniqueValues;
|
|
1078
|
-
if (!searchQuery) return values;
|
|
1079
|
-
const query = searchQuery.toLowerCase();
|
|
1080
|
-
return values.filter((v) => v.toLowerCase().includes(query));
|
|
1081
|
-
}, [stats.uniqueValues, searchQuery]);
|
|
1082
|
-
const allValues = (0, import_react6.useMemo)(() => {
|
|
1083
|
-
const values = [...filteredValues];
|
|
1084
|
-
if (hasBlankValues && (!searchQuery || "(blank)".includes(searchQuery.toLowerCase()))) {
|
|
1085
|
-
values.unshift("(blank)");
|
|
1086
|
-
}
|
|
1087
|
-
return values;
|
|
1088
|
-
}, [filteredValues, hasBlankValues, searchQuery]);
|
|
1089
|
-
const isAllSelected = (0, import_react6.useMemo)(
|
|
1090
|
-
() => allValues.every((v) => localSelected.has(v)),
|
|
1091
|
-
[allValues, localSelected]
|
|
1092
|
-
);
|
|
1093
|
-
const toggleValue = (0, import_react6.useCallback)((value) => {
|
|
1094
|
-
setLocalSelected((prev) => {
|
|
1095
|
-
const next = new Set(prev);
|
|
1096
|
-
if (next.has(value)) {
|
|
1097
|
-
next.delete(value);
|
|
1098
|
-
} else {
|
|
1099
|
-
next.add(value);
|
|
1100
|
-
}
|
|
1101
|
-
return next;
|
|
1102
|
-
});
|
|
1103
|
-
}, []);
|
|
1104
|
-
const selectAll = (0, import_react6.useCallback)(() => {
|
|
1105
|
-
setLocalSelected((prev) => {
|
|
1106
|
-
const next = new Set(prev);
|
|
1107
|
-
for (const value of allValues) {
|
|
1108
|
-
next.add(value);
|
|
1109
|
-
}
|
|
1110
|
-
return next;
|
|
1111
|
-
});
|
|
1112
|
-
}, [allValues]);
|
|
1113
|
-
const clearAll = (0, import_react6.useCallback)(() => {
|
|
1114
|
-
setLocalSelected(/* @__PURE__ */ new Set());
|
|
1115
|
-
}, []);
|
|
1116
|
-
const applyFilter = (0, import_react6.useCallback)(() => {
|
|
1117
|
-
if (localSelected.size === 0) {
|
|
1118
|
-
onFilter([]);
|
|
1119
|
-
} else {
|
|
1120
|
-
onFilter(Array.from(localSelected));
|
|
1121
|
-
}
|
|
1122
|
-
onClose();
|
|
1123
|
-
}, [localSelected, onFilter, onClose]);
|
|
1124
|
-
const sortAscending = (0, import_react6.useCallback)(() => {
|
|
1125
|
-
onSort(sortDirection === "asc" ? null : "asc");
|
|
1126
|
-
}, [sortDirection, onSort]);
|
|
1127
|
-
const sortDescending = (0, import_react6.useCallback)(() => {
|
|
1128
|
-
onSort(sortDirection === "desc" ? null : "desc");
|
|
1129
|
-
}, [sortDirection, onSort]);
|
|
1130
|
-
const clearFilter = (0, import_react6.useCallback)(() => {
|
|
1131
|
-
setLocalSelected(/* @__PURE__ */ new Set());
|
|
1132
|
-
onFilter([]);
|
|
1133
|
-
onClose();
|
|
1134
|
-
}, [onFilter, onClose]);
|
|
1135
|
-
const handleRangeChange = (0, import_react6.useCallback)((range) => {
|
|
1136
|
-
setLocalRange(range);
|
|
1137
|
-
}, []);
|
|
1138
|
-
const applyRangeFilter = (0, import_react6.useCallback)(() => {
|
|
1139
|
-
onRangeFilter?.(localRange);
|
|
1140
|
-
onClose();
|
|
1141
|
-
}, [localRange, onRangeFilter, onClose]);
|
|
1142
|
-
const clearRangeFilter = (0, import_react6.useCallback)(() => {
|
|
1143
|
-
setLocalRange(null);
|
|
1144
|
-
onRangeFilter?.(null);
|
|
1145
|
-
onClose();
|
|
1146
|
-
}, [onRangeFilter, onClose]);
|
|
1147
|
-
(0, import_react6.useEffect)(() => {
|
|
1148
|
-
const handleClickOutside = (event) => {
|
|
1149
|
-
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
1150
|
-
onClose();
|
|
1151
|
-
}
|
|
1152
|
-
};
|
|
1153
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
1154
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
1155
|
-
}, [onClose]);
|
|
1156
|
-
(0, import_react6.useEffect)(() => {
|
|
1157
|
-
const handleKeydown = (event) => {
|
|
1158
|
-
if (event.key === "Escape") {
|
|
1159
|
-
onClose();
|
|
1160
|
-
} else if (event.key === "Enter" && event.ctrlKey) {
|
|
1161
|
-
applyFilter();
|
|
1162
|
-
}
|
|
1163
|
-
};
|
|
1164
|
-
document.addEventListener("keydown", handleKeydown);
|
|
1165
|
-
return () => document.removeEventListener("keydown", handleKeydown);
|
|
1166
|
-
}, [onClose, applyFilter]);
|
|
1167
|
-
(0, import_react6.useEffect)(() => {
|
|
1168
|
-
searchInputRef.current?.focus();
|
|
1169
|
-
}, []);
|
|
1170
|
-
(0, import_react6.useEffect)(() => {
|
|
1171
|
-
setLocalSelected(new Set(selectedValues));
|
|
1172
|
-
}, [selectedValues]);
|
|
1173
|
-
(0, import_react6.useEffect)(() => {
|
|
1174
|
-
setLocalRange(numericRange ?? null);
|
|
1175
|
-
if (numericRange) {
|
|
1176
|
-
setFilterMode("range");
|
|
1177
|
-
}
|
|
1178
|
-
}, [numericRange]);
|
|
1179
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: dropdownRef, className: "vpg-filter-dropdown", children: [
|
|
1180
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-filter-header", children: [
|
|
1181
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "vpg-filter-title", children: columnName }),
|
|
1182
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "vpg-filter-count", children: [
|
|
1183
|
-
stats.uniqueValues.length.toLocaleString(),
|
|
1184
|
-
" unique"
|
|
1185
|
-
] })
|
|
1186
|
-
] }),
|
|
1187
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-sort-controls", children: [
|
|
1188
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1189
|
-
"button",
|
|
1190
|
-
{
|
|
1191
|
-
className: `vpg-sort-btn ${sortDirection === "asc" ? "active" : ""}`,
|
|
1192
|
-
title: isNumericColumn ? "Sort Low to High" : "Sort A to Z",
|
|
1193
|
-
onClick: sortAscending,
|
|
1194
|
-
children: [
|
|
1195
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1196
|
-
"path",
|
|
1197
|
-
{
|
|
1198
|
-
strokeLinecap: "round",
|
|
1199
|
-
strokeLinejoin: "round",
|
|
1200
|
-
strokeWidth: 2,
|
|
1201
|
-
d: "M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12"
|
|
1202
|
-
}
|
|
1203
|
-
) }),
|
|
1204
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: isNumericColumn ? "1\u21929" : "A\u2192Z" })
|
|
1205
|
-
]
|
|
1206
|
-
}
|
|
1207
|
-
),
|
|
1208
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1209
|
-
"button",
|
|
1210
|
-
{
|
|
1211
|
-
className: `vpg-sort-btn ${sortDirection === "desc" ? "active" : ""}`,
|
|
1212
|
-
title: isNumericColumn ? "Sort High to Low" : "Sort Z to A",
|
|
1213
|
-
onClick: sortDescending,
|
|
1214
|
-
children: [
|
|
1215
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1216
|
-
"path",
|
|
1217
|
-
{
|
|
1218
|
-
strokeLinecap: "round",
|
|
1219
|
-
strokeLinejoin: "round",
|
|
1220
|
-
strokeWidth: 2,
|
|
1221
|
-
d: "M3 4h13M3 8h9m-9 4h9m5-4v12m0 0l-4-4m4 4l4-4"
|
|
1222
|
-
}
|
|
1223
|
-
) }),
|
|
1224
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: isNumericColumn ? "9\u21921" : "Z\u2192A" })
|
|
1225
|
-
]
|
|
1226
|
-
}
|
|
1227
|
-
)
|
|
1228
|
-
] }),
|
|
1229
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "vpg-divider" }),
|
|
1230
|
-
isNumericColumn && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-filter-tabs", children: [
|
|
1231
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1232
|
-
"button",
|
|
1233
|
-
{
|
|
1234
|
-
className: `vpg-tab-btn ${filterMode === "values" ? "active" : ""}`,
|
|
1235
|
-
onClick: () => setFilterMode("values"),
|
|
1236
|
-
children: [
|
|
1237
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1238
|
-
"path",
|
|
1239
|
-
{
|
|
1240
|
-
strokeLinecap: "round",
|
|
1241
|
-
strokeLinejoin: "round",
|
|
1242
|
-
strokeWidth: 2,
|
|
1243
|
-
d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
|
|
1244
|
-
}
|
|
1245
|
-
) }),
|
|
1246
|
-
"Values"
|
|
1247
|
-
]
|
|
1248
|
-
}
|
|
1249
|
-
),
|
|
1250
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1251
|
-
"button",
|
|
1252
|
-
{
|
|
1253
|
-
className: `vpg-tab-btn ${filterMode === "range" ? "active" : ""}`,
|
|
1254
|
-
onClick: () => setFilterMode("range"),
|
|
1255
|
-
children: [
|
|
1256
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1257
|
-
"path",
|
|
1258
|
-
{
|
|
1259
|
-
strokeLinecap: "round",
|
|
1260
|
-
strokeLinejoin: "round",
|
|
1261
|
-
strokeWidth: 2,
|
|
1262
|
-
d: "M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
|
|
1263
|
-
}
|
|
1264
|
-
) }),
|
|
1265
|
-
"Range"
|
|
1266
|
-
]
|
|
1267
|
-
}
|
|
1268
|
-
)
|
|
1269
|
-
] }),
|
|
1270
|
-
(!isNumericColumn || filterMode === "values") && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1271
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-search-container", children: [
|
|
1272
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-search-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1273
|
-
"path",
|
|
1274
|
-
{
|
|
1275
|
-
strokeLinecap: "round",
|
|
1276
|
-
strokeLinejoin: "round",
|
|
1277
|
-
strokeWidth: 2,
|
|
1278
|
-
d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
1279
|
-
}
|
|
1280
|
-
) }),
|
|
1281
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1282
|
-
"input",
|
|
1283
|
-
{
|
|
1284
|
-
ref: searchInputRef,
|
|
1285
|
-
type: "text",
|
|
1286
|
-
value: searchQuery,
|
|
1287
|
-
onChange: (e) => setSearchQuery(e.target.value),
|
|
1288
|
-
placeholder: "Search values...",
|
|
1289
|
-
className: "vpg-search-input"
|
|
1290
|
-
}
|
|
1291
|
-
),
|
|
1292
|
-
searchQuery && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "vpg-clear-search", onClick: () => setSearchQuery(""), children: "\xD7" })
|
|
1293
|
-
] }),
|
|
1294
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-bulk-actions", children: [
|
|
1295
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { className: "vpg-bulk-btn", onClick: selectAll, children: [
|
|
1296
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1297
|
-
"path",
|
|
1298
|
-
{
|
|
1299
|
-
strokeLinecap: "round",
|
|
1300
|
-
strokeLinejoin: "round",
|
|
1301
|
-
strokeWidth: 2,
|
|
1302
|
-
d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
1303
|
-
}
|
|
1304
|
-
) }),
|
|
1305
|
-
"Select All"
|
|
1306
|
-
] }),
|
|
1307
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { className: "vpg-bulk-btn", onClick: clearAll, children: [
|
|
1308
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1309
|
-
"path",
|
|
1310
|
-
{
|
|
1311
|
-
strokeLinecap: "round",
|
|
1312
|
-
strokeLinejoin: "round",
|
|
1313
|
-
strokeWidth: 2,
|
|
1314
|
-
d: "M6 18L18 6M6 6l12 12"
|
|
1315
|
-
}
|
|
1316
|
-
) }),
|
|
1317
|
-
"Clear All"
|
|
1318
|
-
] })
|
|
1319
|
-
] }),
|
|
1320
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-values-list", children: [
|
|
1321
|
-
allValues.map((value) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1322
|
-
"label",
|
|
1323
|
-
{
|
|
1324
|
-
className: `vpg-value-item ${localSelected.has(value) ? "selected" : ""}`,
|
|
1325
|
-
children: [
|
|
1326
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1327
|
-
"input",
|
|
1328
|
-
{
|
|
1329
|
-
type: "checkbox",
|
|
1330
|
-
checked: localSelected.has(value),
|
|
1331
|
-
onChange: () => toggleValue(value),
|
|
1332
|
-
className: "vpg-value-checkbox"
|
|
1333
|
-
}
|
|
1334
|
-
),
|
|
1335
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `vpg-value-text ${value === "(blank)" ? "vpg-blank" : ""}`, children: value })
|
|
1336
|
-
]
|
|
1337
|
-
},
|
|
1338
|
-
value
|
|
1339
|
-
)),
|
|
1340
|
-
allValues.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "vpg-no-results", children: "No matching values" })
|
|
1341
|
-
] }),
|
|
1342
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vpg-filter-footer", children: [
|
|
1343
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "vpg-btn-clear", onClick: clearFilter, children: "Clear Filter" }),
|
|
1344
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "vpg-btn-apply", onClick: applyFilter, children: "Apply" })
|
|
1345
|
-
] })
|
|
1346
|
-
] }),
|
|
1347
|
-
isNumericColumn && filterMode === "range" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1348
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1349
|
-
NumericRangeFilter,
|
|
1350
|
-
{
|
|
1351
|
-
dataMin: stats.numericMin,
|
|
1352
|
-
dataMax: stats.numericMax,
|
|
1353
|
-
currentRange: localRange,
|
|
1354
|
-
onChange: handleRangeChange
|
|
1128
|
+
if (value === null || value === void 0)
|
|
1129
|
+
continue;
|
|
1130
|
+
const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
|
|
1131
|
+
if (strValue.includes(term)) {
|
|
1132
|
+
return true;
|
|
1355
1133
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
]
|
|
1134
|
+
}
|
|
1135
|
+
return false;
|
|
1136
|
+
});
|
|
1137
|
+
}, [data, columns, searchTerm, caseSensitive]);
|
|
1138
|
+
const clearSearch = (0, import_react5.useCallback)(() => {
|
|
1139
|
+
setSearchTerm("");
|
|
1140
|
+
}, []);
|
|
1141
|
+
return {
|
|
1142
|
+
searchTerm,
|
|
1143
|
+
setSearchTerm,
|
|
1144
|
+
caseSensitive,
|
|
1145
|
+
setCaseSensitive,
|
|
1146
|
+
filteredData,
|
|
1147
|
+
clearSearch
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function useRowSelection(data) {
|
|
1151
|
+
const [selectedRowIndices, setSelectedRowIndices] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
|
|
1152
|
+
const selectedRows = (0, import_react5.useMemo)(() => {
|
|
1153
|
+
return Array.from(selectedRowIndices).sort((a, b) => a - b).map((idx) => data[idx]).filter(Boolean);
|
|
1154
|
+
}, [data, selectedRowIndices]);
|
|
1155
|
+
const allSelected = (0, import_react5.useMemo)(() => {
|
|
1156
|
+
return data.length > 0 && selectedRowIndices.size === data.length;
|
|
1157
|
+
}, [data.length, selectedRowIndices.size]);
|
|
1158
|
+
const someSelected = (0, import_react5.useMemo)(() => {
|
|
1159
|
+
return selectedRowIndices.size > 0 && selectedRowIndices.size < data.length;
|
|
1160
|
+
}, [data.length, selectedRowIndices.size]);
|
|
1161
|
+
const toggleRow = (0, import_react5.useCallback)((index) => {
|
|
1162
|
+
setSelectedRowIndices((prev) => {
|
|
1163
|
+
const next = new Set(prev);
|
|
1164
|
+
if (next.has(index)) {
|
|
1165
|
+
next.delete(index);
|
|
1166
|
+
} else {
|
|
1167
|
+
next.add(index);
|
|
1168
|
+
}
|
|
1169
|
+
return next;
|
|
1170
|
+
});
|
|
1171
|
+
}, []);
|
|
1172
|
+
const selectRow = (0, import_react5.useCallback)((index) => {
|
|
1173
|
+
setSelectedRowIndices((prev) => /* @__PURE__ */ new Set([...prev, index]));
|
|
1174
|
+
}, []);
|
|
1175
|
+
const deselectRow = (0, import_react5.useCallback)((index) => {
|
|
1176
|
+
setSelectedRowIndices((prev) => {
|
|
1177
|
+
const next = new Set(prev);
|
|
1178
|
+
next.delete(index);
|
|
1179
|
+
return next;
|
|
1180
|
+
});
|
|
1181
|
+
}, []);
|
|
1182
|
+
const selectAll = (0, import_react5.useCallback)(() => {
|
|
1183
|
+
setSelectedRowIndices(new Set(data.map((_, idx) => idx)));
|
|
1184
|
+
}, [data]);
|
|
1185
|
+
const deselectAll = (0, import_react5.useCallback)(() => {
|
|
1186
|
+
setSelectedRowIndices(/* @__PURE__ */ new Set());
|
|
1187
|
+
}, []);
|
|
1188
|
+
const toggleAll = (0, import_react5.useCallback)(() => {
|
|
1189
|
+
if (allSelected) {
|
|
1190
|
+
deselectAll();
|
|
1191
|
+
} else {
|
|
1192
|
+
selectAll();
|
|
1193
|
+
}
|
|
1194
|
+
}, [allSelected, selectAll, deselectAll]);
|
|
1195
|
+
const isSelected = (0, import_react5.useCallback)(
|
|
1196
|
+
(index) => {
|
|
1197
|
+
return selectedRowIndices.has(index);
|
|
1198
|
+
},
|
|
1199
|
+
[selectedRowIndices]
|
|
1200
|
+
);
|
|
1201
|
+
const selectRange = (0, import_react5.useCallback)((startIndex, endIndex) => {
|
|
1202
|
+
const min = Math.min(startIndex, endIndex);
|
|
1203
|
+
const max = Math.max(startIndex, endIndex);
|
|
1204
|
+
setSelectedRowIndices((prev) => {
|
|
1205
|
+
const next = new Set(prev);
|
|
1206
|
+
for (let i = min; i <= max; i++) {
|
|
1207
|
+
next.add(i);
|
|
1208
|
+
}
|
|
1209
|
+
return next;
|
|
1210
|
+
});
|
|
1211
|
+
}, []);
|
|
1212
|
+
return {
|
|
1213
|
+
selectedRowIndices,
|
|
1214
|
+
selectedRows,
|
|
1215
|
+
allSelected,
|
|
1216
|
+
someSelected,
|
|
1217
|
+
toggleRow,
|
|
1218
|
+
selectRow,
|
|
1219
|
+
deselectRow,
|
|
1220
|
+
selectAll,
|
|
1221
|
+
deselectAll,
|
|
1222
|
+
toggleAll,
|
|
1223
|
+
isSelected,
|
|
1224
|
+
selectRange
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
function useColumnResize(initialWidths, minWidth = 60, maxWidth = 600) {
|
|
1228
|
+
const [columnWidths, setColumnWidths] = (0, import_react5.useState)({ ...initialWidths });
|
|
1229
|
+
const [isResizing, setIsResizing] = (0, import_react5.useState)(false);
|
|
1230
|
+
const [resizingColumn, setResizingColumn] = (0, import_react5.useState)(null);
|
|
1231
|
+
const startResize = (0, import_react5.useCallback)(
|
|
1232
|
+
(columnId, event) => {
|
|
1233
|
+
setIsResizing(true);
|
|
1234
|
+
setResizingColumn(columnId);
|
|
1235
|
+
const startX = event.clientX;
|
|
1236
|
+
const startWidth = columnWidths[columnId] || 150;
|
|
1237
|
+
const handleMouseMove = (e) => {
|
|
1238
|
+
const diff = e.clientX - startX;
|
|
1239
|
+
const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + diff));
|
|
1240
|
+
setColumnWidths((prev) => ({
|
|
1241
|
+
...prev,
|
|
1242
|
+
[columnId]: newWidth
|
|
1243
|
+
}));
|
|
1244
|
+
};
|
|
1245
|
+
const handleMouseUp = () => {
|
|
1246
|
+
setIsResizing(false);
|
|
1247
|
+
setResizingColumn(null);
|
|
1248
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
1249
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
1250
|
+
};
|
|
1251
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
1252
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
1253
|
+
},
|
|
1254
|
+
[columnWidths, minWidth, maxWidth]
|
|
1255
|
+
);
|
|
1256
|
+
const resetColumnWidth = (0, import_react5.useCallback)(
|
|
1257
|
+
(columnId) => {
|
|
1258
|
+
if (initialWidths[columnId]) {
|
|
1259
|
+
setColumnWidths((prev) => ({
|
|
1260
|
+
...prev,
|
|
1261
|
+
[columnId]: initialWidths[columnId]
|
|
1262
|
+
}));
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
[initialWidths]
|
|
1266
|
+
);
|
|
1267
|
+
const resetAllWidths = (0, import_react5.useCallback)(() => {
|
|
1268
|
+
setColumnWidths({ ...initialWidths });
|
|
1269
|
+
}, [initialWidths]);
|
|
1270
|
+
return {
|
|
1271
|
+
columnWidths,
|
|
1272
|
+
setColumnWidths,
|
|
1273
|
+
isResizing,
|
|
1274
|
+
resizingColumn,
|
|
1275
|
+
startResize,
|
|
1276
|
+
resetColumnWidth,
|
|
1277
|
+
resetAllWidths
|
|
1278
|
+
};
|
|
1363
1279
|
}
|
|
1364
1280
|
|
|
1365
|
-
// src/
|
|
1366
|
-
var
|
|
1367
|
-
var
|
|
1281
|
+
// src/hooks/useLicense.ts
|
|
1282
|
+
var import_tinypivot_core4 = require("@smallwebco/tinypivot-core");
|
|
1283
|
+
var import_react6 = require("react");
|
|
1284
|
+
var globalLicenseInfo = (0, import_tinypivot_core4.getFreeLicenseInfo)();
|
|
1285
|
+
var globalDemoMode = false;
|
|
1286
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
1287
|
+
function notifyListeners() {
|
|
1288
|
+
listeners.forEach((listener) => listener());
|
|
1289
|
+
}
|
|
1290
|
+
async function setLicenseKey(key) {
|
|
1291
|
+
globalLicenseInfo = await (0, import_tinypivot_core4.validateLicenseKey)(key);
|
|
1292
|
+
if (!globalLicenseInfo.isValid) {
|
|
1293
|
+
console.warn("[TinyPivot] Invalid or expired license key. Running in free mode.");
|
|
1294
|
+
} else if (globalLicenseInfo.type !== "free") {
|
|
1295
|
+
console.info(`[TinyPivot] Pro license activated (${globalLicenseInfo.type})`);
|
|
1296
|
+
}
|
|
1297
|
+
notifyListeners();
|
|
1298
|
+
}
|
|
1299
|
+
async function enableDemoMode(secret) {
|
|
1300
|
+
const demoLicense = await (0, import_tinypivot_core4.getDemoLicenseInfo)(secret);
|
|
1301
|
+
if (!demoLicense) {
|
|
1302
|
+
console.warn("[TinyPivot] Demo mode activation failed - invalid secret");
|
|
1303
|
+
return false;
|
|
1304
|
+
}
|
|
1305
|
+
globalDemoMode = true;
|
|
1306
|
+
globalLicenseInfo = demoLicense;
|
|
1307
|
+
console.info("[TinyPivot] Demo mode enabled - all Pro features unlocked for evaluation");
|
|
1308
|
+
notifyListeners();
|
|
1309
|
+
return true;
|
|
1310
|
+
}
|
|
1311
|
+
function configureLicenseSecret(secret) {
|
|
1312
|
+
(0, import_tinypivot_core4.configureLicenseSecret)(secret);
|
|
1313
|
+
}
|
|
1314
|
+
function useLicense() {
|
|
1315
|
+
const [, forceUpdate] = (0, import_react6.useState)({});
|
|
1316
|
+
(0, import_react6.useState)(() => {
|
|
1317
|
+
const update = () => forceUpdate({});
|
|
1318
|
+
listeners.add(update);
|
|
1319
|
+
return () => listeners.delete(update);
|
|
1320
|
+
});
|
|
1321
|
+
const isDemo = globalDemoMode;
|
|
1322
|
+
const licenseInfo = globalLicenseInfo;
|
|
1323
|
+
const isPro = (0, import_react6.useMemo)(
|
|
1324
|
+
() => globalDemoMode || (0, import_tinypivot_core4.isPro)(licenseInfo),
|
|
1325
|
+
[licenseInfo]
|
|
1326
|
+
);
|
|
1327
|
+
const canUsePivot = (0, import_react6.useMemo)(
|
|
1328
|
+
() => globalDemoMode || (0, import_tinypivot_core4.canUsePivot)(licenseInfo),
|
|
1329
|
+
[licenseInfo]
|
|
1330
|
+
);
|
|
1331
|
+
const canUseAdvancedAggregations = (0, import_react6.useMemo)(
|
|
1332
|
+
() => globalDemoMode || licenseInfo.features.advancedAggregations,
|
|
1333
|
+
[licenseInfo]
|
|
1334
|
+
);
|
|
1335
|
+
const canUsePercentageMode = (0, import_react6.useMemo)(
|
|
1336
|
+
() => globalDemoMode || licenseInfo.features.percentageMode,
|
|
1337
|
+
[licenseInfo]
|
|
1338
|
+
);
|
|
1339
|
+
const showWatermark = (0, import_react6.useMemo)(
|
|
1340
|
+
() => (0, import_tinypivot_core4.shouldShowWatermark)(licenseInfo, globalDemoMode),
|
|
1341
|
+
[licenseInfo]
|
|
1342
|
+
);
|
|
1343
|
+
const requirePro = (0, import_react6.useCallback)((feature) => {
|
|
1344
|
+
if (!isPro) {
|
|
1345
|
+
(0, import_tinypivot_core4.logProRequired)(feature);
|
|
1346
|
+
return false;
|
|
1347
|
+
}
|
|
1348
|
+
return true;
|
|
1349
|
+
}, [isPro]);
|
|
1350
|
+
return {
|
|
1351
|
+
licenseInfo,
|
|
1352
|
+
isDemo,
|
|
1353
|
+
isPro,
|
|
1354
|
+
canUsePivot,
|
|
1355
|
+
canUseAdvancedAggregations,
|
|
1356
|
+
canUsePercentageMode,
|
|
1357
|
+
showWatermark,
|
|
1358
|
+
requirePro
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1368
1361
|
|
|
1369
|
-
// src/
|
|
1370
|
-
var import_react7 = require("react");
|
|
1371
|
-
var import_react_dom = require("react-dom");
|
|
1362
|
+
// src/hooks/usePivotTable.ts
|
|
1372
1363
|
var import_tinypivot_core5 = require("@smallwebco/tinypivot-core");
|
|
1373
|
-
var
|
|
1374
|
-
function
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
const [
|
|
1382
|
-
const [
|
|
1383
|
-
const
|
|
1384
|
-
|
|
1385
|
-
|
|
1364
|
+
var import_react7 = require("react");
|
|
1365
|
+
function usePivotTable(data) {
|
|
1366
|
+
const { canUsePivot, requirePro } = useLicense();
|
|
1367
|
+
const [rowFields, setRowFieldsState] = (0, import_react7.useState)([]);
|
|
1368
|
+
const [columnFields, setColumnFieldsState] = (0, import_react7.useState)([]);
|
|
1369
|
+
const [valueFields, setValueFields] = (0, import_react7.useState)([]);
|
|
1370
|
+
const [showRowTotals, setShowRowTotals] = (0, import_react7.useState)(true);
|
|
1371
|
+
const [showColumnTotals, setShowColumnTotals] = (0, import_react7.useState)(true);
|
|
1372
|
+
const [calculatedFields, setCalculatedFields] = (0, import_react7.useState)(() => (0, import_tinypivot_core5.loadCalculatedFields)());
|
|
1373
|
+
const [currentStorageKey, setCurrentStorageKey] = (0, import_react7.useState)(null);
|
|
1374
|
+
const availableFields = (0, import_react7.useMemo)(() => {
|
|
1375
|
+
return (0, import_tinypivot_core5.computeAvailableFields)(data);
|
|
1376
|
+
}, [data]);
|
|
1377
|
+
const unassignedFields = (0, import_react7.useMemo)(() => {
|
|
1378
|
+
return (0, import_tinypivot_core5.getUnassignedFields)(availableFields, rowFields, columnFields, valueFields);
|
|
1379
|
+
}, [availableFields, rowFields, columnFields, valueFields]);
|
|
1380
|
+
const isConfigured = (0, import_react7.useMemo)(() => {
|
|
1381
|
+
return (0, import_tinypivot_core5.isPivotConfigured)({
|
|
1382
|
+
rowFields,
|
|
1383
|
+
columnFields,
|
|
1384
|
+
valueFields,
|
|
1385
|
+
showRowTotals,
|
|
1386
|
+
showColumnTotals
|
|
1387
|
+
});
|
|
1388
|
+
}, [rowFields, columnFields, valueFields, showRowTotals, showColumnTotals]);
|
|
1389
|
+
const pivotResult = (0, import_react7.useMemo)(() => {
|
|
1390
|
+
if (!isConfigured)
|
|
1391
|
+
return null;
|
|
1392
|
+
if (!canUsePivot)
|
|
1393
|
+
return null;
|
|
1394
|
+
return (0, import_tinypivot_core5.computePivotResult)(data, {
|
|
1395
|
+
rowFields,
|
|
1396
|
+
columnFields,
|
|
1397
|
+
valueFields,
|
|
1398
|
+
showRowTotals,
|
|
1399
|
+
showColumnTotals,
|
|
1400
|
+
calculatedFields
|
|
1401
|
+
});
|
|
1402
|
+
}, [data, isConfigured, canUsePivot, rowFields, columnFields, valueFields, showRowTotals, showColumnTotals, calculatedFields]);
|
|
1386
1403
|
(0, import_react7.useEffect)(() => {
|
|
1387
|
-
if (
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1404
|
+
if (data.length === 0)
|
|
1405
|
+
return;
|
|
1406
|
+
const newKeys = Object.keys(data[0]);
|
|
1407
|
+
const storageKey = (0, import_tinypivot_core5.generateStorageKey)(newKeys);
|
|
1408
|
+
if (storageKey !== currentStorageKey) {
|
|
1409
|
+
setCurrentStorageKey(storageKey);
|
|
1410
|
+
const savedConfig = (0, import_tinypivot_core5.loadPivotConfig)(storageKey);
|
|
1411
|
+
if (savedConfig && (0, import_tinypivot_core5.isConfigValidForFields)(savedConfig, newKeys)) {
|
|
1412
|
+
setRowFieldsState(savedConfig.rowFields);
|
|
1413
|
+
setColumnFieldsState(savedConfig.columnFields);
|
|
1414
|
+
setValueFields(savedConfig.valueFields);
|
|
1415
|
+
setShowRowTotals(savedConfig.showRowTotals);
|
|
1416
|
+
setShowColumnTotals(savedConfig.showColumnTotals);
|
|
1417
|
+
if (savedConfig.calculatedFields) {
|
|
1418
|
+
setCalculatedFields(savedConfig.calculatedFields);
|
|
1419
|
+
}
|
|
1420
|
+
} else {
|
|
1421
|
+
const currentConfig = {
|
|
1422
|
+
rowFields,
|
|
1423
|
+
columnFields,
|
|
1424
|
+
valueFields,
|
|
1425
|
+
showRowTotals,
|
|
1426
|
+
showColumnTotals
|
|
1427
|
+
};
|
|
1428
|
+
if (!(0, import_tinypivot_core5.isConfigValidForFields)(currentConfig, newKeys)) {
|
|
1429
|
+
setRowFieldsState([]);
|
|
1430
|
+
setColumnFieldsState([]);
|
|
1431
|
+
setValueFields([]);
|
|
1432
|
+
}
|
|
1398
1433
|
}
|
|
1399
|
-
setError(null);
|
|
1400
1434
|
}
|
|
1401
|
-
}, [
|
|
1402
|
-
|
|
1403
|
-
if (!
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1435
|
+
}, [data]);
|
|
1436
|
+
(0, import_react7.useEffect)(() => {
|
|
1437
|
+
if (!currentStorageKey)
|
|
1438
|
+
return;
|
|
1439
|
+
const config = {
|
|
1440
|
+
rowFields,
|
|
1441
|
+
columnFields,
|
|
1442
|
+
valueFields,
|
|
1443
|
+
showRowTotals,
|
|
1444
|
+
showColumnTotals,
|
|
1445
|
+
calculatedFields
|
|
1446
|
+
};
|
|
1447
|
+
(0, import_tinypivot_core5.savePivotConfig)(currentStorageKey, config);
|
|
1448
|
+
}, [currentStorageKey, rowFields, columnFields, valueFields, showRowTotals, showColumnTotals, calculatedFields]);
|
|
1449
|
+
const addRowField = (0, import_react7.useCallback)(
|
|
1450
|
+
(field) => {
|
|
1451
|
+
if (!rowFields.includes(field)) {
|
|
1452
|
+
setRowFieldsState((prev) => [...prev, field]);
|
|
1410
1453
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1454
|
+
},
|
|
1455
|
+
[rowFields]
|
|
1456
|
+
);
|
|
1457
|
+
const removeRowField = (0, import_react7.useCallback)((field) => {
|
|
1458
|
+
setRowFieldsState((prev) => prev.filter((f) => f !== field));
|
|
1413
1459
|
}, []);
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1460
|
+
const setRowFields = (0, import_react7.useCallback)((fields) => {
|
|
1461
|
+
setRowFieldsState(fields);
|
|
1462
|
+
}, []);
|
|
1463
|
+
const addColumnField = (0, import_react7.useCallback)(
|
|
1464
|
+
(field) => {
|
|
1465
|
+
if (!columnFields.includes(field)) {
|
|
1466
|
+
setColumnFieldsState((prev) => [...prev, field]);
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
[columnFields]
|
|
1470
|
+
);
|
|
1471
|
+
const removeColumnField = (0, import_react7.useCallback)((field) => {
|
|
1472
|
+
setColumnFieldsState((prev) => prev.filter((f) => f !== field));
|
|
1473
|
+
}, []);
|
|
1474
|
+
const setColumnFields = (0, import_react7.useCallback)((fields) => {
|
|
1475
|
+
setColumnFieldsState(fields);
|
|
1476
|
+
}, []);
|
|
1477
|
+
const addValueField = (0, import_react7.useCallback)(
|
|
1478
|
+
(field, aggregation = "sum") => {
|
|
1479
|
+
if (aggregation !== "sum" && !requirePro(`${aggregation} aggregation`)) {
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
setValueFields((prev) => {
|
|
1483
|
+
if (prev.some((v) => v.field === field && v.aggregation === aggregation)) {
|
|
1484
|
+
return prev;
|
|
1485
|
+
}
|
|
1486
|
+
return [...prev, { field, aggregation }];
|
|
1487
|
+
});
|
|
1488
|
+
},
|
|
1489
|
+
[requirePro]
|
|
1490
|
+
);
|
|
1491
|
+
const removeValueField = (0, import_react7.useCallback)((field, aggregation) => {
|
|
1492
|
+
setValueFields((prev) => {
|
|
1493
|
+
if (aggregation) {
|
|
1494
|
+
return prev.filter((v) => !(v.field === field && v.aggregation === aggregation));
|
|
1418
1495
|
}
|
|
1419
|
-
return prev
|
|
1496
|
+
return prev.filter((v) => v.field !== field);
|
|
1420
1497
|
});
|
|
1421
1498
|
}, []);
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1499
|
+
const updateValueFieldAggregation = (0, import_react7.useCallback)(
|
|
1500
|
+
(field, oldAgg, newAgg) => {
|
|
1501
|
+
setValueFields(
|
|
1502
|
+
(prev) => prev.map((v) => {
|
|
1503
|
+
if (v.field === field && v.aggregation === oldAgg) {
|
|
1504
|
+
return { ...v, aggregation: newAgg };
|
|
1505
|
+
}
|
|
1506
|
+
return v;
|
|
1507
|
+
})
|
|
1508
|
+
);
|
|
1509
|
+
},
|
|
1510
|
+
[]
|
|
1511
|
+
);
|
|
1512
|
+
const clearConfig = (0, import_react7.useCallback)(() => {
|
|
1513
|
+
setRowFieldsState([]);
|
|
1514
|
+
setColumnFieldsState([]);
|
|
1515
|
+
setValueFields([]);
|
|
1516
|
+
}, []);
|
|
1517
|
+
const autoSuggestConfig = (0, import_react7.useCallback)(() => {
|
|
1518
|
+
if (!requirePro("Pivot Table - Auto Suggest"))
|
|
1425
1519
|
return;
|
|
1426
|
-
|
|
1427
|
-
const validationResult = (0, import_tinypivot_core5.validateSimpleFormula)(formula, availableFields);
|
|
1428
|
-
if (validationResult) {
|
|
1429
|
-
setError(validationResult);
|
|
1520
|
+
if (availableFields.length === 0)
|
|
1430
1521
|
return;
|
|
1522
|
+
const categoricalFields = availableFields.filter((f) => !f.isNumeric && f.uniqueCount < 50);
|
|
1523
|
+
const numericFields = availableFields.filter((f) => f.isNumeric);
|
|
1524
|
+
if (categoricalFields.length > 0 && numericFields.length > 0) {
|
|
1525
|
+
setRowFieldsState([categoricalFields[0].field]);
|
|
1526
|
+
setValueFields([{ field: numericFields[0].field, aggregation: "sum" }]);
|
|
1431
1527
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
] }),
|
|
1485
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-form-group", children: [
|
|
1486
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "vpg-label-small", children: "Operators" }),
|
|
1487
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-button-group", children: [
|
|
1488
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("+"), children: "+" }),
|
|
1489
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("-"), children: "\u2212" }),
|
|
1490
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("*"), children: "\xD7" }),
|
|
1491
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("/"), children: "\xF7" }),
|
|
1492
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("("), children: "(" }),
|
|
1493
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator(")"), children: ")" })
|
|
1494
|
-
] })
|
|
1495
|
-
] }),
|
|
1496
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-form-group", children: [
|
|
1497
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "vpg-label-small", children: "Insert Field" }),
|
|
1498
|
-
availableFields.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "vpg-button-group vpg-field-buttons", children: availableFields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1499
|
-
"button",
|
|
1500
|
-
{
|
|
1501
|
-
className: "vpg-insert-btn vpg-field-btn",
|
|
1502
|
-
onClick: () => insertField(field),
|
|
1503
|
-
children: field
|
|
1504
|
-
},
|
|
1505
|
-
field
|
|
1506
|
-
)) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "vpg-no-fields", children: "No numeric fields available" })
|
|
1507
|
-
] }),
|
|
1508
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-form-row", children: [
|
|
1509
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-form-group vpg-form-group-half", children: [
|
|
1510
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "vpg-label", children: "Format As" }),
|
|
1511
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1512
|
-
"select",
|
|
1513
|
-
{
|
|
1514
|
-
className: "vpg-select",
|
|
1515
|
-
value: formatAs,
|
|
1516
|
-
onChange: (e) => setFormatAs(e.target.value),
|
|
1517
|
-
children: [
|
|
1518
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "number", children: "Number" }),
|
|
1519
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "percent", children: "Percentage" }),
|
|
1520
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "currency", children: "Currency ($)" })
|
|
1521
|
-
]
|
|
1522
|
-
}
|
|
1523
|
-
)
|
|
1524
|
-
] }),
|
|
1525
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-form-group vpg-form-group-half", children: [
|
|
1526
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "vpg-label", children: "Decimals" }),
|
|
1527
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1528
|
-
"input",
|
|
1529
|
-
{
|
|
1530
|
-
type: "number",
|
|
1531
|
-
className: "vpg-input",
|
|
1532
|
-
min: 0,
|
|
1533
|
-
max: 6,
|
|
1534
|
-
value: decimals,
|
|
1535
|
-
onChange: (e) => setDecimals(Number(e.target.value))
|
|
1536
|
-
}
|
|
1537
|
-
)
|
|
1538
|
-
] })
|
|
1539
|
-
] }),
|
|
1540
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "vpg-error vpg-error-box", children: error })
|
|
1541
|
-
] }),
|
|
1542
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "vpg-modal-footer", children: [
|
|
1543
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "vpg-btn vpg-btn-secondary", onClick: onClose, children: "Cancel" }),
|
|
1544
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { className: "vpg-btn vpg-btn-primary", onClick: handleSave, children: [
|
|
1545
|
-
existingField ? "Update" : "Add",
|
|
1546
|
-
" Field"
|
|
1547
|
-
] })
|
|
1548
|
-
] })
|
|
1549
|
-
] }) });
|
|
1550
|
-
if (typeof document === "undefined") return null;
|
|
1551
|
-
return (0, import_react_dom.createPortal)(modalContent, document.body);
|
|
1528
|
+
}, [availableFields, requirePro]);
|
|
1529
|
+
const addCalculatedField = (0, import_react7.useCallback)((field) => {
|
|
1530
|
+
setCalculatedFields((prev) => {
|
|
1531
|
+
const existing = prev.findIndex((f) => f.id === field.id);
|
|
1532
|
+
let updated;
|
|
1533
|
+
if (existing >= 0) {
|
|
1534
|
+
updated = [...prev.slice(0, existing), field, ...prev.slice(existing + 1)];
|
|
1535
|
+
} else {
|
|
1536
|
+
updated = [...prev, field];
|
|
1537
|
+
}
|
|
1538
|
+
(0, import_tinypivot_core5.saveCalculatedFields)(updated);
|
|
1539
|
+
return updated;
|
|
1540
|
+
});
|
|
1541
|
+
}, []);
|
|
1542
|
+
const removeCalculatedField = (0, import_react7.useCallback)((id) => {
|
|
1543
|
+
setCalculatedFields((prev) => {
|
|
1544
|
+
const updated = prev.filter((f) => f.id !== id);
|
|
1545
|
+
(0, import_tinypivot_core5.saveCalculatedFields)(updated);
|
|
1546
|
+
return updated;
|
|
1547
|
+
});
|
|
1548
|
+
setValueFields((prev) => prev.filter((v) => v.field !== `calc:${id}`));
|
|
1549
|
+
}, []);
|
|
1550
|
+
return {
|
|
1551
|
+
// State
|
|
1552
|
+
rowFields,
|
|
1553
|
+
columnFields,
|
|
1554
|
+
valueFields,
|
|
1555
|
+
showRowTotals,
|
|
1556
|
+
showColumnTotals,
|
|
1557
|
+
calculatedFields,
|
|
1558
|
+
// Computed
|
|
1559
|
+
availableFields,
|
|
1560
|
+
unassignedFields,
|
|
1561
|
+
isConfigured,
|
|
1562
|
+
pivotResult,
|
|
1563
|
+
// Actions
|
|
1564
|
+
addRowField,
|
|
1565
|
+
removeRowField,
|
|
1566
|
+
addColumnField,
|
|
1567
|
+
removeColumnField,
|
|
1568
|
+
addValueField,
|
|
1569
|
+
removeValueField,
|
|
1570
|
+
updateValueFieldAggregation,
|
|
1571
|
+
clearConfig,
|
|
1572
|
+
setShowRowTotals,
|
|
1573
|
+
setShowColumnTotals,
|
|
1574
|
+
autoSuggestConfig,
|
|
1575
|
+
setRowFields,
|
|
1576
|
+
setColumnFields,
|
|
1577
|
+
addCalculatedField,
|
|
1578
|
+
removeCalculatedField
|
|
1579
|
+
};
|
|
1552
1580
|
}
|
|
1553
1581
|
|
|
1554
1582
|
// src/components/PivotConfig.tsx
|
|
1583
|
+
var import_tinypivot_core6 = require("@smallwebco/tinypivot-core");
|
|
1584
|
+
var import_react8 = require("react");
|
|
1555
1585
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1556
1586
|
function aggregationRequiresPro(agg) {
|
|
1557
1587
|
return agg !== "sum";
|
|
1558
1588
|
}
|
|
1559
1589
|
function getFieldIcon(type, isCalculated) {
|
|
1560
|
-
if (isCalculated)
|
|
1590
|
+
if (isCalculated)
|
|
1591
|
+
return "\u0192";
|
|
1561
1592
|
switch (type) {
|
|
1562
1593
|
case "number":
|
|
1563
1594
|
return "#";
|
|
@@ -1579,7 +1610,7 @@ function PivotConfig({
|
|
|
1579
1610
|
onShowRowTotalsChange,
|
|
1580
1611
|
onShowColumnTotalsChange,
|
|
1581
1612
|
onClearConfig,
|
|
1582
|
-
onAutoSuggest,
|
|
1613
|
+
onAutoSuggest: _onAutoSuggest,
|
|
1583
1614
|
onDragStart,
|
|
1584
1615
|
onDragEnd,
|
|
1585
1616
|
onUpdateAggregation,
|
|
@@ -1599,12 +1630,10 @@ function PivotConfig({
|
|
|
1599
1630
|
const isAggregationAvailable = (0, import_react8.useCallback)((agg) => {
|
|
1600
1631
|
return !aggregationRequiresPro(agg) || canUseAdvancedAggregations;
|
|
1601
1632
|
}, [canUseAdvancedAggregations]);
|
|
1602
|
-
const numericFieldNames = (0, import_react8.useMemo)(
|
|
1603
|
-
() => availableFields.filter((f) => f.isNumeric).map((f) => f.field),
|
|
1604
|
-
[availableFields]
|
|
1605
|
-
);
|
|
1633
|
+
const numericFieldNames = (0, import_react8.useMemo)(() => availableFields.filter((f) => f.isNumeric).map((f) => f.field), [availableFields]);
|
|
1606
1634
|
const calculatedFieldsAsStats = (0, import_react8.useMemo)(() => {
|
|
1607
|
-
if (!calculatedFields)
|
|
1635
|
+
if (!calculatedFields)
|
|
1636
|
+
return [];
|
|
1608
1637
|
return calculatedFields.map((calc) => ({
|
|
1609
1638
|
field: `calc:${calc.id}`,
|
|
1610
1639
|
type: "number",
|
|
@@ -1639,7 +1668,8 @@ function PivotConfig({
|
|
|
1639
1668
|
);
|
|
1640
1669
|
}, [allAvailableFields, rowFields, columnFields, valueFields]);
|
|
1641
1670
|
const filteredUnassignedFields = (0, import_react8.useMemo)(() => {
|
|
1642
|
-
if (!fieldSearch.trim())
|
|
1671
|
+
if (!fieldSearch.trim())
|
|
1672
|
+
return unassignedFields;
|
|
1643
1673
|
const search = fieldSearch.toLowerCase().trim();
|
|
1644
1674
|
return unassignedFields.filter((f) => {
|
|
1645
1675
|
const fieldName = f.field.toLowerCase();
|
|
@@ -1844,7 +1874,8 @@ function PivotConfig({
|
|
|
1844
1874
|
] }),
|
|
1845
1875
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-unassigned-section", children: [
|
|
1846
1876
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "vpg-section-header", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-section-label", children: [
|
|
1847
|
-
"Available
|
|
1877
|
+
"Available",
|
|
1878
|
+
" ",
|
|
1848
1879
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vpg-count", children: unassignedFields.length })
|
|
1849
1880
|
] }) }),
|
|
1850
1881
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "vpg-field-search", children: [
|
|
@@ -1898,7 +1929,8 @@ function PivotConfig({
|
|
|
1898
1929
|
onClick: (e) => {
|
|
1899
1930
|
e.stopPropagation();
|
|
1900
1931
|
const calcField = calculatedFields?.find((c) => c.id === field.calcId);
|
|
1901
|
-
if (calcField)
|
|
1932
|
+
if (calcField)
|
|
1933
|
+
openCalcModal(calcField);
|
|
1902
1934
|
},
|
|
1903
1935
|
children: "\u270E"
|
|
1904
1936
|
}
|
|
@@ -1961,8 +1993,8 @@ function PivotConfig({
|
|
|
1961
1993
|
}
|
|
1962
1994
|
|
|
1963
1995
|
// src/components/PivotSkeleton.tsx
|
|
1964
|
-
var import_react9 = require("react");
|
|
1965
1996
|
var import_tinypivot_core7 = require("@smallwebco/tinypivot-core");
|
|
1997
|
+
var import_react9 = require("react");
|
|
1966
1998
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1967
1999
|
function PivotSkeleton({
|
|
1968
2000
|
rowFields,
|
|
@@ -2017,7 +2049,8 @@ function PivotSkeleton({
|
|
|
2017
2049
|
const [showCopyToast, setShowCopyToast] = (0, import_react9.useState)(false);
|
|
2018
2050
|
const [copyToastMessage, setCopyToastMessage] = (0, import_react9.useState)("");
|
|
2019
2051
|
const selectionBounds = (0, import_react9.useMemo)(() => {
|
|
2020
|
-
if (!selectionStart || !selectionEnd)
|
|
2052
|
+
if (!selectionStart || !selectionEnd)
|
|
2053
|
+
return null;
|
|
2021
2054
|
return {
|
|
2022
2055
|
minRow: Math.min(selectionStart.row, selectionEnd.row),
|
|
2023
2056
|
maxRow: Math.max(selectionStart.row, selectionEnd.row),
|
|
@@ -2063,7 +2096,8 @@ function PivotSkeleton({
|
|
|
2063
2096
|
return () => document.removeEventListener("mouseup", handleMouseUp);
|
|
2064
2097
|
}, []);
|
|
2065
2098
|
const sortedRowIndices = (0, import_react9.useMemo)(() => {
|
|
2066
|
-
if (!pivotResult)
|
|
2099
|
+
if (!pivotResult)
|
|
2100
|
+
return [];
|
|
2067
2101
|
const indices = pivotResult.rowHeaders.map((_, i) => i);
|
|
2068
2102
|
const headers = pivotResult.rowHeaders;
|
|
2069
2103
|
const data = pivotResult.data;
|
|
@@ -2077,9 +2111,12 @@ function PivotSkeleton({
|
|
|
2077
2111
|
const colIdx = sortTarget;
|
|
2078
2112
|
const aVal = data[a]?.[colIdx]?.value ?? null;
|
|
2079
2113
|
const bVal = data[b]?.[colIdx]?.value ?? null;
|
|
2080
|
-
if (aVal === null && bVal === null)
|
|
2081
|
-
|
|
2082
|
-
else if (
|
|
2114
|
+
if (aVal === null && bVal === null)
|
|
2115
|
+
cmp = 0;
|
|
2116
|
+
else if (aVal === null)
|
|
2117
|
+
cmp = 1;
|
|
2118
|
+
else if (bVal === null)
|
|
2119
|
+
cmp = -1;
|
|
2083
2120
|
else cmp = aVal - bVal;
|
|
2084
2121
|
}
|
|
2085
2122
|
return sortDirection === "asc" ? cmp : -cmp;
|
|
@@ -2087,12 +2124,14 @@ function PivotSkeleton({
|
|
|
2087
2124
|
return indices;
|
|
2088
2125
|
}, [pivotResult, sortTarget, sortDirection]);
|
|
2089
2126
|
const copySelectionToClipboard = (0, import_react9.useCallback)(() => {
|
|
2090
|
-
if (!selectionBounds || !pivotResult)
|
|
2127
|
+
if (!selectionBounds || !pivotResult)
|
|
2128
|
+
return;
|
|
2091
2129
|
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
2092
2130
|
const lines = [];
|
|
2093
2131
|
for (let r = minRow; r <= maxRow; r++) {
|
|
2094
2132
|
const sortedIdx = sortedRowIndices[r];
|
|
2095
|
-
if (sortedIdx === void 0)
|
|
2133
|
+
if (sortedIdx === void 0)
|
|
2134
|
+
continue;
|
|
2096
2135
|
const rowValues = [];
|
|
2097
2136
|
for (let c = minCol; c <= maxCol; c++) {
|
|
2098
2137
|
const cell = pivotResult.data[sortedIdx]?.[c];
|
|
@@ -2112,7 +2151,8 @@ function PivotSkeleton({
|
|
|
2112
2151
|
}, [selectionBounds, pivotResult, sortedRowIndices]);
|
|
2113
2152
|
(0, import_react9.useEffect)(() => {
|
|
2114
2153
|
const handleKeydown = (event) => {
|
|
2115
|
-
if (!selectionBounds)
|
|
2154
|
+
if (!selectionBounds)
|
|
2155
|
+
return;
|
|
2116
2156
|
if ((event.ctrlKey || event.metaKey) && event.key === "c") {
|
|
2117
2157
|
event.preventDefault();
|
|
2118
2158
|
copySelectionToClipboard();
|
|
@@ -2128,13 +2168,15 @@ function PivotSkeleton({
|
|
|
2128
2168
|
return () => document.removeEventListener("keydown", handleKeydown);
|
|
2129
2169
|
}, [selectionBounds, copySelectionToClipboard]);
|
|
2130
2170
|
const selectionStats = (0, import_react9.useMemo)(() => {
|
|
2131
|
-
if (!selectionBounds || !pivotResult)
|
|
2171
|
+
if (!selectionBounds || !pivotResult)
|
|
2172
|
+
return null;
|
|
2132
2173
|
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
2133
2174
|
const values = [];
|
|
2134
2175
|
let count = 0;
|
|
2135
2176
|
for (let r = minRow; r <= maxRow; r++) {
|
|
2136
2177
|
const sortedIdx = sortedRowIndices[r];
|
|
2137
|
-
if (sortedIdx === void 0)
|
|
2178
|
+
if (sortedIdx === void 0)
|
|
2179
|
+
continue;
|
|
2138
2180
|
for (let c = minCol; c <= maxCol; c++) {
|
|
2139
2181
|
const cell = pivotResult.data[sortedIdx]?.[c];
|
|
2140
2182
|
count++;
|
|
@@ -2143,7 +2185,8 @@ function PivotSkeleton({
|
|
|
2143
2185
|
}
|
|
2144
2186
|
}
|
|
2145
2187
|
}
|
|
2146
|
-
if (count <= 1)
|
|
2188
|
+
if (count <= 1)
|
|
2189
|
+
return null;
|
|
2147
2190
|
const sum = values.reduce((a, b) => a + b, 0);
|
|
2148
2191
|
const avg = values.length > 0 ? sum / values.length : 0;
|
|
2149
2192
|
return {
|
|
@@ -2154,8 +2197,10 @@ function PivotSkeleton({
|
|
|
2154
2197
|
};
|
|
2155
2198
|
}, [selectionBounds, pivotResult, sortedRowIndices]);
|
|
2156
2199
|
const formatStatValue = (0, import_react9.useCallback)((val) => {
|
|
2157
|
-
if (Math.abs(val) >= 1e6)
|
|
2158
|
-
|
|
2200
|
+
if (Math.abs(val) >= 1e6)
|
|
2201
|
+
return `${(val / 1e6).toFixed(2)}M`;
|
|
2202
|
+
if (Math.abs(val) >= 1e3)
|
|
2203
|
+
return `${(val / 1e3).toFixed(2)}K`;
|
|
2159
2204
|
return val.toFixed(2);
|
|
2160
2205
|
}, []);
|
|
2161
2206
|
const columnHeaderCells = (0, import_react9.useMemo)(() => {
|
|
@@ -2187,12 +2232,14 @@ function PivotSkeleton({
|
|
|
2187
2232
|
}, [pivotResult, valueFields]);
|
|
2188
2233
|
const hasActiveFilters = activeFilters && activeFilters.length > 0;
|
|
2189
2234
|
const filterSummary = (0, import_react9.useMemo)(() => {
|
|
2190
|
-
if (!activeFilters || activeFilters.length === 0)
|
|
2235
|
+
if (!activeFilters || activeFilters.length === 0)
|
|
2236
|
+
return "";
|
|
2191
2237
|
return activeFilters.map((f) => f.column).join(", ");
|
|
2192
2238
|
}, [activeFilters]);
|
|
2193
2239
|
const [showFilterTooltip, setShowFilterTooltip] = (0, import_react9.useState)(false);
|
|
2194
2240
|
const filterTooltipDetails = (0, import_react9.useMemo)(() => {
|
|
2195
|
-
if (!activeFilters || activeFilters.length === 0)
|
|
2241
|
+
if (!activeFilters || activeFilters.length === 0)
|
|
2242
|
+
return [];
|
|
2196
2243
|
return activeFilters.map((f) => {
|
|
2197
2244
|
if (f.isRange && f.displayText) {
|
|
2198
2245
|
return {
|
|
@@ -2234,10 +2281,13 @@ function PivotSkeleton({
|
|
|
2234
2281
|
setDragOverArea(null);
|
|
2235
2282
|
return;
|
|
2236
2283
|
}
|
|
2237
|
-
if (rowFields.includes(field))
|
|
2238
|
-
|
|
2284
|
+
if (rowFields.includes(field))
|
|
2285
|
+
onRemoveRowField(field);
|
|
2286
|
+
if (columnFields.includes(field))
|
|
2287
|
+
onRemoveColumnField(field);
|
|
2239
2288
|
const existingValue = valueFields.find((v) => v.field === field);
|
|
2240
|
-
if (existingValue)
|
|
2289
|
+
if (existingValue)
|
|
2290
|
+
onRemoveValueField(field, existingValue.aggregation);
|
|
2241
2291
|
switch (area) {
|
|
2242
2292
|
case "row":
|
|
2243
2293
|
onAddRowField(field);
|
|
@@ -2320,6 +2370,14 @@ function PivotSkeleton({
|
|
|
2320
2370
|
[reorderDropTarget]
|
|
2321
2371
|
);
|
|
2322
2372
|
const currentFontSize = fontSize;
|
|
2373
|
+
const rowHeaderWidth = 180;
|
|
2374
|
+
const rowHeaderColWidth = (0, import_react9.useMemo)(() => {
|
|
2375
|
+
const numCols = Math.max(rowFields.length, 1);
|
|
2376
|
+
return Math.max(rowHeaderWidth / numCols, 80);
|
|
2377
|
+
}, [rowFields.length]);
|
|
2378
|
+
const getRowHeaderLeftOffset = (0, import_react9.useCallback)((fieldIdx) => {
|
|
2379
|
+
return fieldIdx * rowHeaderColWidth;
|
|
2380
|
+
}, [rowHeaderColWidth]);
|
|
2323
2381
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2324
2382
|
"div",
|
|
2325
2383
|
{
|
|
@@ -2369,14 +2427,18 @@ function PivotSkeleton({
|
|
|
2369
2427
|
}
|
|
2370
2428
|
),
|
|
2371
2429
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "vpg-filter-text", children: [
|
|
2372
|
-
"Filtered:
|
|
2430
|
+
"Filtered:",
|
|
2431
|
+
" ",
|
|
2373
2432
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: filterSummary }),
|
|
2374
2433
|
filteredRowCount !== void 0 && totalRowCount !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "vpg-filter-count", children: [
|
|
2375
2434
|
"(",
|
|
2376
2435
|
filteredRowCount.toLocaleString(),
|
|
2377
|
-
"
|
|
2436
|
+
" ",
|
|
2437
|
+
"of",
|
|
2438
|
+
" ",
|
|
2378
2439
|
totalRowCount.toLocaleString(),
|
|
2379
|
-
"
|
|
2440
|
+
" ",
|
|
2441
|
+
"rows)"
|
|
2380
2442
|
] })
|
|
2381
2443
|
] }),
|
|
2382
2444
|
showFilterTooltip && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vpg-filter-tooltip", children: [
|
|
@@ -2388,16 +2450,21 @@ function PivotSkeleton({
|
|
|
2388
2450
|
filter.remaining > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "vpg-tooltip-more", children: [
|
|
2389
2451
|
"+",
|
|
2390
2452
|
filter.remaining,
|
|
2391
|
-
"
|
|
2453
|
+
" ",
|
|
2454
|
+
"more"
|
|
2392
2455
|
] })
|
|
2393
2456
|
] }) })
|
|
2394
2457
|
] }, filter.column)),
|
|
2395
2458
|
filteredRowCount !== void 0 && totalRowCount !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vpg-tooltip-summary", children: [
|
|
2396
|
-
"Showing
|
|
2459
|
+
"Showing",
|
|
2460
|
+
" ",
|
|
2397
2461
|
filteredRowCount.toLocaleString(),
|
|
2398
|
-
"
|
|
2462
|
+
" ",
|
|
2463
|
+
"of",
|
|
2464
|
+
" ",
|
|
2399
2465
|
totalRowCount.toLocaleString(),
|
|
2400
|
-
"
|
|
2466
|
+
" ",
|
|
2467
|
+
"rows"
|
|
2401
2468
|
] })
|
|
2402
2469
|
] })
|
|
2403
2470
|
]
|
|
@@ -2406,17 +2473,20 @@ function PivotSkeleton({
|
|
|
2406
2473
|
isConfigured && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vpg-config-summary", children: [
|
|
2407
2474
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "vpg-summary-badge vpg-rows", children: [
|
|
2408
2475
|
rowFields.length,
|
|
2409
|
-
"
|
|
2476
|
+
" ",
|
|
2477
|
+
"row",
|
|
2410
2478
|
rowFields.length !== 1 ? "s" : ""
|
|
2411
2479
|
] }),
|
|
2412
2480
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "vpg-summary-badge vpg-cols", children: [
|
|
2413
2481
|
columnFields.length,
|
|
2414
|
-
"
|
|
2482
|
+
" ",
|
|
2483
|
+
"col",
|
|
2415
2484
|
columnFields.length !== 1 ? "s" : ""
|
|
2416
2485
|
] }),
|
|
2417
2486
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "vpg-summary-badge vpg-vals", children: [
|
|
2418
2487
|
valueFields.length,
|
|
2419
|
-
"
|
|
2488
|
+
" ",
|
|
2489
|
+
"val",
|
|
2420
2490
|
valueFields.length !== 1 ? "s" : ""
|
|
2421
2491
|
] })
|
|
2422
2492
|
] })
|
|
@@ -2587,31 +2657,39 @@ function PivotSkeleton({
|
|
|
2587
2657
|
}
|
|
2588
2658
|
),
|
|
2589
2659
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "vpg-placeholder-text", children: valueFields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
2590
|
-
"Add a
|
|
2660
|
+
"Add a",
|
|
2661
|
+
" ",
|
|
2591
2662
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: "Values" }),
|
|
2592
|
-
"
|
|
2663
|
+
" ",
|
|
2664
|
+
"field to see your pivot table"
|
|
2593
2665
|
] }) : rowFields.length === 0 && columnFields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
2594
|
-
"Add
|
|
2666
|
+
"Add",
|
|
2667
|
+
" ",
|
|
2595
2668
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: "Row" }),
|
|
2596
|
-
"
|
|
2669
|
+
" ",
|
|
2670
|
+
"or",
|
|
2671
|
+
" ",
|
|
2597
2672
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: "Column" }),
|
|
2598
|
-
"
|
|
2673
|
+
" ",
|
|
2674
|
+
"fields to group your data"
|
|
2599
2675
|
] }) : "Your pivot table will appear here" })
|
|
2600
2676
|
] }) }),
|
|
2601
2677
|
isConfigured && pivotResult && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "vpg-table-container", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("table", { className: "vpg-pivot-table", children: [
|
|
2602
2678
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("thead", { children: columnHeaderCells.map((headerRow, levelIdx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("tr", { className: "vpg-column-header-row", children: [
|
|
2603
|
-
levelIdx === 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2679
|
+
levelIdx === 0 && (rowFields.length > 0 ? rowFields : ["Rows"]).map((field, fieldIdx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2604
2680
|
"th",
|
|
2605
2681
|
{
|
|
2606
2682
|
className: "vpg-row-header-label",
|
|
2607
2683
|
rowSpan: columnHeaderCells.length,
|
|
2684
|
+
style: { width: `${rowHeaderColWidth}px`, minWidth: "80px", left: `${getRowHeaderLeftOffset(fieldIdx)}px` },
|
|
2608
2685
|
onClick: () => toggleSort("row"),
|
|
2609
2686
|
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vpg-header-content", children: [
|
|
2610
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children:
|
|
2611
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `vpg-sort-indicator ${sortTarget === "row" ? "active" : ""}`, children: sortTarget === "row" ? sortDirection === "asc" ? "\u2191" : "\u2193" : "\u21C5" })
|
|
2687
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: field }),
|
|
2688
|
+
(fieldIdx === rowFields.length - 1 || rowFields.length === 0) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: `vpg-sort-indicator ${sortTarget === "row" ? "active" : ""}`, children: sortTarget === "row" ? sortDirection === "asc" ? "\u2191" : "\u2193" : "\u21C5" })
|
|
2612
2689
|
] })
|
|
2613
|
-
}
|
|
2614
|
-
|
|
2690
|
+
},
|
|
2691
|
+
`row-header-${fieldIdx}`
|
|
2692
|
+
)),
|
|
2615
2693
|
headerRow.map((cell, idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2616
2694
|
"th",
|
|
2617
2695
|
{
|
|
@@ -2629,7 +2707,15 @@ function PivotSkeleton({
|
|
|
2629
2707
|
] }, `header-${levelIdx}`)) }),
|
|
2630
2708
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("tbody", { children: [
|
|
2631
2709
|
sortedRowIndices.map((sortedIdx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("tr", { className: "vpg-data-row", children: [
|
|
2632
|
-
|
|
2710
|
+
pivotResult.rowHeaders[sortedIdx].map((val, idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2711
|
+
"th",
|
|
2712
|
+
{
|
|
2713
|
+
className: "vpg-row-header-cell",
|
|
2714
|
+
style: { width: `${rowHeaderColWidth}px`, minWidth: "80px", left: `${getRowHeaderLeftOffset(idx)}px` },
|
|
2715
|
+
children: val
|
|
2716
|
+
},
|
|
2717
|
+
`row-${sortedIdx}-${idx}`
|
|
2718
|
+
)),
|
|
2633
2719
|
pivotResult.data[sortedIdx].map((cell, colIdx) => {
|
|
2634
2720
|
const displayRowIdx = sortedRowIndices.indexOf(sortedIdx);
|
|
2635
2721
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
@@ -2646,7 +2732,15 @@ function PivotSkeleton({
|
|
|
2646
2732
|
pivotResult.rowTotals[sortedIdx] && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { className: "vpg-data-cell vpg-total-cell", children: pivotResult.rowTotals[sortedIdx].formattedValue })
|
|
2647
2733
|
] }, sortedIdx)),
|
|
2648
2734
|
pivotResult.columnTotals.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("tr", { className: "vpg-totals-row", children: [
|
|
2649
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2735
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2736
|
+
"th",
|
|
2737
|
+
{
|
|
2738
|
+
className: "vpg-row-header-cell vpg-total-label",
|
|
2739
|
+
colSpan: Math.max(rowFields.length, 1),
|
|
2740
|
+
style: { width: `${rowHeaderWidth}px` },
|
|
2741
|
+
children: "Total"
|
|
2742
|
+
}
|
|
2743
|
+
),
|
|
2650
2744
|
pivotResult.columnTotals.map((cell, colIdx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { className: "vpg-data-cell vpg-total-cell", children: cell.formattedValue }, colIdx)),
|
|
2651
2745
|
pivotResult.rowTotals.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { className: "vpg-data-cell vpg-grand-total-cell", children: pivotResult.grandTotal.formattedValue })
|
|
2652
2746
|
] })
|
|
@@ -2655,9 +2749,11 @@ function PivotSkeleton({
|
|
|
2655
2749
|
isConfigured && pivotResult && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vpg-skeleton-footer", children: [
|
|
2656
2750
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "vpg-footer-info", children: [
|
|
2657
2751
|
pivotResult.rowHeaders.length,
|
|
2658
|
-
"
|
|
2752
|
+
" ",
|
|
2753
|
+
"rows \xD7",
|
|
2659
2754
|
pivotResult.data[0]?.length || 0,
|
|
2660
|
-
"
|
|
2755
|
+
" ",
|
|
2756
|
+
"columns"
|
|
2661
2757
|
] }),
|
|
2662
2758
|
selectionStats && selectionStats.count > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vpg-selection-stats", children: [
|
|
2663
2759
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "vpg-stat", children: [
|
|
@@ -2810,12 +2906,15 @@ function DataGrid({
|
|
|
2810
2906
|
removeCalculatedField
|
|
2811
2907
|
} = usePivotTable(filteredDataForPivot);
|
|
2812
2908
|
const activeFilterInfo = (0, import_react10.useMemo)(() => {
|
|
2813
|
-
if (activeFilters.length === 0)
|
|
2909
|
+
if (activeFilters.length === 0)
|
|
2910
|
+
return null;
|
|
2814
2911
|
return activeFilters.map((f) => {
|
|
2815
2912
|
if (f.type === "range" && f.range) {
|
|
2816
2913
|
const parts = [];
|
|
2817
|
-
if (f.range.min !== null)
|
|
2818
|
-
|
|
2914
|
+
if (f.range.min !== null)
|
|
2915
|
+
parts.push(`\u2265 ${f.range.min}`);
|
|
2916
|
+
if (f.range.max !== null)
|
|
2917
|
+
parts.push(`\u2264 ${f.range.max}`);
|
|
2819
2918
|
return {
|
|
2820
2919
|
column: f.column,
|
|
2821
2920
|
valueCount: 1,
|
|
@@ -2840,7 +2939,8 @@ function DataGrid({
|
|
|
2840
2939
|
return rows.filter((row) => {
|
|
2841
2940
|
for (const col of columnKeys) {
|
|
2842
2941
|
const value = row.original[col];
|
|
2843
|
-
if (value === null || value === void 0)
|
|
2942
|
+
if (value === null || value === void 0)
|
|
2943
|
+
continue;
|
|
2844
2944
|
if (String(value).toLowerCase().includes(term)) {
|
|
2845
2945
|
return true;
|
|
2846
2946
|
}
|
|
@@ -2850,11 +2950,13 @@ function DataGrid({
|
|
|
2850
2950
|
}, [rows, globalSearchTerm, enableSearch, columnKeys]);
|
|
2851
2951
|
const totalSearchedRows = searchFilteredData.length;
|
|
2852
2952
|
const totalPages = (0, import_react10.useMemo)(() => {
|
|
2853
|
-
if (!enablePagination)
|
|
2953
|
+
if (!enablePagination)
|
|
2954
|
+
return 1;
|
|
2854
2955
|
return Math.max(1, Math.ceil(totalSearchedRows / pageSize));
|
|
2855
2956
|
}, [enablePagination, totalSearchedRows, pageSize]);
|
|
2856
2957
|
const paginatedRows = (0, import_react10.useMemo)(() => {
|
|
2857
|
-
if (!enablePagination)
|
|
2958
|
+
if (!enablePagination)
|
|
2959
|
+
return searchFilteredData;
|
|
2858
2960
|
const start = (currentPage - 1) * pageSize;
|
|
2859
2961
|
const end = start + pageSize;
|
|
2860
2962
|
return searchFilteredData.slice(start, end);
|
|
@@ -2863,7 +2965,8 @@ function DataGrid({
|
|
|
2863
2965
|
setCurrentPage(1);
|
|
2864
2966
|
}, [columnFilters, globalSearchTerm]);
|
|
2865
2967
|
const selectionBounds = (0, import_react10.useMemo)(() => {
|
|
2866
|
-
if (!selectionStart || !selectionEnd)
|
|
2968
|
+
if (!selectionStart || !selectionEnd)
|
|
2969
|
+
return null;
|
|
2867
2970
|
return {
|
|
2868
2971
|
minRow: Math.min(selectionStart.row, selectionEnd.row),
|
|
2869
2972
|
maxRow: Math.max(selectionStart.row, selectionEnd.row),
|
|
@@ -2872,16 +2975,19 @@ function DataGrid({
|
|
|
2872
2975
|
};
|
|
2873
2976
|
}, [selectionStart, selectionEnd]);
|
|
2874
2977
|
const selectionStats = (0, import_react10.useMemo)(() => {
|
|
2875
|
-
if (!selectionBounds)
|
|
2978
|
+
if (!selectionBounds)
|
|
2979
|
+
return null;
|
|
2876
2980
|
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
2877
2981
|
const values = [];
|
|
2878
2982
|
let count = 0;
|
|
2879
2983
|
for (let r = minRow; r <= maxRow; r++) {
|
|
2880
2984
|
const row = rows[r];
|
|
2881
|
-
if (!row)
|
|
2985
|
+
if (!row)
|
|
2986
|
+
continue;
|
|
2882
2987
|
for (let c = minCol; c <= maxCol; c++) {
|
|
2883
2988
|
const colId = columnKeys[c];
|
|
2884
|
-
if (!colId)
|
|
2989
|
+
if (!colId)
|
|
2990
|
+
continue;
|
|
2885
2991
|
const value = row.original[colId];
|
|
2886
2992
|
count++;
|
|
2887
2993
|
if (value !== null && value !== void 0 && value !== "") {
|
|
@@ -2892,19 +2998,23 @@ function DataGrid({
|
|
|
2892
2998
|
}
|
|
2893
2999
|
}
|
|
2894
3000
|
}
|
|
2895
|
-
if (values.length === 0)
|
|
3001
|
+
if (values.length === 0)
|
|
3002
|
+
return { count, sum: null, avg: null, numericCount: 0 };
|
|
2896
3003
|
const sum = values.reduce((a, b) => a + b, 0);
|
|
2897
3004
|
const avg = sum / values.length;
|
|
2898
3005
|
return { count, sum, avg, numericCount: values.length };
|
|
2899
3006
|
}, [selectionBounds, rows, columnKeys]);
|
|
2900
3007
|
(0, import_react10.useEffect)(() => {
|
|
2901
|
-
if (typeof document === "undefined")
|
|
2902
|
-
|
|
3008
|
+
if (typeof document === "undefined")
|
|
3009
|
+
return;
|
|
3010
|
+
if (data.length === 0)
|
|
3011
|
+
return;
|
|
2903
3012
|
const widths = {};
|
|
2904
3013
|
const sampleSize = Math.min(100, data.length);
|
|
2905
3014
|
const canvas = document.createElement("canvas");
|
|
2906
3015
|
const ctx = canvas.getContext("2d");
|
|
2907
|
-
if (!ctx)
|
|
3016
|
+
if (!ctx)
|
|
3017
|
+
return;
|
|
2908
3018
|
ctx.font = "13px system-ui, -apple-system, sans-serif";
|
|
2909
3019
|
for (const key of columnKeys) {
|
|
2910
3020
|
let maxWidth = ctx.measureText(key).width + 56;
|
|
@@ -2920,7 +3030,8 @@ function DataGrid({
|
|
|
2920
3030
|
}, [data, columnKeys]);
|
|
2921
3031
|
const startColumnResize = (0, import_react10.useCallback)(
|
|
2922
3032
|
(columnId, event) => {
|
|
2923
|
-
if (!enableColumnResize)
|
|
3033
|
+
if (!enableColumnResize)
|
|
3034
|
+
return;
|
|
2924
3035
|
event.preventDefault();
|
|
2925
3036
|
event.stopPropagation();
|
|
2926
3037
|
setResizingColumnId(columnId);
|
|
@@ -2930,7 +3041,8 @@ function DataGrid({
|
|
|
2930
3041
|
[enableColumnResize, columnWidths]
|
|
2931
3042
|
);
|
|
2932
3043
|
(0, import_react10.useEffect)(() => {
|
|
2933
|
-
if (!resizingColumnId)
|
|
3044
|
+
if (!resizingColumnId)
|
|
3045
|
+
return;
|
|
2934
3046
|
const handleResizeMove = (event) => {
|
|
2935
3047
|
const diff = event.clientX - resizeStartX;
|
|
2936
3048
|
const newWidth = Math.max(MIN_COL_WIDTH, Math.min(MAX_COL_WIDTH, resizeStartWidth + diff));
|
|
@@ -2951,7 +3063,8 @@ function DataGrid({
|
|
|
2951
3063
|
}, [resizingColumnId, resizeStartX, resizeStartWidth]);
|
|
2952
3064
|
const startVerticalResize = (0, import_react10.useCallback)(
|
|
2953
3065
|
(event) => {
|
|
2954
|
-
if (!enableVerticalResize)
|
|
3066
|
+
if (!enableVerticalResize)
|
|
3067
|
+
return;
|
|
2955
3068
|
event.preventDefault();
|
|
2956
3069
|
setIsResizingVertically(true);
|
|
2957
3070
|
setVerticalResizeStartY(event.clientY);
|
|
@@ -2960,7 +3073,8 @@ function DataGrid({
|
|
|
2960
3073
|
[enableVerticalResize, gridHeight]
|
|
2961
3074
|
);
|
|
2962
3075
|
(0, import_react10.useEffect)(() => {
|
|
2963
|
-
if (!isResizingVertically)
|
|
3076
|
+
if (!isResizingVertically)
|
|
3077
|
+
return;
|
|
2964
3078
|
const handleVerticalResizeMove = (event) => {
|
|
2965
3079
|
const diff = event.clientY - verticalResizeStartY;
|
|
2966
3080
|
const newHeight = Math.max(minHeight, Math.min(maxHeight, verticalResizeStartHeight + diff));
|
|
@@ -2978,7 +3092,8 @@ function DataGrid({
|
|
|
2978
3092
|
}, [isResizingVertically, verticalResizeStartY, verticalResizeStartHeight, minHeight, maxHeight]);
|
|
2979
3093
|
const handleExport = (0, import_react10.useCallback)(() => {
|
|
2980
3094
|
if (viewMode === "pivot") {
|
|
2981
|
-
if (!pivotResult)
|
|
3095
|
+
if (!pivotResult)
|
|
3096
|
+
return;
|
|
2982
3097
|
const pivotFilename = exportFilename.replace(".csv", "-pivot.csv");
|
|
2983
3098
|
exportPivotToCSV(
|
|
2984
3099
|
{
|
|
@@ -3022,7 +3137,8 @@ function DataGrid({
|
|
|
3022
3137
|
onExport
|
|
3023
3138
|
]);
|
|
3024
3139
|
const copySelectionToClipboard = (0, import_react10.useCallback)(() => {
|
|
3025
|
-
if (!selectionBounds || !enableClipboard)
|
|
3140
|
+
if (!selectionBounds || !enableClipboard)
|
|
3141
|
+
return;
|
|
3026
3142
|
const text = formatSelectionForClipboard(
|
|
3027
3143
|
rows.map((r) => r.original),
|
|
3028
3144
|
columnKeys,
|
|
@@ -3179,7 +3295,8 @@ function DataGrid({
|
|
|
3179
3295
|
[selectionBounds, selectedCell]
|
|
3180
3296
|
);
|
|
3181
3297
|
const formatStatValue = (value) => {
|
|
3182
|
-
if (value === null)
|
|
3298
|
+
if (value === null)
|
|
3299
|
+
return "-";
|
|
3183
3300
|
if (Math.abs(value) >= 1e3) {
|
|
3184
3301
|
return value.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
3185
3302
|
}
|
|
@@ -3190,12 +3307,15 @@ function DataGrid({
|
|
|
3190
3307
|
return !noFormatPatterns.test(columnId);
|
|
3191
3308
|
};
|
|
3192
3309
|
const formatCellValueDisplay = (value, columnId) => {
|
|
3193
|
-
if (value === null || value === void 0)
|
|
3194
|
-
|
|
3310
|
+
if (value === null || value === void 0)
|
|
3311
|
+
return "";
|
|
3312
|
+
if (value === "")
|
|
3313
|
+
return "";
|
|
3195
3314
|
const stats = getColumnStats(columnId);
|
|
3196
3315
|
if (stats.type === "number") {
|
|
3197
3316
|
const num = typeof value === "number" ? value : Number.parseFloat(String(value));
|
|
3198
|
-
if (Number.isNaN(num))
|
|
3317
|
+
if (Number.isNaN(num))
|
|
3318
|
+
return String(value);
|
|
3199
3319
|
if (shouldFormatNumber(columnId) && Math.abs(num) >= 1e3) {
|
|
3200
3320
|
return num.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
3201
3321
|
}
|
|
@@ -3357,13 +3477,15 @@ function DataGrid({
|
|
|
3357
3477
|
) }),
|
|
3358
3478
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
3359
3479
|
activeFilterCount,
|
|
3360
|
-
"
|
|
3480
|
+
" ",
|
|
3481
|
+
"filter",
|
|
3361
3482
|
activeFilterCount > 1 ? "s" : ""
|
|
3362
3483
|
] })
|
|
3363
3484
|
] }),
|
|
3364
3485
|
globalSearchTerm && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "vpg-search-info", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
3365
3486
|
totalSearchedRows,
|
|
3366
|
-
"
|
|
3487
|
+
" ",
|
|
3488
|
+
"match",
|
|
3367
3489
|
totalSearchedRows !== 1 ? "es" : ""
|
|
3368
3490
|
] }) })
|
|
3369
3491
|
] }),
|
|
@@ -3384,7 +3506,8 @@ function DataGrid({
|
|
|
3384
3506
|
}
|
|
3385
3507
|
) }),
|
|
3386
3508
|
showPivotConfig ? "Hide" : "Show",
|
|
3387
|
-
"
|
|
3509
|
+
" ",
|
|
3510
|
+
"Config"
|
|
3388
3511
|
]
|
|
3389
3512
|
}
|
|
3390
3513
|
),
|
|
@@ -3506,7 +3629,7 @@ function DataGrid({
|
|
|
3506
3629
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "vpg-clear-link", onClick: clearAllFilters, children: "Clear all filters" })
|
|
3507
3630
|
] }),
|
|
3508
3631
|
!loading && filteredRowCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "vpg-table-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { className: "vpg-table", style: { minWidth: `${totalTableWidth}px` }, children: [
|
|
3509
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: columnKeys.map((colId
|
|
3632
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: columnKeys.map((colId) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
3510
3633
|
"th",
|
|
3511
3634
|
{
|
|
3512
3635
|
className: `vpg-header-cell ${hasActiveFilter(colId) ? "vpg-has-filter" : ""} ${getSortDirection(colId) !== null ? "vpg-is-sorted" : ""} ${activeFilterColumn === colId ? "vpg-is-active" : ""}`,
|
|
@@ -3635,7 +3758,7 @@ function DataGrid({
|
|
|
3635
3758
|
onShowColumnTotalsChange: setPivotShowColumnTotals,
|
|
3636
3759
|
onClearConfig: clearPivotConfig,
|
|
3637
3760
|
onAutoSuggest: autoSuggestConfig,
|
|
3638
|
-
onDragStart: (field,
|
|
3761
|
+
onDragStart: (field, _e) => setDraggingField(field),
|
|
3639
3762
|
onDragEnd: () => setDraggingField(null),
|
|
3640
3763
|
onUpdateAggregation: updateValueFieldAggregation,
|
|
3641
3764
|
onAddRowField: addRowField,
|
|
@@ -3687,11 +3810,13 @@ function DataGrid({
|
|
|
3687
3810
|
totalSearchedRows !== totalRowCount && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "vpg-filtered-note", children: [
|
|
3688
3811
|
"(",
|
|
3689
3812
|
totalRowCount.toLocaleString(),
|
|
3690
|
-
"
|
|
3813
|
+
" ",
|
|
3814
|
+
"total)"
|
|
3691
3815
|
] })
|
|
3692
3816
|
] }) : filteredRowCount === totalRowCount && totalSearchedRows === totalRowCount ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
3693
3817
|
totalRowCount.toLocaleString(),
|
|
3694
|
-
"
|
|
3818
|
+
" ",
|
|
3819
|
+
"records"
|
|
3695
3820
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
3696
3821
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vpg-filtered-count", children: totalSearchedRows.toLocaleString() }),
|
|
3697
3822
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vpg-separator", children: "of" }),
|
|
@@ -3702,7 +3827,8 @@ function DataGrid({
|
|
|
3702
3827
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vpg-separator", children: "\u2022" }),
|
|
3703
3828
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
|
|
3704
3829
|
totalRowCount.toLocaleString(),
|
|
3705
|
-
"
|
|
3830
|
+
" ",
|
|
3831
|
+
"source records"
|
|
3706
3832
|
] })
|
|
3707
3833
|
] }) }),
|
|
3708
3834
|
enablePagination && viewMode === "grid" && totalPages > 1 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vpg-pagination", children: [
|
|
@@ -3741,9 +3867,12 @@ function DataGrid({
|
|
|
3741
3867
|
}
|
|
3742
3868
|
),
|
|
3743
3869
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "vpg-page-info", children: [
|
|
3744
|
-
"Page
|
|
3870
|
+
"Page",
|
|
3871
|
+
" ",
|
|
3745
3872
|
currentPage,
|
|
3746
|
-
"
|
|
3873
|
+
" ",
|
|
3874
|
+
"of",
|
|
3875
|
+
" ",
|
|
3747
3876
|
totalPages
|
|
3748
3877
|
] }),
|
|
3749
3878
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|