@wordrhyme/auto-crud 0.10.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +658 -421
- package/dist/index.d.cts +142 -97
- package/dist/index.d.ts +142 -97
- package/dist/index.js +658 -421
- 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;
|
|
@@ -1457,13 +1450,14 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
|
|
|
1457
1450
|
const [open, setOpen] = react.useState(false);
|
|
1458
1451
|
const addButtonRef = react.useRef(null);
|
|
1459
1452
|
const columns = react.useMemo(() => {
|
|
1460
|
-
return table.getAllColumns().filter((column) =>
|
|
1453
|
+
return table.getAllColumns().filter((column) => {
|
|
1454
|
+
if (!column.getCanFilter()) return false;
|
|
1455
|
+
const modes = column.columnDef.meta?.modes;
|
|
1456
|
+
if (modes && !modes.includes("advanced")) return false;
|
|
1457
|
+
return true;
|
|
1458
|
+
});
|
|
1461
1459
|
}, [table]);
|
|
1462
|
-
const
|
|
1463
|
-
const [filters, setFilters] = useReadableFilters(columns, {
|
|
1464
|
-
...queryStateOptions,
|
|
1465
|
-
debounceMs: 0
|
|
1466
|
-
});
|
|
1460
|
+
const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
|
|
1467
1461
|
const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
|
|
1468
1462
|
const [joinOperator, setJoinOperator] = useQueryState(table.options.meta?.queryKeys?.joinOperator ?? "", parseAsStringEnum(["and", "or"]).withDefault("and"));
|
|
1469
1463
|
const onFilterAdd = react.useCallback(() => {
|
|
@@ -1499,7 +1493,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
|
|
|
1499
1493
|
});
|
|
1500
1494
|
}, [filters, setFilters]);
|
|
1501
1495
|
const onFiltersReset = react.useCallback(() => {
|
|
1502
|
-
setFilters(
|
|
1496
|
+
setFilters([]);
|
|
1503
1497
|
setJoinOperator("and");
|
|
1504
1498
|
}, [setFilters, setJoinOperator]);
|
|
1505
1499
|
react.useEffect(() => {
|
|
@@ -1943,7 +1937,12 @@ const REMOVE_FILTER_SHORTCUTS = ["backspace", "delete"];
|
|
|
1943
1937
|
function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = THROTTLE_MS$1, shallow = true, disabled,...props }) {
|
|
1944
1938
|
const id = react.useId();
|
|
1945
1939
|
const columns = react.useMemo(() => {
|
|
1946
|
-
return table.getAllColumns().filter((column) =>
|
|
1940
|
+
return table.getAllColumns().filter((column) => {
|
|
1941
|
+
if (!column.columnDef.enableColumnFilter) return false;
|
|
1942
|
+
const modes = column.columnDef.meta?.modes;
|
|
1943
|
+
if (modes && !modes.includes("command")) return false;
|
|
1944
|
+
return true;
|
|
1945
|
+
});
|
|
1947
1946
|
}, [table]);
|
|
1948
1947
|
const [open, setOpen] = react.useState(false);
|
|
1949
1948
|
const [selectedColumn, setSelectedColumn] = react.useState(null);
|
|
@@ -1963,11 +1962,7 @@ function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = T
|
|
|
1963
1962
|
setSelectedColumn(null);
|
|
1964
1963
|
}
|
|
1965
1964
|
}, [inputValue, selectedColumn]);
|
|
1966
|
-
const
|
|
1967
|
-
const [filters, setFilters] = useReadableFilters(columns, {
|
|
1968
|
-
...queryStateOptions,
|
|
1969
|
-
debounceMs: 0
|
|
1970
|
-
});
|
|
1965
|
+
const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
|
|
1971
1966
|
const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
|
|
1972
1967
|
const onFilterAdd = react.useCallback((column, value) => {
|
|
1973
1968
|
if (!value.trim() && column.columnDef.meta?.variant !== "boolean") return;
|
|
@@ -2704,7 +2699,7 @@ function DataTableViewOptions({ table, disabled,...props }) {
|
|
|
2704
2699
|
//#endregion
|
|
2705
2700
|
//#region src/components/auto-crud/auto-table-simple-filters.tsx
|
|
2706
2701
|
function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilters, onFiltersChange }) {
|
|
2707
|
-
const columns = react.useMemo(() => table.getAllColumns().filter((col) => col.
|
|
2702
|
+
const columns = react.useMemo(() => table.getAllColumns().filter((col) => col.getCanFilter()), [table]);
|
|
2708
2703
|
const queryStateOptions = table.options.meta?.queryStateOptions;
|
|
2709
2704
|
const [queryFilters, setQueryFilters] = useReadableFilters(columns, {
|
|
2710
2705
|
...queryStateOptions,
|
|
@@ -2724,9 +2719,10 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
|
|
|
2724
2719
|
react.useEffect(() => {
|
|
2725
2720
|
setMounted(true);
|
|
2726
2721
|
}, []);
|
|
2727
|
-
const
|
|
2728
|
-
|
|
2729
|
-
|
|
2722
|
+
const filterMap = react.useMemo(() => new Map(filters.map((f) => [f.id, f])), [filters]);
|
|
2723
|
+
const getFilterValue = react.useCallback((columnId) => {
|
|
2724
|
+
return filterMap.get(columnId)?.value;
|
|
2725
|
+
}, [filterMap]);
|
|
2730
2726
|
const updateFilter = react.useCallback((columnId, value) => {
|
|
2731
2727
|
const column = columns.find((c) => c.id === columnId);
|
|
2732
2728
|
if (!column) return;
|
|
@@ -2749,56 +2745,154 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
|
|
|
2749
2745
|
setFilters
|
|
2750
2746
|
]);
|
|
2751
2747
|
const hasFilters = filters.length > 0;
|
|
2748
|
+
const filterColumns = react.useMemo(() => columns.filter((col) => {
|
|
2749
|
+
const meta = col.columnDef.meta;
|
|
2750
|
+
const enableFilter = col.columnDef.enableColumnFilter !== false;
|
|
2751
|
+
if (!meta?.variant || !enableFilter) return false;
|
|
2752
|
+
const modes = meta.modes;
|
|
2753
|
+
if (modes && !modes.includes("simple")) return false;
|
|
2754
|
+
return true;
|
|
2755
|
+
}), [columns]);
|
|
2756
|
+
const [expanded, setExpanded] = react.useState(false);
|
|
2757
|
+
const [visibleCount, setVisibleCount] = react.useState(null);
|
|
2758
|
+
const containerRef = react.useRef(null);
|
|
2759
|
+
const calcVisibleCount = react.useCallback(() => {
|
|
2760
|
+
const el = containerRef.current;
|
|
2761
|
+
if (!el) return;
|
|
2762
|
+
const items = el.querySelectorAll("[data-filter-item]");
|
|
2763
|
+
if (items.length === 0) return;
|
|
2764
|
+
const restoreList = [];
|
|
2765
|
+
el.querySelectorAll(".hidden").forEach((node) => {
|
|
2766
|
+
const htmlNode = node;
|
|
2767
|
+
htmlNode.classList.remove("hidden");
|
|
2768
|
+
restoreList.push(htmlNode);
|
|
2769
|
+
});
|
|
2770
|
+
el.style.flex = "1 1 0%";
|
|
2771
|
+
el.offsetHeight;
|
|
2772
|
+
const firstTop = items[0].offsetTop;
|
|
2773
|
+
let count = 0;
|
|
2774
|
+
for (const item of Array.from(items)) {
|
|
2775
|
+
if (item.offsetTop > firstTop) break;
|
|
2776
|
+
count++;
|
|
2777
|
+
}
|
|
2778
|
+
const btn = el.querySelector("[data-filter-toggle]");
|
|
2779
|
+
if (btn && count < items.length && count > 0) {
|
|
2780
|
+
if (btn.offsetTop > firstTop) count--;
|
|
2781
|
+
}
|
|
2782
|
+
el.style.flex = "";
|
|
2783
|
+
restoreList.forEach((node) => node.classList.add("hidden"));
|
|
2784
|
+
setVisibleCount(count >= items.length ? null : count);
|
|
2785
|
+
}, []);
|
|
2786
|
+
react.useLayoutEffect(() => {
|
|
2787
|
+
if (!mounted) return;
|
|
2788
|
+
calcVisibleCount();
|
|
2789
|
+
}, [
|
|
2790
|
+
mounted,
|
|
2791
|
+
filterColumns.length,
|
|
2792
|
+
calcVisibleCount
|
|
2793
|
+
]);
|
|
2794
|
+
react.useEffect(() => {
|
|
2795
|
+
const parent = containerRef.current?.closest("[data-filter-parent]");
|
|
2796
|
+
if (!mounted || !parent) return;
|
|
2797
|
+
let lastWidth = parent.offsetWidth;
|
|
2798
|
+
let rafId = 0;
|
|
2799
|
+
const ro = new ResizeObserver(() => {
|
|
2800
|
+
const newWidth = parent.offsetWidth;
|
|
2801
|
+
if (newWidth === lastWidth) return;
|
|
2802
|
+
lastWidth = newWidth;
|
|
2803
|
+
cancelAnimationFrame(rafId);
|
|
2804
|
+
rafId = requestAnimationFrame(calcVisibleCount);
|
|
2805
|
+
});
|
|
2806
|
+
ro.observe(parent);
|
|
2807
|
+
return () => {
|
|
2808
|
+
cancelAnimationFrame(rafId);
|
|
2809
|
+
ro.disconnect();
|
|
2810
|
+
};
|
|
2811
|
+
}, [
|
|
2812
|
+
mounted,
|
|
2813
|
+
filterColumns.length,
|
|
2814
|
+
calcVisibleCount
|
|
2815
|
+
]);
|
|
2752
2816
|
if (!mounted) return null;
|
|
2753
2817
|
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
|
-
|
|
2818
|
+
ref: containerRef,
|
|
2819
|
+
className: "flex flex-wrap items-center gap-2 min-w-0",
|
|
2820
|
+
children: [
|
|
2821
|
+
filterColumns.map((column, index) => {
|
|
2822
|
+
const meta = column.columnDef.meta;
|
|
2823
|
+
const value = getFilterValue(column.id);
|
|
2824
|
+
const isHidden = !expanded && visibleCount !== null && index >= visibleCount;
|
|
2825
|
+
switch (meta.variant) {
|
|
2826
|
+
case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2827
|
+
className: isHidden ? "hidden" : "",
|
|
2828
|
+
"data-filter-item": true,
|
|
2829
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Input, {
|
|
2830
|
+
placeholder: meta.placeholder ?? meta.label ?? column.id,
|
|
2831
|
+
value: typeof value === "string" ? value : "",
|
|
2832
|
+
onChange: (e) => updateFilter(column.id, e.target.value || void 0),
|
|
2833
|
+
className: "h-8 w-36 shrink-0"
|
|
2834
|
+
})
|
|
2835
|
+
}, column.id);
|
|
2836
|
+
case "select":
|
|
2837
|
+
case "multiSelect": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2838
|
+
className: isHidden ? "hidden" : "",
|
|
2839
|
+
"data-filter-item": true,
|
|
2840
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleFacetedFilter, {
|
|
2841
|
+
title: meta.label ?? column.id,
|
|
2842
|
+
options: meta.options ?? [],
|
|
2843
|
+
multiple: meta.variant === "multiSelect",
|
|
2844
|
+
value: Array.isArray(value) ? value : value ? [value] : [],
|
|
2845
|
+
onChange: (v) => updateFilter(column.id, v.length ? v : void 0)
|
|
2846
|
+
})
|
|
2847
|
+
}, column.id);
|
|
2848
|
+
case "range": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2849
|
+
className: isHidden ? "hidden" : "",
|
|
2850
|
+
"data-filter-item": true,
|
|
2851
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleSliderFilter, {
|
|
2852
|
+
title: meta.label ?? column.id,
|
|
2853
|
+
range: meta.range,
|
|
2854
|
+
unit: meta.unit,
|
|
2855
|
+
value: Array.isArray(value) ? value.map(Number) : void 0,
|
|
2856
|
+
onChange: (v) => updateFilter(column.id, v ? v.map(String) : void 0)
|
|
2857
|
+
})
|
|
2858
|
+
}, column.id);
|
|
2859
|
+
case "date":
|
|
2860
|
+
case "dateRange": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2861
|
+
className: isHidden ? "hidden" : "",
|
|
2862
|
+
"data-filter-item": true,
|
|
2863
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SimpleDateFilter, {
|
|
2864
|
+
title: meta.label ?? column.id,
|
|
2865
|
+
multiple: meta.variant === "dateRange",
|
|
2866
|
+
value,
|
|
2867
|
+
onChange: (v) => updateFilter(column.id, v)
|
|
2868
|
+
})
|
|
2869
|
+
}, column.id);
|
|
2870
|
+
default: return null;
|
|
2871
|
+
}
|
|
2872
|
+
}),
|
|
2873
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
|
|
2874
|
+
"data-filter-toggle": true,
|
|
2875
|
+
variant: "ghost",
|
|
2876
|
+
size: "icon",
|
|
2877
|
+
className: cn("h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground", visibleCount === null && "hidden"),
|
|
2878
|
+
onClick: () => setExpanded((prev) => !prev),
|
|
2879
|
+
"aria-label": expanded ? "收起筛选" : "展开更多筛选",
|
|
2880
|
+
"aria-expanded": expanded,
|
|
2881
|
+
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" })
|
|
2882
|
+
}),
|
|
2883
|
+
hasFilters && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
2884
|
+
variant: "outline",
|
|
2885
|
+
size: "sm",
|
|
2886
|
+
className: "h-8 border-dashed shrink-0",
|
|
2887
|
+
onClick: () => setFilters([]),
|
|
2888
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-4" }), "Reset"]
|
|
2889
|
+
})
|
|
2890
|
+
]
|
|
2797
2891
|
});
|
|
2798
2892
|
}
|
|
2799
2893
|
function SimpleFacetedFilter({ title, options, multiple, value, onChange }) {
|
|
2800
2894
|
const [open, setOpen] = react.useState(false);
|
|
2801
|
-
const selectedValues = new Set(value);
|
|
2895
|
+
const selectedValues = react.useMemo(() => new Set(value), [value]);
|
|
2802
2896
|
const onItemSelect = (option, isSelected) => {
|
|
2803
2897
|
if (multiple) {
|
|
2804
2898
|
const newValues = new Set(selectedValues);
|
|
@@ -2829,6 +2923,12 @@ function SimpleFacetedFilter({ title, options, multiple, value, onChange }) {
|
|
|
2829
2923
|
tabIndex: 0,
|
|
2830
2924
|
className: "rounded-sm opacity-70 hover:opacity-100",
|
|
2831
2925
|
onClick: onReset,
|
|
2926
|
+
onKeyDown: (e) => {
|
|
2927
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2928
|
+
e.preventDefault();
|
|
2929
|
+
onReset(e);
|
|
2930
|
+
}
|
|
2931
|
+
},
|
|
2832
2932
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
|
|
2833
2933
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PlusCircle, {}),
|
|
2834
2934
|
title,
|
|
@@ -2936,6 +3036,12 @@ function SimpleSliderFilter({ title, range, unit, value, onChange }) {
|
|
|
2936
3036
|
tabIndex: 0,
|
|
2937
3037
|
className: "rounded-sm opacity-70 hover:opacity-100",
|
|
2938
3038
|
onClick: onReset,
|
|
3039
|
+
onKeyDown: (e) => {
|
|
3040
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
3041
|
+
e.preventDefault();
|
|
3042
|
+
onReset(e);
|
|
3043
|
+
}
|
|
3044
|
+
},
|
|
2939
3045
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
|
|
2940
3046
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PlusCircle, {}),
|
|
2941
3047
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }),
|
|
@@ -3059,6 +3165,12 @@ function SimpleDateFilter({ title, multiple, value, onChange }) {
|
|
|
3059
3165
|
tabIndex: 0,
|
|
3060
3166
|
className: "rounded-sm opacity-70 hover:opacity-100",
|
|
3061
3167
|
onClick: onReset,
|
|
3168
|
+
onKeyDown: (e) => {
|
|
3169
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
3170
|
+
e.preventDefault();
|
|
3171
|
+
onReset(e);
|
|
3172
|
+
}
|
|
3173
|
+
},
|
|
3062
3174
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
|
|
3063
3175
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CalendarIcon, {}),
|
|
3064
3176
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }),
|
|
@@ -3671,33 +3783,47 @@ function inferFilterVariantByFieldName(fieldName) {
|
|
|
3671
3783
|
|
|
3672
3784
|
//#endregion
|
|
3673
3785
|
//#region src/lib/schema-bridge/zod-to-columns.tsx
|
|
3786
|
+
const parsedFieldCache = /* @__PURE__ */ new WeakMap();
|
|
3787
|
+
const baseTableSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
3674
3788
|
/**
|
|
3675
3789
|
* 解析 Zod 字段类型
|
|
3676
3790
|
*/
|
|
3677
3791
|
function parseZodField(schema) {
|
|
3792
|
+
const cached = parsedFieldCache.get(schema);
|
|
3793
|
+
if (cached) return cached;
|
|
3794
|
+
const finalize = (result) => {
|
|
3795
|
+
parsedFieldCache.set(schema, result);
|
|
3796
|
+
return result;
|
|
3797
|
+
};
|
|
3678
3798
|
const def = schema._zod?.def ?? schema._def;
|
|
3679
|
-
const testNull = schema.safeParse(null);
|
|
3680
|
-
const required = !schema.safeParse(void 0).success && !testNull.success;
|
|
3681
3799
|
const typeName = def?.typeName;
|
|
3682
|
-
|
|
3800
|
+
const isOptional = def?.type === "optional" || typeName === "ZodOptional";
|
|
3801
|
+
const isNullable = def?.type === "nullable" || typeName === "ZodNullable";
|
|
3802
|
+
if (isOptional || isNullable) {
|
|
3683
3803
|
const innerType = def?.innerType;
|
|
3684
|
-
if (innerType) return {
|
|
3804
|
+
if (innerType) return finalize({
|
|
3685
3805
|
...parseZodField(innerType),
|
|
3686
3806
|
required: false
|
|
3687
|
-
};
|
|
3807
|
+
});
|
|
3808
|
+
}
|
|
3809
|
+
let required;
|
|
3810
|
+
if (isOptional || isNullable) required = false;
|
|
3811
|
+
else {
|
|
3812
|
+
const testNull = schema.safeParse(null);
|
|
3813
|
+
required = !schema.safeParse(void 0).success && !testNull.success;
|
|
3688
3814
|
}
|
|
3689
|
-
if (def?.type === "boolean") return {
|
|
3815
|
+
if (def?.type === "boolean") return finalize({
|
|
3690
3816
|
type: "boolean",
|
|
3691
3817
|
required
|
|
3692
|
-
};
|
|
3693
|
-
if (def?.type === "number") return {
|
|
3818
|
+
});
|
|
3819
|
+
if (def?.type === "number") return finalize({
|
|
3694
3820
|
type: "number",
|
|
3695
3821
|
required
|
|
3696
|
-
};
|
|
3697
|
-
if (def?.type === "date") return {
|
|
3822
|
+
});
|
|
3823
|
+
if (def?.type === "date") return finalize({
|
|
3698
3824
|
type: "date",
|
|
3699
3825
|
required
|
|
3700
|
-
};
|
|
3826
|
+
});
|
|
3701
3827
|
if (def?.type === "enum") {
|
|
3702
3828
|
let enumValues$1;
|
|
3703
3829
|
if (def?.entries) {
|
|
@@ -3708,20 +3834,20 @@ function parseZodField(schema) {
|
|
|
3708
3834
|
const values = def.values;
|
|
3709
3835
|
if (Array.isArray(values)) enumValues$1 = values.map(String);
|
|
3710
3836
|
}
|
|
3711
|
-
if (enumValues$1 && enumValues$1.length > 0) return {
|
|
3837
|
+
if (enumValues$1 && enumValues$1.length > 0) return finalize({
|
|
3712
3838
|
type: "enum",
|
|
3713
3839
|
required,
|
|
3714
3840
|
enumValues: enumValues$1
|
|
3715
|
-
};
|
|
3841
|
+
});
|
|
3716
3842
|
}
|
|
3717
|
-
if (def?.type === "string") return {
|
|
3843
|
+
if (def?.type === "string") return finalize({
|
|
3718
3844
|
type: "string",
|
|
3719
3845
|
required
|
|
3720
|
-
};
|
|
3721
|
-
if (typeName === "ZodBoolean") return {
|
|
3846
|
+
});
|
|
3847
|
+
if (typeName === "ZodBoolean") return finalize({
|
|
3722
3848
|
type: "boolean",
|
|
3723
3849
|
required
|
|
3724
|
-
};
|
|
3850
|
+
});
|
|
3725
3851
|
let enumValues;
|
|
3726
3852
|
if (def?.entries) {
|
|
3727
3853
|
const entries = def.entries;
|
|
@@ -3731,51 +3857,51 @@ function parseZodField(schema) {
|
|
|
3731
3857
|
const values = def.values;
|
|
3732
3858
|
if (Array.isArray(values)) enumValues = values.map(String);
|
|
3733
3859
|
}
|
|
3734
|
-
if (enumValues && Array.isArray(enumValues) && enumValues.length > 0) return {
|
|
3860
|
+
if (enumValues && Array.isArray(enumValues) && enumValues.length > 0) return finalize({
|
|
3735
3861
|
type: "enum",
|
|
3736
3862
|
required,
|
|
3737
3863
|
enumValues
|
|
3738
|
-
};
|
|
3739
|
-
if (typeName === "ZodNumber") return {
|
|
3864
|
+
});
|
|
3865
|
+
if (typeName === "ZodNumber") return finalize({
|
|
3740
3866
|
type: "number",
|
|
3741
3867
|
required
|
|
3742
|
-
};
|
|
3743
|
-
if (typeName === "ZodDate") return {
|
|
3868
|
+
});
|
|
3869
|
+
if (typeName === "ZodDate") return finalize({
|
|
3744
3870
|
type: "date",
|
|
3745
3871
|
required
|
|
3746
|
-
};
|
|
3747
|
-
if (typeName === "ZodArray") return {
|
|
3872
|
+
});
|
|
3873
|
+
if (typeName === "ZodArray") return finalize({
|
|
3748
3874
|
type: "array",
|
|
3749
3875
|
required
|
|
3750
|
-
};
|
|
3751
|
-
if (typeName === "ZodObject") return {
|
|
3876
|
+
});
|
|
3877
|
+
if (typeName === "ZodObject") return finalize({
|
|
3752
3878
|
type: "object",
|
|
3753
3879
|
required
|
|
3754
|
-
};
|
|
3880
|
+
});
|
|
3755
3881
|
const testString = schema.safeParse("test");
|
|
3756
3882
|
const testNumber = schema.safeParse(123);
|
|
3757
3883
|
const testBoolean = schema.safeParse(true);
|
|
3758
3884
|
const testDate = schema.safeParse(17040672e5);
|
|
3759
|
-
if (testBoolean.success && !testString.success && !testNumber.success) return {
|
|
3885
|
+
if (testBoolean.success && !testString.success && !testNumber.success) return finalize({
|
|
3760
3886
|
type: "boolean",
|
|
3761
3887
|
required
|
|
3762
|
-
};
|
|
3763
|
-
if (testDate.success && !testString.success) return {
|
|
3888
|
+
});
|
|
3889
|
+
if (testDate.success && !testString.success) return finalize({
|
|
3764
3890
|
type: "date",
|
|
3765
3891
|
required
|
|
3766
|
-
};
|
|
3767
|
-
if (testNumber.success && !testString.success) return {
|
|
3892
|
+
});
|
|
3893
|
+
if (testNumber.success && !testString.success) return finalize({
|
|
3768
3894
|
type: "number",
|
|
3769
3895
|
required
|
|
3770
|
-
};
|
|
3771
|
-
if (schema.safeParse([]).success && !testString.success) return {
|
|
3896
|
+
});
|
|
3897
|
+
if (schema.safeParse([]).success && !testString.success) return finalize({
|
|
3772
3898
|
type: "array",
|
|
3773
3899
|
required
|
|
3774
|
-
};
|
|
3775
|
-
return {
|
|
3900
|
+
});
|
|
3901
|
+
return finalize({
|
|
3776
3902
|
type: "string",
|
|
3777
3903
|
required
|
|
3778
|
-
};
|
|
3904
|
+
});
|
|
3779
3905
|
}
|
|
3780
3906
|
/**
|
|
3781
3907
|
* 渲染单元格内容
|
|
@@ -3817,9 +3943,14 @@ function renderCell(value, type) {
|
|
|
3817
3943
|
* 从 Zod Schema 创建 Table Schema (ColumnDef 数组)
|
|
3818
3944
|
*/
|
|
3819
3945
|
function createTableSchema(schema, options) {
|
|
3946
|
+
const { overrides, exclude = [] } = options ?? {};
|
|
3947
|
+
const canCache = !(!!overrides && Object.keys(overrides).length > 0) && exclude.length === 0;
|
|
3948
|
+
if (canCache) {
|
|
3949
|
+
const cached = baseTableSchemaCache.get(schema);
|
|
3950
|
+
if (cached) return [...cached];
|
|
3951
|
+
}
|
|
3820
3952
|
const shape = schema.shape;
|
|
3821
3953
|
const columns = [];
|
|
3822
|
-
const { overrides, exclude = [] } = options ?? {};
|
|
3823
3954
|
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
3824
3955
|
if (exclude.includes(key)) continue;
|
|
3825
3956
|
const override = overrides?.[key];
|
|
@@ -3854,6 +3985,7 @@ function createTableSchema(schema, options) {
|
|
|
3854
3985
|
...restOverride
|
|
3855
3986
|
});
|
|
3856
3987
|
}
|
|
3988
|
+
if (canCache) baseTableSchemaCache.set(schema, columns);
|
|
3857
3989
|
return columns;
|
|
3858
3990
|
}
|
|
3859
3991
|
/**
|
|
@@ -4222,25 +4354,32 @@ function createParser(config) {
|
|
|
4222
4354
|
})
|
|
4223
4355
|
};
|
|
4224
4356
|
}
|
|
4225
|
-
const sortingItemSchema = zod.z.object({
|
|
4226
|
-
id: zod.z.string(),
|
|
4227
|
-
desc: zod.z.boolean()
|
|
4228
|
-
});
|
|
4229
4357
|
const getSortingStateParser = (columnIds) => {
|
|
4230
4358
|
const validKeys = columnIds ? columnIds instanceof Set ? columnIds : new Set(columnIds) : null;
|
|
4231
4359
|
return createParser({
|
|
4232
4360
|
parse: (value) => {
|
|
4233
4361
|
try {
|
|
4234
|
-
|
|
4235
|
-
const
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4362
|
+
if (!value || value.trim() === "") return [];
|
|
4363
|
+
const items = value.split(",").map((item) => {
|
|
4364
|
+
const lastDot = item.lastIndexOf(".");
|
|
4365
|
+
if (lastDot === -1) return null;
|
|
4366
|
+
const id = item.slice(0, lastDot);
|
|
4367
|
+
const dir = item.slice(lastDot + 1);
|
|
4368
|
+
if (!id || dir !== "asc" && dir !== "desc") return null;
|
|
4369
|
+
return {
|
|
4370
|
+
id,
|
|
4371
|
+
desc: dir === "desc"
|
|
4372
|
+
};
|
|
4373
|
+
});
|
|
4374
|
+
if (items.some((item) => item === null)) return null;
|
|
4375
|
+
const result = items;
|
|
4376
|
+
if (validKeys && result.some((item) => !validKeys.has(item.id))) return null;
|
|
4377
|
+
return result;
|
|
4239
4378
|
} catch {
|
|
4240
4379
|
return null;
|
|
4241
4380
|
}
|
|
4242
4381
|
},
|
|
4243
|
-
serialize: (value) =>
|
|
4382
|
+
serialize: (value) => value.map((item) => `${item.id}.${item.desc ? "desc" : "asc"}`).join(",")
|
|
4244
4383
|
});
|
|
4245
4384
|
};
|
|
4246
4385
|
const filterItemSchema = zod.z.object({
|
|
@@ -4363,6 +4502,13 @@ function useDataTable(props) {
|
|
|
4363
4502
|
filterableColumns,
|
|
4364
4503
|
enableAdvancedFilter
|
|
4365
4504
|
]);
|
|
4505
|
+
const getCoreRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getCoreRowModel)(), []);
|
|
4506
|
+
const getFilteredRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getFilteredRowModel)(), []);
|
|
4507
|
+
const getPaginationRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getPaginationRowModel)(), []);
|
|
4508
|
+
const getSortedRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getSortedRowModel)(), []);
|
|
4509
|
+
const getFacetedRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getFacetedRowModel)(), []);
|
|
4510
|
+
const getFacetedUniqueValuesFn = react.useMemo(() => (0, __tanstack_react_table.getFacetedUniqueValues)(), []);
|
|
4511
|
+
const getFacetedMinMaxValuesFn = react.useMemo(() => (0, __tanstack_react_table.getFacetedMinMaxValues)(), []);
|
|
4366
4512
|
const table = (0, __tanstack_react_table.useReactTable)({
|
|
4367
4513
|
...tableProps,
|
|
4368
4514
|
columns,
|
|
@@ -4385,13 +4531,13 @@ function useDataTable(props) {
|
|
|
4385
4531
|
onSortingChange,
|
|
4386
4532
|
onColumnFiltersChange,
|
|
4387
4533
|
onColumnVisibilityChange: setColumnVisibility,
|
|
4388
|
-
getCoreRowModel:
|
|
4389
|
-
getFilteredRowModel:
|
|
4390
|
-
getPaginationRowModel:
|
|
4391
|
-
getSortedRowModel:
|
|
4392
|
-
getFacetedRowModel:
|
|
4393
|
-
getFacetedUniqueValues:
|
|
4394
|
-
getFacetedMinMaxValues:
|
|
4534
|
+
getCoreRowModel: getCoreRowModelFn,
|
|
4535
|
+
getFilteredRowModel: getFilteredRowModelFn,
|
|
4536
|
+
getPaginationRowModel: getPaginationRowModelFn,
|
|
4537
|
+
getSortedRowModel: getSortedRowModelFn,
|
|
4538
|
+
getFacetedRowModel: getFacetedRowModelFn,
|
|
4539
|
+
getFacetedUniqueValues: getFacetedUniqueValuesFn,
|
|
4540
|
+
getFacetedMinMaxValues: getFacetedMinMaxValuesFn,
|
|
4395
4541
|
manualPagination: true,
|
|
4396
4542
|
manualSorting: true,
|
|
4397
4543
|
manualFiltering: true,
|
|
@@ -4422,6 +4568,11 @@ function useDataTable(props) {
|
|
|
4422
4568
|
|
|
4423
4569
|
//#endregion
|
|
4424
4570
|
//#region src/components/auto-crud/auto-table.tsx
|
|
4571
|
+
const DEFAULT_MODES = [
|
|
4572
|
+
"simple",
|
|
4573
|
+
"advanced",
|
|
4574
|
+
"command"
|
|
4575
|
+
];
|
|
4425
4576
|
/** 过滤模式配置 */
|
|
4426
4577
|
const filterModeConfig = {
|
|
4427
4578
|
simple: {
|
|
@@ -4441,12 +4592,8 @@ const filterModeConfig = {
|
|
|
4441
4592
|
}
|
|
4442
4593
|
};
|
|
4443
4594
|
function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, initialSorting, enableExport = true, onSelectedCountChange, getSelectedRows }) {
|
|
4444
|
-
const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] :
|
|
4445
|
-
|
|
4446
|
-
"advanced",
|
|
4447
|
-
"command"
|
|
4448
|
-
];
|
|
4449
|
-
const [currentMode, setCurrentMode] = (0, react.useState)(modes[0] ?? "simple");
|
|
4595
|
+
const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
|
|
4596
|
+
const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
|
|
4450
4597
|
const showToggle = modes.length > 1;
|
|
4451
4598
|
const { table, shallow, debounceMs, throttleMs } = useDataTable({
|
|
4452
4599
|
data,
|
|
@@ -4467,13 +4614,18 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
|
|
|
4467
4614
|
]),
|
|
4468
4615
|
pageCount,
|
|
4469
4616
|
enableAdvancedFilter: true,
|
|
4470
|
-
initialState: {
|
|
4617
|
+
initialState: (0, react.useMemo)(() => ({
|
|
4471
4618
|
sorting: initialSorting,
|
|
4472
4619
|
columnPinning: pinnedColumns ?? {
|
|
4473
4620
|
left: enableRowSelection ? ["select"] : [],
|
|
4474
4621
|
right: actions ? ["actions"] : []
|
|
4475
4622
|
}
|
|
4476
|
-
},
|
|
4623
|
+
}), [
|
|
4624
|
+
initialSorting,
|
|
4625
|
+
pinnedColumns,
|
|
4626
|
+
enableRowSelection,
|
|
4627
|
+
actions
|
|
4628
|
+
]),
|
|
4477
4629
|
shallow: false,
|
|
4478
4630
|
clearOnDefault: true
|
|
4479
4631
|
});
|
|
@@ -4514,7 +4666,7 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
|
|
|
4514
4666
|
})
|
|
4515
4667
|
})
|
|
4516
4668
|
})] }) : null;
|
|
4517
|
-
const
|
|
4669
|
+
const filtersContent = (0, react.useMemo)(() => {
|
|
4518
4670
|
switch (currentMode) {
|
|
4519
4671
|
case "simple": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoTableSimpleFilters, {
|
|
4520
4672
|
table,
|
|
@@ -4534,18 +4686,25 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
|
|
|
4534
4686
|
align: "start"
|
|
4535
4687
|
});
|
|
4536
4688
|
}
|
|
4537
|
-
}
|
|
4689
|
+
}, [
|
|
4690
|
+
currentMode,
|
|
4691
|
+
table,
|
|
4692
|
+
shallow,
|
|
4693
|
+
debounceMs,
|
|
4694
|
+
throttleMs
|
|
4695
|
+
]);
|
|
4538
4696
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4539
4697
|
className: "space-y-4",
|
|
4540
4698
|
children: [
|
|
4541
4699
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4542
4700
|
className: "flex w-full items-start justify-between gap-2 p-1",
|
|
4543
4701
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4544
|
-
className: "flex flex-1
|
|
4702
|
+
className: "flex flex-1 items-start gap-2 min-h-[40px]",
|
|
4703
|
+
"data-filter-parent": true,
|
|
4545
4704
|
children: [currentMode !== "simple" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableSortList, {
|
|
4546
4705
|
table,
|
|
4547
4706
|
align: "start"
|
|
4548
|
-
}),
|
|
4707
|
+
}), filtersContent]
|
|
4549
4708
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4550
4709
|
className: "flex items-center gap-2",
|
|
4551
4710
|
children: [
|
|
@@ -5125,92 +5284,61 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
|
|
|
5125
5284
|
});
|
|
5126
5285
|
}
|
|
5127
5286
|
|
|
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
5287
|
//#endregion
|
|
5202
5288
|
//#region src/components/auto-crud/auto-crud-table.tsx
|
|
5203
5289
|
/**
|
|
5204
5290
|
* 从统一配置生成表格 overrides
|
|
5291
|
+
* 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
|
|
5205
5292
|
*/
|
|
5206
5293
|
function buildTableOverrides(fields, legacyOverrides) {
|
|
5207
5294
|
const result = { ...legacyOverrides };
|
|
5208
|
-
if (fields) for (const [key, config] of Object.entries(fields))
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5295
|
+
if (fields) for (const [key, config] of Object.entries(fields)) {
|
|
5296
|
+
const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
|
|
5297
|
+
let filterMeta;
|
|
5298
|
+
if (config.filter && typeof config.filter === "object") {
|
|
5299
|
+
if (config.filter.enabled !== false && config.filter.hidden !== true) {
|
|
5300
|
+
const { enabled: _, hidden: __,...filterProps } = config.filter;
|
|
5301
|
+
if (Object.keys(filterProps).length > 0) filterMeta = filterProps;
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
if (config.filter === false) result[key] = {
|
|
5305
|
+
...result[key],
|
|
5306
|
+
enableColumnFilter: false
|
|
5307
|
+
};
|
|
5308
|
+
else if (config.filter && typeof config.filter === "object") {
|
|
5309
|
+
if (config.filter.enabled === false || config.filter.hidden === true) result[key] = {
|
|
5310
|
+
...result[key],
|
|
5311
|
+
enableColumnFilter: false
|
|
5312
|
+
};
|
|
5313
|
+
}
|
|
5314
|
+
if (tableMeta || filterMeta) result[key] = {
|
|
5315
|
+
...result[key],
|
|
5316
|
+
meta: {
|
|
5317
|
+
...result[key]?.meta ?? {},
|
|
5318
|
+
...tableMeta ?? {},
|
|
5319
|
+
...filterMeta ?? {}
|
|
5320
|
+
}
|
|
5321
|
+
};
|
|
5322
|
+
if (config.label) result[key] = {
|
|
5323
|
+
...result[key],
|
|
5324
|
+
label: config.label
|
|
5325
|
+
};
|
|
5326
|
+
if (config.hidden) result[key] = {
|
|
5327
|
+
...result[key],
|
|
5328
|
+
hidden: true
|
|
5329
|
+
};
|
|
5330
|
+
if (config.table === false) result[key] = {
|
|
5331
|
+
...result[key],
|
|
5332
|
+
hidden: true
|
|
5333
|
+
};
|
|
5334
|
+
else if (config.table && typeof config.table === "object") {
|
|
5335
|
+
const { meta,...tableProps } = config.table;
|
|
5336
|
+
result[key] = {
|
|
5337
|
+
...result[key],
|
|
5338
|
+
...tableProps
|
|
5339
|
+
};
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5214
5342
|
return result;
|
|
5215
5343
|
}
|
|
5216
5344
|
/**
|
|
@@ -5223,12 +5351,24 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
|
|
|
5223
5351
|
...result[field],
|
|
5224
5352
|
"x-hidden": true
|
|
5225
5353
|
};
|
|
5226
|
-
if (fields) for (const [key, config] of Object.entries(fields))
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5354
|
+
if (fields) for (const [key, config] of Object.entries(fields)) {
|
|
5355
|
+
if (config.label) result[key] = {
|
|
5356
|
+
...result[key],
|
|
5357
|
+
title: config.label
|
|
5358
|
+
};
|
|
5359
|
+
if (config.hidden) result[key] = {
|
|
5360
|
+
...result[key],
|
|
5361
|
+
"x-hidden": true
|
|
5362
|
+
};
|
|
5363
|
+
if (config.form === false) result[key] = {
|
|
5364
|
+
...result[key],
|
|
5365
|
+
"x-hidden": true
|
|
5366
|
+
};
|
|
5367
|
+
else if (config.form && typeof config.form === "object") result[key] = {
|
|
5368
|
+
...result[key],
|
|
5369
|
+
...config.form
|
|
5370
|
+
};
|
|
5371
|
+
}
|
|
5232
5372
|
return result;
|
|
5233
5373
|
}
|
|
5234
5374
|
/**
|
|
@@ -5237,7 +5377,9 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
|
|
|
5237
5377
|
function buildHiddenColumns(fields, legacyHidden, denyFields) {
|
|
5238
5378
|
const hidden = new Set([...legacyHidden ?? [], ...denyFields ?? []]);
|
|
5239
5379
|
if (fields) {
|
|
5240
|
-
for (const [key, config] of Object.entries(fields)) if (config.hidden
|
|
5380
|
+
for (const [key, config] of Object.entries(fields)) if (config.hidden) hidden.add(key);
|
|
5381
|
+
else if (config.table === false) hidden.add(key);
|
|
5382
|
+
else if (config.table && typeof config.table === "object" && config.table.hidden) hidden.add(key);
|
|
5241
5383
|
}
|
|
5242
5384
|
return Array.from(hidden);
|
|
5243
5385
|
}
|
|
@@ -5364,8 +5506,8 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
5364
5506
|
const canImport = can.import && !!resource.handlers.import;
|
|
5365
5507
|
const canExport = can.export;
|
|
5366
5508
|
const [importOpen, setImportOpen] = react.useState(false);
|
|
5367
|
-
const [exportOpen, setExportOpen] = react.useState(false);
|
|
5368
5509
|
const [selectedCount, setSelectedCount] = react.useState(0);
|
|
5510
|
+
const [exporting, setExporting] = react.useState(false);
|
|
5369
5511
|
const getSelectedRowsRef = react.useRef(null);
|
|
5370
5512
|
const importColumns = react.useMemo(() => {
|
|
5371
5513
|
const shape = schema.shape;
|
|
@@ -5394,10 +5536,31 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
5394
5536
|
title,
|
|
5395
5537
|
denyFields
|
|
5396
5538
|
]);
|
|
5397
|
-
const
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5539
|
+
const handleExportClick = react.useCallback(async () => {
|
|
5540
|
+
const mode = selectedCount > 0 ? "selected" : "filtered";
|
|
5541
|
+
setExporting(true);
|
|
5542
|
+
try {
|
|
5543
|
+
await handleExport(mode);
|
|
5544
|
+
} finally {
|
|
5545
|
+
setExporting(false);
|
|
5546
|
+
}
|
|
5547
|
+
}, [selectedCount, handleExport]);
|
|
5548
|
+
const tableOverrides = react.useMemo(() => buildTableOverrides(fields, tableConfig?.overrides), [fields, tableConfig?.overrides]);
|
|
5549
|
+
const formOverrides = react.useMemo(() => buildFormOverrides(fields, formConfig?.overrides, denyFields), [
|
|
5550
|
+
fields,
|
|
5551
|
+
formConfig?.overrides,
|
|
5552
|
+
denyFields
|
|
5553
|
+
]);
|
|
5554
|
+
const hiddenColumns = react.useMemo(() => buildHiddenColumns(fields, tableConfig?.hidden, denyFields), [
|
|
5555
|
+
fields,
|
|
5556
|
+
tableConfig?.hidden,
|
|
5557
|
+
denyFields
|
|
5558
|
+
]);
|
|
5559
|
+
const batchFields = react.useMemo(() => buildBatchUpdateFields(schema, tableConfig?.batchFields, fields), [
|
|
5560
|
+
schema,
|
|
5561
|
+
tableConfig?.batchFields,
|
|
5562
|
+
fields
|
|
5563
|
+
]);
|
|
5401
5564
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5402
5565
|
className: "space-y-4",
|
|
5403
5566
|
children: [
|
|
@@ -5429,8 +5592,9 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
5429
5592
|
canExport && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
5430
5593
|
variant: "outline",
|
|
5431
5594
|
size: "sm",
|
|
5432
|
-
onClick:
|
|
5433
|
-
|
|
5595
|
+
onClick: handleExportClick,
|
|
5596
|
+
disabled: exporting || selectedCount === 0 && !resource.handlers.export,
|
|
5597
|
+
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
5598
|
}),
|
|
5435
5599
|
can.create && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
|
|
5436
5600
|
onClick: resource.handlers.openCreate,
|
|
@@ -5497,18 +5661,84 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
5497
5661
|
onOpenChange: setImportOpen,
|
|
5498
5662
|
onImport: resource.handlers.import,
|
|
5499
5663
|
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
5664
|
})
|
|
5508
5665
|
]
|
|
5509
5666
|
});
|
|
5510
5667
|
}
|
|
5511
5668
|
|
|
5669
|
+
//#endregion
|
|
5670
|
+
//#region src/components/auto-crud/export-dialog.tsx
|
|
5671
|
+
function ExportDialog({ open, onOpenChange, selectedCount, onExport, canExportFiltered = false }) {
|
|
5672
|
+
const [exporting, setExporting] = react.useState(null);
|
|
5673
|
+
const [done, setDone] = react.useState(false);
|
|
5674
|
+
const reset = react.useCallback(() => {
|
|
5675
|
+
setExporting(null);
|
|
5676
|
+
setDone(false);
|
|
5677
|
+
}, []);
|
|
5678
|
+
const handleOpenChange = react.useCallback((value) => {
|
|
5679
|
+
if (!value) reset();
|
|
5680
|
+
onOpenChange(value);
|
|
5681
|
+
}, [onOpenChange, reset]);
|
|
5682
|
+
const handleExport = react.useCallback(async (mode) => {
|
|
5683
|
+
setExporting(mode);
|
|
5684
|
+
try {
|
|
5685
|
+
await onExport(mode);
|
|
5686
|
+
setDone(true);
|
|
5687
|
+
setTimeout(() => handleOpenChange(false), 800);
|
|
5688
|
+
} catch {
|
|
5689
|
+
setExporting(null);
|
|
5690
|
+
}
|
|
5691
|
+
}, [onExport, handleOpenChange]);
|
|
5692
|
+
const hasSelected = selectedCount > 0;
|
|
5693
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Dialog, {
|
|
5694
|
+
open,
|
|
5695
|
+
onOpenChange: handleOpenChange,
|
|
5696
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogContent, {
|
|
5697
|
+
className: "sm:max-w-[400px]",
|
|
5698
|
+
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", {
|
|
5699
|
+
className: "flex flex-col items-center gap-3 py-6",
|
|
5700
|
+
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", {
|
|
5701
|
+
className: "text-sm text-muted-foreground",
|
|
5702
|
+
children: "导出成功"
|
|
5703
|
+
})]
|
|
5704
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5705
|
+
className: "grid gap-3 py-2",
|
|
5706
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
5707
|
+
variant: "outline",
|
|
5708
|
+
className: "h-auto justify-start gap-3 px-4 py-3",
|
|
5709
|
+
disabled: !hasSelected || exporting !== null,
|
|
5710
|
+
onClick: () => handleExport("selected"),
|
|
5711
|
+
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", {
|
|
5712
|
+
className: "flex flex-col items-start",
|
|
5713
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5714
|
+
className: "font-medium",
|
|
5715
|
+
children: ["导出选中", hasSelected ? ` (${selectedCount} 条)` : ""]
|
|
5716
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5717
|
+
className: "text-xs text-muted-foreground",
|
|
5718
|
+
children: hasSelected ? "导出当前勾选的数据行" : "请先在表格中勾选数据行"
|
|
5719
|
+
})]
|
|
5720
|
+
})]
|
|
5721
|
+
}), canExportFiltered && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
|
|
5722
|
+
variant: "outline",
|
|
5723
|
+
className: "h-auto justify-start gap-3 px-4 py-3",
|
|
5724
|
+
disabled: exporting !== null,
|
|
5725
|
+
onClick: () => handleExport("filtered"),
|
|
5726
|
+
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", {
|
|
5727
|
+
className: "flex flex-col items-start",
|
|
5728
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5729
|
+
className: "font-medium",
|
|
5730
|
+
children: "导出筛选结果"
|
|
5731
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5732
|
+
className: "text-xs text-muted-foreground",
|
|
5733
|
+
children: "导出当前筛选条件下的所有数据"
|
|
5734
|
+
})]
|
|
5735
|
+
})]
|
|
5736
|
+
})]
|
|
5737
|
+
})]
|
|
5738
|
+
})
|
|
5739
|
+
});
|
|
5740
|
+
}
|
|
5741
|
+
|
|
5512
5742
|
//#endregion
|
|
5513
5743
|
//#region src/components/data-table/data-table-advanced-toolbar.tsx
|
|
5514
5744
|
function DataTableAdvancedToolbar({ table, children, className,...props }) {
|
|
@@ -6062,6 +6292,8 @@ function DataTableToolbarFilter({ column }) {
|
|
|
6062
6292
|
const columnMeta = column.columnDef.meta;
|
|
6063
6293
|
return react.useCallback(() => {
|
|
6064
6294
|
if (!columnMeta?.variant) return null;
|
|
6295
|
+
const modes = columnMeta.modes;
|
|
6296
|
+
if (modes && !modes.includes("advanced")) return null;
|
|
6065
6297
|
switch (columnMeta.variant) {
|
|
6066
6298
|
case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Input, {
|
|
6067
6299
|
placeholder: columnMeta.placeholder ?? columnMeta.label,
|
|
@@ -6183,6 +6415,10 @@ function modalReducer(state, action) {
|
|
|
6183
6415
|
function useAutoCrudResource({ router, schema, query: queryTransform, options = {} }) {
|
|
6184
6416
|
const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher } = options;
|
|
6185
6417
|
const toast = (0, react.useMemo)(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
|
|
6418
|
+
const hooksRef = (0, react.useRef)(hooks);
|
|
6419
|
+
(0, react.useEffect)(() => {
|
|
6420
|
+
hooksRef.current = hooks;
|
|
6421
|
+
}, [hooks]);
|
|
6186
6422
|
const columns = (0, react.useMemo)(() => createTableSchema(schema), [schema]);
|
|
6187
6423
|
const [urlParams] = useQueryStates({
|
|
6188
6424
|
page: parseAsInteger.withDefault(1),
|
|
@@ -6246,15 +6482,16 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
6246
6482
|
payload: variant
|
|
6247
6483
|
}), []);
|
|
6248
6484
|
const submitCreate = (0, react.useCallback)(async (values) => {
|
|
6485
|
+
const currentHooks = hooksRef.current;
|
|
6249
6486
|
try {
|
|
6250
|
-
if (
|
|
6251
|
-
const result = await
|
|
6487
|
+
if (currentHooks?.beforeCreate) {
|
|
6488
|
+
const result = await currentHooks.beforeCreate(values);
|
|
6252
6489
|
if (typeof result === "boolean") {
|
|
6253
6490
|
if (result) {
|
|
6254
6491
|
toast.success("创建成功");
|
|
6255
6492
|
closeModals();
|
|
6256
6493
|
listQuery.refetch();
|
|
6257
|
-
|
|
6494
|
+
currentHooks?.onSuccess?.("create", values);
|
|
6258
6495
|
}
|
|
6259
6496
|
return;
|
|
6260
6497
|
}
|
|
@@ -6265,36 +6502,36 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
6265
6502
|
toast.success("创建成功");
|
|
6266
6503
|
closeModals();
|
|
6267
6504
|
listQuery.refetch();
|
|
6268
|
-
|
|
6505
|
+
currentHooks?.onSuccess?.("create", data);
|
|
6269
6506
|
},
|
|
6270
6507
|
onError: (error) => {
|
|
6271
6508
|
const err = error;
|
|
6272
6509
|
toast.error(`创建失败: ${err.message}`);
|
|
6273
|
-
|
|
6510
|
+
currentHooks?.onError?.("create", err);
|
|
6274
6511
|
}
|
|
6275
6512
|
});
|
|
6276
6513
|
} catch (error) {
|
|
6277
6514
|
const err = error;
|
|
6278
6515
|
toast.error(`创建失败: ${err.message}`);
|
|
6279
|
-
|
|
6516
|
+
currentHooks?.onError?.("create", err);
|
|
6280
6517
|
}
|
|
6281
6518
|
}, [
|
|
6282
6519
|
createMutation,
|
|
6283
6520
|
closeModals,
|
|
6284
|
-
listQuery
|
|
6285
|
-
hooks
|
|
6521
|
+
listQuery
|
|
6286
6522
|
]);
|
|
6287
6523
|
const submitUpdate = (0, react.useCallback)(async (values) => {
|
|
6524
|
+
const currentHooks = hooksRef.current;
|
|
6288
6525
|
if (!modal.selected) return;
|
|
6289
6526
|
try {
|
|
6290
|
-
if (
|
|
6291
|
-
const result = await
|
|
6527
|
+
if (currentHooks?.beforeUpdate) {
|
|
6528
|
+
const result = await currentHooks.beforeUpdate(values, modal.selected);
|
|
6292
6529
|
if (typeof result === "boolean") {
|
|
6293
6530
|
if (result) {
|
|
6294
6531
|
toast.success("更新成功");
|
|
6295
6532
|
closeModals();
|
|
6296
6533
|
listQuery.refetch();
|
|
6297
|
-
|
|
6534
|
+
currentHooks?.onSuccess?.("update", values);
|
|
6298
6535
|
}
|
|
6299
6536
|
return;
|
|
6300
6537
|
}
|
|
@@ -6309,38 +6546,38 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
6309
6546
|
toast.success("更新成功");
|
|
6310
6547
|
closeModals();
|
|
6311
6548
|
listQuery.refetch();
|
|
6312
|
-
|
|
6549
|
+
currentHooks?.onSuccess?.("update", data);
|
|
6313
6550
|
},
|
|
6314
6551
|
onError: (error) => {
|
|
6315
6552
|
const err = error;
|
|
6316
6553
|
toast.error(`更新失败: ${err.message}`);
|
|
6317
|
-
|
|
6554
|
+
currentHooks?.onError?.("update", err);
|
|
6318
6555
|
}
|
|
6319
6556
|
});
|
|
6320
6557
|
} catch (error) {
|
|
6321
6558
|
const err = error;
|
|
6322
6559
|
toast.error(`更新失败: ${err.message}`);
|
|
6323
|
-
|
|
6560
|
+
currentHooks?.onError?.("update", err);
|
|
6324
6561
|
}
|
|
6325
6562
|
}, [
|
|
6326
6563
|
modal.selected,
|
|
6327
6564
|
idKey,
|
|
6328
6565
|
updateMutation,
|
|
6329
6566
|
closeModals,
|
|
6330
|
-
listQuery
|
|
6331
|
-
hooks
|
|
6567
|
+
listQuery
|
|
6332
6568
|
]);
|
|
6333
6569
|
const confirmDelete = (0, react.useCallback)(async () => {
|
|
6570
|
+
const currentHooks = hooksRef.current;
|
|
6334
6571
|
if (!modal.selected) return;
|
|
6335
6572
|
try {
|
|
6336
|
-
if (
|
|
6337
|
-
const result = await
|
|
6573
|
+
if (currentHooks?.beforeDelete) {
|
|
6574
|
+
const result = await currentHooks.beforeDelete(modal.selected);
|
|
6338
6575
|
if (typeof result === "boolean") {
|
|
6339
6576
|
if (result) {
|
|
6340
6577
|
toast.success("删除成功");
|
|
6341
6578
|
closeModals();
|
|
6342
6579
|
listQuery.refetch();
|
|
6343
|
-
|
|
6580
|
+
currentHooks?.onSuccess?.("delete", modal.selected);
|
|
6344
6581
|
}
|
|
6345
6582
|
return;
|
|
6346
6583
|
}
|
|
@@ -6351,28 +6588,28 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
6351
6588
|
toast.success("删除成功");
|
|
6352
6589
|
closeModals();
|
|
6353
6590
|
listQuery.refetch();
|
|
6354
|
-
|
|
6591
|
+
currentHooks?.onSuccess?.("delete", data);
|
|
6355
6592
|
},
|
|
6356
6593
|
onError: (error) => {
|
|
6357
6594
|
const err = error;
|
|
6358
6595
|
toast.error(`删除失败: ${err.message}`);
|
|
6359
|
-
|
|
6596
|
+
currentHooks?.onError?.("delete", err);
|
|
6360
6597
|
}
|
|
6361
6598
|
});
|
|
6362
6599
|
} catch (error) {
|
|
6363
6600
|
const err = error;
|
|
6364
6601
|
toast.error(`删除失败: ${err.message}`);
|
|
6365
|
-
|
|
6602
|
+
currentHooks?.onError?.("delete", err);
|
|
6366
6603
|
}
|
|
6367
6604
|
}, [
|
|
6368
6605
|
modal.selected,
|
|
6369
6606
|
idKey,
|
|
6370
6607
|
deleteMutation,
|
|
6371
6608
|
closeModals,
|
|
6372
|
-
listQuery
|
|
6373
|
-
hooks
|
|
6609
|
+
listQuery
|
|
6374
6610
|
]);
|
|
6375
6611
|
const deleteMany = (0, react.useCallback)((rows) => {
|
|
6612
|
+
const currentHooks = hooksRef.current;
|
|
6376
6613
|
if (!deleteManyMutation) {
|
|
6377
6614
|
toast.error("批量删除功能未启用");
|
|
6378
6615
|
return;
|
|
@@ -6382,21 +6619,21 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
6382
6619
|
onSuccess: (data) => {
|
|
6383
6620
|
toast.success(`成功删除 ${ids.length} 条记录`);
|
|
6384
6621
|
listQuery.refetch();
|
|
6385
|
-
|
|
6622
|
+
currentHooks?.onSuccess?.("delete", data);
|
|
6386
6623
|
},
|
|
6387
6624
|
onError: (error) => {
|
|
6388
6625
|
const err = error;
|
|
6389
6626
|
toast.error(`批量删除失败: ${err.message}`);
|
|
6390
|
-
|
|
6627
|
+
currentHooks?.onError?.("delete", err);
|
|
6391
6628
|
}
|
|
6392
6629
|
});
|
|
6393
6630
|
}, [
|
|
6394
6631
|
deleteManyMutation,
|
|
6395
6632
|
idKey,
|
|
6396
|
-
listQuery
|
|
6397
|
-
hooks
|
|
6633
|
+
listQuery
|
|
6398
6634
|
]);
|
|
6399
6635
|
const updateMany = (0, react.useCallback)((rows, data) => {
|
|
6636
|
+
const currentHooks = hooksRef.current;
|
|
6400
6637
|
if (!updateManyMutation) {
|
|
6401
6638
|
toast.error("批量更新功能未启用");
|
|
6402
6639
|
return;
|
|
@@ -6409,19 +6646,18 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
6409
6646
|
onSuccess: (result) => {
|
|
6410
6647
|
toast.success(`成功更新 ${ids.length} 条记录`);
|
|
6411
6648
|
listQuery.refetch();
|
|
6412
|
-
|
|
6649
|
+
currentHooks?.onSuccess?.("update", result);
|
|
6413
6650
|
},
|
|
6414
6651
|
onError: (error) => {
|
|
6415
6652
|
const err = error;
|
|
6416
6653
|
toast.error(`批量更新失败: ${err.message}`);
|
|
6417
|
-
|
|
6654
|
+
currentHooks?.onError?.("update", err);
|
|
6418
6655
|
}
|
|
6419
6656
|
});
|
|
6420
6657
|
}, [
|
|
6421
6658
|
updateManyMutation,
|
|
6422
6659
|
idKey,
|
|
6423
|
-
listQuery
|
|
6424
|
-
hooks
|
|
6660
|
+
listQuery
|
|
6425
6661
|
]);
|
|
6426
6662
|
const copyRow = (0, react.useCallback)((row) => {
|
|
6427
6663
|
dispatch({
|
|
@@ -6742,12 +6978,12 @@ function createMemoryDataSource(initialData = []) {
|
|
|
6742
6978
|
});
|
|
6743
6979
|
});
|
|
6744
6980
|
if (params.sort && params.sort.length > 0) {
|
|
6745
|
-
const
|
|
6981
|
+
const sortItem = params.sort[0];
|
|
6746
6982
|
filtered.sort((a, b) => {
|
|
6747
|
-
const aVal = a[id];
|
|
6748
|
-
const bVal = b[id];
|
|
6983
|
+
const aVal = a[sortItem.id];
|
|
6984
|
+
const bVal = b[sortItem.id];
|
|
6749
6985
|
const result = aVal > bVal ? 1 : aVal < bVal ? -1 : 0;
|
|
6750
|
-
return desc ? -result : result;
|
|
6986
|
+
return sortItem.desc ? -result : result;
|
|
6751
6987
|
});
|
|
6752
6988
|
}
|
|
6753
6989
|
const start = (params.page - 1) * params.perPage;
|
|
@@ -6775,11 +7011,12 @@ function createMemoryDataSource(initialData = []) {
|
|
|
6775
7011
|
async update(id, updateData) {
|
|
6776
7012
|
const index = data.findIndex((item) => item.id === id);
|
|
6777
7013
|
if (index === -1) throw new Error(`Item with id ${id} not found`);
|
|
6778
|
-
|
|
7014
|
+
const updated = {
|
|
6779
7015
|
...data[index],
|
|
6780
7016
|
...updateData
|
|
6781
7017
|
};
|
|
6782
|
-
|
|
7018
|
+
data[index] = updated;
|
|
7019
|
+
return updated;
|
|
6783
7020
|
},
|
|
6784
7021
|
async delete(id) {
|
|
6785
7022
|
const index = data.findIndex((item) => item.id === id);
|