@wordrhyme/auto-crud 0.10.0 → 1.0.0
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/dist/index.cjs +421 -316
- package/dist/index.d.cts +124 -93
- package/dist/index.d.ts +124 -93
- package/dist/index.js +421 -316
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,8 @@ let tailwind_merge = require("tailwind-merge");
|
|
|
35
35
|
tailwind_merge = __toESM(tailwind_merge);
|
|
36
36
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
37
37
|
react_jsx_runtime = __toESM(react_jsx_runtime);
|
|
38
|
+
let nanoid = require("nanoid");
|
|
39
|
+
nanoid = __toESM(nanoid);
|
|
38
40
|
let __dnd_kit_core = require("@dnd-kit/core");
|
|
39
41
|
__dnd_kit_core = __toESM(__dnd_kit_core);
|
|
40
42
|
let __dnd_kit_modifiers = require("@dnd-kit/modifiers");
|
|
@@ -47,8 +49,6 @@ let __radix_ui_react_slot = require("@radix-ui/react-slot");
|
|
|
47
49
|
__radix_ui_react_slot = __toESM(__radix_ui_react_slot);
|
|
48
50
|
let react_dom = require("react-dom");
|
|
49
51
|
react_dom = __toESM(react_dom);
|
|
50
|
-
let nanoid = require("nanoid");
|
|
51
|
-
nanoid = __toESM(nanoid);
|
|
52
52
|
let __radix_ui_react_direction = require("@radix-ui/react-direction");
|
|
53
53
|
__radix_ui_react_direction = __toESM(__radix_ui_react_direction);
|
|
54
54
|
let zod = require("zod");
|
|
@@ -474,6 +474,104 @@ function DataTable({ table, actionBar, children, className,...props }) {
|
|
|
474
474
|
});
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/lib/id.ts
|
|
479
|
+
const prefixes = {};
|
|
480
|
+
function generateId(prefixOrOptions, inputOptions = {}) {
|
|
481
|
+
const finalOptions = typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions;
|
|
482
|
+
const prefix = typeof prefixOrOptions === "object" ? void 0 : prefixOrOptions;
|
|
483
|
+
const { length = 12, separator = "_" } = finalOptions;
|
|
484
|
+
const id = (0, nanoid.customAlphabet)("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", length)();
|
|
485
|
+
return prefix && prefix in prefixes ? `${prefixes[prefix]}${separator}${id}` : id;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region src/lib/readable-filters.ts
|
|
490
|
+
const OP_SUFFIX = "__op";
|
|
491
|
+
const INDEX_SEPARATOR = "__";
|
|
492
|
+
const RANGE_VARIANTS = new Set(["range", "dateRange"]);
|
|
493
|
+
function getColumnId(column) {
|
|
494
|
+
if ("id" in column && column.id) return String(column.id);
|
|
495
|
+
if ("accessorKey" in column && column.accessorKey) return String(column.accessorKey);
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
function getColumnVariant(column) {
|
|
499
|
+
if ("columnDef" in column) return column.columnDef.meta?.variant;
|
|
500
|
+
return column.meta?.variant;
|
|
501
|
+
}
|
|
502
|
+
function splitKey(key) {
|
|
503
|
+
const match = key.match(/^(.*?)(?:__(\d+))?$/);
|
|
504
|
+
if (!match) return { base: key };
|
|
505
|
+
const index = match[2] ? Number(match[2]) : void 0;
|
|
506
|
+
return {
|
|
507
|
+
base: match[1] ?? key,
|
|
508
|
+
index
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function isFilterKey(key, columnIds) {
|
|
512
|
+
const { base } = splitKey(key.endsWith(OP_SUFFIX) ? key.slice(0, -4) : key);
|
|
513
|
+
return columnIds.has(base);
|
|
514
|
+
}
|
|
515
|
+
function parseValueByVariant(value, variant) {
|
|
516
|
+
if (variant === "multiSelect") return value ? value.split(",").filter(Boolean) : [];
|
|
517
|
+
if (RANGE_VARIANTS.has(variant)) {
|
|
518
|
+
const parts = value.split(",");
|
|
519
|
+
return [parts[0] ?? "", parts[1] ?? ""];
|
|
520
|
+
}
|
|
521
|
+
if (variant === "select") return value.split(",").filter(Boolean)[0] ?? "";
|
|
522
|
+
return value;
|
|
523
|
+
}
|
|
524
|
+
function serializeValue(value, variant) {
|
|
525
|
+
if (Array.isArray(value)) {
|
|
526
|
+
if (RANGE_VARIANTS.has(variant)) return [value[0] ?? "", value[1] ?? ""].join(",");
|
|
527
|
+
return value.join(",");
|
|
528
|
+
}
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
function parseReadableFilters(params, columns) {
|
|
532
|
+
const columnsById = /* @__PURE__ */ new Map();
|
|
533
|
+
for (const column of columns) {
|
|
534
|
+
const id = getColumnId(column);
|
|
535
|
+
if (id) columnsById.set(id, column);
|
|
536
|
+
}
|
|
537
|
+
const filters = [];
|
|
538
|
+
for (const [key, rawValue] of params.entries()) {
|
|
539
|
+
if (key.endsWith(OP_SUFFIX)) continue;
|
|
540
|
+
const { base } = splitKey(key);
|
|
541
|
+
const column = columnsById.get(base);
|
|
542
|
+
if (!column) continue;
|
|
543
|
+
const variant = getColumnVariant(column) ?? "text";
|
|
544
|
+
const opKey = `${key}${OP_SUFFIX}`;
|
|
545
|
+
const operator = params.get(opKey) ?? getDefaultFilterOperator(variant);
|
|
546
|
+
const parsedValue = operator === "isEmpty" || operator === "isNotEmpty" ? "" : parseValueByVariant(rawValue, variant);
|
|
547
|
+
filters.push({
|
|
548
|
+
id: base,
|
|
549
|
+
value: parsedValue,
|
|
550
|
+
variant,
|
|
551
|
+
operator,
|
|
552
|
+
filterId: key || generateId({ length: 8 })
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
return filters;
|
|
556
|
+
}
|
|
557
|
+
function applyReadableFilters(params, columns, filters) {
|
|
558
|
+
const columnIds = new Set(columns.map((column) => getColumnId(column)).filter(Boolean));
|
|
559
|
+
for (const key of Array.from(params.keys())) if (isFilterKey(key, columnIds)) params.delete(key);
|
|
560
|
+
const counters = /* @__PURE__ */ new Map();
|
|
561
|
+
for (const filter of filters) {
|
|
562
|
+
const columnId = String(filter.id);
|
|
563
|
+
const count = (counters.get(columnId) ?? 0) + 1;
|
|
564
|
+
counters.set(columnId, count);
|
|
565
|
+
const key = count === 1 ? columnId : `${columnId}${INDEX_SEPARATOR}${count}`;
|
|
566
|
+
const variant = filter.variant ?? "text";
|
|
567
|
+
const value = serializeValue(filter.value, variant);
|
|
568
|
+
params.set(key, filter.operator === "isEmpty" || filter.operator === "isNotEmpty" ? "" : value);
|
|
569
|
+
const defaultOperator = getDefaultFilterOperator(variant);
|
|
570
|
+
if (filter.operator !== defaultOperator) params.set(`${key}${OP_SUFFIX}`, filter.operator);
|
|
571
|
+
}
|
|
572
|
+
return params;
|
|
573
|
+
}
|
|
574
|
+
|
|
477
575
|
//#endregion
|
|
478
576
|
//#region src/hooks/use-url-state.ts
|
|
479
577
|
const parseAsInteger = {
|
|
@@ -724,6 +822,54 @@ function useQueryStates(parsers, options = {}) {
|
|
|
724
822
|
}, [setValues])];
|
|
725
823
|
}
|
|
726
824
|
|
|
825
|
+
//#endregion
|
|
826
|
+
//#region src/hooks/use-readable-filters.ts
|
|
827
|
+
const URL_CHANGE_EVENT = "urlchange";
|
|
828
|
+
function useReadableFilters(columns, options = {}) {
|
|
829
|
+
const { history = "replace", scroll = false } = options;
|
|
830
|
+
const columnSnapshot = react.useMemo(() => columns, [columns]);
|
|
831
|
+
const lastWrittenUrlRef = react.useRef("");
|
|
832
|
+
const filtersRef = react.useRef(null);
|
|
833
|
+
const [filters, setFilters] = react.useState(() => {
|
|
834
|
+
const initial = parseReadableFilters(getUrlParams(), columnSnapshot);
|
|
835
|
+
filtersRef.current = initial;
|
|
836
|
+
return initial;
|
|
837
|
+
});
|
|
838
|
+
react.useEffect(() => {
|
|
839
|
+
const handleUrlChange = () => {
|
|
840
|
+
if (window.location.search === lastWrittenUrlRef.current) return;
|
|
841
|
+
const next = parseReadableFilters(getUrlParams(), columnSnapshot);
|
|
842
|
+
filtersRef.current = next;
|
|
843
|
+
setFilters(next);
|
|
844
|
+
};
|
|
845
|
+
window.addEventListener("popstate", handleUrlChange);
|
|
846
|
+
window.addEventListener(URL_CHANGE_EVENT, handleUrlChange);
|
|
847
|
+
return () => {
|
|
848
|
+
window.removeEventListener("popstate", handleUrlChange);
|
|
849
|
+
window.removeEventListener(URL_CHANGE_EVENT, handleUrlChange);
|
|
850
|
+
};
|
|
851
|
+
}, [columnSnapshot]);
|
|
852
|
+
const writeToUrl = react.useCallback((next) => {
|
|
853
|
+
const params = getUrlParams();
|
|
854
|
+
applyReadableFilters(params, columnSnapshot, next);
|
|
855
|
+
setSearchParams(params, {
|
|
856
|
+
history,
|
|
857
|
+
scroll
|
|
858
|
+
});
|
|
859
|
+
lastWrittenUrlRef.current = `?${params.toString()}`;
|
|
860
|
+
}, [
|
|
861
|
+
columnSnapshot,
|
|
862
|
+
history,
|
|
863
|
+
scroll
|
|
864
|
+
]);
|
|
865
|
+
return [filters, react.useCallback((updater) => {
|
|
866
|
+
const next = typeof updater === "function" ? updater(filtersRef.current) : updater;
|
|
867
|
+
filtersRef.current = next;
|
|
868
|
+
setFilters(next);
|
|
869
|
+
writeToUrl(next);
|
|
870
|
+
}, [writeToUrl])];
|
|
871
|
+
}
|
|
872
|
+
|
|
727
873
|
//#endregion
|
|
728
874
|
//#region src/components/data-table/data-table-range-filter.tsx
|
|
729
875
|
function DataTableRangeFilter({ filter, column, inputId, onFilterUpdate, className,...props }) {
|
|
@@ -1291,159 +1437,6 @@ function formatDate(date, opts = {}) {
|
|
|
1291
1437
|
}
|
|
1292
1438
|
}
|
|
1293
1439
|
|
|
1294
|
-
//#endregion
|
|
1295
|
-
//#region src/lib/id.ts
|
|
1296
|
-
const prefixes = {};
|
|
1297
|
-
function generateId(prefixOrOptions, inputOptions = {}) {
|
|
1298
|
-
const finalOptions = typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions;
|
|
1299
|
-
const prefix = typeof prefixOrOptions === "object" ? void 0 : prefixOrOptions;
|
|
1300
|
-
const { length = 12, separator = "_" } = finalOptions;
|
|
1301
|
-
const id = (0, nanoid.customAlphabet)("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", length)();
|
|
1302
|
-
return prefix && prefix in prefixes ? `${prefixes[prefix]}${separator}${id}` : id;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
//#endregion
|
|
1306
|
-
//#region src/lib/readable-filters.ts
|
|
1307
|
-
const OP_SUFFIX = "__op";
|
|
1308
|
-
const INDEX_SEPARATOR = "__";
|
|
1309
|
-
const RANGE_VARIANTS = new Set(["range", "dateRange"]);
|
|
1310
|
-
function getColumnId(column) {
|
|
1311
|
-
if ("id" in column && column.id) return String(column.id);
|
|
1312
|
-
if ("accessorKey" in column && column.accessorKey) return String(column.accessorKey);
|
|
1313
|
-
return null;
|
|
1314
|
-
}
|
|
1315
|
-
function getColumnVariant(column) {
|
|
1316
|
-
if ("columnDef" in column) return column.columnDef.meta?.variant;
|
|
1317
|
-
return column.meta?.variant;
|
|
1318
|
-
}
|
|
1319
|
-
function splitKey(key) {
|
|
1320
|
-
const match = key.match(/^(.*?)(?:__(\d+))?$/);
|
|
1321
|
-
if (!match) return { base: key };
|
|
1322
|
-
const index = match[2] ? Number(match[2]) : void 0;
|
|
1323
|
-
return {
|
|
1324
|
-
base: match[1] ?? key,
|
|
1325
|
-
index
|
|
1326
|
-
};
|
|
1327
|
-
}
|
|
1328
|
-
function isFilterKey(key, columnIds) {
|
|
1329
|
-
const { base } = splitKey(key.endsWith(OP_SUFFIX) ? key.slice(0, -4) : key);
|
|
1330
|
-
return columnIds.has(base);
|
|
1331
|
-
}
|
|
1332
|
-
function parseValueByVariant(value, variant) {
|
|
1333
|
-
if (variant === "multiSelect") return value ? value.split(",").filter(Boolean) : [];
|
|
1334
|
-
if (RANGE_VARIANTS.has(variant)) {
|
|
1335
|
-
const parts = value.split(",");
|
|
1336
|
-
return [parts[0] ?? "", parts[1] ?? ""];
|
|
1337
|
-
}
|
|
1338
|
-
if (variant === "select") return value.split(",").filter(Boolean)[0] ?? "";
|
|
1339
|
-
return value;
|
|
1340
|
-
}
|
|
1341
|
-
function serializeValue(value, variant) {
|
|
1342
|
-
if (Array.isArray(value)) {
|
|
1343
|
-
if (RANGE_VARIANTS.has(variant)) return [value[0] ?? "", value[1] ?? ""].join(",");
|
|
1344
|
-
return value.join(",");
|
|
1345
|
-
}
|
|
1346
|
-
return value;
|
|
1347
|
-
}
|
|
1348
|
-
function parseReadableFilters(params, columns) {
|
|
1349
|
-
const columnsById = /* @__PURE__ */ new Map();
|
|
1350
|
-
for (const column of columns) {
|
|
1351
|
-
const id = getColumnId(column);
|
|
1352
|
-
if (id) columnsById.set(id, column);
|
|
1353
|
-
}
|
|
1354
|
-
const filters = [];
|
|
1355
|
-
for (const [key, rawValue] of params.entries()) {
|
|
1356
|
-
if (key.endsWith(OP_SUFFIX)) continue;
|
|
1357
|
-
const { base } = splitKey(key);
|
|
1358
|
-
const column = columnsById.get(base);
|
|
1359
|
-
if (!column) continue;
|
|
1360
|
-
const variant = getColumnVariant(column) ?? "text";
|
|
1361
|
-
const opKey = `${key}${OP_SUFFIX}`;
|
|
1362
|
-
const operator = params.get(opKey) ?? getDefaultFilterOperator(variant);
|
|
1363
|
-
if (!rawValue && operator !== "isEmpty" && operator !== "isNotEmpty") continue;
|
|
1364
|
-
const parsedValue = operator === "isEmpty" || operator === "isNotEmpty" ? "" : parseValueByVariant(rawValue, variant);
|
|
1365
|
-
filters.push({
|
|
1366
|
-
id: base,
|
|
1367
|
-
value: parsedValue,
|
|
1368
|
-
variant,
|
|
1369
|
-
operator,
|
|
1370
|
-
filterId: key || generateId({ length: 8 })
|
|
1371
|
-
});
|
|
1372
|
-
}
|
|
1373
|
-
return filters;
|
|
1374
|
-
}
|
|
1375
|
-
function applyReadableFilters(params, columns, filters) {
|
|
1376
|
-
const columnIds = new Set(columns.map((column) => getColumnId(column)).filter(Boolean));
|
|
1377
|
-
for (const key of Array.from(params.keys())) if (isFilterKey(key, columnIds)) params.delete(key);
|
|
1378
|
-
const counters = /* @__PURE__ */ new Map();
|
|
1379
|
-
for (const filter of filters) {
|
|
1380
|
-
const columnId = String(filter.id);
|
|
1381
|
-
const count = (counters.get(columnId) ?? 0) + 1;
|
|
1382
|
-
counters.set(columnId, count);
|
|
1383
|
-
const key = count === 1 ? columnId : `${columnId}${INDEX_SEPARATOR}${count}`;
|
|
1384
|
-
const variant = filter.variant ?? "text";
|
|
1385
|
-
const value = serializeValue(filter.value, variant);
|
|
1386
|
-
params.set(key, filter.operator === "isEmpty" || filter.operator === "isNotEmpty" ? "" : value);
|
|
1387
|
-
const defaultOperator = getDefaultFilterOperator(variant);
|
|
1388
|
-
if (filter.operator !== defaultOperator) params.set(`${key}${OP_SUFFIX}`, filter.operator);
|
|
1389
|
-
}
|
|
1390
|
-
return params;
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
//#endregion
|
|
1394
|
-
//#region src/hooks/use-readable-filters.ts
|
|
1395
|
-
const URL_CHANGE_EVENT = "urlchange";
|
|
1396
|
-
function useReadableFilters(columns, options = {}) {
|
|
1397
|
-
const { debounceMs = 300, history = "replace", scroll = false } = options;
|
|
1398
|
-
const columnSnapshot = react.useMemo(() => columns, [columns]);
|
|
1399
|
-
const skipWriteRef = react.useRef(true);
|
|
1400
|
-
const [filters, setFilters] = react.useState(() => parseReadableFilters(getUrlParams(), columnSnapshot));
|
|
1401
|
-
react.useEffect(() => {
|
|
1402
|
-
const handleUrlChange = () => {
|
|
1403
|
-
skipWriteRef.current = true;
|
|
1404
|
-
setFilters(parseReadableFilters(getUrlParams(), columnSnapshot));
|
|
1405
|
-
};
|
|
1406
|
-
window.addEventListener("popstate", handleUrlChange);
|
|
1407
|
-
window.addEventListener(URL_CHANGE_EVENT, handleUrlChange);
|
|
1408
|
-
return () => {
|
|
1409
|
-
window.removeEventListener("popstate", handleUrlChange);
|
|
1410
|
-
window.removeEventListener(URL_CHANGE_EVENT, handleUrlChange);
|
|
1411
|
-
};
|
|
1412
|
-
}, [columnSnapshot]);
|
|
1413
|
-
const writeFilters = react.useCallback((next) => {
|
|
1414
|
-
const params = getUrlParams();
|
|
1415
|
-
applyReadableFilters(params, columnSnapshot, next);
|
|
1416
|
-
setSearchParams(params, {
|
|
1417
|
-
history,
|
|
1418
|
-
scroll
|
|
1419
|
-
});
|
|
1420
|
-
}, [
|
|
1421
|
-
columnSnapshot,
|
|
1422
|
-
history,
|
|
1423
|
-
scroll
|
|
1424
|
-
]);
|
|
1425
|
-
const updateFilters = react.useCallback((updater) => {
|
|
1426
|
-
setFilters((prev) => {
|
|
1427
|
-
return typeof updater === "function" ? updater(prev) : updater;
|
|
1428
|
-
});
|
|
1429
|
-
}, []);
|
|
1430
|
-
const debouncedWrite = useDebouncedCallback(writeFilters, debounceMs);
|
|
1431
|
-
react.useEffect(() => {
|
|
1432
|
-
if (skipWriteRef.current) {
|
|
1433
|
-
skipWriteRef.current = false;
|
|
1434
|
-
return;
|
|
1435
|
-
}
|
|
1436
|
-
if (debounceMs > 0) debouncedWrite(filters);
|
|
1437
|
-
else writeFilters(filters);
|
|
1438
|
-
}, [
|
|
1439
|
-
filters,
|
|
1440
|
-
debounceMs,
|
|
1441
|
-
debouncedWrite,
|
|
1442
|
-
writeFilters
|
|
1443
|
-
]);
|
|
1444
|
-
return [filters, updateFilters];
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
1440
|
//#endregion
|
|
1448
1441
|
//#region src/components/data-table/data-table-filter-list.tsx
|
|
1449
1442
|
const DEBOUNCE_MS$2 = 300;
|
|
@@ -1459,11 +1452,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
|
|
|
1459
1452
|
const columns = react.useMemo(() => {
|
|
1460
1453
|
return table.getAllColumns().filter((column) => column.columnDef.enableColumnFilter);
|
|
1461
1454
|
}, [table]);
|
|
1462
|
-
const
|
|
1463
|
-
const [filters, setFilters] = useReadableFilters(columns, {
|
|
1464
|
-
...queryStateOptions,
|
|
1465
|
-
debounceMs: 0
|
|
1466
|
-
});
|
|
1455
|
+
const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
|
|
1467
1456
|
const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
|
|
1468
1457
|
const [joinOperator, setJoinOperator] = useQueryState(table.options.meta?.queryKeys?.joinOperator ?? "", parseAsStringEnum(["and", "or"]).withDefault("and"));
|
|
1469
1458
|
const onFilterAdd = react.useCallback(() => {
|
|
@@ -1499,7 +1488,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
|
|
|
1499
1488
|
});
|
|
1500
1489
|
}, [filters, setFilters]);
|
|
1501
1490
|
const onFiltersReset = react.useCallback(() => {
|
|
1502
|
-
setFilters(
|
|
1491
|
+
setFilters([]);
|
|
1503
1492
|
setJoinOperator("and");
|
|
1504
1493
|
}, [setFilters, setJoinOperator]);
|
|
1505
1494
|
react.useEffect(() => {
|
|
@@ -1963,11 +1952,7 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
|
|
|
1963
1952
|
setSelectedColumn(null);
|
|
1964
1953
|
}
|
|
1965
1954
|
}, [inputValue, selectedColumn]);
|
|
1966
|
-
const
|
|
1967
|
-
const [filters, setFilters] = useReadableFilters(columns, {
|
|
1968
|
-
...queryStateOptions,
|
|
1969
|
-
debounceMs: 0
|
|
1970
|
-
});
|
|
1955
|
+
const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
|
|
1971
1956
|
const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
|
|
1972
1957
|
const onFilterAdd = react.useCallback((column, value) => {
|
|
1973
1958
|
if (!value.trim() && column.columnDef.meta?.variant !== "boolean") return;
|
|
@@ -2749,51 +2734,142 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
|
|
|
2749
2734
|
setFilters
|
|
2750
2735
|
]);
|
|
2751
2736
|
const hasFilters = filters.length > 0;
|
|
2737
|
+
const filterColumns = react.useMemo(() => columns.filter((col) => col.columnDef.meta?.variant && col.columnDef.enableColumnFilter !== false), [columns]);
|
|
2738
|
+
const [expanded, setExpanded] = react.useState(false);
|
|
2739
|
+
const [visibleCount, setVisibleCount] = react.useState(null);
|
|
2740
|
+
const containerRef = react.useRef(null);
|
|
2741
|
+
const calcVisibleCount = react.useCallback(() => {
|
|
2742
|
+
const el = containerRef.current;
|
|
2743
|
+
if (!el) return;
|
|
2744
|
+
const items = el.querySelectorAll("[data-filter-item]");
|
|
2745
|
+
if (items.length === 0) return;
|
|
2746
|
+
const restoreList = [];
|
|
2747
|
+
el.querySelectorAll(".hidden").forEach((node) => {
|
|
2748
|
+
const htmlNode = node;
|
|
2749
|
+
htmlNode.classList.remove("hidden");
|
|
2750
|
+
restoreList.push(htmlNode);
|
|
2751
|
+
});
|
|
2752
|
+
el.style.flex = "1 1 0%";
|
|
2753
|
+
el.offsetHeight;
|
|
2754
|
+
const firstTop = items[0].offsetTop;
|
|
2755
|
+
let count = 0;
|
|
2756
|
+
for (const item of Array.from(items)) {
|
|
2757
|
+
if (item.offsetTop > firstTop) break;
|
|
2758
|
+
count++;
|
|
2759
|
+
}
|
|
2760
|
+
const btn = el.querySelector("[data-filter-toggle]");
|
|
2761
|
+
if (btn && count < items.length && count > 0) {
|
|
2762
|
+
if (btn.offsetTop > firstTop) count--;
|
|
2763
|
+
}
|
|
2764
|
+
el.style.flex = "";
|
|
2765
|
+
restoreList.forEach((node) => node.classList.add("hidden"));
|
|
2766
|
+
setVisibleCount(count >= items.length ? null : count);
|
|
2767
|
+
}, []);
|
|
2768
|
+
react.useLayoutEffect(() => {
|
|
2769
|
+
if (!mounted) return;
|
|
2770
|
+
calcVisibleCount();
|
|
2771
|
+
}, [
|
|
2772
|
+
mounted,
|
|
2773
|
+
filterColumns.length,
|
|
2774
|
+
calcVisibleCount
|
|
2775
|
+
]);
|
|
2776
|
+
react.useEffect(() => {
|
|
2777
|
+
const parent = containerRef.current?.closest("[data-filter-parent]");
|
|
2778
|
+
if (!mounted || !parent) return;
|
|
2779
|
+
let lastWidth = parent.offsetWidth;
|
|
2780
|
+
let rafId = 0;
|
|
2781
|
+
const ro = new ResizeObserver(() => {
|
|
2782
|
+
const newWidth = parent.offsetWidth;
|
|
2783
|
+
if (newWidth === lastWidth) return;
|
|
2784
|
+
lastWidth = newWidth;
|
|
2785
|
+
cancelAnimationFrame(rafId);
|
|
2786
|
+
rafId = requestAnimationFrame(calcVisibleCount);
|
|
2787
|
+
});
|
|
2788
|
+
ro.observe(parent);
|
|
2789
|
+
return () => {
|
|
2790
|
+
cancelAnimationFrame(rafId);
|
|
2791
|
+
ro.disconnect();
|
|
2792
|
+
};
|
|
2793
|
+
}, [
|
|
2794
|
+
mounted,
|
|
2795
|
+
filterColumns.length,
|
|
2796
|
+
calcVisibleCount
|
|
2797
|
+
]);
|
|
2752
2798
|
if (!mounted) return null;
|
|
2753
2799
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2800
|
+
ref: containerRef,
|
|
2801
|
+
className: "flex flex-wrap items-center gap-2 min-w-0",
|
|
2802
|
+
children: [
|
|
2803
|
+
filterColumns.map((column, index) => {
|
|
2804
|
+
const meta = column.columnDef.meta;
|
|
2805
|
+
const value = getFilterValue(column.id);
|
|
2806
|
+
const isHidden = !expanded && visibleCount !== null && index >= visibleCount;
|
|
2807
|
+
switch (meta.variant) {
|
|
2808
|
+
case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2809
|
+
className: isHidden ? "hidden" : "",
|
|
2810
|
+
"data-filter-item": true,
|
|
2811
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Input, {
|
|
2812
|
+
placeholder: meta.placeholder ?? meta.label ?? column.id,
|
|
2813
|
+
value: typeof value === "string" ? value : "",
|
|
2814
|
+
onChange: (e) => updateFilter(column.id, e.target.value || void 0),
|
|
2815
|
+
className: "h-8 w-36 shrink-0"
|
|
2816
|
+
})
|
|
2817
|
+
}, column.id);
|
|
2818
|
+
case "select":
|
|
2819
|
+
case "multiSelect": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2820
|
+
className: isHidden ? "hidden" : "",
|
|
2821
|
+
"data-filter-item": true,
|
|
2822
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleFacetedFilter, {
|
|
2823
|
+
title: meta.label ?? column.id,
|
|
2824
|
+
options: meta.options ?? [],
|
|
2825
|
+
multiple: meta.variant === "multiSelect",
|
|
2826
|
+
value: Array.isArray(value) ? value : value ? [value] : [],
|
|
2827
|
+
onChange: (v) => updateFilter(column.id, v.length ? v : void 0)
|
|
2828
|
+
})
|
|
2829
|
+
}, column.id);
|
|
2830
|
+
case "range": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2831
|
+
className: isHidden ? "hidden" : "",
|
|
2832
|
+
"data-filter-item": true,
|
|
2833
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleSliderFilter, {
|
|
2834
|
+
title: meta.label ?? column.id,
|
|
2835
|
+
range: meta.range,
|
|
2836
|
+
unit: meta.unit,
|
|
2837
|
+
value: Array.isArray(value) ? value.map(Number) : void 0,
|
|
2838
|
+
onChange: (v) => updateFilter(column.id, v ? v.map(String) : void 0)
|
|
2839
|
+
})
|
|
2840
|
+
}, column.id);
|
|
2841
|
+
case "date":
|
|
2842
|
+
case "dateRange": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2843
|
+
className: isHidden ? "hidden" : "",
|
|
2844
|
+
"data-filter-item": true,
|
|
2845
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleDateFilter, {
|
|
2846
|
+
title: meta.label ?? column.id,
|
|
2847
|
+
multiple: meta.variant === "dateRange",
|
|
2848
|
+
value,
|
|
2849
|
+
onChange: (v) => updateFilter(column.id, v)
|
|
2850
|
+
})
|
|
2851
|
+
}, column.id);
|
|
2852
|
+
default: return null;
|
|
2853
|
+
}
|
|
2854
|
+
}),
|
|
2855
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
|
|
2856
|
+
"data-filter-toggle": true,
|
|
2857
|
+
variant: "ghost",
|
|
2858
|
+
size: "icon",
|
|
2859
|
+
className: cn("h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground", visibleCount === null && "hidden"),
|
|
2860
|
+
onClick: () => setExpanded((prev) => !prev),
|
|
2861
|
+
"aria-label": expanded ? "收起筛选" : "展开更多筛选",
|
|
2862
|
+
"aria-expanded": expanded,
|
|
2863
|
+
children: expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronUp, { className: "size-4" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "size-4" })
|
|
2864
|
+
}),
|
|
2865
|
+
hasFilters && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
2866
|
+
variant: "outline",
|
|
2867
|
+
size: "sm",
|
|
2868
|
+
className: "h-8 border-dashed shrink-0",
|
|
2869
|
+
onClick: () => setFilters([]),
|
|
2870
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-4" }), "Reset"]
|
|
2871
|
+
})
|
|
2872
|
+
]
|
|
2797
2873
|
});
|
|
2798
2874
|
}
|
|
2799
2875
|
function SimpleFacetedFilter({ title, options, multiple, value, onChange }) {
|
|
@@ -4222,25 +4298,32 @@ function createParser(config) {
|
|
|
4222
4298
|
})
|
|
4223
4299
|
};
|
|
4224
4300
|
}
|
|
4225
|
-
const sortingItemSchema = zod.z.object({
|
|
4226
|
-
id: zod.z.string(),
|
|
4227
|
-
desc: zod.z.boolean()
|
|
4228
|
-
});
|
|
4229
4301
|
const getSortingStateParser = (columnIds) => {
|
|
4230
4302
|
const validKeys = columnIds ? columnIds instanceof Set ? columnIds : new Set(columnIds) : null;
|
|
4231
4303
|
return createParser({
|
|
4232
4304
|
parse: (value) => {
|
|
4233
4305
|
try {
|
|
4234
|
-
|
|
4235
|
-
const
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4306
|
+
if (!value || value.trim() === "") return [];
|
|
4307
|
+
const items = value.split(",").map((item) => {
|
|
4308
|
+
const lastDot = item.lastIndexOf(".");
|
|
4309
|
+
if (lastDot === -1) return null;
|
|
4310
|
+
const id = item.slice(0, lastDot);
|
|
4311
|
+
const dir = item.slice(lastDot + 1);
|
|
4312
|
+
if (!id || dir !== "asc" && dir !== "desc") return null;
|
|
4313
|
+
return {
|
|
4314
|
+
id,
|
|
4315
|
+
desc: dir === "desc"
|
|
4316
|
+
};
|
|
4317
|
+
});
|
|
4318
|
+
if (items.some((item) => item === null)) return null;
|
|
4319
|
+
const result = items;
|
|
4320
|
+
if (validKeys && result.some((item) => !validKeys.has(item.id))) return null;
|
|
4321
|
+
return result;
|
|
4239
4322
|
} catch {
|
|
4240
4323
|
return null;
|
|
4241
4324
|
}
|
|
4242
4325
|
},
|
|
4243
|
-
serialize: (value) =>
|
|
4326
|
+
serialize: (value) => value.map((item) => `${item.id}.${item.desc ? "desc" : "asc"}`).join(",")
|
|
4244
4327
|
});
|
|
4245
4328
|
};
|
|
4246
4329
|
const filterItemSchema = zod.z.object({
|
|
@@ -4446,7 +4529,7 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
|
|
|
4446
4529
|
"advanced",
|
|
4447
4530
|
"command"
|
|
4448
4531
|
];
|
|
4449
|
-
const [currentMode, setCurrentMode] = (
|
|
4532
|
+
const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
|
|
4450
4533
|
const showToggle = modes.length > 1;
|
|
4451
4534
|
const { table, shallow, debounceMs, throttleMs } = useDataTable({
|
|
4452
4535
|
data,
|
|
@@ -4541,7 +4624,8 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
|
|
|
4541
4624
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4542
4625
|
className: "flex w-full items-start justify-between gap-2 p-1",
|
|
4543
4626
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4544
|
-
className: "flex flex-1
|
|
4627
|
+
className: "flex flex-1 items-start gap-2 min-h-[40px]",
|
|
4628
|
+
"data-filter-parent": true,
|
|
4545
4629
|
children: [currentMode !== "simple" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableSortList, {
|
|
4546
4630
|
table,
|
|
4547
4631
|
align: "start"
|
|
@@ -5125,92 +5209,36 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
|
|
|
5125
5209
|
});
|
|
5126
5210
|
}
|
|
5127
5211
|
|
|
5128
|
-
//#endregion
|
|
5129
|
-
//#region src/components/auto-crud/export-dialog.tsx
|
|
5130
|
-
function ExportDialog({ open, onOpenChange, selectedCount, onExport, canExportFiltered = false }) {
|
|
5131
|
-
const [exporting, setExporting] = react.useState(null);
|
|
5132
|
-
const [done, setDone] = react.useState(false);
|
|
5133
|
-
const reset = react.useCallback(() => {
|
|
5134
|
-
setExporting(null);
|
|
5135
|
-
setDone(false);
|
|
5136
|
-
}, []);
|
|
5137
|
-
const handleOpenChange = react.useCallback((value) => {
|
|
5138
|
-
if (!value) reset();
|
|
5139
|
-
onOpenChange(value);
|
|
5140
|
-
}, [onOpenChange, reset]);
|
|
5141
|
-
const handleExport = react.useCallback(async (mode) => {
|
|
5142
|
-
setExporting(mode);
|
|
5143
|
-
try {
|
|
5144
|
-
await onExport(mode);
|
|
5145
|
-
setDone(true);
|
|
5146
|
-
setTimeout(() => handleOpenChange(false), 800);
|
|
5147
|
-
} catch {
|
|
5148
|
-
setExporting(null);
|
|
5149
|
-
}
|
|
5150
|
-
}, [onExport, handleOpenChange]);
|
|
5151
|
-
const hasSelected = selectedCount > 0;
|
|
5152
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Dialog, {
|
|
5153
|
-
open,
|
|
5154
|
-
onOpenChange: handleOpenChange,
|
|
5155
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogContent, {
|
|
5156
|
-
className: "sm:max-w-[400px]",
|
|
5157
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogTitle, { children: "导出数据" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogDescription, { children: "选择导出范围" })] }), done ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5158
|
-
className: "flex flex-col items-center gap-3 py-6",
|
|
5159
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CheckCircle2, { className: "h-10 w-10 text-green-500" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5160
|
-
className: "text-sm text-muted-foreground",
|
|
5161
|
-
children: "导出成功"
|
|
5162
|
-
})]
|
|
5163
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5164
|
-
className: "grid gap-3 py-2",
|
|
5165
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
5166
|
-
variant: "outline",
|
|
5167
|
-
className: "h-auto justify-start gap-3 px-4 py-3",
|
|
5168
|
-
disabled: !hasSelected || exporting !== null,
|
|
5169
|
-
onClick: () => handleExport("selected"),
|
|
5170
|
-
children: [exporting === "selected" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "h-5 w-5 shrink-0 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FileDown, { className: "h-5 w-5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5171
|
-
className: "flex flex-col items-start",
|
|
5172
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5173
|
-
className: "font-medium",
|
|
5174
|
-
children: ["导出选中", hasSelected ? ` (${selectedCount} 条)` : ""]
|
|
5175
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5176
|
-
className: "text-xs text-muted-foreground",
|
|
5177
|
-
children: hasSelected ? "导出当前勾选的数据行" : "请先在表格中勾选数据行"
|
|
5178
|
-
})]
|
|
5179
|
-
})]
|
|
5180
|
-
}), canExportFiltered && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
5181
|
-
variant: "outline",
|
|
5182
|
-
className: "h-auto justify-start gap-3 px-4 py-3",
|
|
5183
|
-
disabled: exporting !== null,
|
|
5184
|
-
onClick: () => handleExport("filtered"),
|
|
5185
|
-
children: [exporting === "filtered" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "h-5 w-5 shrink-0 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FileDown, { className: "h-5 w-5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5186
|
-
className: "flex flex-col items-start",
|
|
5187
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5188
|
-
className: "font-medium",
|
|
5189
|
-
children: "导出筛选结果"
|
|
5190
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5191
|
-
className: "text-xs text-muted-foreground",
|
|
5192
|
-
children: "导出当前筛选条件下的所有数据"
|
|
5193
|
-
})]
|
|
5194
|
-
})]
|
|
5195
|
-
})]
|
|
5196
|
-
})]
|
|
5197
|
-
})
|
|
5198
|
-
});
|
|
5199
|
-
}
|
|
5200
|
-
|
|
5201
5212
|
//#endregion
|
|
5202
5213
|
//#region src/components/auto-crud/auto-crud-table.tsx
|
|
5203
5214
|
/**
|
|
5204
5215
|
* 从统一配置生成表格 overrides
|
|
5216
|
+
* 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
|
|
5205
5217
|
*/
|
|
5206
5218
|
function buildTableOverrides(fields, legacyOverrides) {
|
|
5207
5219
|
const result = { ...legacyOverrides };
|
|
5208
|
-
if (fields) for (const [key, config] of Object.entries(fields))
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5220
|
+
if (fields) for (const [key, config] of Object.entries(fields)) {
|
|
5221
|
+
let filterMeta;
|
|
5222
|
+
if (config.filter && config.filter.enabled !== false && !config.filter.hidden) {
|
|
5223
|
+
const { enabled: _, hidden: __,...filterProps } = config.filter;
|
|
5224
|
+
if (Object.keys(filterProps).length > 0) filterMeta = filterProps;
|
|
5225
|
+
}
|
|
5226
|
+
result[key] = {
|
|
5227
|
+
...result[key],
|
|
5228
|
+
...config.label && { label: config.label },
|
|
5229
|
+
...config.hidden && { hidden: true },
|
|
5230
|
+
...config.table,
|
|
5231
|
+
...filterMeta && { meta: {
|
|
5232
|
+
...result[key]?.meta ?? {},
|
|
5233
|
+
...config.table?.meta ?? {},
|
|
5234
|
+
...filterMeta
|
|
5235
|
+
} }
|
|
5236
|
+
};
|
|
5237
|
+
if (config.filter?.enabled === false || config.filter?.hidden === true) result[key] = {
|
|
5238
|
+
...result[key],
|
|
5239
|
+
enableColumnFilter: false
|
|
5240
|
+
};
|
|
5241
|
+
}
|
|
5214
5242
|
return result;
|
|
5215
5243
|
}
|
|
5216
5244
|
/**
|
|
@@ -5364,8 +5392,8 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
5364
5392
|
const canImport = can.import && !!resource.handlers.import;
|
|
5365
5393
|
const canExport = can.export;
|
|
5366
5394
|
const [importOpen, setImportOpen] = react.useState(false);
|
|
5367
|
-
const [exportOpen, setExportOpen] = react.useState(false);
|
|
5368
5395
|
const [selectedCount, setSelectedCount] = react.useState(0);
|
|
5396
|
+
const [exporting, setExporting] = react.useState(false);
|
|
5369
5397
|
const getSelectedRowsRef = react.useRef(null);
|
|
5370
5398
|
const importColumns = react.useMemo(() => {
|
|
5371
5399
|
const shape = schema.shape;
|
|
@@ -5394,6 +5422,15 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
5394
5422
|
title,
|
|
5395
5423
|
denyFields
|
|
5396
5424
|
]);
|
|
5425
|
+
const handleExportClick = react.useCallback(async () => {
|
|
5426
|
+
const mode = selectedCount > 0 ? "selected" : "filtered";
|
|
5427
|
+
setExporting(true);
|
|
5428
|
+
try {
|
|
5429
|
+
await handleExport(mode);
|
|
5430
|
+
} finally {
|
|
5431
|
+
setExporting(false);
|
|
5432
|
+
}
|
|
5433
|
+
}, [selectedCount, handleExport]);
|
|
5397
5434
|
const tableOverrides = buildTableOverrides(fields, tableConfig?.overrides);
|
|
5398
5435
|
const formOverrides = buildFormOverrides(fields, formConfig?.overrides, denyFields);
|
|
5399
5436
|
const hiddenColumns = buildHiddenColumns(fields, tableConfig?.hidden, denyFields);
|
|
@@ -5429,8 +5466,9 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
5429
5466
|
canExport && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
5430
5467
|
variant: "outline",
|
|
5431
5468
|
size: "sm",
|
|
5432
|
-
onClick:
|
|
5433
|
-
|
|
5469
|
+
onClick: handleExportClick,
|
|
5470
|
+
disabled: exporting || selectedCount === 0 && !resource.handlers.export,
|
|
5471
|
+
children: [exporting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: "mr-2 h-4 w-4" }), selectedCount > 0 ? `导出(${selectedCount})` : "导出"]
|
|
5434
5472
|
}),
|
|
5435
5473
|
can.create && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
|
|
5436
5474
|
onClick: resource.handlers.openCreate,
|
|
@@ -5497,18 +5535,84 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
5497
5535
|
onOpenChange: setImportOpen,
|
|
5498
5536
|
onImport: resource.handlers.import,
|
|
5499
5537
|
columns: importColumns
|
|
5500
|
-
}),
|
|
5501
|
-
canExport && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExportDialog, {
|
|
5502
|
-
open: exportOpen,
|
|
5503
|
-
onOpenChange: setExportOpen,
|
|
5504
|
-
selectedCount,
|
|
5505
|
-
onExport: handleExport,
|
|
5506
|
-
canExportFiltered: !!resource.handlers.export
|
|
5507
5538
|
})
|
|
5508
5539
|
]
|
|
5509
5540
|
});
|
|
5510
5541
|
}
|
|
5511
5542
|
|
|
5543
|
+
//#endregion
|
|
5544
|
+
//#region src/components/auto-crud/export-dialog.tsx
|
|
5545
|
+
function ExportDialog({ open, onOpenChange, selectedCount, onExport, canExportFiltered = false }) {
|
|
5546
|
+
const [exporting, setExporting] = react.useState(null);
|
|
5547
|
+
const [done, setDone] = react.useState(false);
|
|
5548
|
+
const reset = react.useCallback(() => {
|
|
5549
|
+
setExporting(null);
|
|
5550
|
+
setDone(false);
|
|
5551
|
+
}, []);
|
|
5552
|
+
const handleOpenChange = react.useCallback((value) => {
|
|
5553
|
+
if (!value) reset();
|
|
5554
|
+
onOpenChange(value);
|
|
5555
|
+
}, [onOpenChange, reset]);
|
|
5556
|
+
const handleExport = react.useCallback(async (mode) => {
|
|
5557
|
+
setExporting(mode);
|
|
5558
|
+
try {
|
|
5559
|
+
await onExport(mode);
|
|
5560
|
+
setDone(true);
|
|
5561
|
+
setTimeout(() => handleOpenChange(false), 800);
|
|
5562
|
+
} catch {
|
|
5563
|
+
setExporting(null);
|
|
5564
|
+
}
|
|
5565
|
+
}, [onExport, handleOpenChange]);
|
|
5566
|
+
const hasSelected = selectedCount > 0;
|
|
5567
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Dialog, {
|
|
5568
|
+
open,
|
|
5569
|
+
onOpenChange: handleOpenChange,
|
|
5570
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogContent, {
|
|
5571
|
+
className: "sm:max-w-[400px]",
|
|
5572
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogTitle, { children: "导出数据" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogDescription, { children: "选择导出范围" })] }), done ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5573
|
+
className: "flex flex-col items-center gap-3 py-6",
|
|
5574
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CheckCircle2, { className: "h-10 w-10 text-green-500" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5575
|
+
className: "text-sm text-muted-foreground",
|
|
5576
|
+
children: "导出成功"
|
|
5577
|
+
})]
|
|
5578
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5579
|
+
className: "grid gap-3 py-2",
|
|
5580
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
5581
|
+
variant: "outline",
|
|
5582
|
+
className: "h-auto justify-start gap-3 px-4 py-3",
|
|
5583
|
+
disabled: !hasSelected || exporting !== null,
|
|
5584
|
+
onClick: () => handleExport("selected"),
|
|
5585
|
+
children: [exporting === "selected" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "h-5 w-5 shrink-0 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FileDown, { className: "h-5 w-5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5586
|
+
className: "flex flex-col items-start",
|
|
5587
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5588
|
+
className: "font-medium",
|
|
5589
|
+
children: ["导出选中", hasSelected ? ` (${selectedCount} 条)` : ""]
|
|
5590
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5591
|
+
className: "text-xs text-muted-foreground",
|
|
5592
|
+
children: hasSelected ? "导出当前勾选的数据行" : "请先在表格中勾选数据行"
|
|
5593
|
+
})]
|
|
5594
|
+
})]
|
|
5595
|
+
}), canExportFiltered && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
5596
|
+
variant: "outline",
|
|
5597
|
+
className: "h-auto justify-start gap-3 px-4 py-3",
|
|
5598
|
+
disabled: exporting !== null,
|
|
5599
|
+
onClick: () => handleExport("filtered"),
|
|
5600
|
+
children: [exporting === "filtered" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "h-5 w-5 shrink-0 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FileDown, { className: "h-5 w-5 shrink-0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5601
|
+
className: "flex flex-col items-start",
|
|
5602
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5603
|
+
className: "font-medium",
|
|
5604
|
+
children: "导出筛选结果"
|
|
5605
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5606
|
+
className: "text-xs text-muted-foreground",
|
|
5607
|
+
children: "导出当前筛选条件下的所有数据"
|
|
5608
|
+
})]
|
|
5609
|
+
})]
|
|
5610
|
+
})]
|
|
5611
|
+
})]
|
|
5612
|
+
})
|
|
5613
|
+
});
|
|
5614
|
+
}
|
|
5615
|
+
|
|
5512
5616
|
//#endregion
|
|
5513
5617
|
//#region src/components/data-table/data-table-advanced-toolbar.tsx
|
|
5514
5618
|
function DataTableAdvancedToolbar({ table, children, className,...props }) {
|
|
@@ -6742,12 +6846,12 @@ function createMemoryDataSource(initialData = []) {
|
|
|
6742
6846
|
});
|
|
6743
6847
|
});
|
|
6744
6848
|
if (params.sort && params.sort.length > 0) {
|
|
6745
|
-
const
|
|
6849
|
+
const sortItem = params.sort[0];
|
|
6746
6850
|
filtered.sort((a, b) => {
|
|
6747
|
-
const aVal = a[id];
|
|
6748
|
-
const bVal = b[id];
|
|
6851
|
+
const aVal = a[sortItem.id];
|
|
6852
|
+
const bVal = b[sortItem.id];
|
|
6749
6853
|
const result = aVal > bVal ? 1 : aVal < bVal ? -1 : 0;
|
|
6750
|
-
return desc ? -result : result;
|
|
6854
|
+
return sortItem.desc ? -result : result;
|
|
6751
6855
|
});
|
|
6752
6856
|
}
|
|
6753
6857
|
const start = (params.page - 1) * params.perPage;
|
|
@@ -6775,11 +6879,12 @@ function createMemoryDataSource(initialData = []) {
|
|
|
6775
6879
|
async update(id, updateData) {
|
|
6776
6880
|
const index = data.findIndex((item) => item.id === id);
|
|
6777
6881
|
if (index === -1) throw new Error(`Item with id ${id} not found`);
|
|
6778
|
-
|
|
6882
|
+
const updated = {
|
|
6779
6883
|
...data[index],
|
|
6780
6884
|
...updateData
|
|
6781
6885
|
};
|
|
6782
|
-
|
|
6886
|
+
data[index] = updated;
|
|
6887
|
+
return updated;
|
|
6783
6888
|
},
|
|
6784
6889
|
async delete(id) {
|
|
6785
6890
|
const index = data.findIndex((item) => item.id === id);
|