@smallwebco/tinypivot-react 1.0.49 → 1.0.51
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 +1615 -1512
- 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 +1604 -1501
- package/dist/index.js.map +1 -1
- package/package.json +39 -39
package/dist/index.js
CHANGED
|
@@ -1,1296 +1,701 @@
|
|
|
1
|
-
// src/components/
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (cellValue === null || cellValue === void 0 || cellValue === "") {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
const num = typeof cellValue === "number" ? cellValue : Number.parseFloat(String(cellValue));
|
|
22
|
-
if (Number.isNaN(num)) return false;
|
|
23
|
-
const { min, max } = filterValue;
|
|
24
|
-
if (min !== null && num < min) return false;
|
|
25
|
-
if (max !== null && num > max) return false;
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
if (Array.isArray(filterValue) && filterValue.length > 0) {
|
|
29
|
-
const cellValue = row.getValue(columnId);
|
|
30
|
-
const cellString = cellValue === null || cellValue === void 0 || cellValue === "" ? "(blank)" : String(cellValue);
|
|
31
|
-
return filterValue.includes(cellString);
|
|
32
|
-
}
|
|
33
|
-
return true;
|
|
34
|
-
};
|
|
35
|
-
function useExcelGrid(options) {
|
|
36
|
-
const { data, enableSorting = true, enableFiltering = true } = options;
|
|
37
|
-
const [sorting, setSorting] = useState([]);
|
|
38
|
-
const [columnFilters, setColumnFilters] = useState([]);
|
|
39
|
-
const [columnVisibility, setColumnVisibility] = useState({});
|
|
40
|
-
const [globalFilter, setGlobalFilter] = useState("");
|
|
41
|
-
const [columnStatsCache, setColumnStatsCache] = useState({});
|
|
42
|
-
const dataSignature = useMemo(
|
|
43
|
-
() => `${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
44
|
-
[data]
|
|
45
|
-
);
|
|
46
|
-
const columnKeys = useMemo(() => {
|
|
47
|
-
if (data.length === 0) return [];
|
|
48
|
-
return Object.keys(data[0]);
|
|
49
|
-
}, [data]);
|
|
50
|
-
const getColumnStats = useCallback(
|
|
51
|
-
(columnKey) => {
|
|
52
|
-
const cacheKey = `${columnKey}-${dataSignature}`;
|
|
53
|
-
if (!columnStatsCache[cacheKey]) {
|
|
54
|
-
const stats = getColumnUniqueValues(data, columnKey);
|
|
55
|
-
setColumnStatsCache((prev) => ({ ...prev, [cacheKey]: stats }));
|
|
56
|
-
return stats;
|
|
57
|
-
}
|
|
58
|
-
return columnStatsCache[cacheKey];
|
|
59
|
-
},
|
|
60
|
-
[data, columnStatsCache, dataSignature]
|
|
61
|
-
);
|
|
62
|
-
const clearStatsCache = useCallback(() => {
|
|
63
|
-
setColumnStatsCache({});
|
|
64
|
-
}, []);
|
|
1
|
+
// src/components/CalculatedFieldModal.tsx
|
|
2
|
+
import { validateSimpleFormula } from "@smallwebco/tinypivot-core";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
function CalculatedFieldModal({
|
|
7
|
+
show,
|
|
8
|
+
availableFields,
|
|
9
|
+
existingField,
|
|
10
|
+
onClose,
|
|
11
|
+
onSave
|
|
12
|
+
}) {
|
|
13
|
+
const [name, setName] = useState("");
|
|
14
|
+
const [formula, setFormula] = useState("");
|
|
15
|
+
const [formatAs, setFormatAs] = useState("number");
|
|
16
|
+
const [decimals, setDecimals] = useState(2);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
65
18
|
useEffect(() => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
meta: {
|
|
78
|
-
type: stats.type,
|
|
79
|
-
uniqueCount: stats.uniqueValues.length
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
});
|
|
83
|
-
}, [columnKeys, getColumnStats]);
|
|
84
|
-
const table = useReactTable({
|
|
85
|
-
data,
|
|
86
|
-
columns: columnDefs,
|
|
87
|
-
state: {
|
|
88
|
-
sorting,
|
|
89
|
-
columnFilters,
|
|
90
|
-
columnVisibility,
|
|
91
|
-
globalFilter
|
|
92
|
-
},
|
|
93
|
-
onSortingChange: setSorting,
|
|
94
|
-
onColumnFiltersChange: setColumnFilters,
|
|
95
|
-
onColumnVisibilityChange: setColumnVisibility,
|
|
96
|
-
onGlobalFilterChange: setGlobalFilter,
|
|
97
|
-
getCoreRowModel: getCoreRowModel(),
|
|
98
|
-
getSortedRowModel: enableSorting ? getSortedRowModel() : void 0,
|
|
99
|
-
getFilteredRowModel: enableFiltering ? getFilteredRowModel() : void 0,
|
|
100
|
-
filterFns: {
|
|
101
|
-
multiSelect: multiSelectFilter
|
|
102
|
-
},
|
|
103
|
-
enableSorting,
|
|
104
|
-
enableFilters: enableFiltering
|
|
105
|
-
});
|
|
106
|
-
const filteredRowCount = table.getFilteredRowModel().rows.length;
|
|
107
|
-
const totalRowCount = data.length;
|
|
108
|
-
const activeFilters = useMemo(() => {
|
|
109
|
-
return columnFilters.map((f) => {
|
|
110
|
-
const filterValue = f.value;
|
|
111
|
-
if (filterValue && isNumericRange(filterValue)) {
|
|
112
|
-
return {
|
|
113
|
-
column: f.id,
|
|
114
|
-
type: "range",
|
|
115
|
-
range: filterValue,
|
|
116
|
-
values: []
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
column: f.id,
|
|
121
|
-
type: "values",
|
|
122
|
-
values: Array.isArray(filterValue) ? filterValue : [],
|
|
123
|
-
range: null
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
}, [columnFilters]);
|
|
127
|
-
const hasActiveFilter = useCallback(
|
|
128
|
-
(columnId) => {
|
|
129
|
-
const column = table.getColumn(columnId);
|
|
130
|
-
if (!column) return false;
|
|
131
|
-
const filterValue = column.getFilterValue();
|
|
132
|
-
if (!filterValue) return false;
|
|
133
|
-
if (isNumericRange(filterValue)) {
|
|
134
|
-
return filterValue.min !== null || filterValue.max !== null;
|
|
135
|
-
}
|
|
136
|
-
return Array.isArray(filterValue) && filterValue.length > 0;
|
|
137
|
-
},
|
|
138
|
-
[table]
|
|
139
|
-
);
|
|
140
|
-
const setColumnFilter = useCallback(
|
|
141
|
-
(columnId, values) => {
|
|
142
|
-
const column = table.getColumn(columnId);
|
|
143
|
-
if (column) {
|
|
144
|
-
column.setFilterValue(values.length === 0 ? void 0 : values);
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
[table]
|
|
148
|
-
);
|
|
149
|
-
const setNumericRangeFilter = useCallback(
|
|
150
|
-
(columnId, range) => {
|
|
151
|
-
const column = table.getColumn(columnId);
|
|
152
|
-
if (column) {
|
|
153
|
-
if (!range || range.min === null && range.max === null) {
|
|
154
|
-
column.setFilterValue(void 0);
|
|
155
|
-
} else {
|
|
156
|
-
column.setFilterValue(range);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
[table]
|
|
161
|
-
);
|
|
162
|
-
const getNumericRangeFilter = useCallback(
|
|
163
|
-
(columnId) => {
|
|
164
|
-
const column = table.getColumn(columnId);
|
|
165
|
-
if (!column) return null;
|
|
166
|
-
const filterValue = column.getFilterValue();
|
|
167
|
-
if (filterValue && isNumericRange(filterValue)) {
|
|
168
|
-
return filterValue;
|
|
19
|
+
if (show) {
|
|
20
|
+
if (existingField) {
|
|
21
|
+
setName(existingField.name);
|
|
22
|
+
setFormula(existingField.formula);
|
|
23
|
+
setFormatAs(existingField.formatAs || "number");
|
|
24
|
+
setDecimals(existingField.decimals ?? 2);
|
|
25
|
+
} else {
|
|
26
|
+
setName("");
|
|
27
|
+
setFormula("");
|
|
28
|
+
setFormatAs("number");
|
|
29
|
+
setDecimals(2);
|
|
169
30
|
}
|
|
31
|
+
setError(null);
|
|
32
|
+
}
|
|
33
|
+
}, [show, existingField]);
|
|
34
|
+
const validationError = useMemo(() => {
|
|
35
|
+
if (!formula.trim())
|
|
170
36
|
return null;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
setColumnFilters([]);
|
|
178
|
-
}, [table]);
|
|
179
|
-
const getColumnFilterValues = useCallback(
|
|
180
|
-
(columnId) => {
|
|
181
|
-
const column = table.getColumn(columnId);
|
|
182
|
-
if (!column) return [];
|
|
183
|
-
const filterValue = column.getFilterValue();
|
|
184
|
-
return Array.isArray(filterValue) ? filterValue : [];
|
|
185
|
-
},
|
|
186
|
-
[table]
|
|
187
|
-
);
|
|
188
|
-
const toggleSort = useCallback((columnId) => {
|
|
189
|
-
setSorting((prev) => {
|
|
190
|
-
const current = prev.find((s) => s.id === columnId);
|
|
191
|
-
if (!current) {
|
|
192
|
-
return [{ id: columnId, desc: false }];
|
|
193
|
-
} else if (!current.desc) {
|
|
194
|
-
return [{ id: columnId, desc: true }];
|
|
195
|
-
} else {
|
|
196
|
-
return [];
|
|
37
|
+
return validateSimpleFormula(formula, availableFields);
|
|
38
|
+
}, [formula, availableFields]);
|
|
39
|
+
const insertField = useCallback((field) => {
|
|
40
|
+
setFormula((prev) => {
|
|
41
|
+
if (prev.trim() && !prev.endsWith(" ")) {
|
|
42
|
+
return `${prev} ${field}`;
|
|
197
43
|
}
|
|
44
|
+
return prev + field;
|
|
198
45
|
});
|
|
199
46
|
}, []);
|
|
200
|
-
const
|
|
201
|
-
(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
sorting,
|
|
213
|
-
columnFilters,
|
|
214
|
-
columnVisibility,
|
|
215
|
-
globalFilter,
|
|
216
|
-
columnKeys,
|
|
217
|
-
setSorting,
|
|
218
|
-
setColumnFilters,
|
|
219
|
-
setGlobalFilter,
|
|
220
|
-
// Computed
|
|
221
|
-
filteredRowCount,
|
|
222
|
-
totalRowCount,
|
|
223
|
-
activeFilters,
|
|
224
|
-
// Methods
|
|
225
|
-
getColumnStats,
|
|
226
|
-
clearStatsCache,
|
|
227
|
-
hasActiveFilter,
|
|
228
|
-
setColumnFilter,
|
|
229
|
-
getColumnFilterValues,
|
|
230
|
-
clearAllFilters,
|
|
231
|
-
toggleSort,
|
|
232
|
-
getSortDirection,
|
|
233
|
-
// Numeric range filters
|
|
234
|
-
setNumericRangeFilter,
|
|
235
|
-
getNumericRangeFilter
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// src/hooks/usePivotTable.ts
|
|
240
|
-
import { useState as useState3, useMemo as useMemo3, useEffect as useEffect2, useCallback as useCallback3 } from "react";
|
|
241
|
-
import {
|
|
242
|
-
computeAvailableFields,
|
|
243
|
-
getUnassignedFields,
|
|
244
|
-
isPivotConfigured,
|
|
245
|
-
computePivotResult,
|
|
246
|
-
generateStorageKey,
|
|
247
|
-
savePivotConfig,
|
|
248
|
-
loadPivotConfig,
|
|
249
|
-
isConfigValidForFields,
|
|
250
|
-
getAggregationLabel,
|
|
251
|
-
loadCalculatedFields,
|
|
252
|
-
saveCalculatedFields
|
|
253
|
-
} from "@smallwebco/tinypivot-core";
|
|
254
|
-
|
|
255
|
-
// src/hooks/useLicense.ts
|
|
256
|
-
import { useState as useState2, useCallback as useCallback2, useMemo as useMemo2 } from "react";
|
|
257
|
-
import {
|
|
258
|
-
validateLicenseKey,
|
|
259
|
-
configureLicenseSecret as coreConfigureLicenseSecret,
|
|
260
|
-
getDemoLicenseInfo,
|
|
261
|
-
getFreeLicenseInfo,
|
|
262
|
-
canUsePivot as coreCanUsePivot,
|
|
263
|
-
isPro as coreIsPro,
|
|
264
|
-
shouldShowWatermark as coreShouldShowWatermark,
|
|
265
|
-
logProRequired
|
|
266
|
-
} from "@smallwebco/tinypivot-core";
|
|
267
|
-
var globalLicenseInfo = getFreeLicenseInfo();
|
|
268
|
-
var globalDemoMode = false;
|
|
269
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
270
|
-
function notifyListeners() {
|
|
271
|
-
listeners.forEach((listener) => listener());
|
|
272
|
-
}
|
|
273
|
-
async function setLicenseKey(key) {
|
|
274
|
-
globalLicenseInfo = await validateLicenseKey(key);
|
|
275
|
-
if (!globalLicenseInfo.isValid) {
|
|
276
|
-
console.warn("[TinyPivot] Invalid or expired license key. Running in free mode.");
|
|
277
|
-
} else if (globalLicenseInfo.type !== "free") {
|
|
278
|
-
console.info(`[TinyPivot] Pro license activated (${globalLicenseInfo.type})`);
|
|
279
|
-
}
|
|
280
|
-
notifyListeners();
|
|
281
|
-
}
|
|
282
|
-
async function enableDemoMode(secret) {
|
|
283
|
-
const demoLicense = await getDemoLicenseInfo(secret);
|
|
284
|
-
if (!demoLicense) {
|
|
285
|
-
console.warn("[TinyPivot] Demo mode activation failed - invalid secret");
|
|
286
|
-
return false;
|
|
287
|
-
}
|
|
288
|
-
globalDemoMode = true;
|
|
289
|
-
globalLicenseInfo = demoLicense;
|
|
290
|
-
console.info("[TinyPivot] Demo mode enabled - all Pro features unlocked for evaluation");
|
|
291
|
-
notifyListeners();
|
|
292
|
-
return true;
|
|
293
|
-
}
|
|
294
|
-
function configureLicenseSecret(secret) {
|
|
295
|
-
coreConfigureLicenseSecret(secret);
|
|
296
|
-
}
|
|
297
|
-
function useLicense() {
|
|
298
|
-
const [, forceUpdate] = useState2({});
|
|
299
|
-
useState2(() => {
|
|
300
|
-
const update = () => forceUpdate({});
|
|
301
|
-
listeners.add(update);
|
|
302
|
-
return () => listeners.delete(update);
|
|
303
|
-
});
|
|
304
|
-
const isDemo = globalDemoMode;
|
|
305
|
-
const licenseInfo = globalLicenseInfo;
|
|
306
|
-
const isPro = useMemo2(
|
|
307
|
-
() => globalDemoMode || coreIsPro(licenseInfo),
|
|
308
|
-
[licenseInfo]
|
|
309
|
-
);
|
|
310
|
-
const canUsePivot = useMemo2(
|
|
311
|
-
() => globalDemoMode || coreCanUsePivot(licenseInfo),
|
|
312
|
-
[licenseInfo]
|
|
313
|
-
);
|
|
314
|
-
const canUseAdvancedAggregations = useMemo2(
|
|
315
|
-
() => globalDemoMode || licenseInfo.features.advancedAggregations,
|
|
316
|
-
[licenseInfo]
|
|
317
|
-
);
|
|
318
|
-
const canUsePercentageMode = useMemo2(
|
|
319
|
-
() => globalDemoMode || licenseInfo.features.percentageMode,
|
|
320
|
-
[licenseInfo]
|
|
321
|
-
);
|
|
322
|
-
const showWatermark = useMemo2(
|
|
323
|
-
() => coreShouldShowWatermark(licenseInfo, globalDemoMode),
|
|
324
|
-
[licenseInfo]
|
|
325
|
-
);
|
|
326
|
-
const requirePro = useCallback2((feature) => {
|
|
327
|
-
if (!isPro) {
|
|
328
|
-
logProRequired(feature);
|
|
329
|
-
return false;
|
|
47
|
+
const insertOperator = useCallback((op) => {
|
|
48
|
+
setFormula((prev) => {
|
|
49
|
+
if (prev.trim() && !prev.endsWith(" ")) {
|
|
50
|
+
return `${prev} ${op} `;
|
|
51
|
+
}
|
|
52
|
+
return `${prev + op} `;
|
|
53
|
+
});
|
|
54
|
+
}, []);
|
|
55
|
+
const handleSave = useCallback(() => {
|
|
56
|
+
if (!name.trim()) {
|
|
57
|
+
setError("Name is required");
|
|
58
|
+
return;
|
|
330
59
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
60
|
+
const validationResult = validateSimpleFormula(formula, availableFields);
|
|
61
|
+
if (validationResult) {
|
|
62
|
+
setError(validationResult);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const field = {
|
|
66
|
+
id: existingField?.id || `calc_${Date.now()}`,
|
|
67
|
+
name: name.trim(),
|
|
68
|
+
formula: formula.trim(),
|
|
69
|
+
formatAs,
|
|
70
|
+
decimals
|
|
71
|
+
};
|
|
72
|
+
onSave(field);
|
|
73
|
+
onClose();
|
|
74
|
+
}, [name, formula, formatAs, decimals, existingField, availableFields, onSave, onClose]);
|
|
75
|
+
const handleOverlayClick = useCallback((e) => {
|
|
76
|
+
if (e.target === e.currentTarget) {
|
|
77
|
+
onClose();
|
|
78
|
+
}
|
|
79
|
+
}, [onClose]);
|
|
80
|
+
if (!show)
|
|
81
|
+
return null;
|
|
82
|
+
const modalContent = /* @__PURE__ */ jsx("div", { className: "vpg-modal-overlay", onClick: handleOverlayClick, children: /* @__PURE__ */ jsxs("div", { className: "vpg-modal", children: [
|
|
83
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-modal-header", children: [
|
|
84
|
+
/* @__PURE__ */ jsxs("h3", { children: [
|
|
85
|
+
existingField ? "Edit" : "Create",
|
|
86
|
+
" ",
|
|
87
|
+
"Calculated Field"
|
|
88
|
+
] }),
|
|
89
|
+
/* @__PURE__ */ jsx("button", { className: "vpg-modal-close", onClick: onClose, children: "\xD7" })
|
|
90
|
+
] }),
|
|
91
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-modal-body", children: [
|
|
92
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-form-group", children: [
|
|
93
|
+
/* @__PURE__ */ jsx("label", { className: "vpg-label", children: "Name" }),
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
"input",
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
className: "vpg-input",
|
|
99
|
+
placeholder: "e.g., Profit Margin %",
|
|
100
|
+
value: name,
|
|
101
|
+
onChange: (e) => setName(e.target.value)
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] }),
|
|
105
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-form-group", children: [
|
|
106
|
+
/* @__PURE__ */ jsx("label", { className: "vpg-label", children: "Formula" }),
|
|
107
|
+
/* @__PURE__ */ jsx(
|
|
108
|
+
"textarea",
|
|
109
|
+
{
|
|
110
|
+
className: "vpg-textarea",
|
|
111
|
+
placeholder: "e.g., revenue / units",
|
|
112
|
+
rows: 2,
|
|
113
|
+
value: formula,
|
|
114
|
+
onChange: (e) => setFormula(e.target.value)
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
/* @__PURE__ */ jsx("div", { className: "vpg-formula-hint", children: "Use field names with math operators: + - * / ( )" }),
|
|
118
|
+
validationError && /* @__PURE__ */ jsx("div", { className: "vpg-error", children: validationError })
|
|
119
|
+
] }),
|
|
120
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-form-group", children: [
|
|
121
|
+
/* @__PURE__ */ jsx("label", { className: "vpg-label-small", children: "Operators" }),
|
|
122
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-button-group", children: [
|
|
123
|
+
/* @__PURE__ */ jsx("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("+"), children: "+" }),
|
|
124
|
+
/* @__PURE__ */ jsx("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("-"), children: "\u2212" }),
|
|
125
|
+
/* @__PURE__ */ jsx("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("*"), children: "\xD7" }),
|
|
126
|
+
/* @__PURE__ */ jsx("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("/"), children: "\xF7" }),
|
|
127
|
+
/* @__PURE__ */ jsx("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("("), children: "(" }),
|
|
128
|
+
/* @__PURE__ */ jsx("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator(")"), children: ")" })
|
|
129
|
+
] })
|
|
130
|
+
] }),
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-form-group", children: [
|
|
132
|
+
/* @__PURE__ */ jsx("label", { className: "vpg-label-small", children: "Insert Field" }),
|
|
133
|
+
availableFields.length > 0 ? /* @__PURE__ */ jsx("div", { className: "vpg-button-group vpg-field-buttons", children: availableFields.map((field) => /* @__PURE__ */ jsx(
|
|
134
|
+
"button",
|
|
135
|
+
{
|
|
136
|
+
className: "vpg-insert-btn vpg-field-btn",
|
|
137
|
+
onClick: () => insertField(field),
|
|
138
|
+
children: field
|
|
139
|
+
},
|
|
140
|
+
field
|
|
141
|
+
)) }) : /* @__PURE__ */ jsx("div", { className: "vpg-no-fields", children: "No numeric fields available" })
|
|
142
|
+
] }),
|
|
143
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-form-row", children: [
|
|
144
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-form-group vpg-form-group-half", children: [
|
|
145
|
+
/* @__PURE__ */ jsx("label", { className: "vpg-label", children: "Format As" }),
|
|
146
|
+
/* @__PURE__ */ jsxs(
|
|
147
|
+
"select",
|
|
148
|
+
{
|
|
149
|
+
className: "vpg-select",
|
|
150
|
+
value: formatAs,
|
|
151
|
+
onChange: (e) => setFormatAs(e.target.value),
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ jsx("option", { value: "number", children: "Number" }),
|
|
154
|
+
/* @__PURE__ */ jsx("option", { value: "percent", children: "Percentage" }),
|
|
155
|
+
/* @__PURE__ */ jsx("option", { value: "currency", children: "Currency ($)" })
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
] }),
|
|
160
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-form-group vpg-form-group-half", children: [
|
|
161
|
+
/* @__PURE__ */ jsx("label", { className: "vpg-label", children: "Decimals" }),
|
|
162
|
+
/* @__PURE__ */ jsx(
|
|
163
|
+
"input",
|
|
164
|
+
{
|
|
165
|
+
type: "number",
|
|
166
|
+
className: "vpg-input",
|
|
167
|
+
min: 0,
|
|
168
|
+
max: 6,
|
|
169
|
+
value: decimals,
|
|
170
|
+
onChange: (e) => setDecimals(Number(e.target.value))
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
] })
|
|
174
|
+
] }),
|
|
175
|
+
error && /* @__PURE__ */ jsx("div", { className: "vpg-error vpg-error-box", children: error })
|
|
176
|
+
] }),
|
|
177
|
+
/* @__PURE__ */ jsxs("div", { className: "vpg-modal-footer", children: [
|
|
178
|
+
/* @__PURE__ */ jsx("button", { className: "vpg-btn vpg-btn-secondary", onClick: onClose, children: "Cancel" }),
|
|
179
|
+
/* @__PURE__ */ jsxs("button", { className: "vpg-btn vpg-btn-primary", onClick: handleSave, children: [
|
|
180
|
+
existingField ? "Update" : "Add",
|
|
181
|
+
" ",
|
|
182
|
+
"Field"
|
|
183
|
+
] })
|
|
184
|
+
] })
|
|
185
|
+
] }) });
|
|
186
|
+
if (typeof document === "undefined")
|
|
187
|
+
return null;
|
|
188
|
+
return createPortal(modalContent, document.body);
|
|
343
189
|
}
|
|
344
190
|
|
|
345
|
-
// src/
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
191
|
+
// src/components/ColumnFilter.tsx
|
|
192
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo3, useRef, useState as useState3 } from "react";
|
|
193
|
+
|
|
194
|
+
// src/components/NumericRangeFilter.tsx
|
|
195
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
|
|
196
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
197
|
+
function NumericRangeFilter({
|
|
198
|
+
dataMin,
|
|
199
|
+
dataMax,
|
|
200
|
+
currentRange,
|
|
201
|
+
onChange
|
|
202
|
+
}) {
|
|
203
|
+
const [localMin, setLocalMin] = useState2(currentRange?.min ?? null);
|
|
204
|
+
const [localMax, setLocalMax] = useState2(currentRange?.max ?? null);
|
|
205
|
+
const step = useMemo2(() => {
|
|
206
|
+
const range = dataMax - dataMin;
|
|
207
|
+
if (range === 0)
|
|
208
|
+
return 1;
|
|
209
|
+
if (range <= 1)
|
|
210
|
+
return 0.01;
|
|
211
|
+
if (range <= 10)
|
|
212
|
+
return 0.1;
|
|
213
|
+
if (range <= 100)
|
|
214
|
+
return 1;
|
|
215
|
+
if (range <= 1e3)
|
|
216
|
+
return 10;
|
|
217
|
+
return 10 ** (Math.floor(Math.log10(range)) - 2);
|
|
218
|
+
}, [dataMin, dataMax]);
|
|
219
|
+
const formatValue = useCallback2((val) => {
|
|
220
|
+
if (val === null)
|
|
221
|
+
return "";
|
|
222
|
+
if (Number.isInteger(val))
|
|
223
|
+
return val.toLocaleString();
|
|
224
|
+
return val.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
225
|
+
}, []);
|
|
226
|
+
const isFilterActive = localMin !== null || localMax !== null;
|
|
227
|
+
const minPercent = useMemo2(() => {
|
|
228
|
+
if (localMin === null || dataMax === dataMin)
|
|
229
|
+
return 0;
|
|
230
|
+
return (localMin - dataMin) / (dataMax - dataMin) * 100;
|
|
231
|
+
}, [localMin, dataMin, dataMax]);
|
|
232
|
+
const maxPercent = useMemo2(() => {
|
|
233
|
+
if (localMax === null || dataMax === dataMin)
|
|
234
|
+
return 100;
|
|
235
|
+
return (localMax - dataMin) / (dataMax - dataMin) * 100;
|
|
236
|
+
}, [localMax, dataMin, dataMax]);
|
|
237
|
+
const handleMinSlider = useCallback2((event) => {
|
|
238
|
+
const value = Number.parseFloat(event.target.value);
|
|
239
|
+
setLocalMin(() => {
|
|
240
|
+
if (localMax !== null && value > localMax) {
|
|
241
|
+
return localMax;
|
|
242
|
+
}
|
|
243
|
+
return value;
|
|
380
244
|
});
|
|
381
|
-
}, [
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
setCurrentStorageKey(storageKey);
|
|
388
|
-
const savedConfig = loadPivotConfig(storageKey);
|
|
389
|
-
if (savedConfig && isConfigValidForFields(savedConfig, newKeys)) {
|
|
390
|
-
setRowFieldsState(savedConfig.rowFields);
|
|
391
|
-
setColumnFieldsState(savedConfig.columnFields);
|
|
392
|
-
setValueFields(savedConfig.valueFields);
|
|
393
|
-
setShowRowTotals(savedConfig.showRowTotals);
|
|
394
|
-
setShowColumnTotals(savedConfig.showColumnTotals);
|
|
395
|
-
if (savedConfig.calculatedFields) {
|
|
396
|
-
setCalculatedFields(savedConfig.calculatedFields);
|
|
397
|
-
}
|
|
398
|
-
} else {
|
|
399
|
-
const currentConfig = {
|
|
400
|
-
rowFields,
|
|
401
|
-
columnFields,
|
|
402
|
-
valueFields,
|
|
403
|
-
showRowTotals,
|
|
404
|
-
showColumnTotals
|
|
405
|
-
};
|
|
406
|
-
if (!isConfigValidForFields(currentConfig, newKeys)) {
|
|
407
|
-
setRowFieldsState([]);
|
|
408
|
-
setColumnFieldsState([]);
|
|
409
|
-
setValueFields([]);
|
|
410
|
-
}
|
|
245
|
+
}, [localMax]);
|
|
246
|
+
const handleMaxSlider = useCallback2((event) => {
|
|
247
|
+
const value = Number.parseFloat(event.target.value);
|
|
248
|
+
setLocalMax(() => {
|
|
249
|
+
if (localMin !== null && value < localMin) {
|
|
250
|
+
return localMin;
|
|
411
251
|
}
|
|
252
|
+
return value;
|
|
253
|
+
});
|
|
254
|
+
}, [localMin]);
|
|
255
|
+
const handleSliderChange = useCallback2(() => {
|
|
256
|
+
if (localMin === null && localMax === null) {
|
|
257
|
+
onChange(null);
|
|
258
|
+
} else {
|
|
259
|
+
onChange({ min: localMin, max: localMax });
|
|
412
260
|
}
|
|
413
|
-
}, [
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}, []);
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}, []);
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
261
|
+
}, [localMin, localMax, onChange]);
|
|
262
|
+
const handleMinInput = useCallback2((event) => {
|
|
263
|
+
const value = event.target.value === "" ? null : Number.parseFloat(event.target.value);
|
|
264
|
+
if (value !== null && !Number.isNaN(value)) {
|
|
265
|
+
setLocalMin(Math.max(dataMin, Math.min(value, localMax ?? dataMax)));
|
|
266
|
+
} else if (value === null) {
|
|
267
|
+
setLocalMin(null);
|
|
268
|
+
}
|
|
269
|
+
}, [dataMin, dataMax, localMax]);
|
|
270
|
+
const handleMaxInput = useCallback2((event) => {
|
|
271
|
+
const value = event.target.value === "" ? null : Number.parseFloat(event.target.value);
|
|
272
|
+
if (value !== null && !Number.isNaN(value)) {
|
|
273
|
+
setLocalMax(Math.min(dataMax, Math.max(value, localMin ?? dataMin)));
|
|
274
|
+
} else if (value === null) {
|
|
275
|
+
setLocalMax(null);
|
|
276
|
+
}
|
|
277
|
+
}, [dataMin, dataMax, localMin]);
|
|
278
|
+
const handleInputBlur = useCallback2(() => {
|
|
279
|
+
if (localMin === null && localMax === null) {
|
|
280
|
+
onChange(null);
|
|
281
|
+
} else {
|
|
282
|
+
onChange({ min: localMin, max: localMax });
|
|
283
|
+
}
|
|
284
|
+
}, [localMin, localMax, onChange]);
|
|
285
|
+
const clearFilter = useCallback2(() => {
|
|
286
|
+
setLocalMin(null);
|
|
287
|
+
setLocalMax(null);
|
|
288
|
+
onChange(null);
|
|
289
|
+
}, [onChange]);
|
|
290
|
+
const setFullRange = useCallback2(() => {
|
|
291
|
+
setLocalMin(dataMin);
|
|
292
|
+
setLocalMax(dataMax);
|
|
293
|
+
onChange({ min: dataMin, max: dataMax });
|
|
294
|
+
}, [dataMin, dataMax, onChange]);
|
|
295
|
+
useEffect2(() => {
|
|
296
|
+
setLocalMin(currentRange?.min ?? null);
|
|
297
|
+
setLocalMax(currentRange?.max ?? null);
|
|
298
|
+
}, [currentRange]);
|
|
299
|
+
return /* @__PURE__ */ jsxs2("div", { className: "vpg-range-filter", children: [
|
|
300
|
+
/* @__PURE__ */ jsxs2("div", { className: "vpg-range-info", children: [
|
|
301
|
+
/* @__PURE__ */ jsx2("span", { className: "vpg-range-label", children: "Data range:" }),
|
|
302
|
+
/* @__PURE__ */ jsxs2("span", { className: "vpg-range-bounds", children: [
|
|
303
|
+
formatValue(dataMin),
|
|
304
|
+
" ",
|
|
305
|
+
"\u2013",
|
|
306
|
+
formatValue(dataMax)
|
|
307
|
+
] })
|
|
308
|
+
] }),
|
|
309
|
+
/* @__PURE__ */ jsxs2("div", { className: "vpg-slider-container", children: [
|
|
310
|
+
/* @__PURE__ */ jsx2("div", { className: "vpg-slider-track", children: /* @__PURE__ */ jsx2(
|
|
311
|
+
"div",
|
|
312
|
+
{
|
|
313
|
+
className: "vpg-slider-fill",
|
|
314
|
+
style: {
|
|
315
|
+
left: `${minPercent}%`,
|
|
316
|
+
right: `${100 - maxPercent}%`
|
|
317
|
+
}
|
|
462
318
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
319
|
+
) }),
|
|
320
|
+
/* @__PURE__ */ jsx2(
|
|
321
|
+
"input",
|
|
322
|
+
{
|
|
323
|
+
type: "range",
|
|
324
|
+
className: "vpg-slider vpg-slider-min",
|
|
325
|
+
min: dataMin,
|
|
326
|
+
max: dataMax,
|
|
327
|
+
step,
|
|
328
|
+
value: localMin ?? dataMin,
|
|
329
|
+
onChange: handleMinSlider,
|
|
330
|
+
onMouseUp: handleSliderChange,
|
|
331
|
+
onTouchEnd: handleSliderChange
|
|
332
|
+
}
|
|
333
|
+
),
|
|
334
|
+
/* @__PURE__ */ jsx2(
|
|
335
|
+
"input",
|
|
336
|
+
{
|
|
337
|
+
type: "range",
|
|
338
|
+
className: "vpg-slider vpg-slider-max",
|
|
339
|
+
min: dataMin,
|
|
340
|
+
max: dataMax,
|
|
341
|
+
step,
|
|
342
|
+
value: localMax ?? dataMax,
|
|
343
|
+
onChange: handleMaxSlider,
|
|
344
|
+
onMouseUp: handleSliderChange,
|
|
345
|
+
onTouchEnd: handleSliderChange
|
|
346
|
+
}
|
|
347
|
+
)
|
|
348
|
+
] }),
|
|
349
|
+
/* @__PURE__ */ jsxs2("div", { className: "vpg-range-inputs", children: [
|
|
350
|
+
/* @__PURE__ */ jsxs2("div", { className: "vpg-input-group", children: [
|
|
351
|
+
/* @__PURE__ */ jsx2("label", { className: "vpg-input-label", children: "Min" }),
|
|
352
|
+
/* @__PURE__ */ jsx2(
|
|
353
|
+
"input",
|
|
354
|
+
{
|
|
355
|
+
type: "number",
|
|
356
|
+
className: "vpg-range-input",
|
|
357
|
+
placeholder: formatValue(dataMin),
|
|
358
|
+
value: localMin ?? "",
|
|
359
|
+
step,
|
|
360
|
+
onChange: handleMinInput,
|
|
361
|
+
onBlur: handleInputBlur
|
|
482
362
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
)
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
363
|
+
)
|
|
364
|
+
] }),
|
|
365
|
+
/* @__PURE__ */ jsx2("span", { className: "vpg-input-separator", children: "to" }),
|
|
366
|
+
/* @__PURE__ */ jsxs2("div", { className: "vpg-input-group", children: [
|
|
367
|
+
/* @__PURE__ */ jsx2("label", { className: "vpg-input-label", children: "Max" }),
|
|
368
|
+
/* @__PURE__ */ jsx2(
|
|
369
|
+
"input",
|
|
370
|
+
{
|
|
371
|
+
type: "number",
|
|
372
|
+
className: "vpg-range-input",
|
|
373
|
+
placeholder: formatValue(dataMax),
|
|
374
|
+
value: localMax ?? "",
|
|
375
|
+
step,
|
|
376
|
+
onChange: handleMaxInput,
|
|
377
|
+
onBlur: handleInputBlur
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
] })
|
|
381
|
+
] }),
|
|
382
|
+
/* @__PURE__ */ jsxs2("div", { className: "vpg-range-actions", children: [
|
|
383
|
+
/* @__PURE__ */ jsxs2(
|
|
384
|
+
"button",
|
|
385
|
+
{
|
|
386
|
+
className: "vpg-range-btn",
|
|
387
|
+
disabled: !isFilterActive,
|
|
388
|
+
onClick: clearFilter,
|
|
389
|
+
children: [
|
|
390
|
+
/* @__PURE__ */ jsx2("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
391
|
+
"path",
|
|
392
|
+
{
|
|
393
|
+
strokeLinecap: "round",
|
|
394
|
+
strokeLinejoin: "round",
|
|
395
|
+
strokeWidth: 2,
|
|
396
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
397
|
+
}
|
|
398
|
+
) }),
|
|
399
|
+
"Clear"
|
|
400
|
+
]
|
|
401
|
+
}
|
|
402
|
+
),
|
|
403
|
+
/* @__PURE__ */ jsxs2("button", { className: "vpg-range-btn", onClick: setFullRange, children: [
|
|
404
|
+
/* @__PURE__ */ jsx2("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
405
|
+
"path",
|
|
406
|
+
{
|
|
407
|
+
strokeLinecap: "round",
|
|
408
|
+
strokeLinejoin: "round",
|
|
409
|
+
strokeWidth: 2,
|
|
410
|
+
d: "M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"
|
|
411
|
+
}
|
|
412
|
+
) }),
|
|
413
|
+
"Full Range"
|
|
414
|
+
] })
|
|
415
|
+
] }),
|
|
416
|
+
isFilterActive && /* @__PURE__ */ jsxs2("div", { className: "vpg-filter-summary", children: [
|
|
417
|
+
/* @__PURE__ */ jsx2("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
418
|
+
"path",
|
|
419
|
+
{
|
|
420
|
+
strokeLinecap: "round",
|
|
421
|
+
strokeLinejoin: "round",
|
|
422
|
+
strokeWidth: 2,
|
|
423
|
+
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"
|
|
424
|
+
}
|
|
425
|
+
) }),
|
|
426
|
+
/* @__PURE__ */ jsxs2("span", { children: [
|
|
427
|
+
"Showing values",
|
|
428
|
+
" ",
|
|
429
|
+
localMin !== null && /* @__PURE__ */ jsxs2("strong", { children: [
|
|
430
|
+
"\u2265",
|
|
431
|
+
formatValue(localMin)
|
|
432
|
+
] }),
|
|
433
|
+
localMin !== null && localMax !== null && " and ",
|
|
434
|
+
localMax !== null && /* @__PURE__ */ jsxs2("strong", { children: [
|
|
435
|
+
"\u2264",
|
|
436
|
+
formatValue(localMax)
|
|
437
|
+
] })
|
|
438
|
+
] })
|
|
439
|
+
] })
|
|
440
|
+
] });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/components/ColumnFilter.tsx
|
|
444
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
445
|
+
function ColumnFilter({
|
|
446
|
+
columnName,
|
|
447
|
+
stats,
|
|
448
|
+
selectedValues,
|
|
449
|
+
sortDirection,
|
|
450
|
+
numericRange,
|
|
451
|
+
onFilter,
|
|
452
|
+
onSort,
|
|
453
|
+
onClose,
|
|
454
|
+
onRangeFilter
|
|
455
|
+
}) {
|
|
456
|
+
const [searchQuery, setSearchQuery] = useState3("");
|
|
457
|
+
const [localSelected, setLocalSelected] = useState3(new Set(selectedValues));
|
|
458
|
+
const dropdownRef = useRef(null);
|
|
459
|
+
const searchInputRef = useRef(null);
|
|
460
|
+
const isNumericColumn = stats.type === "number" && stats.numericMin !== void 0 && stats.numericMax !== void 0;
|
|
461
|
+
const [filterMode, setFilterMode] = useState3(numericRange ? "range" : "values");
|
|
462
|
+
const [localRange, setLocalRange] = useState3(numericRange ?? null);
|
|
463
|
+
const hasBlankValues = stats.nullCount > 0;
|
|
464
|
+
const filteredValues = useMemo3(() => {
|
|
465
|
+
const values = stats.uniqueValues;
|
|
466
|
+
if (!searchQuery)
|
|
467
|
+
return values;
|
|
468
|
+
const query = searchQuery.toLowerCase();
|
|
469
|
+
return values.filter((v) => v.toLowerCase().includes(query));
|
|
470
|
+
}, [stats.uniqueValues, searchQuery]);
|
|
471
|
+
const allValues = useMemo3(() => {
|
|
472
|
+
const values = [...filteredValues];
|
|
473
|
+
if (hasBlankValues && (!searchQuery || "(blank)".includes(searchQuery.toLowerCase()))) {
|
|
474
|
+
values.unshift("(blank)");
|
|
502
475
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
476
|
+
return values;
|
|
477
|
+
}, [filteredValues, hasBlankValues, searchQuery]);
|
|
478
|
+
const _isAllSelected = useMemo3(
|
|
479
|
+
() => allValues.every((v) => localSelected.has(v)),
|
|
480
|
+
[allValues, localSelected]
|
|
481
|
+
);
|
|
482
|
+
const toggleValue = useCallback3((value) => {
|
|
483
|
+
setLocalSelected((prev) => {
|
|
484
|
+
const next = new Set(prev);
|
|
485
|
+
if (next.has(value)) {
|
|
486
|
+
next.delete(value);
|
|
510
487
|
} else {
|
|
511
|
-
|
|
488
|
+
next.add(value);
|
|
512
489
|
}
|
|
513
|
-
|
|
514
|
-
return updated;
|
|
515
|
-
});
|
|
516
|
-
}, []);
|
|
517
|
-
const removeCalculatedField = useCallback3((id) => {
|
|
518
|
-
setCalculatedFields((prev) => {
|
|
519
|
-
const updated = prev.filter((f) => f.id !== id);
|
|
520
|
-
saveCalculatedFields(updated);
|
|
521
|
-
return updated;
|
|
490
|
+
return next;
|
|
522
491
|
});
|
|
523
|
-
setValueFields((prev) => prev.filter((v) => v.field !== `calc:${id}`));
|
|
524
492
|
}, []);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
rowFields,
|
|
528
|
-
columnFields,
|
|
529
|
-
valueFields,
|
|
530
|
-
showRowTotals,
|
|
531
|
-
showColumnTotals,
|
|
532
|
-
calculatedFields,
|
|
533
|
-
// Computed
|
|
534
|
-
availableFields,
|
|
535
|
-
unassignedFields,
|
|
536
|
-
isConfigured,
|
|
537
|
-
pivotResult,
|
|
538
|
-
// Actions
|
|
539
|
-
addRowField,
|
|
540
|
-
removeRowField,
|
|
541
|
-
addColumnField,
|
|
542
|
-
removeColumnField,
|
|
543
|
-
addValueField,
|
|
544
|
-
removeValueField,
|
|
545
|
-
updateValueFieldAggregation,
|
|
546
|
-
clearConfig,
|
|
547
|
-
setShowRowTotals,
|
|
548
|
-
setShowColumnTotals,
|
|
549
|
-
autoSuggestConfig,
|
|
550
|
-
setRowFields,
|
|
551
|
-
setColumnFields,
|
|
552
|
-
addCalculatedField,
|
|
553
|
-
removeCalculatedField
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// src/hooks/useGridFeatures.ts
|
|
558
|
-
import { useState as useState4, useMemo as useMemo4, useCallback as useCallback4 } from "react";
|
|
559
|
-
import {
|
|
560
|
-
exportToCSV as coreExportToCSV,
|
|
561
|
-
exportPivotToCSV as coreExportPivotToCSV,
|
|
562
|
-
copyToClipboard as coreCopyToClipboard,
|
|
563
|
-
formatSelectionForClipboard as coreFormatSelection
|
|
564
|
-
} from "@smallwebco/tinypivot-core";
|
|
565
|
-
function exportToCSV(data, columns, options) {
|
|
566
|
-
coreExportToCSV(data, columns, options);
|
|
567
|
-
}
|
|
568
|
-
function exportPivotToCSV(pivotData, rowFields, columnFields, valueFields, options) {
|
|
569
|
-
coreExportPivotToCSV(pivotData, rowFields, columnFields, valueFields, options);
|
|
570
|
-
}
|
|
571
|
-
function copyToClipboard(text, onSuccess, onError) {
|
|
572
|
-
coreCopyToClipboard(text, onSuccess, onError);
|
|
573
|
-
}
|
|
574
|
-
function formatSelectionForClipboard(rows, columns, selectionBounds) {
|
|
575
|
-
return coreFormatSelection(rows, columns, selectionBounds);
|
|
576
|
-
}
|
|
577
|
-
function usePagination(data, options = {}) {
|
|
578
|
-
const [pageSize, setPageSize] = useState4(options.pageSize ?? 50);
|
|
579
|
-
const [currentPage, setCurrentPage] = useState4(options.currentPage ?? 1);
|
|
580
|
-
const totalPages = useMemo4(
|
|
581
|
-
() => Math.max(1, Math.ceil(data.length / pageSize)),
|
|
582
|
-
[data.length, pageSize]
|
|
583
|
-
);
|
|
584
|
-
const paginatedData = useMemo4(() => {
|
|
585
|
-
const start = (currentPage - 1) * pageSize;
|
|
586
|
-
const end = start + pageSize;
|
|
587
|
-
return data.slice(start, end);
|
|
588
|
-
}, [data, currentPage, pageSize]);
|
|
589
|
-
const startIndex = useMemo4(() => (currentPage - 1) * pageSize + 1, [currentPage, pageSize]);
|
|
590
|
-
const endIndex = useMemo4(
|
|
591
|
-
() => Math.min(currentPage * pageSize, data.length),
|
|
592
|
-
[currentPage, pageSize, data.length]
|
|
593
|
-
);
|
|
594
|
-
const goToPage = useCallback4(
|
|
595
|
-
(page) => {
|
|
596
|
-
setCurrentPage(Math.max(1, Math.min(page, totalPages)));
|
|
597
|
-
},
|
|
598
|
-
[totalPages]
|
|
599
|
-
);
|
|
600
|
-
const nextPage = useCallback4(() => {
|
|
601
|
-
if (currentPage < totalPages) {
|
|
602
|
-
setCurrentPage((prev) => prev + 1);
|
|
603
|
-
}
|
|
604
|
-
}, [currentPage, totalPages]);
|
|
605
|
-
const prevPage = useCallback4(() => {
|
|
606
|
-
if (currentPage > 1) {
|
|
607
|
-
setCurrentPage((prev) => prev - 1);
|
|
608
|
-
}
|
|
609
|
-
}, [currentPage]);
|
|
610
|
-
const firstPage = useCallback4(() => {
|
|
611
|
-
setCurrentPage(1);
|
|
612
|
-
}, []);
|
|
613
|
-
const lastPage = useCallback4(() => {
|
|
614
|
-
setCurrentPage(totalPages);
|
|
615
|
-
}, [totalPages]);
|
|
616
|
-
const updatePageSize = useCallback4((size) => {
|
|
617
|
-
setPageSize(size);
|
|
618
|
-
setCurrentPage(1);
|
|
619
|
-
}, []);
|
|
620
|
-
return {
|
|
621
|
-
pageSize,
|
|
622
|
-
currentPage,
|
|
623
|
-
totalPages,
|
|
624
|
-
paginatedData,
|
|
625
|
-
startIndex,
|
|
626
|
-
endIndex,
|
|
627
|
-
goToPage,
|
|
628
|
-
nextPage,
|
|
629
|
-
prevPage,
|
|
630
|
-
firstPage,
|
|
631
|
-
lastPage,
|
|
632
|
-
setPageSize: updatePageSize
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
function useGlobalSearch(data, columns) {
|
|
636
|
-
const [searchTerm, setSearchTerm] = useState4("");
|
|
637
|
-
const [caseSensitive, setCaseSensitive] = useState4(false);
|
|
638
|
-
const filteredData = useMemo4(() => {
|
|
639
|
-
if (!searchTerm.trim()) {
|
|
640
|
-
return data;
|
|
641
|
-
}
|
|
642
|
-
const term = caseSensitive ? searchTerm.trim() : searchTerm.trim().toLowerCase();
|
|
643
|
-
return data.filter((row) => {
|
|
644
|
-
for (const col of columns) {
|
|
645
|
-
const value = row[col];
|
|
646
|
-
if (value === null || value === void 0) continue;
|
|
647
|
-
const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
|
|
648
|
-
if (strValue.includes(term)) {
|
|
649
|
-
return true;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return false;
|
|
653
|
-
});
|
|
654
|
-
}, [data, columns, searchTerm, caseSensitive]);
|
|
655
|
-
const clearSearch = useCallback4(() => {
|
|
656
|
-
setSearchTerm("");
|
|
657
|
-
}, []);
|
|
658
|
-
return {
|
|
659
|
-
searchTerm,
|
|
660
|
-
setSearchTerm,
|
|
661
|
-
caseSensitive,
|
|
662
|
-
setCaseSensitive,
|
|
663
|
-
filteredData,
|
|
664
|
-
clearSearch
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
function useRowSelection(data) {
|
|
668
|
-
const [selectedRowIndices, setSelectedRowIndices] = useState4(/* @__PURE__ */ new Set());
|
|
669
|
-
const selectedRows = useMemo4(() => {
|
|
670
|
-
return Array.from(selectedRowIndices).sort((a, b) => a - b).map((idx) => data[idx]).filter(Boolean);
|
|
671
|
-
}, [data, selectedRowIndices]);
|
|
672
|
-
const allSelected = useMemo4(() => {
|
|
673
|
-
return data.length > 0 && selectedRowIndices.size === data.length;
|
|
674
|
-
}, [data.length, selectedRowIndices.size]);
|
|
675
|
-
const someSelected = useMemo4(() => {
|
|
676
|
-
return selectedRowIndices.size > 0 && selectedRowIndices.size < data.length;
|
|
677
|
-
}, [data.length, selectedRowIndices.size]);
|
|
678
|
-
const toggleRow = useCallback4((index) => {
|
|
679
|
-
setSelectedRowIndices((prev) => {
|
|
493
|
+
const selectAll = useCallback3(() => {
|
|
494
|
+
setLocalSelected((prev) => {
|
|
680
495
|
const next = new Set(prev);
|
|
681
|
-
|
|
682
|
-
next.
|
|
683
|
-
} else {
|
|
684
|
-
next.add(index);
|
|
496
|
+
for (const value of allValues) {
|
|
497
|
+
next.add(value);
|
|
685
498
|
}
|
|
686
499
|
return next;
|
|
687
500
|
});
|
|
501
|
+
}, [allValues]);
|
|
502
|
+
const clearAll = useCallback3(() => {
|
|
503
|
+
setLocalSelected(/* @__PURE__ */ new Set());
|
|
688
504
|
}, []);
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const deselectRow = useCallback4((index) => {
|
|
693
|
-
setSelectedRowIndices((prev) => {
|
|
694
|
-
const next = new Set(prev);
|
|
695
|
-
next.delete(index);
|
|
696
|
-
return next;
|
|
697
|
-
});
|
|
698
|
-
}, []);
|
|
699
|
-
const selectAll = useCallback4(() => {
|
|
700
|
-
setSelectedRowIndices(new Set(data.map((_, idx) => idx)));
|
|
701
|
-
}, [data]);
|
|
702
|
-
const deselectAll = useCallback4(() => {
|
|
703
|
-
setSelectedRowIndices(/* @__PURE__ */ new Set());
|
|
704
|
-
}, []);
|
|
705
|
-
const toggleAll = useCallback4(() => {
|
|
706
|
-
if (allSelected) {
|
|
707
|
-
deselectAll();
|
|
505
|
+
const applyFilter = useCallback3(() => {
|
|
506
|
+
if (localSelected.size === 0) {
|
|
507
|
+
onFilter([]);
|
|
708
508
|
} else {
|
|
709
|
-
|
|
509
|
+
onFilter(Array.from(localSelected));
|
|
710
510
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
return next;
|
|
727
|
-
});
|
|
511
|
+
onClose();
|
|
512
|
+
}, [localSelected, onFilter, onClose]);
|
|
513
|
+
const sortAscending = useCallback3(() => {
|
|
514
|
+
onSort(sortDirection === "asc" ? null : "asc");
|
|
515
|
+
}, [sortDirection, onSort]);
|
|
516
|
+
const sortDescending = useCallback3(() => {
|
|
517
|
+
onSort(sortDirection === "desc" ? null : "desc");
|
|
518
|
+
}, [sortDirection, onSort]);
|
|
519
|
+
const clearFilter = useCallback3(() => {
|
|
520
|
+
setLocalSelected(/* @__PURE__ */ new Set());
|
|
521
|
+
onFilter([]);
|
|
522
|
+
onClose();
|
|
523
|
+
}, [onFilter, onClose]);
|
|
524
|
+
const handleRangeChange = useCallback3((range) => {
|
|
525
|
+
setLocalRange(range);
|
|
728
526
|
}, []);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
function useColumnResize(initialWidths, minWidth = 60, maxWidth = 600) {
|
|
745
|
-
const [columnWidths, setColumnWidths] = useState4({ ...initialWidths });
|
|
746
|
-
const [isResizing, setIsResizing] = useState4(false);
|
|
747
|
-
const [resizingColumn, setResizingColumn] = useState4(null);
|
|
748
|
-
const startResize = useCallback4(
|
|
749
|
-
(columnId, event) => {
|
|
750
|
-
setIsResizing(true);
|
|
751
|
-
setResizingColumn(columnId);
|
|
752
|
-
const startX = event.clientX;
|
|
753
|
-
const startWidth = columnWidths[columnId] || 150;
|
|
754
|
-
const handleMouseMove = (e) => {
|
|
755
|
-
const diff = e.clientX - startX;
|
|
756
|
-
const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + diff));
|
|
757
|
-
setColumnWidths((prev) => ({
|
|
758
|
-
...prev,
|
|
759
|
-
[columnId]: newWidth
|
|
760
|
-
}));
|
|
761
|
-
};
|
|
762
|
-
const handleMouseUp = () => {
|
|
763
|
-
setIsResizing(false);
|
|
764
|
-
setResizingColumn(null);
|
|
765
|
-
document.removeEventListener("mousemove", handleMouseMove);
|
|
766
|
-
document.removeEventListener("mouseup", handleMouseUp);
|
|
767
|
-
};
|
|
768
|
-
document.addEventListener("mousemove", handleMouseMove);
|
|
769
|
-
document.addEventListener("mouseup", handleMouseUp);
|
|
770
|
-
},
|
|
771
|
-
[columnWidths, minWidth, maxWidth]
|
|
772
|
-
);
|
|
773
|
-
const resetColumnWidth = useCallback4(
|
|
774
|
-
(columnId) => {
|
|
775
|
-
if (initialWidths[columnId]) {
|
|
776
|
-
setColumnWidths((prev) => ({
|
|
777
|
-
...prev,
|
|
778
|
-
[columnId]: initialWidths[columnId]
|
|
779
|
-
}));
|
|
527
|
+
const applyRangeFilter = useCallback3(() => {
|
|
528
|
+
onRangeFilter?.(localRange);
|
|
529
|
+
onClose();
|
|
530
|
+
}, [localRange, onRangeFilter, onClose]);
|
|
531
|
+
const clearRangeFilter = useCallback3(() => {
|
|
532
|
+
setLocalRange(null);
|
|
533
|
+
onRangeFilter?.(null);
|
|
534
|
+
onClose();
|
|
535
|
+
}, [onRangeFilter, onClose]);
|
|
536
|
+
useEffect3(() => {
|
|
537
|
+
const handleClickOutside = (event) => {
|
|
538
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
539
|
+
onClose();
|
|
780
540
|
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
resizingColumn,
|
|
792
|
-
startResize,
|
|
793
|
-
resetColumnWidth,
|
|
794
|
-
resetAllWidths
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// src/components/ColumnFilter.tsx
|
|
799
|
-
import { useState as useState6, useEffect as useEffect4, useRef, useCallback as useCallback6, useMemo as useMemo6 } from "react";
|
|
800
|
-
|
|
801
|
-
// src/components/NumericRangeFilter.tsx
|
|
802
|
-
import { useState as useState5, useCallback as useCallback5, useMemo as useMemo5, useEffect as useEffect3 } from "react";
|
|
803
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
804
|
-
function NumericRangeFilter({
|
|
805
|
-
dataMin,
|
|
806
|
-
dataMax,
|
|
807
|
-
currentRange,
|
|
808
|
-
onChange
|
|
809
|
-
}) {
|
|
810
|
-
const [localMin, setLocalMin] = useState5(currentRange?.min ?? null);
|
|
811
|
-
const [localMax, setLocalMax] = useState5(currentRange?.max ?? null);
|
|
812
|
-
const step = useMemo5(() => {
|
|
813
|
-
const range = dataMax - dataMin;
|
|
814
|
-
if (range === 0) return 1;
|
|
815
|
-
if (range <= 1) return 0.01;
|
|
816
|
-
if (range <= 10) return 0.1;
|
|
817
|
-
if (range <= 100) return 1;
|
|
818
|
-
if (range <= 1e3) return 10;
|
|
819
|
-
return Math.pow(10, Math.floor(Math.log10(range)) - 2);
|
|
820
|
-
}, [dataMin, dataMax]);
|
|
821
|
-
const formatValue = useCallback5((val) => {
|
|
822
|
-
if (val === null) return "";
|
|
823
|
-
if (Number.isInteger(val)) return val.toLocaleString();
|
|
824
|
-
return val.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
825
|
-
}, []);
|
|
826
|
-
const isFilterActive = localMin !== null || localMax !== null;
|
|
827
|
-
const minPercent = useMemo5(() => {
|
|
828
|
-
if (localMin === null || dataMax === dataMin) return 0;
|
|
829
|
-
return (localMin - dataMin) / (dataMax - dataMin) * 100;
|
|
830
|
-
}, [localMin, dataMin, dataMax]);
|
|
831
|
-
const maxPercent = useMemo5(() => {
|
|
832
|
-
if (localMax === null || dataMax === dataMin) return 100;
|
|
833
|
-
return (localMax - dataMin) / (dataMax - dataMin) * 100;
|
|
834
|
-
}, [localMax, dataMin, dataMax]);
|
|
835
|
-
const handleMinSlider = useCallback5((event) => {
|
|
836
|
-
const value = Number.parseFloat(event.target.value);
|
|
837
|
-
setLocalMin((prev) => {
|
|
838
|
-
if (localMax !== null && value > localMax) {
|
|
839
|
-
return localMax;
|
|
840
|
-
}
|
|
841
|
-
return value;
|
|
842
|
-
});
|
|
843
|
-
}, [localMax]);
|
|
844
|
-
const handleMaxSlider = useCallback5((event) => {
|
|
845
|
-
const value = Number.parseFloat(event.target.value);
|
|
846
|
-
setLocalMax((prev) => {
|
|
847
|
-
if (localMin !== null && value < localMin) {
|
|
848
|
-
return localMin;
|
|
541
|
+
};
|
|
542
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
543
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
544
|
+
}, [onClose]);
|
|
545
|
+
useEffect3(() => {
|
|
546
|
+
const handleKeydown = (event) => {
|
|
547
|
+
if (event.key === "Escape") {
|
|
548
|
+
onClose();
|
|
549
|
+
} else if (event.key === "Enter" && event.ctrlKey) {
|
|
550
|
+
applyFilter();
|
|
849
551
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
if (localMin === null && localMax === null) {
|
|
855
|
-
onChange(null);
|
|
856
|
-
} else {
|
|
857
|
-
onChange({ min: localMin, max: localMax });
|
|
858
|
-
}
|
|
859
|
-
}, [localMin, localMax, onChange]);
|
|
860
|
-
const handleMinInput = useCallback5((event) => {
|
|
861
|
-
const value = event.target.value === "" ? null : Number.parseFloat(event.target.value);
|
|
862
|
-
if (value !== null && !Number.isNaN(value)) {
|
|
863
|
-
setLocalMin(Math.max(dataMin, Math.min(value, localMax ?? dataMax)));
|
|
864
|
-
} else if (value === null) {
|
|
865
|
-
setLocalMin(null);
|
|
866
|
-
}
|
|
867
|
-
}, [dataMin, dataMax, localMax]);
|
|
868
|
-
const handleMaxInput = useCallback5((event) => {
|
|
869
|
-
const value = event.target.value === "" ? null : Number.parseFloat(event.target.value);
|
|
870
|
-
if (value !== null && !Number.isNaN(value)) {
|
|
871
|
-
setLocalMax(Math.min(dataMax, Math.max(value, localMin ?? dataMin)));
|
|
872
|
-
} else if (value === null) {
|
|
873
|
-
setLocalMax(null);
|
|
874
|
-
}
|
|
875
|
-
}, [dataMin, dataMax, localMin]);
|
|
876
|
-
const handleInputBlur = useCallback5(() => {
|
|
877
|
-
if (localMin === null && localMax === null) {
|
|
878
|
-
onChange(null);
|
|
879
|
-
} else {
|
|
880
|
-
onChange({ min: localMin, max: localMax });
|
|
881
|
-
}
|
|
882
|
-
}, [localMin, localMax, onChange]);
|
|
883
|
-
const clearFilter = useCallback5(() => {
|
|
884
|
-
setLocalMin(null);
|
|
885
|
-
setLocalMax(null);
|
|
886
|
-
onChange(null);
|
|
887
|
-
}, [onChange]);
|
|
888
|
-
const setFullRange = useCallback5(() => {
|
|
889
|
-
setLocalMin(dataMin);
|
|
890
|
-
setLocalMax(dataMax);
|
|
891
|
-
onChange({ min: dataMin, max: dataMax });
|
|
892
|
-
}, [dataMin, dataMax, onChange]);
|
|
552
|
+
};
|
|
553
|
+
document.addEventListener("keydown", handleKeydown);
|
|
554
|
+
return () => document.removeEventListener("keydown", handleKeydown);
|
|
555
|
+
}, [onClose, applyFilter]);
|
|
893
556
|
useEffect3(() => {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
557
|
+
searchInputRef.current?.focus();
|
|
558
|
+
}, []);
|
|
559
|
+
useEffect3(() => {
|
|
560
|
+
setLocalSelected(new Set(selectedValues));
|
|
561
|
+
}, [selectedValues]);
|
|
562
|
+
useEffect3(() => {
|
|
563
|
+
setLocalRange(numericRange ?? null);
|
|
564
|
+
if (numericRange) {
|
|
565
|
+
setFilterMode("range");
|
|
566
|
+
}
|
|
567
|
+
}, [numericRange]);
|
|
568
|
+
return /* @__PURE__ */ jsxs3("div", { ref: dropdownRef, className: "vpg-filter-dropdown", children: [
|
|
569
|
+
/* @__PURE__ */ jsxs3("div", { className: "vpg-filter-header", children: [
|
|
570
|
+
/* @__PURE__ */ jsx3("span", { className: "vpg-filter-title", children: columnName }),
|
|
571
|
+
/* @__PURE__ */ jsxs3("span", { className: "vpg-filter-count", children: [
|
|
572
|
+
stats.uniqueValues.length.toLocaleString(),
|
|
573
|
+
" ",
|
|
574
|
+
"unique"
|
|
904
575
|
] })
|
|
905
576
|
] }),
|
|
906
|
-
/* @__PURE__ */
|
|
907
|
-
/* @__PURE__ */
|
|
908
|
-
"
|
|
909
|
-
{
|
|
910
|
-
className: "vpg-slider-fill",
|
|
911
|
-
style: {
|
|
912
|
-
left: `${minPercent}%`,
|
|
913
|
-
right: `${100 - maxPercent}%`
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
) }),
|
|
917
|
-
/* @__PURE__ */ jsx(
|
|
918
|
-
"input",
|
|
577
|
+
/* @__PURE__ */ jsxs3("div", { className: "vpg-sort-controls", children: [
|
|
578
|
+
/* @__PURE__ */ jsxs3(
|
|
579
|
+
"button",
|
|
919
580
|
{
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
581
|
+
className: `vpg-sort-btn ${sortDirection === "asc" ? "active" : ""}`,
|
|
582
|
+
title: isNumericColumn ? "Sort Low to High" : "Sort A to Z",
|
|
583
|
+
onClick: sortAscending,
|
|
584
|
+
children: [
|
|
585
|
+
/* @__PURE__ */ jsx3("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3(
|
|
586
|
+
"path",
|
|
587
|
+
{
|
|
588
|
+
strokeLinecap: "round",
|
|
589
|
+
strokeLinejoin: "round",
|
|
590
|
+
strokeWidth: 2,
|
|
591
|
+
d: "M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12"
|
|
592
|
+
}
|
|
593
|
+
) }),
|
|
594
|
+
/* @__PURE__ */ jsx3("span", { children: isNumericColumn ? "1\u21929" : "A\u2192Z" })
|
|
595
|
+
]
|
|
929
596
|
}
|
|
930
597
|
),
|
|
931
|
-
/* @__PURE__ */
|
|
932
|
-
"
|
|
598
|
+
/* @__PURE__ */ jsxs3(
|
|
599
|
+
"button",
|
|
933
600
|
{
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
601
|
+
className: `vpg-sort-btn ${sortDirection === "desc" ? "active" : ""}`,
|
|
602
|
+
title: isNumericColumn ? "Sort High to Low" : "Sort Z to A",
|
|
603
|
+
onClick: sortDescending,
|
|
604
|
+
children: [
|
|
605
|
+
/* @__PURE__ */ jsx3("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3(
|
|
606
|
+
"path",
|
|
607
|
+
{
|
|
608
|
+
strokeLinecap: "round",
|
|
609
|
+
strokeLinejoin: "round",
|
|
610
|
+
strokeWidth: 2,
|
|
611
|
+
d: "M3 4h13M3 8h9m-9 4h9m5-4v12m0 0l-4-4m4 4l4-4"
|
|
612
|
+
}
|
|
613
|
+
) }),
|
|
614
|
+
/* @__PURE__ */ jsx3("span", { children: isNumericColumn ? "9\u21921" : "Z\u2192A" })
|
|
615
|
+
]
|
|
943
616
|
}
|
|
944
617
|
)
|
|
945
618
|
] }),
|
|
946
|
-
/* @__PURE__ */
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
/* @__PURE__ */ jsx(
|
|
950
|
-
"input",
|
|
951
|
-
{
|
|
952
|
-
type: "number",
|
|
953
|
-
className: "vpg-range-input",
|
|
954
|
-
placeholder: formatValue(dataMin),
|
|
955
|
-
value: localMin ?? "",
|
|
956
|
-
step,
|
|
957
|
-
onChange: handleMinInput,
|
|
958
|
-
onBlur: handleInputBlur
|
|
959
|
-
}
|
|
960
|
-
)
|
|
961
|
-
] }),
|
|
962
|
-
/* @__PURE__ */ jsx("span", { className: "vpg-input-separator", children: "to" }),
|
|
963
|
-
/* @__PURE__ */ jsxs("div", { className: "vpg-input-group", children: [
|
|
964
|
-
/* @__PURE__ */ jsx("label", { className: "vpg-input-label", children: "Max" }),
|
|
965
|
-
/* @__PURE__ */ jsx(
|
|
966
|
-
"input",
|
|
967
|
-
{
|
|
968
|
-
type: "number",
|
|
969
|
-
className: "vpg-range-input",
|
|
970
|
-
placeholder: formatValue(dataMax),
|
|
971
|
-
value: localMax ?? "",
|
|
972
|
-
step,
|
|
973
|
-
onChange: handleMaxInput,
|
|
974
|
-
onBlur: handleInputBlur
|
|
975
|
-
}
|
|
976
|
-
)
|
|
977
|
-
] })
|
|
978
|
-
] }),
|
|
979
|
-
/* @__PURE__ */ jsxs("div", { className: "vpg-range-actions", children: [
|
|
980
|
-
/* @__PURE__ */ jsxs(
|
|
619
|
+
/* @__PURE__ */ jsx3("div", { className: "vpg-divider" }),
|
|
620
|
+
isNumericColumn && /* @__PURE__ */ jsxs3("div", { className: "vpg-filter-tabs", children: [
|
|
621
|
+
/* @__PURE__ */ jsxs3(
|
|
981
622
|
"button",
|
|
982
623
|
{
|
|
983
|
-
className:
|
|
984
|
-
|
|
985
|
-
onClick: clearFilter,
|
|
624
|
+
className: `vpg-tab-btn ${filterMode === "values" ? "active" : ""}`,
|
|
625
|
+
onClick: () => setFilterMode("values"),
|
|
986
626
|
children: [
|
|
987
|
-
/* @__PURE__ */
|
|
627
|
+
/* @__PURE__ */ jsx3("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3(
|
|
988
628
|
"path",
|
|
989
629
|
{
|
|
990
630
|
strokeLinecap: "round",
|
|
991
631
|
strokeLinejoin: "round",
|
|
992
632
|
strokeWidth: 2,
|
|
993
|
-
d: "
|
|
633
|
+
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"
|
|
994
634
|
}
|
|
995
635
|
) }),
|
|
996
|
-
"
|
|
636
|
+
"Values"
|
|
997
637
|
]
|
|
998
638
|
}
|
|
999
639
|
),
|
|
1000
|
-
/* @__PURE__ */
|
|
1001
|
-
|
|
640
|
+
/* @__PURE__ */ jsxs3(
|
|
641
|
+
"button",
|
|
642
|
+
{
|
|
643
|
+
className: `vpg-tab-btn ${filterMode === "range" ? "active" : ""}`,
|
|
644
|
+
onClick: () => setFilterMode("range"),
|
|
645
|
+
children: [
|
|
646
|
+
/* @__PURE__ */ jsx3("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3(
|
|
647
|
+
"path",
|
|
648
|
+
{
|
|
649
|
+
strokeLinecap: "round",
|
|
650
|
+
strokeLinejoin: "round",
|
|
651
|
+
strokeWidth: 2,
|
|
652
|
+
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"
|
|
653
|
+
}
|
|
654
|
+
) }),
|
|
655
|
+
"Range"
|
|
656
|
+
]
|
|
657
|
+
}
|
|
658
|
+
)
|
|
659
|
+
] }),
|
|
660
|
+
(!isNumericColumn || filterMode === "values") && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
661
|
+
/* @__PURE__ */ jsxs3("div", { className: "vpg-search-container", children: [
|
|
662
|
+
/* @__PURE__ */ jsx3("svg", { className: "vpg-search-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3(
|
|
1002
663
|
"path",
|
|
1003
664
|
{
|
|
1004
665
|
strokeLinecap: "round",
|
|
1005
666
|
strokeLinejoin: "round",
|
|
1006
667
|
strokeWidth: 2,
|
|
1007
|
-
d: "
|
|
668
|
+
d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
1008
669
|
}
|
|
1009
670
|
) }),
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
/* @__PURE__ */
|
|
1024
|
-
"
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
671
|
+
/* @__PURE__ */ jsx3(
|
|
672
|
+
"input",
|
|
673
|
+
{
|
|
674
|
+
ref: searchInputRef,
|
|
675
|
+
type: "text",
|
|
676
|
+
value: searchQuery,
|
|
677
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
678
|
+
placeholder: "Search values...",
|
|
679
|
+
className: "vpg-search-input"
|
|
680
|
+
}
|
|
681
|
+
),
|
|
682
|
+
searchQuery && /* @__PURE__ */ jsx3("button", { className: "vpg-clear-search", onClick: () => setSearchQuery(""), children: "\xD7" })
|
|
683
|
+
] }),
|
|
684
|
+
/* @__PURE__ */ jsxs3("div", { className: "vpg-bulk-actions", children: [
|
|
685
|
+
/* @__PURE__ */ jsxs3("button", { className: "vpg-bulk-btn", onClick: selectAll, children: [
|
|
686
|
+
/* @__PURE__ */ jsx3("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3(
|
|
687
|
+
"path",
|
|
688
|
+
{
|
|
689
|
+
strokeLinecap: "round",
|
|
690
|
+
strokeLinejoin: "round",
|
|
691
|
+
strokeWidth: 2,
|
|
692
|
+
d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
693
|
+
}
|
|
694
|
+
) }),
|
|
695
|
+
"Select All"
|
|
1029
696
|
] }),
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
"\u2264 ",
|
|
1033
|
-
formatValue(localMax)
|
|
1034
|
-
] })
|
|
1035
|
-
] })
|
|
1036
|
-
] })
|
|
1037
|
-
] });
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
// src/components/ColumnFilter.tsx
|
|
1041
|
-
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1042
|
-
function ColumnFilter({
|
|
1043
|
-
columnName,
|
|
1044
|
-
stats,
|
|
1045
|
-
selectedValues,
|
|
1046
|
-
sortDirection,
|
|
1047
|
-
numericRange,
|
|
1048
|
-
onFilter,
|
|
1049
|
-
onSort,
|
|
1050
|
-
onClose,
|
|
1051
|
-
onRangeFilter
|
|
1052
|
-
}) {
|
|
1053
|
-
const [searchQuery, setSearchQuery] = useState6("");
|
|
1054
|
-
const [localSelected, setLocalSelected] = useState6(new Set(selectedValues));
|
|
1055
|
-
const dropdownRef = useRef(null);
|
|
1056
|
-
const searchInputRef = useRef(null);
|
|
1057
|
-
const isNumericColumn = stats.type === "number" && stats.numericMin !== void 0 && stats.numericMax !== void 0;
|
|
1058
|
-
const [filterMode, setFilterMode] = useState6(numericRange ? "range" : "values");
|
|
1059
|
-
const [localRange, setLocalRange] = useState6(numericRange ?? null);
|
|
1060
|
-
const hasBlankValues = stats.nullCount > 0;
|
|
1061
|
-
const filteredValues = useMemo6(() => {
|
|
1062
|
-
const values = stats.uniqueValues;
|
|
1063
|
-
if (!searchQuery) return values;
|
|
1064
|
-
const query = searchQuery.toLowerCase();
|
|
1065
|
-
return values.filter((v) => v.toLowerCase().includes(query));
|
|
1066
|
-
}, [stats.uniqueValues, searchQuery]);
|
|
1067
|
-
const allValues = useMemo6(() => {
|
|
1068
|
-
const values = [...filteredValues];
|
|
1069
|
-
if (hasBlankValues && (!searchQuery || "(blank)".includes(searchQuery.toLowerCase()))) {
|
|
1070
|
-
values.unshift("(blank)");
|
|
1071
|
-
}
|
|
1072
|
-
return values;
|
|
1073
|
-
}, [filteredValues, hasBlankValues, searchQuery]);
|
|
1074
|
-
const isAllSelected = useMemo6(
|
|
1075
|
-
() => allValues.every((v) => localSelected.has(v)),
|
|
1076
|
-
[allValues, localSelected]
|
|
1077
|
-
);
|
|
1078
|
-
const toggleValue = useCallback6((value) => {
|
|
1079
|
-
setLocalSelected((prev) => {
|
|
1080
|
-
const next = new Set(prev);
|
|
1081
|
-
if (next.has(value)) {
|
|
1082
|
-
next.delete(value);
|
|
1083
|
-
} else {
|
|
1084
|
-
next.add(value);
|
|
1085
|
-
}
|
|
1086
|
-
return next;
|
|
1087
|
-
});
|
|
1088
|
-
}, []);
|
|
1089
|
-
const selectAll = useCallback6(() => {
|
|
1090
|
-
setLocalSelected((prev) => {
|
|
1091
|
-
const next = new Set(prev);
|
|
1092
|
-
for (const value of allValues) {
|
|
1093
|
-
next.add(value);
|
|
1094
|
-
}
|
|
1095
|
-
return next;
|
|
1096
|
-
});
|
|
1097
|
-
}, [allValues]);
|
|
1098
|
-
const clearAll = useCallback6(() => {
|
|
1099
|
-
setLocalSelected(/* @__PURE__ */ new Set());
|
|
1100
|
-
}, []);
|
|
1101
|
-
const applyFilter = useCallback6(() => {
|
|
1102
|
-
if (localSelected.size === 0) {
|
|
1103
|
-
onFilter([]);
|
|
1104
|
-
} else {
|
|
1105
|
-
onFilter(Array.from(localSelected));
|
|
1106
|
-
}
|
|
1107
|
-
onClose();
|
|
1108
|
-
}, [localSelected, onFilter, onClose]);
|
|
1109
|
-
const sortAscending = useCallback6(() => {
|
|
1110
|
-
onSort(sortDirection === "asc" ? null : "asc");
|
|
1111
|
-
}, [sortDirection, onSort]);
|
|
1112
|
-
const sortDescending = useCallback6(() => {
|
|
1113
|
-
onSort(sortDirection === "desc" ? null : "desc");
|
|
1114
|
-
}, [sortDirection, onSort]);
|
|
1115
|
-
const clearFilter = useCallback6(() => {
|
|
1116
|
-
setLocalSelected(/* @__PURE__ */ new Set());
|
|
1117
|
-
onFilter([]);
|
|
1118
|
-
onClose();
|
|
1119
|
-
}, [onFilter, onClose]);
|
|
1120
|
-
const handleRangeChange = useCallback6((range) => {
|
|
1121
|
-
setLocalRange(range);
|
|
1122
|
-
}, []);
|
|
1123
|
-
const applyRangeFilter = useCallback6(() => {
|
|
1124
|
-
onRangeFilter?.(localRange);
|
|
1125
|
-
onClose();
|
|
1126
|
-
}, [localRange, onRangeFilter, onClose]);
|
|
1127
|
-
const clearRangeFilter = useCallback6(() => {
|
|
1128
|
-
setLocalRange(null);
|
|
1129
|
-
onRangeFilter?.(null);
|
|
1130
|
-
onClose();
|
|
1131
|
-
}, [onRangeFilter, onClose]);
|
|
1132
|
-
useEffect4(() => {
|
|
1133
|
-
const handleClickOutside = (event) => {
|
|
1134
|
-
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
1135
|
-
onClose();
|
|
1136
|
-
}
|
|
1137
|
-
};
|
|
1138
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
1139
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
1140
|
-
}, [onClose]);
|
|
1141
|
-
useEffect4(() => {
|
|
1142
|
-
const handleKeydown = (event) => {
|
|
1143
|
-
if (event.key === "Escape") {
|
|
1144
|
-
onClose();
|
|
1145
|
-
} else if (event.key === "Enter" && event.ctrlKey) {
|
|
1146
|
-
applyFilter();
|
|
1147
|
-
}
|
|
1148
|
-
};
|
|
1149
|
-
document.addEventListener("keydown", handleKeydown);
|
|
1150
|
-
return () => document.removeEventListener("keydown", handleKeydown);
|
|
1151
|
-
}, [onClose, applyFilter]);
|
|
1152
|
-
useEffect4(() => {
|
|
1153
|
-
searchInputRef.current?.focus();
|
|
1154
|
-
}, []);
|
|
1155
|
-
useEffect4(() => {
|
|
1156
|
-
setLocalSelected(new Set(selectedValues));
|
|
1157
|
-
}, [selectedValues]);
|
|
1158
|
-
useEffect4(() => {
|
|
1159
|
-
setLocalRange(numericRange ?? null);
|
|
1160
|
-
if (numericRange) {
|
|
1161
|
-
setFilterMode("range");
|
|
1162
|
-
}
|
|
1163
|
-
}, [numericRange]);
|
|
1164
|
-
return /* @__PURE__ */ jsxs2("div", { ref: dropdownRef, className: "vpg-filter-dropdown", children: [
|
|
1165
|
-
/* @__PURE__ */ jsxs2("div", { className: "vpg-filter-header", children: [
|
|
1166
|
-
/* @__PURE__ */ jsx2("span", { className: "vpg-filter-title", children: columnName }),
|
|
1167
|
-
/* @__PURE__ */ jsxs2("span", { className: "vpg-filter-count", children: [
|
|
1168
|
-
stats.uniqueValues.length.toLocaleString(),
|
|
1169
|
-
" unique"
|
|
1170
|
-
] })
|
|
1171
|
-
] }),
|
|
1172
|
-
/* @__PURE__ */ jsxs2("div", { className: "vpg-sort-controls", children: [
|
|
1173
|
-
/* @__PURE__ */ jsxs2(
|
|
1174
|
-
"button",
|
|
1175
|
-
{
|
|
1176
|
-
className: `vpg-sort-btn ${sortDirection === "asc" ? "active" : ""}`,
|
|
1177
|
-
title: isNumericColumn ? "Sort Low to High" : "Sort A to Z",
|
|
1178
|
-
onClick: sortAscending,
|
|
1179
|
-
children: [
|
|
1180
|
-
/* @__PURE__ */ jsx2("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
1181
|
-
"path",
|
|
1182
|
-
{
|
|
1183
|
-
strokeLinecap: "round",
|
|
1184
|
-
strokeLinejoin: "round",
|
|
1185
|
-
strokeWidth: 2,
|
|
1186
|
-
d: "M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12"
|
|
1187
|
-
}
|
|
1188
|
-
) }),
|
|
1189
|
-
/* @__PURE__ */ jsx2("span", { children: isNumericColumn ? "1\u21929" : "A\u2192Z" })
|
|
1190
|
-
]
|
|
1191
|
-
}
|
|
1192
|
-
),
|
|
1193
|
-
/* @__PURE__ */ jsxs2(
|
|
1194
|
-
"button",
|
|
1195
|
-
{
|
|
1196
|
-
className: `vpg-sort-btn ${sortDirection === "desc" ? "active" : ""}`,
|
|
1197
|
-
title: isNumericColumn ? "Sort High to Low" : "Sort Z to A",
|
|
1198
|
-
onClick: sortDescending,
|
|
1199
|
-
children: [
|
|
1200
|
-
/* @__PURE__ */ jsx2("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
1201
|
-
"path",
|
|
1202
|
-
{
|
|
1203
|
-
strokeLinecap: "round",
|
|
1204
|
-
strokeLinejoin: "round",
|
|
1205
|
-
strokeWidth: 2,
|
|
1206
|
-
d: "M3 4h13M3 8h9m-9 4h9m5-4v12m0 0l-4-4m4 4l4-4"
|
|
1207
|
-
}
|
|
1208
|
-
) }),
|
|
1209
|
-
/* @__PURE__ */ jsx2("span", { children: isNumericColumn ? "9\u21921" : "Z\u2192A" })
|
|
1210
|
-
]
|
|
1211
|
-
}
|
|
1212
|
-
)
|
|
1213
|
-
] }),
|
|
1214
|
-
/* @__PURE__ */ jsx2("div", { className: "vpg-divider" }),
|
|
1215
|
-
isNumericColumn && /* @__PURE__ */ jsxs2("div", { className: "vpg-filter-tabs", children: [
|
|
1216
|
-
/* @__PURE__ */ jsxs2(
|
|
1217
|
-
"button",
|
|
1218
|
-
{
|
|
1219
|
-
className: `vpg-tab-btn ${filterMode === "values" ? "active" : ""}`,
|
|
1220
|
-
onClick: () => setFilterMode("values"),
|
|
1221
|
-
children: [
|
|
1222
|
-
/* @__PURE__ */ jsx2("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
1223
|
-
"path",
|
|
1224
|
-
{
|
|
1225
|
-
strokeLinecap: "round",
|
|
1226
|
-
strokeLinejoin: "round",
|
|
1227
|
-
strokeWidth: 2,
|
|
1228
|
-
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"
|
|
1229
|
-
}
|
|
1230
|
-
) }),
|
|
1231
|
-
"Values"
|
|
1232
|
-
]
|
|
1233
|
-
}
|
|
1234
|
-
),
|
|
1235
|
-
/* @__PURE__ */ jsxs2(
|
|
1236
|
-
"button",
|
|
1237
|
-
{
|
|
1238
|
-
className: `vpg-tab-btn ${filterMode === "range" ? "active" : ""}`,
|
|
1239
|
-
onClick: () => setFilterMode("range"),
|
|
1240
|
-
children: [
|
|
1241
|
-
/* @__PURE__ */ jsx2("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
1242
|
-
"path",
|
|
1243
|
-
{
|
|
1244
|
-
strokeLinecap: "round",
|
|
1245
|
-
strokeLinejoin: "round",
|
|
1246
|
-
strokeWidth: 2,
|
|
1247
|
-
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"
|
|
1248
|
-
}
|
|
1249
|
-
) }),
|
|
1250
|
-
"Range"
|
|
1251
|
-
]
|
|
1252
|
-
}
|
|
1253
|
-
)
|
|
1254
|
-
] }),
|
|
1255
|
-
(!isNumericColumn || filterMode === "values") && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1256
|
-
/* @__PURE__ */ jsxs2("div", { className: "vpg-search-container", children: [
|
|
1257
|
-
/* @__PURE__ */ jsx2("svg", { className: "vpg-search-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
1258
|
-
"path",
|
|
1259
|
-
{
|
|
1260
|
-
strokeLinecap: "round",
|
|
1261
|
-
strokeLinejoin: "round",
|
|
1262
|
-
strokeWidth: 2,
|
|
1263
|
-
d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
1264
|
-
}
|
|
1265
|
-
) }),
|
|
1266
|
-
/* @__PURE__ */ jsx2(
|
|
1267
|
-
"input",
|
|
1268
|
-
{
|
|
1269
|
-
ref: searchInputRef,
|
|
1270
|
-
type: "text",
|
|
1271
|
-
value: searchQuery,
|
|
1272
|
-
onChange: (e) => setSearchQuery(e.target.value),
|
|
1273
|
-
placeholder: "Search values...",
|
|
1274
|
-
className: "vpg-search-input"
|
|
1275
|
-
}
|
|
1276
|
-
),
|
|
1277
|
-
searchQuery && /* @__PURE__ */ jsx2("button", { className: "vpg-clear-search", onClick: () => setSearchQuery(""), children: "\xD7" })
|
|
1278
|
-
] }),
|
|
1279
|
-
/* @__PURE__ */ jsxs2("div", { className: "vpg-bulk-actions", children: [
|
|
1280
|
-
/* @__PURE__ */ jsxs2("button", { className: "vpg-bulk-btn", onClick: selectAll, children: [
|
|
1281
|
-
/* @__PURE__ */ jsx2("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
1282
|
-
"path",
|
|
1283
|
-
{
|
|
1284
|
-
strokeLinecap: "round",
|
|
1285
|
-
strokeLinejoin: "round",
|
|
1286
|
-
strokeWidth: 2,
|
|
1287
|
-
d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
1288
|
-
}
|
|
1289
|
-
) }),
|
|
1290
|
-
"Select All"
|
|
1291
|
-
] }),
|
|
1292
|
-
/* @__PURE__ */ jsxs2("button", { className: "vpg-bulk-btn", onClick: clearAll, children: [
|
|
1293
|
-
/* @__PURE__ */ jsx2("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
|
|
697
|
+
/* @__PURE__ */ jsxs3("button", { className: "vpg-bulk-btn", onClick: clearAll, children: [
|
|
698
|
+
/* @__PURE__ */ jsx3("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3(
|
|
1294
699
|
"path",
|
|
1295
700
|
{
|
|
1296
701
|
strokeLinecap: "round",
|
|
@@ -1302,13 +707,13 @@ function ColumnFilter({
|
|
|
1302
707
|
"Clear All"
|
|
1303
708
|
] })
|
|
1304
709
|
] }),
|
|
1305
|
-
/* @__PURE__ */
|
|
1306
|
-
allValues.map((value) => /* @__PURE__ */
|
|
710
|
+
/* @__PURE__ */ jsxs3("div", { className: "vpg-values-list", children: [
|
|
711
|
+
allValues.map((value) => /* @__PURE__ */ jsxs3(
|
|
1307
712
|
"label",
|
|
1308
713
|
{
|
|
1309
714
|
className: `vpg-value-item ${localSelected.has(value) ? "selected" : ""}`,
|
|
1310
715
|
children: [
|
|
1311
|
-
/* @__PURE__ */
|
|
716
|
+
/* @__PURE__ */ jsx3(
|
|
1312
717
|
"input",
|
|
1313
718
|
{
|
|
1314
719
|
type: "checkbox",
|
|
@@ -1317,20 +722,20 @@ function ColumnFilter({
|
|
|
1317
722
|
className: "vpg-value-checkbox"
|
|
1318
723
|
}
|
|
1319
724
|
),
|
|
1320
|
-
/* @__PURE__ */
|
|
725
|
+
/* @__PURE__ */ jsx3("span", { className: `vpg-value-text ${value === "(blank)" ? "vpg-blank" : ""}`, children: value })
|
|
1321
726
|
]
|
|
1322
727
|
},
|
|
1323
728
|
value
|
|
1324
729
|
)),
|
|
1325
|
-
allValues.length === 0 && /* @__PURE__ */
|
|
730
|
+
allValues.length === 0 && /* @__PURE__ */ jsx3("div", { className: "vpg-no-results", children: "No matching values" })
|
|
1326
731
|
] }),
|
|
1327
|
-
/* @__PURE__ */
|
|
1328
|
-
/* @__PURE__ */
|
|
1329
|
-
/* @__PURE__ */
|
|
732
|
+
/* @__PURE__ */ jsxs3("div", { className: "vpg-filter-footer", children: [
|
|
733
|
+
/* @__PURE__ */ jsx3("button", { className: "vpg-btn-clear", onClick: clearFilter, children: "Clear Filter" }),
|
|
734
|
+
/* @__PURE__ */ jsx3("button", { className: "vpg-btn-apply", onClick: applyFilter, children: "Apply" })
|
|
1330
735
|
] })
|
|
1331
736
|
] }),
|
|
1332
|
-
isNumericColumn && filterMode === "range" && /* @__PURE__ */
|
|
1333
|
-
/* @__PURE__ */
|
|
737
|
+
isNumericColumn && filterMode === "range" && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
738
|
+
/* @__PURE__ */ jsx3(
|
|
1334
739
|
NumericRangeFilter,
|
|
1335
740
|
{
|
|
1336
741
|
dataMin: stats.numericMin,
|
|
@@ -1339,210 +744,836 @@ function ColumnFilter({
|
|
|
1339
744
|
onChange: handleRangeChange
|
|
1340
745
|
}
|
|
1341
746
|
),
|
|
1342
|
-
/* @__PURE__ */
|
|
1343
|
-
/* @__PURE__ */
|
|
1344
|
-
/* @__PURE__ */
|
|
747
|
+
/* @__PURE__ */ jsxs3("div", { className: "vpg-filter-footer", children: [
|
|
748
|
+
/* @__PURE__ */ jsx3("button", { className: "vpg-btn-clear", onClick: clearRangeFilter, children: "Clear Filter" }),
|
|
749
|
+
/* @__PURE__ */ jsx3("button", { className: "vpg-btn-apply", onClick: applyRangeFilter, children: "Apply" })
|
|
1345
750
|
] })
|
|
1346
751
|
] })
|
|
1347
752
|
] });
|
|
1348
753
|
}
|
|
1349
754
|
|
|
1350
|
-
// src/components/
|
|
1351
|
-
import {
|
|
1352
|
-
import {
|
|
755
|
+
// src/components/DataGrid.tsx
|
|
756
|
+
import { useCallback as useCallback10, useEffect as useEffect7, useMemo as useMemo10, useRef as useRef2, useState as useState10 } from "react";
|
|
757
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
1353
758
|
|
|
1354
|
-
// src/
|
|
1355
|
-
import {
|
|
1356
|
-
import {
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
if (
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
759
|
+
// src/hooks/useExcelGrid.ts
|
|
760
|
+
import { formatCellValue, getColumnUniqueValues, isNumericRange } from "@smallwebco/tinypivot-core";
|
|
761
|
+
import {
|
|
762
|
+
getCoreRowModel,
|
|
763
|
+
getFilteredRowModel,
|
|
764
|
+
getSortedRowModel,
|
|
765
|
+
useReactTable
|
|
766
|
+
} from "@tanstack/react-table";
|
|
767
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo4, useState as useState4 } from "react";
|
|
768
|
+
var multiSelectFilter = (row, columnId, filterValue) => {
|
|
769
|
+
if (!filterValue)
|
|
770
|
+
return true;
|
|
771
|
+
if (isNumericRange(filterValue)) {
|
|
772
|
+
const cellValue = row.getValue(columnId);
|
|
773
|
+
if (cellValue === null || cellValue === void 0 || cellValue === "") {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
const num = typeof cellValue === "number" ? cellValue : Number.parseFloat(String(cellValue));
|
|
777
|
+
if (Number.isNaN(num))
|
|
778
|
+
return false;
|
|
779
|
+
const { min, max } = filterValue;
|
|
780
|
+
if (min !== null && num < min)
|
|
781
|
+
return false;
|
|
782
|
+
if (max !== null && num > max)
|
|
783
|
+
return false;
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
if (Array.isArray(filterValue) && filterValue.length > 0) {
|
|
787
|
+
const cellValue = row.getValue(columnId);
|
|
788
|
+
const cellString = cellValue === null || cellValue === void 0 || cellValue === "" ? "(blank)" : String(cellValue);
|
|
789
|
+
return filterValue.includes(cellString);
|
|
790
|
+
}
|
|
791
|
+
return true;
|
|
792
|
+
};
|
|
793
|
+
function useExcelGrid(options) {
|
|
794
|
+
const { data, enableSorting = true, enableFiltering = true } = options;
|
|
795
|
+
const [sorting, setSorting] = useState4([]);
|
|
796
|
+
const [columnFilters, setColumnFilters] = useState4([]);
|
|
797
|
+
const [columnVisibility, setColumnVisibility] = useState4({});
|
|
798
|
+
const [globalFilter, setGlobalFilter] = useState4("");
|
|
799
|
+
const [columnStatsCache, setColumnStatsCache] = useState4({});
|
|
800
|
+
const dataSignature = useMemo4(
|
|
801
|
+
() => `${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
802
|
+
[data]
|
|
803
|
+
);
|
|
804
|
+
const columnKeys = useMemo4(() => {
|
|
805
|
+
if (data.length === 0)
|
|
806
|
+
return [];
|
|
807
|
+
return Object.keys(data[0]);
|
|
808
|
+
}, [data]);
|
|
809
|
+
const getColumnStats = useCallback4(
|
|
810
|
+
(columnKey) => {
|
|
811
|
+
const cacheKey = `${columnKey}-${dataSignature}`;
|
|
812
|
+
if (!columnStatsCache[cacheKey]) {
|
|
813
|
+
const stats = getColumnUniqueValues(data, columnKey);
|
|
814
|
+
setColumnStatsCache((prev) => ({ ...prev, [cacheKey]: stats }));
|
|
815
|
+
return stats;
|
|
816
|
+
}
|
|
817
|
+
return columnStatsCache[cacheKey];
|
|
818
|
+
},
|
|
819
|
+
[data, columnStatsCache, dataSignature]
|
|
820
|
+
);
|
|
821
|
+
const clearStatsCache = useCallback4(() => {
|
|
822
|
+
setColumnStatsCache({});
|
|
823
|
+
}, []);
|
|
824
|
+
useEffect4(() => {
|
|
825
|
+
clearStatsCache();
|
|
826
|
+
}, [dataSignature, clearStatsCache]);
|
|
827
|
+
const columnDefs = useMemo4(() => {
|
|
828
|
+
return columnKeys.map((key) => {
|
|
829
|
+
const stats = getColumnStats(key);
|
|
830
|
+
return {
|
|
831
|
+
id: key,
|
|
832
|
+
accessorKey: key,
|
|
833
|
+
header: key,
|
|
834
|
+
cell: (info) => formatCellValue(info.getValue(), stats.type),
|
|
835
|
+
filterFn: multiSelectFilter,
|
|
836
|
+
meta: {
|
|
837
|
+
type: stats.type,
|
|
838
|
+
uniqueCount: stats.uniqueValues.length
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
});
|
|
842
|
+
}, [columnKeys, getColumnStats]);
|
|
843
|
+
const table = useReactTable({
|
|
844
|
+
data,
|
|
845
|
+
columns: columnDefs,
|
|
846
|
+
state: {
|
|
847
|
+
sorting,
|
|
848
|
+
columnFilters,
|
|
849
|
+
columnVisibility,
|
|
850
|
+
globalFilter
|
|
851
|
+
},
|
|
852
|
+
onSortingChange: setSorting,
|
|
853
|
+
onColumnFiltersChange: setColumnFilters,
|
|
854
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
855
|
+
onGlobalFilterChange: setGlobalFilter,
|
|
856
|
+
getCoreRowModel: getCoreRowModel(),
|
|
857
|
+
getSortedRowModel: enableSorting ? getSortedRowModel() : void 0,
|
|
858
|
+
getFilteredRowModel: enableFiltering ? getFilteredRowModel() : void 0,
|
|
859
|
+
filterFns: {
|
|
860
|
+
multiSelect: multiSelectFilter
|
|
861
|
+
},
|
|
862
|
+
enableSorting,
|
|
863
|
+
enableFilters: enableFiltering
|
|
864
|
+
});
|
|
865
|
+
const filteredRowCount = table.getFilteredRowModel().rows.length;
|
|
866
|
+
const totalRowCount = data.length;
|
|
867
|
+
const activeFilters = useMemo4(() => {
|
|
868
|
+
return columnFilters.map((f) => {
|
|
869
|
+
const filterValue = f.value;
|
|
870
|
+
if (filterValue && isNumericRange(filterValue)) {
|
|
871
|
+
return {
|
|
872
|
+
column: f.id,
|
|
873
|
+
type: "range",
|
|
874
|
+
range: filterValue,
|
|
875
|
+
values: []
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
column: f.id,
|
|
880
|
+
type: "values",
|
|
881
|
+
values: Array.isArray(filterValue) ? filterValue : [],
|
|
882
|
+
range: null
|
|
883
|
+
};
|
|
884
|
+
});
|
|
885
|
+
}, [columnFilters]);
|
|
886
|
+
const hasActiveFilter = useCallback4(
|
|
887
|
+
(columnId) => {
|
|
888
|
+
const column = table.getColumn(columnId);
|
|
889
|
+
if (!column)
|
|
890
|
+
return false;
|
|
891
|
+
const filterValue = column.getFilterValue();
|
|
892
|
+
if (!filterValue)
|
|
893
|
+
return false;
|
|
894
|
+
if (isNumericRange(filterValue)) {
|
|
895
|
+
return filterValue.min !== null || filterValue.max !== null;
|
|
896
|
+
}
|
|
897
|
+
return Array.isArray(filterValue) && filterValue.length > 0;
|
|
898
|
+
},
|
|
899
|
+
[table]
|
|
900
|
+
);
|
|
901
|
+
const setColumnFilter = useCallback4(
|
|
902
|
+
(columnId, values) => {
|
|
903
|
+
const column = table.getColumn(columnId);
|
|
904
|
+
if (column) {
|
|
905
|
+
column.setFilterValue(values.length === 0 ? void 0 : values);
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
[table]
|
|
909
|
+
);
|
|
910
|
+
const setNumericRangeFilter = useCallback4(
|
|
911
|
+
(columnId, range) => {
|
|
912
|
+
const column = table.getColumn(columnId);
|
|
913
|
+
if (column) {
|
|
914
|
+
if (!range || range.min === null && range.max === null) {
|
|
915
|
+
column.setFilterValue(void 0);
|
|
916
|
+
} else {
|
|
917
|
+
column.setFilterValue(range);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
[table]
|
|
922
|
+
);
|
|
923
|
+
const getNumericRangeFilter = useCallback4(
|
|
924
|
+
(columnId) => {
|
|
925
|
+
const column = table.getColumn(columnId);
|
|
926
|
+
if (!column)
|
|
927
|
+
return null;
|
|
928
|
+
const filterValue = column.getFilterValue();
|
|
929
|
+
if (filterValue && isNumericRange(filterValue)) {
|
|
930
|
+
return filterValue;
|
|
931
|
+
}
|
|
932
|
+
return null;
|
|
933
|
+
},
|
|
934
|
+
[table]
|
|
935
|
+
);
|
|
936
|
+
const clearAllFilters = useCallback4(() => {
|
|
937
|
+
table.resetColumnFilters();
|
|
938
|
+
setGlobalFilter("");
|
|
939
|
+
setColumnFilters([]);
|
|
940
|
+
}, [table]);
|
|
941
|
+
const getColumnFilterValues = useCallback4(
|
|
942
|
+
(columnId) => {
|
|
943
|
+
const column = table.getColumn(columnId);
|
|
944
|
+
if (!column)
|
|
945
|
+
return [];
|
|
946
|
+
const filterValue = column.getFilterValue();
|
|
947
|
+
return Array.isArray(filterValue) ? filterValue : [];
|
|
948
|
+
},
|
|
949
|
+
[table]
|
|
950
|
+
);
|
|
951
|
+
const toggleSort = useCallback4((columnId) => {
|
|
952
|
+
setSorting((prev) => {
|
|
953
|
+
const current = prev.find((s) => s.id === columnId);
|
|
954
|
+
if (!current) {
|
|
955
|
+
return [{ id: columnId, desc: false }];
|
|
956
|
+
} else if (!current.desc) {
|
|
957
|
+
return [{ id: columnId, desc: true }];
|
|
958
|
+
} else {
|
|
959
|
+
return [];
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
}, []);
|
|
963
|
+
const getSortDirection = useCallback4(
|
|
964
|
+
(columnId) => {
|
|
965
|
+
const sort = sorting.find((s) => s.id === columnId);
|
|
966
|
+
if (!sort)
|
|
967
|
+
return null;
|
|
968
|
+
return sort.desc ? "desc" : "asc";
|
|
969
|
+
},
|
|
970
|
+
[sorting]
|
|
971
|
+
);
|
|
972
|
+
return {
|
|
973
|
+
// Table instance
|
|
974
|
+
table,
|
|
975
|
+
// State
|
|
976
|
+
sorting,
|
|
977
|
+
columnFilters,
|
|
978
|
+
columnVisibility,
|
|
979
|
+
globalFilter,
|
|
980
|
+
columnKeys,
|
|
981
|
+
setSorting,
|
|
982
|
+
setColumnFilters,
|
|
983
|
+
setGlobalFilter,
|
|
984
|
+
// Computed
|
|
985
|
+
filteredRowCount,
|
|
986
|
+
totalRowCount,
|
|
987
|
+
activeFilters,
|
|
988
|
+
// Methods
|
|
989
|
+
getColumnStats,
|
|
990
|
+
clearStatsCache,
|
|
991
|
+
hasActiveFilter,
|
|
992
|
+
setColumnFilter,
|
|
993
|
+
getColumnFilterValues,
|
|
994
|
+
clearAllFilters,
|
|
995
|
+
toggleSort,
|
|
996
|
+
getSortDirection,
|
|
997
|
+
// Numeric range filters
|
|
998
|
+
setNumericRangeFilter,
|
|
999
|
+
getNumericRangeFilter
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/hooks/useGridFeatures.ts
|
|
1004
|
+
import {
|
|
1005
|
+
copyToClipboard as coreCopyToClipboard,
|
|
1006
|
+
exportPivotToCSV as coreExportPivotToCSV,
|
|
1007
|
+
exportToCSV as coreExportToCSV,
|
|
1008
|
+
formatSelectionForClipboard as coreFormatSelection
|
|
1009
|
+
} from "@smallwebco/tinypivot-core";
|
|
1010
|
+
import { useCallback as useCallback5, useMemo as useMemo5, useState as useState5 } from "react";
|
|
1011
|
+
function exportToCSV(data, columns, options) {
|
|
1012
|
+
coreExportToCSV(data, columns, options);
|
|
1013
|
+
}
|
|
1014
|
+
function exportPivotToCSV(pivotData, rowFields, columnFields, valueFields, options) {
|
|
1015
|
+
coreExportPivotToCSV(pivotData, rowFields, columnFields, valueFields, options);
|
|
1016
|
+
}
|
|
1017
|
+
function copyToClipboard(text, onSuccess, onError) {
|
|
1018
|
+
coreCopyToClipboard(text, onSuccess, onError);
|
|
1019
|
+
}
|
|
1020
|
+
function formatSelectionForClipboard(rows, columns, selectionBounds) {
|
|
1021
|
+
return coreFormatSelection(rows, columns, selectionBounds);
|
|
1022
|
+
}
|
|
1023
|
+
function usePagination(data, options = {}) {
|
|
1024
|
+
const [pageSize, setPageSize] = useState5(options.pageSize ?? 50);
|
|
1025
|
+
const [currentPage, setCurrentPage] = useState5(options.currentPage ?? 1);
|
|
1026
|
+
const totalPages = useMemo5(
|
|
1027
|
+
() => Math.max(1, Math.ceil(data.length / pageSize)),
|
|
1028
|
+
[data.length, pageSize]
|
|
1029
|
+
);
|
|
1030
|
+
const paginatedData = useMemo5(() => {
|
|
1031
|
+
const start = (currentPage - 1) * pageSize;
|
|
1032
|
+
const end = start + pageSize;
|
|
1033
|
+
return data.slice(start, end);
|
|
1034
|
+
}, [data, currentPage, pageSize]);
|
|
1035
|
+
const startIndex = useMemo5(() => (currentPage - 1) * pageSize + 1, [currentPage, pageSize]);
|
|
1036
|
+
const endIndex = useMemo5(
|
|
1037
|
+
() => Math.min(currentPage * pageSize, data.length),
|
|
1038
|
+
[currentPage, pageSize, data.length]
|
|
1039
|
+
);
|
|
1040
|
+
const goToPage = useCallback5(
|
|
1041
|
+
(page) => {
|
|
1042
|
+
setCurrentPage(Math.max(1, Math.min(page, totalPages)));
|
|
1043
|
+
},
|
|
1044
|
+
[totalPages]
|
|
1045
|
+
);
|
|
1046
|
+
const nextPage = useCallback5(() => {
|
|
1047
|
+
if (currentPage < totalPages) {
|
|
1048
|
+
setCurrentPage((prev) => prev + 1);
|
|
1049
|
+
}
|
|
1050
|
+
}, [currentPage, totalPages]);
|
|
1051
|
+
const prevPage = useCallback5(() => {
|
|
1052
|
+
if (currentPage > 1) {
|
|
1053
|
+
setCurrentPage((prev) => prev - 1);
|
|
1054
|
+
}
|
|
1055
|
+
}, [currentPage]);
|
|
1056
|
+
const firstPage = useCallback5(() => {
|
|
1057
|
+
setCurrentPage(1);
|
|
1058
|
+
}, []);
|
|
1059
|
+
const lastPage = useCallback5(() => {
|
|
1060
|
+
setCurrentPage(totalPages);
|
|
1061
|
+
}, [totalPages]);
|
|
1062
|
+
const updatePageSize = useCallback5((size) => {
|
|
1063
|
+
setPageSize(size);
|
|
1064
|
+
setCurrentPage(1);
|
|
1065
|
+
}, []);
|
|
1066
|
+
return {
|
|
1067
|
+
pageSize,
|
|
1068
|
+
currentPage,
|
|
1069
|
+
totalPages,
|
|
1070
|
+
paginatedData,
|
|
1071
|
+
startIndex,
|
|
1072
|
+
endIndex,
|
|
1073
|
+
goToPage,
|
|
1074
|
+
nextPage,
|
|
1075
|
+
prevPage,
|
|
1076
|
+
firstPage,
|
|
1077
|
+
lastPage,
|
|
1078
|
+
setPageSize: updatePageSize
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function useGlobalSearch(data, columns) {
|
|
1082
|
+
const [searchTerm, setSearchTerm] = useState5("");
|
|
1083
|
+
const [caseSensitive, setCaseSensitive] = useState5(false);
|
|
1084
|
+
const filteredData = useMemo5(() => {
|
|
1085
|
+
if (!searchTerm.trim()) {
|
|
1086
|
+
return data;
|
|
1087
|
+
}
|
|
1088
|
+
const term = caseSensitive ? searchTerm.trim() : searchTerm.trim().toLowerCase();
|
|
1089
|
+
return data.filter((row) => {
|
|
1090
|
+
for (const col of columns) {
|
|
1091
|
+
const value = row[col];
|
|
1092
|
+
if (value === null || value === void 0)
|
|
1093
|
+
continue;
|
|
1094
|
+
const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
|
|
1095
|
+
if (strValue.includes(term)) {
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return false;
|
|
1100
|
+
});
|
|
1101
|
+
}, [data, columns, searchTerm, caseSensitive]);
|
|
1102
|
+
const clearSearch = useCallback5(() => {
|
|
1103
|
+
setSearchTerm("");
|
|
1104
|
+
}, []);
|
|
1105
|
+
return {
|
|
1106
|
+
searchTerm,
|
|
1107
|
+
setSearchTerm,
|
|
1108
|
+
caseSensitive,
|
|
1109
|
+
setCaseSensitive,
|
|
1110
|
+
filteredData,
|
|
1111
|
+
clearSearch
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
function useRowSelection(data) {
|
|
1115
|
+
const [selectedRowIndices, setSelectedRowIndices] = useState5(/* @__PURE__ */ new Set());
|
|
1116
|
+
const selectedRows = useMemo5(() => {
|
|
1117
|
+
return Array.from(selectedRowIndices).sort((a, b) => a - b).map((idx) => data[idx]).filter(Boolean);
|
|
1118
|
+
}, [data, selectedRowIndices]);
|
|
1119
|
+
const allSelected = useMemo5(() => {
|
|
1120
|
+
return data.length > 0 && selectedRowIndices.size === data.length;
|
|
1121
|
+
}, [data.length, selectedRowIndices.size]);
|
|
1122
|
+
const someSelected = useMemo5(() => {
|
|
1123
|
+
return selectedRowIndices.size > 0 && selectedRowIndices.size < data.length;
|
|
1124
|
+
}, [data.length, selectedRowIndices.size]);
|
|
1125
|
+
const toggleRow = useCallback5((index) => {
|
|
1126
|
+
setSelectedRowIndices((prev) => {
|
|
1127
|
+
const next = new Set(prev);
|
|
1128
|
+
if (next.has(index)) {
|
|
1129
|
+
next.delete(index);
|
|
1130
|
+
} else {
|
|
1131
|
+
next.add(index);
|
|
1132
|
+
}
|
|
1133
|
+
return next;
|
|
1134
|
+
});
|
|
1135
|
+
}, []);
|
|
1136
|
+
const selectRow = useCallback5((index) => {
|
|
1137
|
+
setSelectedRowIndices((prev) => /* @__PURE__ */ new Set([...prev, index]));
|
|
1138
|
+
}, []);
|
|
1139
|
+
const deselectRow = useCallback5((index) => {
|
|
1140
|
+
setSelectedRowIndices((prev) => {
|
|
1141
|
+
const next = new Set(prev);
|
|
1142
|
+
next.delete(index);
|
|
1143
|
+
return next;
|
|
1144
|
+
});
|
|
1145
|
+
}, []);
|
|
1146
|
+
const selectAll = useCallback5(() => {
|
|
1147
|
+
setSelectedRowIndices(new Set(data.map((_, idx) => idx)));
|
|
1148
|
+
}, [data]);
|
|
1149
|
+
const deselectAll = useCallback5(() => {
|
|
1150
|
+
setSelectedRowIndices(/* @__PURE__ */ new Set());
|
|
1151
|
+
}, []);
|
|
1152
|
+
const toggleAll = useCallback5(() => {
|
|
1153
|
+
if (allSelected) {
|
|
1154
|
+
deselectAll();
|
|
1155
|
+
} else {
|
|
1156
|
+
selectAll();
|
|
1157
|
+
}
|
|
1158
|
+
}, [allSelected, selectAll, deselectAll]);
|
|
1159
|
+
const isSelected = useCallback5(
|
|
1160
|
+
(index) => {
|
|
1161
|
+
return selectedRowIndices.has(index);
|
|
1162
|
+
},
|
|
1163
|
+
[selectedRowIndices]
|
|
1164
|
+
);
|
|
1165
|
+
const selectRange = useCallback5((startIndex, endIndex) => {
|
|
1166
|
+
const min = Math.min(startIndex, endIndex);
|
|
1167
|
+
const max = Math.max(startIndex, endIndex);
|
|
1168
|
+
setSelectedRowIndices((prev) => {
|
|
1169
|
+
const next = new Set(prev);
|
|
1170
|
+
for (let i = min; i <= max; i++) {
|
|
1171
|
+
next.add(i);
|
|
1172
|
+
}
|
|
1173
|
+
return next;
|
|
1174
|
+
});
|
|
1175
|
+
}, []);
|
|
1176
|
+
return {
|
|
1177
|
+
selectedRowIndices,
|
|
1178
|
+
selectedRows,
|
|
1179
|
+
allSelected,
|
|
1180
|
+
someSelected,
|
|
1181
|
+
toggleRow,
|
|
1182
|
+
selectRow,
|
|
1183
|
+
deselectRow,
|
|
1184
|
+
selectAll,
|
|
1185
|
+
deselectAll,
|
|
1186
|
+
toggleAll,
|
|
1187
|
+
isSelected,
|
|
1188
|
+
selectRange
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
function useColumnResize(initialWidths, minWidth = 60, maxWidth = 600) {
|
|
1192
|
+
const [columnWidths, setColumnWidths] = useState5({ ...initialWidths });
|
|
1193
|
+
const [isResizing, setIsResizing] = useState5(false);
|
|
1194
|
+
const [resizingColumn, setResizingColumn] = useState5(null);
|
|
1195
|
+
const startResize = useCallback5(
|
|
1196
|
+
(columnId, event) => {
|
|
1197
|
+
setIsResizing(true);
|
|
1198
|
+
setResizingColumn(columnId);
|
|
1199
|
+
const startX = event.clientX;
|
|
1200
|
+
const startWidth = columnWidths[columnId] || 150;
|
|
1201
|
+
const handleMouseMove = (e) => {
|
|
1202
|
+
const diff = e.clientX - startX;
|
|
1203
|
+
const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + diff));
|
|
1204
|
+
setColumnWidths((prev) => ({
|
|
1205
|
+
...prev,
|
|
1206
|
+
[columnId]: newWidth
|
|
1207
|
+
}));
|
|
1208
|
+
};
|
|
1209
|
+
const handleMouseUp = () => {
|
|
1210
|
+
setIsResizing(false);
|
|
1211
|
+
setResizingColumn(null);
|
|
1212
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
1213
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
1214
|
+
};
|
|
1215
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
1216
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
1217
|
+
},
|
|
1218
|
+
[columnWidths, minWidth, maxWidth]
|
|
1219
|
+
);
|
|
1220
|
+
const resetColumnWidth = useCallback5(
|
|
1221
|
+
(columnId) => {
|
|
1222
|
+
if (initialWidths[columnId]) {
|
|
1223
|
+
setColumnWidths((prev) => ({
|
|
1224
|
+
...prev,
|
|
1225
|
+
[columnId]: initialWidths[columnId]
|
|
1226
|
+
}));
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
[initialWidths]
|
|
1230
|
+
);
|
|
1231
|
+
const resetAllWidths = useCallback5(() => {
|
|
1232
|
+
setColumnWidths({ ...initialWidths });
|
|
1233
|
+
}, [initialWidths]);
|
|
1234
|
+
return {
|
|
1235
|
+
columnWidths,
|
|
1236
|
+
setColumnWidths,
|
|
1237
|
+
isResizing,
|
|
1238
|
+
resizingColumn,
|
|
1239
|
+
startResize,
|
|
1240
|
+
resetColumnWidth,
|
|
1241
|
+
resetAllWidths
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/hooks/useLicense.ts
|
|
1246
|
+
import {
|
|
1247
|
+
canUsePivot as coreCanUsePivot,
|
|
1248
|
+
configureLicenseSecret as coreConfigureLicenseSecret,
|
|
1249
|
+
isPro as coreIsPro,
|
|
1250
|
+
shouldShowWatermark as coreShouldShowWatermark,
|
|
1251
|
+
getDemoLicenseInfo,
|
|
1252
|
+
getFreeLicenseInfo,
|
|
1253
|
+
logProRequired,
|
|
1254
|
+
validateLicenseKey
|
|
1255
|
+
} from "@smallwebco/tinypivot-core";
|
|
1256
|
+
import { useCallback as useCallback6, useMemo as useMemo6, useState as useState6 } from "react";
|
|
1257
|
+
var globalLicenseInfo = getFreeLicenseInfo();
|
|
1258
|
+
var globalDemoMode = false;
|
|
1259
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
1260
|
+
function notifyListeners() {
|
|
1261
|
+
listeners.forEach((listener) => listener());
|
|
1262
|
+
}
|
|
1263
|
+
async function setLicenseKey(key) {
|
|
1264
|
+
globalLicenseInfo = await validateLicenseKey(key);
|
|
1265
|
+
if (!globalLicenseInfo.isValid) {
|
|
1266
|
+
console.warn("[TinyPivot] Invalid or expired license key. Running in free mode.");
|
|
1267
|
+
} else if (globalLicenseInfo.type !== "free") {
|
|
1268
|
+
console.info(`[TinyPivot] Pro license activated (${globalLicenseInfo.type})`);
|
|
1269
|
+
}
|
|
1270
|
+
notifyListeners();
|
|
1271
|
+
}
|
|
1272
|
+
async function enableDemoMode(secret) {
|
|
1273
|
+
const demoLicense = await getDemoLicenseInfo(secret);
|
|
1274
|
+
if (!demoLicense) {
|
|
1275
|
+
console.warn("[TinyPivot] Demo mode activation failed - invalid secret");
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
globalDemoMode = true;
|
|
1279
|
+
globalLicenseInfo = demoLicense;
|
|
1280
|
+
console.info("[TinyPivot] Demo mode enabled - all Pro features unlocked for evaluation");
|
|
1281
|
+
notifyListeners();
|
|
1282
|
+
return true;
|
|
1283
|
+
}
|
|
1284
|
+
function configureLicenseSecret(secret) {
|
|
1285
|
+
coreConfigureLicenseSecret(secret);
|
|
1286
|
+
}
|
|
1287
|
+
function useLicense() {
|
|
1288
|
+
const [, forceUpdate] = useState6({});
|
|
1289
|
+
useState6(() => {
|
|
1290
|
+
const update = () => forceUpdate({});
|
|
1291
|
+
listeners.add(update);
|
|
1292
|
+
return () => listeners.delete(update);
|
|
1293
|
+
});
|
|
1294
|
+
const isDemo = globalDemoMode;
|
|
1295
|
+
const licenseInfo = globalLicenseInfo;
|
|
1296
|
+
const isPro = useMemo6(
|
|
1297
|
+
() => globalDemoMode || coreIsPro(licenseInfo),
|
|
1298
|
+
[licenseInfo]
|
|
1299
|
+
);
|
|
1300
|
+
const canUsePivot = useMemo6(
|
|
1301
|
+
() => globalDemoMode || coreCanUsePivot(licenseInfo),
|
|
1302
|
+
[licenseInfo]
|
|
1303
|
+
);
|
|
1304
|
+
const canUseAdvancedAggregations = useMemo6(
|
|
1305
|
+
() => globalDemoMode || licenseInfo.features.advancedAggregations,
|
|
1306
|
+
[licenseInfo]
|
|
1307
|
+
);
|
|
1308
|
+
const canUsePercentageMode = useMemo6(
|
|
1309
|
+
() => globalDemoMode || licenseInfo.features.percentageMode,
|
|
1310
|
+
[licenseInfo]
|
|
1311
|
+
);
|
|
1312
|
+
const showWatermark = useMemo6(
|
|
1313
|
+
() => coreShouldShowWatermark(licenseInfo, globalDemoMode),
|
|
1314
|
+
[licenseInfo]
|
|
1315
|
+
);
|
|
1316
|
+
const requirePro = useCallback6((feature) => {
|
|
1317
|
+
if (!isPro) {
|
|
1318
|
+
logProRequired(feature);
|
|
1319
|
+
return false;
|
|
1320
|
+
}
|
|
1321
|
+
return true;
|
|
1322
|
+
}, [isPro]);
|
|
1323
|
+
return {
|
|
1324
|
+
licenseInfo,
|
|
1325
|
+
isDemo,
|
|
1326
|
+
isPro,
|
|
1327
|
+
canUsePivot,
|
|
1328
|
+
canUseAdvancedAggregations,
|
|
1329
|
+
canUsePercentageMode,
|
|
1330
|
+
showWatermark,
|
|
1331
|
+
requirePro
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// src/hooks/usePivotTable.ts
|
|
1336
|
+
import {
|
|
1337
|
+
computeAvailableFields,
|
|
1338
|
+
computePivotResult,
|
|
1339
|
+
generateStorageKey,
|
|
1340
|
+
getAggregationLabel,
|
|
1341
|
+
getUnassignedFields,
|
|
1342
|
+
isConfigValidForFields,
|
|
1343
|
+
isPivotConfigured,
|
|
1344
|
+
loadCalculatedFields,
|
|
1345
|
+
loadPivotConfig,
|
|
1346
|
+
saveCalculatedFields,
|
|
1347
|
+
savePivotConfig
|
|
1348
|
+
} from "@smallwebco/tinypivot-core";
|
|
1349
|
+
import { useCallback as useCallback7, useEffect as useEffect5, useMemo as useMemo7, useState as useState7 } from "react";
|
|
1350
|
+
function usePivotTable(data) {
|
|
1351
|
+
const { canUsePivot, requirePro } = useLicense();
|
|
1352
|
+
const [rowFields, setRowFieldsState] = useState7([]);
|
|
1353
|
+
const [columnFields, setColumnFieldsState] = useState7([]);
|
|
1354
|
+
const [valueFields, setValueFields] = useState7([]);
|
|
1355
|
+
const [showRowTotals, setShowRowTotals] = useState7(true);
|
|
1356
|
+
const [showColumnTotals, setShowColumnTotals] = useState7(true);
|
|
1357
|
+
const [calculatedFields, setCalculatedFields] = useState7(() => loadCalculatedFields());
|
|
1358
|
+
const [currentStorageKey, setCurrentStorageKey] = useState7(null);
|
|
1359
|
+
const availableFields = useMemo7(() => {
|
|
1360
|
+
return computeAvailableFields(data);
|
|
1361
|
+
}, [data]);
|
|
1362
|
+
const unassignedFields = useMemo7(() => {
|
|
1363
|
+
return getUnassignedFields(availableFields, rowFields, columnFields, valueFields);
|
|
1364
|
+
}, [availableFields, rowFields, columnFields, valueFields]);
|
|
1365
|
+
const isConfigured = useMemo7(() => {
|
|
1366
|
+
return isPivotConfigured({
|
|
1367
|
+
rowFields,
|
|
1368
|
+
columnFields,
|
|
1369
|
+
valueFields,
|
|
1370
|
+
showRowTotals,
|
|
1371
|
+
showColumnTotals
|
|
1372
|
+
});
|
|
1373
|
+
}, [rowFields, columnFields, valueFields, showRowTotals, showColumnTotals]);
|
|
1374
|
+
const pivotResult = useMemo7(() => {
|
|
1375
|
+
if (!isConfigured)
|
|
1376
|
+
return null;
|
|
1377
|
+
if (!canUsePivot)
|
|
1378
|
+
return null;
|
|
1379
|
+
return computePivotResult(data, {
|
|
1380
|
+
rowFields,
|
|
1381
|
+
columnFields,
|
|
1382
|
+
valueFields,
|
|
1383
|
+
showRowTotals,
|
|
1384
|
+
showColumnTotals,
|
|
1385
|
+
calculatedFields
|
|
1386
|
+
});
|
|
1387
|
+
}, [data, isConfigured, canUsePivot, rowFields, columnFields, valueFields, showRowTotals, showColumnTotals, calculatedFields]);
|
|
1388
|
+
useEffect5(() => {
|
|
1389
|
+
if (data.length === 0)
|
|
1390
|
+
return;
|
|
1391
|
+
const newKeys = Object.keys(data[0]);
|
|
1392
|
+
const storageKey = generateStorageKey(newKeys);
|
|
1393
|
+
if (storageKey !== currentStorageKey) {
|
|
1394
|
+
setCurrentStorageKey(storageKey);
|
|
1395
|
+
const savedConfig = loadPivotConfig(storageKey);
|
|
1396
|
+
if (savedConfig && isConfigValidForFields(savedConfig, newKeys)) {
|
|
1397
|
+
setRowFieldsState(savedConfig.rowFields);
|
|
1398
|
+
setColumnFieldsState(savedConfig.columnFields);
|
|
1399
|
+
setValueFields(savedConfig.valueFields);
|
|
1400
|
+
setShowRowTotals(savedConfig.showRowTotals);
|
|
1401
|
+
setShowColumnTotals(savedConfig.showColumnTotals);
|
|
1402
|
+
if (savedConfig.calculatedFields) {
|
|
1403
|
+
setCalculatedFields(savedConfig.calculatedFields);
|
|
1404
|
+
}
|
|
1378
1405
|
} else {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1406
|
+
const currentConfig = {
|
|
1407
|
+
rowFields,
|
|
1408
|
+
columnFields,
|
|
1409
|
+
valueFields,
|
|
1410
|
+
showRowTotals,
|
|
1411
|
+
showColumnTotals
|
|
1412
|
+
};
|
|
1413
|
+
if (!isConfigValidForFields(currentConfig, newKeys)) {
|
|
1414
|
+
setRowFieldsState([]);
|
|
1415
|
+
setColumnFieldsState([]);
|
|
1416
|
+
setValueFields([]);
|
|
1417
|
+
}
|
|
1383
1418
|
}
|
|
1384
|
-
setError(null);
|
|
1385
1419
|
}
|
|
1386
|
-
}, [
|
|
1387
|
-
|
|
1388
|
-
if (!
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1420
|
+
}, [data]);
|
|
1421
|
+
useEffect5(() => {
|
|
1422
|
+
if (!currentStorageKey)
|
|
1423
|
+
return;
|
|
1424
|
+
const config = {
|
|
1425
|
+
rowFields,
|
|
1426
|
+
columnFields,
|
|
1427
|
+
valueFields,
|
|
1428
|
+
showRowTotals,
|
|
1429
|
+
showColumnTotals,
|
|
1430
|
+
calculatedFields
|
|
1431
|
+
};
|
|
1432
|
+
savePivotConfig(currentStorageKey, config);
|
|
1433
|
+
}, [currentStorageKey, rowFields, columnFields, valueFields, showRowTotals, showColumnTotals, calculatedFields]);
|
|
1434
|
+
const addRowField = useCallback7(
|
|
1435
|
+
(field) => {
|
|
1436
|
+
if (!rowFields.includes(field)) {
|
|
1437
|
+
setRowFieldsState((prev) => [...prev, field]);
|
|
1395
1438
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1439
|
+
},
|
|
1440
|
+
[rowFields]
|
|
1441
|
+
);
|
|
1442
|
+
const removeRowField = useCallback7((field) => {
|
|
1443
|
+
setRowFieldsState((prev) => prev.filter((f) => f !== field));
|
|
1398
1444
|
}, []);
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1445
|
+
const setRowFields = useCallback7((fields) => {
|
|
1446
|
+
setRowFieldsState(fields);
|
|
1447
|
+
}, []);
|
|
1448
|
+
const addColumnField = useCallback7(
|
|
1449
|
+
(field) => {
|
|
1450
|
+
if (!columnFields.includes(field)) {
|
|
1451
|
+
setColumnFieldsState((prev) => [...prev, field]);
|
|
1452
|
+
}
|
|
1453
|
+
},
|
|
1454
|
+
[columnFields]
|
|
1455
|
+
);
|
|
1456
|
+
const removeColumnField = useCallback7((field) => {
|
|
1457
|
+
setColumnFieldsState((prev) => prev.filter((f) => f !== field));
|
|
1458
|
+
}, []);
|
|
1459
|
+
const setColumnFields = useCallback7((fields) => {
|
|
1460
|
+
setColumnFieldsState(fields);
|
|
1461
|
+
}, []);
|
|
1462
|
+
const addValueField = useCallback7(
|
|
1463
|
+
(field, aggregation = "sum") => {
|
|
1464
|
+
if (aggregation !== "sum" && !requirePro(`${aggregation} aggregation`)) {
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
setValueFields((prev) => {
|
|
1468
|
+
if (prev.some((v) => v.field === field && v.aggregation === aggregation)) {
|
|
1469
|
+
return prev;
|
|
1470
|
+
}
|
|
1471
|
+
return [...prev, { field, aggregation }];
|
|
1472
|
+
});
|
|
1473
|
+
},
|
|
1474
|
+
[requirePro]
|
|
1475
|
+
);
|
|
1476
|
+
const removeValueField = useCallback7((field, aggregation) => {
|
|
1477
|
+
setValueFields((prev) => {
|
|
1478
|
+
if (aggregation) {
|
|
1479
|
+
return prev.filter((v) => !(v.field === field && v.aggregation === aggregation));
|
|
1403
1480
|
}
|
|
1404
|
-
return prev
|
|
1481
|
+
return prev.filter((v) => v.field !== field);
|
|
1405
1482
|
});
|
|
1406
1483
|
}, []);
|
|
1407
|
-
const
|
|
1408
|
-
|
|
1409
|
-
|
|
1484
|
+
const updateValueFieldAggregation = useCallback7(
|
|
1485
|
+
(field, oldAgg, newAgg) => {
|
|
1486
|
+
setValueFields(
|
|
1487
|
+
(prev) => prev.map((v) => {
|
|
1488
|
+
if (v.field === field && v.aggregation === oldAgg) {
|
|
1489
|
+
return { ...v, aggregation: newAgg };
|
|
1490
|
+
}
|
|
1491
|
+
return v;
|
|
1492
|
+
})
|
|
1493
|
+
);
|
|
1494
|
+
},
|
|
1495
|
+
[]
|
|
1496
|
+
);
|
|
1497
|
+
const clearConfig = useCallback7(() => {
|
|
1498
|
+
setRowFieldsState([]);
|
|
1499
|
+
setColumnFieldsState([]);
|
|
1500
|
+
setValueFields([]);
|
|
1501
|
+
}, []);
|
|
1502
|
+
const autoSuggestConfig = useCallback7(() => {
|
|
1503
|
+
if (!requirePro("Pivot Table - Auto Suggest"))
|
|
1410
1504
|
return;
|
|
1411
|
-
|
|
1412
|
-
const validationResult = validateSimpleFormula(formula, availableFields);
|
|
1413
|
-
if (validationResult) {
|
|
1414
|
-
setError(validationResult);
|
|
1505
|
+
if (availableFields.length === 0)
|
|
1415
1506
|
return;
|
|
1507
|
+
const categoricalFields = availableFields.filter((f) => !f.isNumeric && f.uniqueCount < 50);
|
|
1508
|
+
const numericFields = availableFields.filter((f) => f.isNumeric);
|
|
1509
|
+
if (categoricalFields.length > 0 && numericFields.length > 0) {
|
|
1510
|
+
setRowFieldsState([categoricalFields[0].field]);
|
|
1511
|
+
setValueFields([{ field: numericFields[0].field, aggregation: "sum" }]);
|
|
1416
1512
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
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
|
-
/* @__PURE__ */ jsxs3("div", { className: "vpg-form-group", children: [
|
|
1471
|
-
/* @__PURE__ */ jsx3("label", { className: "vpg-label-small", children: "Operators" }),
|
|
1472
|
-
/* @__PURE__ */ jsxs3("div", { className: "vpg-button-group", children: [
|
|
1473
|
-
/* @__PURE__ */ jsx3("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("+"), children: "+" }),
|
|
1474
|
-
/* @__PURE__ */ jsx3("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("-"), children: "\u2212" }),
|
|
1475
|
-
/* @__PURE__ */ jsx3("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("*"), children: "\xD7" }),
|
|
1476
|
-
/* @__PURE__ */ jsx3("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("/"), children: "\xF7" }),
|
|
1477
|
-
/* @__PURE__ */ jsx3("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator("("), children: "(" }),
|
|
1478
|
-
/* @__PURE__ */ jsx3("button", { className: "vpg-insert-btn vpg-op-btn", onClick: () => insertOperator(")"), children: ")" })
|
|
1479
|
-
] })
|
|
1480
|
-
] }),
|
|
1481
|
-
/* @__PURE__ */ jsxs3("div", { className: "vpg-form-group", children: [
|
|
1482
|
-
/* @__PURE__ */ jsx3("label", { className: "vpg-label-small", children: "Insert Field" }),
|
|
1483
|
-
availableFields.length > 0 ? /* @__PURE__ */ jsx3("div", { className: "vpg-button-group vpg-field-buttons", children: availableFields.map((field) => /* @__PURE__ */ jsx3(
|
|
1484
|
-
"button",
|
|
1485
|
-
{
|
|
1486
|
-
className: "vpg-insert-btn vpg-field-btn",
|
|
1487
|
-
onClick: () => insertField(field),
|
|
1488
|
-
children: field
|
|
1489
|
-
},
|
|
1490
|
-
field
|
|
1491
|
-
)) }) : /* @__PURE__ */ jsx3("div", { className: "vpg-no-fields", children: "No numeric fields available" })
|
|
1492
|
-
] }),
|
|
1493
|
-
/* @__PURE__ */ jsxs3("div", { className: "vpg-form-row", children: [
|
|
1494
|
-
/* @__PURE__ */ jsxs3("div", { className: "vpg-form-group vpg-form-group-half", children: [
|
|
1495
|
-
/* @__PURE__ */ jsx3("label", { className: "vpg-label", children: "Format As" }),
|
|
1496
|
-
/* @__PURE__ */ jsxs3(
|
|
1497
|
-
"select",
|
|
1498
|
-
{
|
|
1499
|
-
className: "vpg-select",
|
|
1500
|
-
value: formatAs,
|
|
1501
|
-
onChange: (e) => setFormatAs(e.target.value),
|
|
1502
|
-
children: [
|
|
1503
|
-
/* @__PURE__ */ jsx3("option", { value: "number", children: "Number" }),
|
|
1504
|
-
/* @__PURE__ */ jsx3("option", { value: "percent", children: "Percentage" }),
|
|
1505
|
-
/* @__PURE__ */ jsx3("option", { value: "currency", children: "Currency ($)" })
|
|
1506
|
-
]
|
|
1507
|
-
}
|
|
1508
|
-
)
|
|
1509
|
-
] }),
|
|
1510
|
-
/* @__PURE__ */ jsxs3("div", { className: "vpg-form-group vpg-form-group-half", children: [
|
|
1511
|
-
/* @__PURE__ */ jsx3("label", { className: "vpg-label", children: "Decimals" }),
|
|
1512
|
-
/* @__PURE__ */ jsx3(
|
|
1513
|
-
"input",
|
|
1514
|
-
{
|
|
1515
|
-
type: "number",
|
|
1516
|
-
className: "vpg-input",
|
|
1517
|
-
min: 0,
|
|
1518
|
-
max: 6,
|
|
1519
|
-
value: decimals,
|
|
1520
|
-
onChange: (e) => setDecimals(Number(e.target.value))
|
|
1521
|
-
}
|
|
1522
|
-
)
|
|
1523
|
-
] })
|
|
1524
|
-
] }),
|
|
1525
|
-
error && /* @__PURE__ */ jsx3("div", { className: "vpg-error vpg-error-box", children: error })
|
|
1526
|
-
] }),
|
|
1527
|
-
/* @__PURE__ */ jsxs3("div", { className: "vpg-modal-footer", children: [
|
|
1528
|
-
/* @__PURE__ */ jsx3("button", { className: "vpg-btn vpg-btn-secondary", onClick: onClose, children: "Cancel" }),
|
|
1529
|
-
/* @__PURE__ */ jsxs3("button", { className: "vpg-btn vpg-btn-primary", onClick: handleSave, children: [
|
|
1530
|
-
existingField ? "Update" : "Add",
|
|
1531
|
-
" Field"
|
|
1532
|
-
] })
|
|
1533
|
-
] })
|
|
1534
|
-
] }) });
|
|
1535
|
-
if (typeof document === "undefined") return null;
|
|
1536
|
-
return createPortal(modalContent, document.body);
|
|
1513
|
+
}, [availableFields, requirePro]);
|
|
1514
|
+
const addCalculatedField = useCallback7((field) => {
|
|
1515
|
+
setCalculatedFields((prev) => {
|
|
1516
|
+
const existing = prev.findIndex((f) => f.id === field.id);
|
|
1517
|
+
let updated;
|
|
1518
|
+
if (existing >= 0) {
|
|
1519
|
+
updated = [...prev.slice(0, existing), field, ...prev.slice(existing + 1)];
|
|
1520
|
+
} else {
|
|
1521
|
+
updated = [...prev, field];
|
|
1522
|
+
}
|
|
1523
|
+
saveCalculatedFields(updated);
|
|
1524
|
+
return updated;
|
|
1525
|
+
});
|
|
1526
|
+
}, []);
|
|
1527
|
+
const removeCalculatedField = useCallback7((id) => {
|
|
1528
|
+
setCalculatedFields((prev) => {
|
|
1529
|
+
const updated = prev.filter((f) => f.id !== id);
|
|
1530
|
+
saveCalculatedFields(updated);
|
|
1531
|
+
return updated;
|
|
1532
|
+
});
|
|
1533
|
+
setValueFields((prev) => prev.filter((v) => v.field !== `calc:${id}`));
|
|
1534
|
+
}, []);
|
|
1535
|
+
return {
|
|
1536
|
+
// State
|
|
1537
|
+
rowFields,
|
|
1538
|
+
columnFields,
|
|
1539
|
+
valueFields,
|
|
1540
|
+
showRowTotals,
|
|
1541
|
+
showColumnTotals,
|
|
1542
|
+
calculatedFields,
|
|
1543
|
+
// Computed
|
|
1544
|
+
availableFields,
|
|
1545
|
+
unassignedFields,
|
|
1546
|
+
isConfigured,
|
|
1547
|
+
pivotResult,
|
|
1548
|
+
// Actions
|
|
1549
|
+
addRowField,
|
|
1550
|
+
removeRowField,
|
|
1551
|
+
addColumnField,
|
|
1552
|
+
removeColumnField,
|
|
1553
|
+
addValueField,
|
|
1554
|
+
removeValueField,
|
|
1555
|
+
updateValueFieldAggregation,
|
|
1556
|
+
clearConfig,
|
|
1557
|
+
setShowRowTotals,
|
|
1558
|
+
setShowColumnTotals,
|
|
1559
|
+
autoSuggestConfig,
|
|
1560
|
+
setRowFields,
|
|
1561
|
+
setColumnFields,
|
|
1562
|
+
addCalculatedField,
|
|
1563
|
+
removeCalculatedField
|
|
1564
|
+
};
|
|
1537
1565
|
}
|
|
1538
1566
|
|
|
1539
1567
|
// src/components/PivotConfig.tsx
|
|
1568
|
+
import { AGGREGATION_OPTIONS, getAggregationSymbol } from "@smallwebco/tinypivot-core";
|
|
1569
|
+
import { useCallback as useCallback8, useMemo as useMemo8, useState as useState8 } from "react";
|
|
1540
1570
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1541
1571
|
function aggregationRequiresPro(agg) {
|
|
1542
1572
|
return agg !== "sum";
|
|
1543
1573
|
}
|
|
1544
1574
|
function getFieldIcon(type, isCalculated) {
|
|
1545
|
-
if (isCalculated)
|
|
1575
|
+
if (isCalculated)
|
|
1576
|
+
return "\u0192";
|
|
1546
1577
|
switch (type) {
|
|
1547
1578
|
case "number":
|
|
1548
1579
|
return "#";
|
|
@@ -1564,7 +1595,7 @@ function PivotConfig({
|
|
|
1564
1595
|
onShowRowTotalsChange,
|
|
1565
1596
|
onShowColumnTotalsChange,
|
|
1566
1597
|
onClearConfig,
|
|
1567
|
-
onAutoSuggest,
|
|
1598
|
+
onAutoSuggest: _onAutoSuggest,
|
|
1568
1599
|
onDragStart,
|
|
1569
1600
|
onDragEnd,
|
|
1570
1601
|
onUpdateAggregation,
|
|
@@ -1584,12 +1615,10 @@ function PivotConfig({
|
|
|
1584
1615
|
const isAggregationAvailable = useCallback8((agg) => {
|
|
1585
1616
|
return !aggregationRequiresPro(agg) || canUseAdvancedAggregations;
|
|
1586
1617
|
}, [canUseAdvancedAggregations]);
|
|
1587
|
-
const numericFieldNames = useMemo8(
|
|
1588
|
-
() => availableFields.filter((f) => f.isNumeric).map((f) => f.field),
|
|
1589
|
-
[availableFields]
|
|
1590
|
-
);
|
|
1618
|
+
const numericFieldNames = useMemo8(() => availableFields.filter((f) => f.isNumeric).map((f) => f.field), [availableFields]);
|
|
1591
1619
|
const calculatedFieldsAsStats = useMemo8(() => {
|
|
1592
|
-
if (!calculatedFields)
|
|
1620
|
+
if (!calculatedFields)
|
|
1621
|
+
return [];
|
|
1593
1622
|
return calculatedFields.map((calc) => ({
|
|
1594
1623
|
field: `calc:${calc.id}`,
|
|
1595
1624
|
type: "number",
|
|
@@ -1624,7 +1653,8 @@ function PivotConfig({
|
|
|
1624
1653
|
);
|
|
1625
1654
|
}, [allAvailableFields, rowFields, columnFields, valueFields]);
|
|
1626
1655
|
const filteredUnassignedFields = useMemo8(() => {
|
|
1627
|
-
if (!fieldSearch.trim())
|
|
1656
|
+
if (!fieldSearch.trim())
|
|
1657
|
+
return unassignedFields;
|
|
1628
1658
|
const search = fieldSearch.toLowerCase().trim();
|
|
1629
1659
|
return unassignedFields.filter((f) => {
|
|
1630
1660
|
const fieldName = f.field.toLowerCase();
|
|
@@ -1829,7 +1859,8 @@ function PivotConfig({
|
|
|
1829
1859
|
] }),
|
|
1830
1860
|
/* @__PURE__ */ jsxs4("div", { className: "vpg-unassigned-section", children: [
|
|
1831
1861
|
/* @__PURE__ */ jsx4("div", { className: "vpg-section-header", children: /* @__PURE__ */ jsxs4("div", { className: "vpg-section-label", children: [
|
|
1832
|
-
"Available
|
|
1862
|
+
"Available",
|
|
1863
|
+
" ",
|
|
1833
1864
|
/* @__PURE__ */ jsx4("span", { className: "vpg-count", children: unassignedFields.length })
|
|
1834
1865
|
] }) }),
|
|
1835
1866
|
/* @__PURE__ */ jsxs4("div", { className: "vpg-field-search", children: [
|
|
@@ -1883,7 +1914,8 @@ function PivotConfig({
|
|
|
1883
1914
|
onClick: (e) => {
|
|
1884
1915
|
e.stopPropagation();
|
|
1885
1916
|
const calcField = calculatedFields?.find((c) => c.id === field.calcId);
|
|
1886
|
-
if (calcField)
|
|
1917
|
+
if (calcField)
|
|
1918
|
+
openCalcModal(calcField);
|
|
1887
1919
|
},
|
|
1888
1920
|
children: "\u270E"
|
|
1889
1921
|
}
|
|
@@ -1946,8 +1978,8 @@ function PivotConfig({
|
|
|
1946
1978
|
}
|
|
1947
1979
|
|
|
1948
1980
|
// src/components/PivotSkeleton.tsx
|
|
1949
|
-
import { useState as useState9, useMemo as useMemo9, useCallback as useCallback9, useEffect as useEffect6 } from "react";
|
|
1950
1981
|
import { getAggregationLabel as getAggregationLabel2, getAggregationSymbol as getAggregationSymbol2 } from "@smallwebco/tinypivot-core";
|
|
1982
|
+
import { useCallback as useCallback9, useEffect as useEffect6, useMemo as useMemo9, useState as useState9 } from "react";
|
|
1951
1983
|
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1952
1984
|
function PivotSkeleton({
|
|
1953
1985
|
rowFields,
|
|
@@ -2002,7 +2034,8 @@ function PivotSkeleton({
|
|
|
2002
2034
|
const [showCopyToast, setShowCopyToast] = useState9(false);
|
|
2003
2035
|
const [copyToastMessage, setCopyToastMessage] = useState9("");
|
|
2004
2036
|
const selectionBounds = useMemo9(() => {
|
|
2005
|
-
if (!selectionStart || !selectionEnd)
|
|
2037
|
+
if (!selectionStart || !selectionEnd)
|
|
2038
|
+
return null;
|
|
2006
2039
|
return {
|
|
2007
2040
|
minRow: Math.min(selectionStart.row, selectionEnd.row),
|
|
2008
2041
|
maxRow: Math.max(selectionStart.row, selectionEnd.row),
|
|
@@ -2048,7 +2081,8 @@ function PivotSkeleton({
|
|
|
2048
2081
|
return () => document.removeEventListener("mouseup", handleMouseUp);
|
|
2049
2082
|
}, []);
|
|
2050
2083
|
const sortedRowIndices = useMemo9(() => {
|
|
2051
|
-
if (!pivotResult)
|
|
2084
|
+
if (!pivotResult)
|
|
2085
|
+
return [];
|
|
2052
2086
|
const indices = pivotResult.rowHeaders.map((_, i) => i);
|
|
2053
2087
|
const headers = pivotResult.rowHeaders;
|
|
2054
2088
|
const data = pivotResult.data;
|
|
@@ -2062,9 +2096,12 @@ function PivotSkeleton({
|
|
|
2062
2096
|
const colIdx = sortTarget;
|
|
2063
2097
|
const aVal = data[a]?.[colIdx]?.value ?? null;
|
|
2064
2098
|
const bVal = data[b]?.[colIdx]?.value ?? null;
|
|
2065
|
-
if (aVal === null && bVal === null)
|
|
2066
|
-
|
|
2067
|
-
else if (
|
|
2099
|
+
if (aVal === null && bVal === null)
|
|
2100
|
+
cmp = 0;
|
|
2101
|
+
else if (aVal === null)
|
|
2102
|
+
cmp = 1;
|
|
2103
|
+
else if (bVal === null)
|
|
2104
|
+
cmp = -1;
|
|
2068
2105
|
else cmp = aVal - bVal;
|
|
2069
2106
|
}
|
|
2070
2107
|
return sortDirection === "asc" ? cmp : -cmp;
|
|
@@ -2072,12 +2109,14 @@ function PivotSkeleton({
|
|
|
2072
2109
|
return indices;
|
|
2073
2110
|
}, [pivotResult, sortTarget, sortDirection]);
|
|
2074
2111
|
const copySelectionToClipboard = useCallback9(() => {
|
|
2075
|
-
if (!selectionBounds || !pivotResult)
|
|
2112
|
+
if (!selectionBounds || !pivotResult)
|
|
2113
|
+
return;
|
|
2076
2114
|
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
2077
2115
|
const lines = [];
|
|
2078
2116
|
for (let r = minRow; r <= maxRow; r++) {
|
|
2079
2117
|
const sortedIdx = sortedRowIndices[r];
|
|
2080
|
-
if (sortedIdx === void 0)
|
|
2118
|
+
if (sortedIdx === void 0)
|
|
2119
|
+
continue;
|
|
2081
2120
|
const rowValues = [];
|
|
2082
2121
|
for (let c = minCol; c <= maxCol; c++) {
|
|
2083
2122
|
const cell = pivotResult.data[sortedIdx]?.[c];
|
|
@@ -2097,7 +2136,8 @@ function PivotSkeleton({
|
|
|
2097
2136
|
}, [selectionBounds, pivotResult, sortedRowIndices]);
|
|
2098
2137
|
useEffect6(() => {
|
|
2099
2138
|
const handleKeydown = (event) => {
|
|
2100
|
-
if (!selectionBounds)
|
|
2139
|
+
if (!selectionBounds)
|
|
2140
|
+
return;
|
|
2101
2141
|
if ((event.ctrlKey || event.metaKey) && event.key === "c") {
|
|
2102
2142
|
event.preventDefault();
|
|
2103
2143
|
copySelectionToClipboard();
|
|
@@ -2113,13 +2153,15 @@ function PivotSkeleton({
|
|
|
2113
2153
|
return () => document.removeEventListener("keydown", handleKeydown);
|
|
2114
2154
|
}, [selectionBounds, copySelectionToClipboard]);
|
|
2115
2155
|
const selectionStats = useMemo9(() => {
|
|
2116
|
-
if (!selectionBounds || !pivotResult)
|
|
2156
|
+
if (!selectionBounds || !pivotResult)
|
|
2157
|
+
return null;
|
|
2117
2158
|
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
2118
2159
|
const values = [];
|
|
2119
2160
|
let count = 0;
|
|
2120
2161
|
for (let r = minRow; r <= maxRow; r++) {
|
|
2121
2162
|
const sortedIdx = sortedRowIndices[r];
|
|
2122
|
-
if (sortedIdx === void 0)
|
|
2163
|
+
if (sortedIdx === void 0)
|
|
2164
|
+
continue;
|
|
2123
2165
|
for (let c = minCol; c <= maxCol; c++) {
|
|
2124
2166
|
const cell = pivotResult.data[sortedIdx]?.[c];
|
|
2125
2167
|
count++;
|
|
@@ -2128,7 +2170,8 @@ function PivotSkeleton({
|
|
|
2128
2170
|
}
|
|
2129
2171
|
}
|
|
2130
2172
|
}
|
|
2131
|
-
if (count <= 1)
|
|
2173
|
+
if (count <= 1)
|
|
2174
|
+
return null;
|
|
2132
2175
|
const sum = values.reduce((a, b) => a + b, 0);
|
|
2133
2176
|
const avg = values.length > 0 ? sum / values.length : 0;
|
|
2134
2177
|
return {
|
|
@@ -2139,8 +2182,10 @@ function PivotSkeleton({
|
|
|
2139
2182
|
};
|
|
2140
2183
|
}, [selectionBounds, pivotResult, sortedRowIndices]);
|
|
2141
2184
|
const formatStatValue = useCallback9((val) => {
|
|
2142
|
-
if (Math.abs(val) >= 1e6)
|
|
2143
|
-
|
|
2185
|
+
if (Math.abs(val) >= 1e6)
|
|
2186
|
+
return `${(val / 1e6).toFixed(2)}M`;
|
|
2187
|
+
if (Math.abs(val) >= 1e3)
|
|
2188
|
+
return `${(val / 1e3).toFixed(2)}K`;
|
|
2144
2189
|
return val.toFixed(2);
|
|
2145
2190
|
}, []);
|
|
2146
2191
|
const columnHeaderCells = useMemo9(() => {
|
|
@@ -2172,12 +2217,14 @@ function PivotSkeleton({
|
|
|
2172
2217
|
}, [pivotResult, valueFields]);
|
|
2173
2218
|
const hasActiveFilters = activeFilters && activeFilters.length > 0;
|
|
2174
2219
|
const filterSummary = useMemo9(() => {
|
|
2175
|
-
if (!activeFilters || activeFilters.length === 0)
|
|
2220
|
+
if (!activeFilters || activeFilters.length === 0)
|
|
2221
|
+
return "";
|
|
2176
2222
|
return activeFilters.map((f) => f.column).join(", ");
|
|
2177
2223
|
}, [activeFilters]);
|
|
2178
2224
|
const [showFilterTooltip, setShowFilterTooltip] = useState9(false);
|
|
2179
2225
|
const filterTooltipDetails = useMemo9(() => {
|
|
2180
|
-
if (!activeFilters || activeFilters.length === 0)
|
|
2226
|
+
if (!activeFilters || activeFilters.length === 0)
|
|
2227
|
+
return [];
|
|
2181
2228
|
return activeFilters.map((f) => {
|
|
2182
2229
|
if (f.isRange && f.displayText) {
|
|
2183
2230
|
return {
|
|
@@ -2219,10 +2266,13 @@ function PivotSkeleton({
|
|
|
2219
2266
|
setDragOverArea(null);
|
|
2220
2267
|
return;
|
|
2221
2268
|
}
|
|
2222
|
-
if (rowFields.includes(field))
|
|
2223
|
-
|
|
2269
|
+
if (rowFields.includes(field))
|
|
2270
|
+
onRemoveRowField(field);
|
|
2271
|
+
if (columnFields.includes(field))
|
|
2272
|
+
onRemoveColumnField(field);
|
|
2224
2273
|
const existingValue = valueFields.find((v) => v.field === field);
|
|
2225
|
-
if (existingValue)
|
|
2274
|
+
if (existingValue)
|
|
2275
|
+
onRemoveValueField(field, existingValue.aggregation);
|
|
2226
2276
|
switch (area) {
|
|
2227
2277
|
case "row":
|
|
2228
2278
|
onAddRowField(field);
|
|
@@ -2362,14 +2412,18 @@ function PivotSkeleton({
|
|
|
2362
2412
|
}
|
|
2363
2413
|
),
|
|
2364
2414
|
/* @__PURE__ */ jsxs5("span", { className: "vpg-filter-text", children: [
|
|
2365
|
-
"Filtered:
|
|
2415
|
+
"Filtered:",
|
|
2416
|
+
" ",
|
|
2366
2417
|
/* @__PURE__ */ jsx5("strong", { children: filterSummary }),
|
|
2367
2418
|
filteredRowCount !== void 0 && totalRowCount !== void 0 && /* @__PURE__ */ jsxs5("span", { className: "vpg-filter-count", children: [
|
|
2368
2419
|
"(",
|
|
2369
2420
|
filteredRowCount.toLocaleString(),
|
|
2370
|
-
"
|
|
2421
|
+
" ",
|
|
2422
|
+
"of",
|
|
2423
|
+
" ",
|
|
2371
2424
|
totalRowCount.toLocaleString(),
|
|
2372
|
-
"
|
|
2425
|
+
" ",
|
|
2426
|
+
"rows)"
|
|
2373
2427
|
] })
|
|
2374
2428
|
] }),
|
|
2375
2429
|
showFilterTooltip && /* @__PURE__ */ jsxs5("div", { className: "vpg-filter-tooltip", children: [
|
|
@@ -2381,16 +2435,21 @@ function PivotSkeleton({
|
|
|
2381
2435
|
filter.remaining > 0 && /* @__PURE__ */ jsxs5("span", { className: "vpg-tooltip-more", children: [
|
|
2382
2436
|
"+",
|
|
2383
2437
|
filter.remaining,
|
|
2384
|
-
"
|
|
2438
|
+
" ",
|
|
2439
|
+
"more"
|
|
2385
2440
|
] })
|
|
2386
2441
|
] }) })
|
|
2387
2442
|
] }, filter.column)),
|
|
2388
2443
|
filteredRowCount !== void 0 && totalRowCount !== void 0 && /* @__PURE__ */ jsxs5("div", { className: "vpg-tooltip-summary", children: [
|
|
2389
|
-
"Showing
|
|
2444
|
+
"Showing",
|
|
2445
|
+
" ",
|
|
2390
2446
|
filteredRowCount.toLocaleString(),
|
|
2391
|
-
"
|
|
2447
|
+
" ",
|
|
2448
|
+
"of",
|
|
2449
|
+
" ",
|
|
2392
2450
|
totalRowCount.toLocaleString(),
|
|
2393
|
-
"
|
|
2451
|
+
" ",
|
|
2452
|
+
"rows"
|
|
2394
2453
|
] })
|
|
2395
2454
|
] })
|
|
2396
2455
|
]
|
|
@@ -2399,17 +2458,20 @@ function PivotSkeleton({
|
|
|
2399
2458
|
isConfigured && /* @__PURE__ */ jsxs5("div", { className: "vpg-config-summary", children: [
|
|
2400
2459
|
/* @__PURE__ */ jsxs5("span", { className: "vpg-summary-badge vpg-rows", children: [
|
|
2401
2460
|
rowFields.length,
|
|
2402
|
-
"
|
|
2461
|
+
" ",
|
|
2462
|
+
"row",
|
|
2403
2463
|
rowFields.length !== 1 ? "s" : ""
|
|
2404
2464
|
] }),
|
|
2405
2465
|
/* @__PURE__ */ jsxs5("span", { className: "vpg-summary-badge vpg-cols", children: [
|
|
2406
2466
|
columnFields.length,
|
|
2407
|
-
"
|
|
2467
|
+
" ",
|
|
2468
|
+
"col",
|
|
2408
2469
|
columnFields.length !== 1 ? "s" : ""
|
|
2409
2470
|
] }),
|
|
2410
2471
|
/* @__PURE__ */ jsxs5("span", { className: "vpg-summary-badge vpg-vals", children: [
|
|
2411
2472
|
valueFields.length,
|
|
2412
|
-
"
|
|
2473
|
+
" ",
|
|
2474
|
+
"val",
|
|
2413
2475
|
valueFields.length !== 1 ? "s" : ""
|
|
2414
2476
|
] })
|
|
2415
2477
|
] })
|
|
@@ -2580,15 +2642,21 @@ function PivotSkeleton({
|
|
|
2580
2642
|
}
|
|
2581
2643
|
),
|
|
2582
2644
|
/* @__PURE__ */ jsx5("span", { className: "vpg-placeholder-text", children: valueFields.length === 0 ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2583
|
-
"Add a
|
|
2645
|
+
"Add a",
|
|
2646
|
+
" ",
|
|
2584
2647
|
/* @__PURE__ */ jsx5("strong", { children: "Values" }),
|
|
2585
|
-
"
|
|
2648
|
+
" ",
|
|
2649
|
+
"field to see your pivot table"
|
|
2586
2650
|
] }) : rowFields.length === 0 && columnFields.length === 0 ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2587
|
-
"Add
|
|
2651
|
+
"Add",
|
|
2652
|
+
" ",
|
|
2588
2653
|
/* @__PURE__ */ jsx5("strong", { children: "Row" }),
|
|
2589
|
-
"
|
|
2654
|
+
" ",
|
|
2655
|
+
"or",
|
|
2656
|
+
" ",
|
|
2590
2657
|
/* @__PURE__ */ jsx5("strong", { children: "Column" }),
|
|
2591
|
-
"
|
|
2658
|
+
" ",
|
|
2659
|
+
"fields to group your data"
|
|
2592
2660
|
] }) : "Your pivot table will appear here" })
|
|
2593
2661
|
] }) }),
|
|
2594
2662
|
isConfigured && pivotResult && /* @__PURE__ */ jsx5("div", { className: "vpg-table-container", children: /* @__PURE__ */ jsxs5("table", { className: "vpg-pivot-table", children: [
|
|
@@ -2666,9 +2734,11 @@ function PivotSkeleton({
|
|
|
2666
2734
|
isConfigured && pivotResult && /* @__PURE__ */ jsxs5("div", { className: "vpg-skeleton-footer", children: [
|
|
2667
2735
|
/* @__PURE__ */ jsxs5("span", { className: "vpg-footer-info", children: [
|
|
2668
2736
|
pivotResult.rowHeaders.length,
|
|
2669
|
-
"
|
|
2737
|
+
" ",
|
|
2738
|
+
"rows \xD7",
|
|
2670
2739
|
pivotResult.data[0]?.length || 0,
|
|
2671
|
-
"
|
|
2740
|
+
" ",
|
|
2741
|
+
"columns"
|
|
2672
2742
|
] }),
|
|
2673
2743
|
selectionStats && selectionStats.count > 1 && /* @__PURE__ */ jsxs5("div", { className: "vpg-selection-stats", children: [
|
|
2674
2744
|
/* @__PURE__ */ jsxs5("span", { className: "vpg-stat", children: [
|
|
@@ -2821,12 +2891,15 @@ function DataGrid({
|
|
|
2821
2891
|
removeCalculatedField
|
|
2822
2892
|
} = usePivotTable(filteredDataForPivot);
|
|
2823
2893
|
const activeFilterInfo = useMemo10(() => {
|
|
2824
|
-
if (activeFilters.length === 0)
|
|
2894
|
+
if (activeFilters.length === 0)
|
|
2895
|
+
return null;
|
|
2825
2896
|
return activeFilters.map((f) => {
|
|
2826
2897
|
if (f.type === "range" && f.range) {
|
|
2827
2898
|
const parts = [];
|
|
2828
|
-
if (f.range.min !== null)
|
|
2829
|
-
|
|
2899
|
+
if (f.range.min !== null)
|
|
2900
|
+
parts.push(`\u2265 ${f.range.min}`);
|
|
2901
|
+
if (f.range.max !== null)
|
|
2902
|
+
parts.push(`\u2264 ${f.range.max}`);
|
|
2830
2903
|
return {
|
|
2831
2904
|
column: f.column,
|
|
2832
2905
|
valueCount: 1,
|
|
@@ -2851,7 +2924,8 @@ function DataGrid({
|
|
|
2851
2924
|
return rows.filter((row) => {
|
|
2852
2925
|
for (const col of columnKeys) {
|
|
2853
2926
|
const value = row.original[col];
|
|
2854
|
-
if (value === null || value === void 0)
|
|
2927
|
+
if (value === null || value === void 0)
|
|
2928
|
+
continue;
|
|
2855
2929
|
if (String(value).toLowerCase().includes(term)) {
|
|
2856
2930
|
return true;
|
|
2857
2931
|
}
|
|
@@ -2861,11 +2935,13 @@ function DataGrid({
|
|
|
2861
2935
|
}, [rows, globalSearchTerm, enableSearch, columnKeys]);
|
|
2862
2936
|
const totalSearchedRows = searchFilteredData.length;
|
|
2863
2937
|
const totalPages = useMemo10(() => {
|
|
2864
|
-
if (!enablePagination)
|
|
2938
|
+
if (!enablePagination)
|
|
2939
|
+
return 1;
|
|
2865
2940
|
return Math.max(1, Math.ceil(totalSearchedRows / pageSize));
|
|
2866
2941
|
}, [enablePagination, totalSearchedRows, pageSize]);
|
|
2867
2942
|
const paginatedRows = useMemo10(() => {
|
|
2868
|
-
if (!enablePagination)
|
|
2943
|
+
if (!enablePagination)
|
|
2944
|
+
return searchFilteredData;
|
|
2869
2945
|
const start = (currentPage - 1) * pageSize;
|
|
2870
2946
|
const end = start + pageSize;
|
|
2871
2947
|
return searchFilteredData.slice(start, end);
|
|
@@ -2874,7 +2950,8 @@ function DataGrid({
|
|
|
2874
2950
|
setCurrentPage(1);
|
|
2875
2951
|
}, [columnFilters, globalSearchTerm]);
|
|
2876
2952
|
const selectionBounds = useMemo10(() => {
|
|
2877
|
-
if (!selectionStart || !selectionEnd)
|
|
2953
|
+
if (!selectionStart || !selectionEnd)
|
|
2954
|
+
return null;
|
|
2878
2955
|
return {
|
|
2879
2956
|
minRow: Math.min(selectionStart.row, selectionEnd.row),
|
|
2880
2957
|
maxRow: Math.max(selectionStart.row, selectionEnd.row),
|
|
@@ -2883,16 +2960,19 @@ function DataGrid({
|
|
|
2883
2960
|
};
|
|
2884
2961
|
}, [selectionStart, selectionEnd]);
|
|
2885
2962
|
const selectionStats = useMemo10(() => {
|
|
2886
|
-
if (!selectionBounds)
|
|
2963
|
+
if (!selectionBounds)
|
|
2964
|
+
return null;
|
|
2887
2965
|
const { minRow, maxRow, minCol, maxCol } = selectionBounds;
|
|
2888
2966
|
const values = [];
|
|
2889
2967
|
let count = 0;
|
|
2890
2968
|
for (let r = minRow; r <= maxRow; r++) {
|
|
2891
2969
|
const row = rows[r];
|
|
2892
|
-
if (!row)
|
|
2970
|
+
if (!row)
|
|
2971
|
+
continue;
|
|
2893
2972
|
for (let c = minCol; c <= maxCol; c++) {
|
|
2894
2973
|
const colId = columnKeys[c];
|
|
2895
|
-
if (!colId)
|
|
2974
|
+
if (!colId)
|
|
2975
|
+
continue;
|
|
2896
2976
|
const value = row.original[colId];
|
|
2897
2977
|
count++;
|
|
2898
2978
|
if (value !== null && value !== void 0 && value !== "") {
|
|
@@ -2903,19 +2983,23 @@ function DataGrid({
|
|
|
2903
2983
|
}
|
|
2904
2984
|
}
|
|
2905
2985
|
}
|
|
2906
|
-
if (values.length === 0)
|
|
2986
|
+
if (values.length === 0)
|
|
2987
|
+
return { count, sum: null, avg: null, numericCount: 0 };
|
|
2907
2988
|
const sum = values.reduce((a, b) => a + b, 0);
|
|
2908
2989
|
const avg = sum / values.length;
|
|
2909
2990
|
return { count, sum, avg, numericCount: values.length };
|
|
2910
2991
|
}, [selectionBounds, rows, columnKeys]);
|
|
2911
2992
|
useEffect7(() => {
|
|
2912
|
-
if (typeof document === "undefined")
|
|
2913
|
-
|
|
2993
|
+
if (typeof document === "undefined")
|
|
2994
|
+
return;
|
|
2995
|
+
if (data.length === 0)
|
|
2996
|
+
return;
|
|
2914
2997
|
const widths = {};
|
|
2915
2998
|
const sampleSize = Math.min(100, data.length);
|
|
2916
2999
|
const canvas = document.createElement("canvas");
|
|
2917
3000
|
const ctx = canvas.getContext("2d");
|
|
2918
|
-
if (!ctx)
|
|
3001
|
+
if (!ctx)
|
|
3002
|
+
return;
|
|
2919
3003
|
ctx.font = "13px system-ui, -apple-system, sans-serif";
|
|
2920
3004
|
for (const key of columnKeys) {
|
|
2921
3005
|
let maxWidth = ctx.measureText(key).width + 56;
|
|
@@ -2931,7 +3015,8 @@ function DataGrid({
|
|
|
2931
3015
|
}, [data, columnKeys]);
|
|
2932
3016
|
const startColumnResize = useCallback10(
|
|
2933
3017
|
(columnId, event) => {
|
|
2934
|
-
if (!enableColumnResize)
|
|
3018
|
+
if (!enableColumnResize)
|
|
3019
|
+
return;
|
|
2935
3020
|
event.preventDefault();
|
|
2936
3021
|
event.stopPropagation();
|
|
2937
3022
|
setResizingColumnId(columnId);
|
|
@@ -2941,7 +3026,8 @@ function DataGrid({
|
|
|
2941
3026
|
[enableColumnResize, columnWidths]
|
|
2942
3027
|
);
|
|
2943
3028
|
useEffect7(() => {
|
|
2944
|
-
if (!resizingColumnId)
|
|
3029
|
+
if (!resizingColumnId)
|
|
3030
|
+
return;
|
|
2945
3031
|
const handleResizeMove = (event) => {
|
|
2946
3032
|
const diff = event.clientX - resizeStartX;
|
|
2947
3033
|
const newWidth = Math.max(MIN_COL_WIDTH, Math.min(MAX_COL_WIDTH, resizeStartWidth + diff));
|
|
@@ -2962,7 +3048,8 @@ function DataGrid({
|
|
|
2962
3048
|
}, [resizingColumnId, resizeStartX, resizeStartWidth]);
|
|
2963
3049
|
const startVerticalResize = useCallback10(
|
|
2964
3050
|
(event) => {
|
|
2965
|
-
if (!enableVerticalResize)
|
|
3051
|
+
if (!enableVerticalResize)
|
|
3052
|
+
return;
|
|
2966
3053
|
event.preventDefault();
|
|
2967
3054
|
setIsResizingVertically(true);
|
|
2968
3055
|
setVerticalResizeStartY(event.clientY);
|
|
@@ -2971,7 +3058,8 @@ function DataGrid({
|
|
|
2971
3058
|
[enableVerticalResize, gridHeight]
|
|
2972
3059
|
);
|
|
2973
3060
|
useEffect7(() => {
|
|
2974
|
-
if (!isResizingVertically)
|
|
3061
|
+
if (!isResizingVertically)
|
|
3062
|
+
return;
|
|
2975
3063
|
const handleVerticalResizeMove = (event) => {
|
|
2976
3064
|
const diff = event.clientY - verticalResizeStartY;
|
|
2977
3065
|
const newHeight = Math.max(minHeight, Math.min(maxHeight, verticalResizeStartHeight + diff));
|
|
@@ -2989,7 +3077,8 @@ function DataGrid({
|
|
|
2989
3077
|
}, [isResizingVertically, verticalResizeStartY, verticalResizeStartHeight, minHeight, maxHeight]);
|
|
2990
3078
|
const handleExport = useCallback10(() => {
|
|
2991
3079
|
if (viewMode === "pivot") {
|
|
2992
|
-
if (!pivotResult)
|
|
3080
|
+
if (!pivotResult)
|
|
3081
|
+
return;
|
|
2993
3082
|
const pivotFilename = exportFilename.replace(".csv", "-pivot.csv");
|
|
2994
3083
|
exportPivotToCSV(
|
|
2995
3084
|
{
|
|
@@ -3033,7 +3122,8 @@ function DataGrid({
|
|
|
3033
3122
|
onExport
|
|
3034
3123
|
]);
|
|
3035
3124
|
const copySelectionToClipboard = useCallback10(() => {
|
|
3036
|
-
if (!selectionBounds || !enableClipboard)
|
|
3125
|
+
if (!selectionBounds || !enableClipboard)
|
|
3126
|
+
return;
|
|
3037
3127
|
const text = formatSelectionForClipboard(
|
|
3038
3128
|
rows.map((r) => r.original),
|
|
3039
3129
|
columnKeys,
|
|
@@ -3190,7 +3280,8 @@ function DataGrid({
|
|
|
3190
3280
|
[selectionBounds, selectedCell]
|
|
3191
3281
|
);
|
|
3192
3282
|
const formatStatValue = (value) => {
|
|
3193
|
-
if (value === null)
|
|
3283
|
+
if (value === null)
|
|
3284
|
+
return "-";
|
|
3194
3285
|
if (Math.abs(value) >= 1e3) {
|
|
3195
3286
|
return value.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
3196
3287
|
}
|
|
@@ -3201,12 +3292,15 @@ function DataGrid({
|
|
|
3201
3292
|
return !noFormatPatterns.test(columnId);
|
|
3202
3293
|
};
|
|
3203
3294
|
const formatCellValueDisplay = (value, columnId) => {
|
|
3204
|
-
if (value === null || value === void 0)
|
|
3205
|
-
|
|
3295
|
+
if (value === null || value === void 0)
|
|
3296
|
+
return "";
|
|
3297
|
+
if (value === "")
|
|
3298
|
+
return "";
|
|
3206
3299
|
const stats = getColumnStats(columnId);
|
|
3207
3300
|
if (stats.type === "number") {
|
|
3208
3301
|
const num = typeof value === "number" ? value : Number.parseFloat(String(value));
|
|
3209
|
-
if (Number.isNaN(num))
|
|
3302
|
+
if (Number.isNaN(num))
|
|
3303
|
+
return String(value);
|
|
3210
3304
|
if (shouldFormatNumber(columnId) && Math.abs(num) >= 1e3) {
|
|
3211
3305
|
return num.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
3212
3306
|
}
|
|
@@ -3368,13 +3462,15 @@ function DataGrid({
|
|
|
3368
3462
|
) }),
|
|
3369
3463
|
/* @__PURE__ */ jsxs6("span", { children: [
|
|
3370
3464
|
activeFilterCount,
|
|
3371
|
-
"
|
|
3465
|
+
" ",
|
|
3466
|
+
"filter",
|
|
3372
3467
|
activeFilterCount > 1 ? "s" : ""
|
|
3373
3468
|
] })
|
|
3374
3469
|
] }),
|
|
3375
3470
|
globalSearchTerm && /* @__PURE__ */ jsx6("div", { className: "vpg-search-info", children: /* @__PURE__ */ jsxs6("span", { children: [
|
|
3376
3471
|
totalSearchedRows,
|
|
3377
|
-
"
|
|
3472
|
+
" ",
|
|
3473
|
+
"match",
|
|
3378
3474
|
totalSearchedRows !== 1 ? "es" : ""
|
|
3379
3475
|
] }) })
|
|
3380
3476
|
] }),
|
|
@@ -3395,7 +3491,8 @@ function DataGrid({
|
|
|
3395
3491
|
}
|
|
3396
3492
|
) }),
|
|
3397
3493
|
showPivotConfig ? "Hide" : "Show",
|
|
3398
|
-
"
|
|
3494
|
+
" ",
|
|
3495
|
+
"Config"
|
|
3399
3496
|
]
|
|
3400
3497
|
}
|
|
3401
3498
|
),
|
|
@@ -3517,7 +3614,7 @@ function DataGrid({
|
|
|
3517
3614
|
/* @__PURE__ */ jsx6("button", { className: "vpg-clear-link", onClick: clearAllFilters, children: "Clear all filters" })
|
|
3518
3615
|
] }),
|
|
3519
3616
|
!loading && filteredRowCount > 0 && /* @__PURE__ */ jsx6("div", { className: "vpg-table-wrapper", children: /* @__PURE__ */ jsxs6("table", { className: "vpg-table", style: { minWidth: `${totalTableWidth}px` }, children: [
|
|
3520
|
-
/* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsx6("tr", { children: columnKeys.map((colId
|
|
3617
|
+
/* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsx6("tr", { children: columnKeys.map((colId) => /* @__PURE__ */ jsxs6(
|
|
3521
3618
|
"th",
|
|
3522
3619
|
{
|
|
3523
3620
|
className: `vpg-header-cell ${hasActiveFilter(colId) ? "vpg-has-filter" : ""} ${getSortDirection(colId) !== null ? "vpg-is-sorted" : ""} ${activeFilterColumn === colId ? "vpg-is-active" : ""}`,
|
|
@@ -3646,7 +3743,7 @@ function DataGrid({
|
|
|
3646
3743
|
onShowColumnTotalsChange: setPivotShowColumnTotals,
|
|
3647
3744
|
onClearConfig: clearPivotConfig,
|
|
3648
3745
|
onAutoSuggest: autoSuggestConfig,
|
|
3649
|
-
onDragStart: (field,
|
|
3746
|
+
onDragStart: (field, _e) => setDraggingField(field),
|
|
3650
3747
|
onDragEnd: () => setDraggingField(null),
|
|
3651
3748
|
onUpdateAggregation: updateValueFieldAggregation,
|
|
3652
3749
|
onAddRowField: addRowField,
|
|
@@ -3698,11 +3795,13 @@ function DataGrid({
|
|
|
3698
3795
|
totalSearchedRows !== totalRowCount && /* @__PURE__ */ jsxs6("span", { className: "vpg-filtered-note", children: [
|
|
3699
3796
|
"(",
|
|
3700
3797
|
totalRowCount.toLocaleString(),
|
|
3701
|
-
"
|
|
3798
|
+
" ",
|
|
3799
|
+
"total)"
|
|
3702
3800
|
] })
|
|
3703
3801
|
] }) : filteredRowCount === totalRowCount && totalSearchedRows === totalRowCount ? /* @__PURE__ */ jsxs6("span", { children: [
|
|
3704
3802
|
totalRowCount.toLocaleString(),
|
|
3705
|
-
"
|
|
3803
|
+
" ",
|
|
3804
|
+
"records"
|
|
3706
3805
|
] }) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
3707
3806
|
/* @__PURE__ */ jsx6("span", { className: "vpg-filtered-count", children: totalSearchedRows.toLocaleString() }),
|
|
3708
3807
|
/* @__PURE__ */ jsx6("span", { className: "vpg-separator", children: "of" }),
|
|
@@ -3713,7 +3812,8 @@ function DataGrid({
|
|
|
3713
3812
|
/* @__PURE__ */ jsx6("span", { className: "vpg-separator", children: "\u2022" }),
|
|
3714
3813
|
/* @__PURE__ */ jsxs6("span", { children: [
|
|
3715
3814
|
totalRowCount.toLocaleString(),
|
|
3716
|
-
"
|
|
3815
|
+
" ",
|
|
3816
|
+
"source records"
|
|
3717
3817
|
] })
|
|
3718
3818
|
] }) }),
|
|
3719
3819
|
enablePagination && viewMode === "grid" && totalPages > 1 && /* @__PURE__ */ jsxs6("div", { className: "vpg-pagination", children: [
|
|
@@ -3752,9 +3852,12 @@ function DataGrid({
|
|
|
3752
3852
|
}
|
|
3753
3853
|
),
|
|
3754
3854
|
/* @__PURE__ */ jsxs6("span", { className: "vpg-page-info", children: [
|
|
3755
|
-
"Page
|
|
3855
|
+
"Page",
|
|
3856
|
+
" ",
|
|
3756
3857
|
currentPage,
|
|
3757
|
-
"
|
|
3858
|
+
" ",
|
|
3859
|
+
"of",
|
|
3860
|
+
" ",
|
|
3758
3861
|
totalPages
|
|
3759
3862
|
] }),
|
|
3760
3863
|
/* @__PURE__ */ jsx6(
|