@wordrhyme/auto-crud 0.5.0 → 0.10.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 CHANGED
@@ -2684,7 +2684,7 @@ function DataTableViewOptions({ table, disabled,...props }) {
2684
2684
  role: "combobox",
2685
2685
  variant: "outline",
2686
2686
  size: "sm",
2687
- className: "ml-auto hidden h-8 font-normal lg:flex",
2687
+ className: "ml-auto flex h-8 font-normal",
2688
2688
  disabled,
2689
2689
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Settings2, { className: "text-muted-foreground" }), "View"]
2690
2690
  })
@@ -3515,454 +3515,146 @@ function ActionBarSeparator(props) {
3515
3515
  }
3516
3516
 
3517
3517
  //#endregion
3518
- //#region src/lib/export.ts
3519
- function exportTableToCSV(table, opts = {}) {
3520
- const { filename = "table", excludeColumns = [], onlySelected = false } = opts;
3521
- const headers = table.getAllLeafColumns().map((column) => column.id).filter((id) => !excludeColumns.includes(id));
3522
- const csvContent = [headers.join(","), ...(onlySelected ? table.getFilteredSelectedRowModel().rows : table.getRowModel().rows).map((row) => headers.map((header) => {
3523
- const cellValue = row.getValue(header);
3524
- return typeof cellValue === "string" ? `"${cellValue.replace(/"/g, "\"\"")}"` : cellValue;
3525
- }).join(","))].join("\n");
3526
- const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
3527
- const url = URL.createObjectURL(blob);
3528
- const link = document.createElement("a");
3529
- link.setAttribute("href", url);
3530
- link.setAttribute("download", `${filename}.csv`);
3531
- link.style.visibility = "hidden";
3532
- document.body.appendChild(link);
3533
- link.click();
3534
- document.body.removeChild(link);
3518
+ //#region src/components/data-table/data-table-column-header.tsx
3519
+ function DataTableColumnHeader({ column, label, className,...props }) {
3520
+ if (!column.getCanSort() && !column.getCanHide()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3521
+ className: cn(className),
3522
+ children: label
3523
+ });
3524
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuTrigger, {
3525
+ className: cn("-ml-1.5 flex h-8 items-center gap-1.5 rounded-md px-2 py-1.5 hover:bg-accent focus:outline-none focus:ring-1 focus:ring-ring data-[state=open]:bg-accent [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground", className),
3526
+ ...props,
3527
+ children: [label, column.getCanSort() && (column.getIsSorted() === "desc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {}) : column.getIsSorted() === "asc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronUp, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronsUpDown, {}))]
3528
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuContent, {
3529
+ align: "start",
3530
+ className: "w-28",
3531
+ children: [column.getCanSort() && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3532
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuCheckboxItem, {
3533
+ className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3534
+ checked: column.getIsSorted() === "asc",
3535
+ onClick: () => column.toggleSorting(false),
3536
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronUp, {}), "Asc"]
3537
+ }),
3538
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuCheckboxItem, {
3539
+ className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3540
+ checked: column.getIsSorted() === "desc",
3541
+ onClick: () => column.toggleSorting(true),
3542
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {}), "Desc"]
3543
+ }),
3544
+ column.getIsSorted() && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuItem, {
3545
+ className: "pl-2 [&_svg]:text-muted-foreground",
3546
+ onClick: () => column.clearSorting(),
3547
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {}), "Reset"]
3548
+ })
3549
+ ] }), column.getCanHide() && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuCheckboxItem, {
3550
+ className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3551
+ checked: !column.getIsVisible(),
3552
+ onClick: () => column.toggleVisibility(false),
3553
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.EyeOff, {}), "Hide"]
3554
+ })]
3555
+ })] });
3535
3556
  }
3536
3557
 
3537
3558
  //#endregion
3538
- //#region src/components/auto-crud/auto-table-action-bar.tsx
3539
- function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUpdateFields, enableExport = true, enableDelete = true, extraActions }) {
3540
- const rows = table.getFilteredSelectedRowModel().rows;
3541
- const onOpenChange = react.useCallback((open) => {
3542
- if (!open) table.toggleAllRowsSelected(false);
3543
- }, [table]);
3544
- const onExport = react.useCallback(() => {
3545
- exportTableToCSV(table, {
3546
- excludeColumns: ["select", "actions"],
3547
- onlySelected: true
3548
- });
3549
- }, [table]);
3550
- const onDelete = react.useCallback(() => {
3551
- if (onDeleteSelected) onDeleteSelected(rows.map((row) => row.original));
3552
- }, [rows, onDeleteSelected]);
3553
- const handleBatchUpdate = react.useCallback((field, value) => {
3554
- if (onUpdateSelected) {
3555
- onUpdateSelected(rows.map((row) => row.original), { [field]: value });
3556
- table.toggleAllRowsSelected(false);
3557
- }
3558
- }, [
3559
- rows,
3560
- onUpdateSelected,
3561
- table
3562
- ]);
3563
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBar, {
3564
- open: rows.length > 0,
3565
- onOpenChange,
3566
- children: [
3567
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBarSelection, { children: [
3568
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3569
- className: "font-medium",
3570
- children: rows.length
3571
- }),
3572
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "selected" }),
3573
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionBarSeparator, {}),
3574
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionBarClose, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {}) })
3575
- ] }),
3576
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionBarSeparator, {}),
3577
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBarGroup, { children: [
3578
- batchUpdateFields?.map((fieldConfig) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuTrigger, {
3579
- asChild: true,
3580
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
3581
- variant: "ghost",
3582
- size: "sm",
3583
- className: "h-7 gap-1 px-2",
3584
- children: [fieldConfig.label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "size-3.5" })]
3585
- })
3586
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuContent, {
3587
- align: "start",
3588
- children: fieldConfig.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
3589
- onClick: () => handleBatchUpdate(fieldConfig.field, option.value),
3590
- children: option.label
3591
- }, option.value))
3592
- })] }, fieldConfig.field)),
3593
- extraActions,
3594
- enableExport && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBarItem, {
3595
- onClick: onExport,
3596
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, {}), "Export"]
3597
- }),
3598
- enableDelete && onDeleteSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBarItem, {
3599
- variant: "destructive",
3600
- onClick: onDelete,
3601
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Trash2, {}), "Delete"]
3602
- })
3603
- ] })
3604
- ]
3605
- });
3559
+ //#region src/lib/humanize.ts
3560
+ /**
3561
+ * 将字符串转为人类可读格式
3562
+ *
3563
+ * @example
3564
+ * humanize("createdAt") // "Created At"
3565
+ * humanize("user_name") // "User Name"
3566
+ * humanize("firstName") // "First Name"
3567
+ */
3568
+ function humanize(str) {
3569
+ return str.replace(/([A-Z])/g, " $1").replace(/[_-]/g, " ").replace(/^./, (s) => s.toUpperCase()).trim();
3606
3570
  }
3607
3571
 
3608
3572
  //#endregion
3609
- //#region src/lib/parsers.ts
3573
+ //#region src/lib/schema-bridge/types.ts
3610
3574
  /**
3611
- * 创建自定义 Parser(替代 nuqs 的 createParser)
3575
+ * 字段类型到 Filter Variant 映射
3576
+ * - enum 默认 multiSelect(通常需要多选)
3577
+ * - date 默认 dateRange(通常需要范围筛选)
3578
+ * - number 默认 range(通常需要范围筛选)
3612
3579
  */
3613
- function createParser(config) {
3614
- return {
3615
- parse: config.parse,
3616
- serialize: (value) => value === null ? "" : config.serialize(value),
3617
- withDefault: (defaultValue) => ({
3618
- parse: (value) => {
3619
- const parsed = config.parse(value);
3620
- return parsed === null ? defaultValue : parsed;
3621
- },
3622
- serialize: config.serialize,
3623
- defaultValue
3624
- })
3580
+ const fieldTypeToFilterVariant = {
3581
+ string: "text",
3582
+ number: "range",
3583
+ boolean: "boolean",
3584
+ date: "dateRange",
3585
+ enum: "multiSelect",
3586
+ array: "multiSelect",
3587
+ object: "text"
3588
+ };
3589
+ /**
3590
+ * 字段类型到 Formily 组件映射
3591
+ */
3592
+ const fieldTypeToFormilyComponent = {
3593
+ string: "Input",
3594
+ number: "NumberInput",
3595
+ boolean: "Switch",
3596
+ date: "DatePicker",
3597
+ enum: "Select",
3598
+ array: "ArrayCards",
3599
+ object: "ObjectField"
3600
+ };
3601
+ /**
3602
+ * 字段名模式到 Formily 组件映射
3603
+ * 优先级高于 fieldTypeToFormilyComponent
3604
+ * 使用正则表达式匹配字段名
3605
+ */
3606
+ const fieldNamePatternToComponent = [
3607
+ {
3608
+ pattern: /^(description|content|body|notes|summary|bio|about|comment|message|text)$/i,
3609
+ component: "Textarea"
3610
+ },
3611
+ {
3612
+ pattern: /(description|content|body|notes|summary|bio|about|comment|message)$/i,
3613
+ component: "Textarea"
3614
+ },
3615
+ {
3616
+ pattern: /^color$/i,
3617
+ component: "ColorSelect"
3618
+ },
3619
+ {
3620
+ pattern: /color$/i,
3621
+ component: "ColorSelect"
3622
+ },
3623
+ {
3624
+ pattern: /^icon$/i,
3625
+ component: "IconPicker"
3626
+ },
3627
+ {
3628
+ pattern: /icon$/i,
3629
+ component: "IconPicker"
3630
+ }
3631
+ ];
3632
+ /**
3633
+ * 根据字段名推断 Formily 组件
3634
+ */
3635
+ function inferComponentByFieldName(fieldName) {
3636
+ for (const rule of fieldNamePatternToComponent) if (rule.pattern.test(fieldName)) return {
3637
+ component: rule.component,
3638
+ props: rule.props
3625
3639
  };
3640
+ return null;
3626
3641
  }
3627
- const sortingItemSchema = zod.z.object({
3628
- id: zod.z.string(),
3629
- desc: zod.z.boolean()
3630
- });
3631
- const getSortingStateParser = (columnIds) => {
3632
- const validKeys = columnIds ? columnIds instanceof Set ? columnIds : new Set(columnIds) : null;
3633
- return createParser({
3634
- parse: (value) => {
3635
- try {
3636
- const parsed = JSON.parse(value);
3637
- const result = zod.z.array(sortingItemSchema).safeParse(parsed);
3638
- if (!result.success) return null;
3639
- if (validKeys && result.data.some((item) => !validKeys.has(item.id))) return null;
3640
- return result.data;
3641
- } catch {
3642
- return null;
3643
- }
3644
- },
3645
- serialize: (value) => JSON.stringify(value)
3646
- });
3647
- };
3648
- const filterItemSchema = zod.z.object({
3649
- id: zod.z.string(),
3650
- value: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]),
3651
- variant: zod.z.enum(dataTableConfig.filterVariants),
3652
- operator: zod.z.enum(dataTableConfig.operators),
3653
- filterId: zod.z.string()
3654
- });
3655
-
3656
- //#endregion
3657
- //#region src/hooks/use-data-table.ts
3658
- const PAGE_KEY = "page";
3659
- const PER_PAGE_KEY = "perPage";
3660
- const SORT_KEY = "sort";
3661
- const FILTERS_KEY = "filters";
3662
- const JOIN_OPERATOR_KEY = "joinOperator";
3663
- const ARRAY_SEPARATOR = ",";
3664
- const DEBOUNCE_MS = 300;
3665
- const THROTTLE_MS = 50;
3666
- function useDataTable(props) {
3667
- const { columns, pageCount = -1, initialState, queryKeys, history = "replace", debounceMs = DEBOUNCE_MS, throttleMs = THROTTLE_MS, clearOnDefault = false, enableAdvancedFilter = false, scroll = false, shallow = true, startTransition,...tableProps } = props;
3668
- const pageKey = queryKeys?.page ?? PAGE_KEY;
3669
- const perPageKey = queryKeys?.perPage ?? PER_PAGE_KEY;
3670
- const sortKey = queryKeys?.sort ?? SORT_KEY;
3671
- const filtersKey = queryKeys?.filters ?? FILTERS_KEY;
3672
- const joinOperatorKey = queryKeys?.joinOperator ?? JOIN_OPERATOR_KEY;
3673
- const queryStateOptions = react.useMemo(() => ({
3674
- history,
3675
- scroll,
3676
- shallow,
3677
- throttleMs,
3678
- debounceMs,
3679
- clearOnDefault,
3680
- startTransition
3681
- }), [
3682
- history,
3683
- scroll,
3684
- shallow,
3685
- throttleMs,
3686
- debounceMs,
3687
- clearOnDefault,
3688
- startTransition
3689
- ]);
3690
- const [rowSelection, setRowSelection] = react.useState(initialState?.rowSelection ?? {});
3691
- const [columnVisibility, setColumnVisibility] = react.useState(initialState?.columnVisibility ?? {});
3692
- const [page, setPage] = useQueryState(pageKey, parseAsInteger.withDefault(1));
3693
- const [perPage, setPerPage] = useQueryState(perPageKey, parseAsInteger.withDefault(initialState?.pagination?.pageSize ?? 10));
3694
- const pagination = react.useMemo(() => {
3695
- return {
3696
- pageIndex: page - 1,
3697
- pageSize: perPage
3698
- };
3699
- }, [page, perPage]);
3700
- const onPaginationChange = react.useCallback((updaterOrValue) => {
3701
- if (typeof updaterOrValue === "function") {
3702
- const newPagination = updaterOrValue(pagination);
3703
- setPage(newPagination.pageIndex + 1);
3704
- setPerPage(newPagination.pageSize);
3705
- } else {
3706
- setPage(updaterOrValue.pageIndex + 1);
3707
- setPerPage(updaterOrValue.pageSize);
3708
- }
3709
- }, [
3710
- pagination,
3711
- setPage,
3712
- setPerPage
3713
- ]);
3714
- const [sorting, setSorting] = useQueryState(sortKey, getSortingStateParser(react.useMemo(() => {
3715
- return new Set(columns.map((column) => column.id).filter(Boolean));
3716
- }, [columns])).withDefault(initialState?.sorting ?? []));
3717
- const onSortingChange = react.useCallback((updaterOrValue) => {
3718
- if (typeof updaterOrValue === "function") setSorting(updaterOrValue(sorting));
3719
- else setSorting(updaterOrValue);
3720
- }, [sorting, setSorting]);
3721
- const filterableColumns = react.useMemo(() => {
3722
- if (enableAdvancedFilter) return [];
3723
- return columns.filter((column) => column.enableColumnFilter);
3724
- }, [columns, enableAdvancedFilter]);
3725
- const [filterValues, setFilterValues] = useQueryStates(react.useMemo(() => {
3726
- if (enableAdvancedFilter) return {};
3727
- return filterableColumns.reduce((acc, column) => {
3728
- if (column.meta?.options) acc[column.id ?? ""] = parseAsArrayOf(parseAsString, ARRAY_SEPARATOR).withDefault([]);
3729
- else acc[column.id ?? ""] = parseAsString.withDefault("");
3730
- return acc;
3731
- }, {});
3732
- }, [filterableColumns, enableAdvancedFilter]));
3733
- const debouncedSetFilterValues = useDebouncedCallback((values) => {
3734
- setPage(1);
3735
- setFilterValues(values);
3736
- }, debounceMs);
3737
- const initialColumnFilters = react.useMemo(() => {
3738
- if (enableAdvancedFilter) return [];
3739
- return Object.entries(filterValues).reduce((filters, [key, value]) => {
3740
- if (value !== null) {
3741
- const processedValue = Array.isArray(value) ? value : typeof value === "string" && /[^a-zA-Z0-9]/.test(value) ? value.split(/[^a-zA-Z0-9]+/).filter(Boolean) : [value];
3742
- filters.push({
3743
- id: key,
3744
- value: processedValue
3745
- });
3746
- }
3747
- return filters;
3748
- }, []);
3749
- }, [filterValues, enableAdvancedFilter]);
3750
- const [columnFilters, setColumnFilters] = react.useState(initialColumnFilters);
3751
- const onColumnFiltersChange = react.useCallback((updaterOrValue) => {
3752
- if (enableAdvancedFilter) return;
3753
- setColumnFilters((prev) => {
3754
- const next = typeof updaterOrValue === "function" ? updaterOrValue(prev) : updaterOrValue;
3755
- const filterUpdates = next.reduce((acc, filter) => {
3756
- if (filterableColumns.find((column) => column.id === filter.id)) acc[filter.id] = filter.value;
3757
- return acc;
3758
- }, {});
3759
- for (const prevFilter of prev) if (!next.some((filter) => filter.id === prevFilter.id)) filterUpdates[prevFilter.id] = null;
3760
- debouncedSetFilterValues(filterUpdates);
3761
- return next;
3762
- });
3763
- }, [
3764
- debouncedSetFilterValues,
3765
- filterableColumns,
3766
- enableAdvancedFilter
3767
- ]);
3768
- const table = (0, __tanstack_react_table.useReactTable)({
3769
- ...tableProps,
3770
- columns,
3771
- initialState,
3772
- pageCount,
3773
- state: {
3774
- pagination,
3775
- sorting,
3776
- columnVisibility,
3777
- rowSelection,
3778
- columnFilters
3779
- },
3780
- defaultColumn: {
3781
- ...tableProps.defaultColumn,
3782
- enableColumnFilter: false
3783
- },
3784
- enableRowSelection: true,
3785
- onRowSelectionChange: setRowSelection,
3786
- onPaginationChange,
3787
- onSortingChange,
3788
- onColumnFiltersChange,
3789
- onColumnVisibilityChange: setColumnVisibility,
3790
- getCoreRowModel: (0, __tanstack_react_table.getCoreRowModel)(),
3791
- getFilteredRowModel: (0, __tanstack_react_table.getFilteredRowModel)(),
3792
- getPaginationRowModel: (0, __tanstack_react_table.getPaginationRowModel)(),
3793
- getSortedRowModel: (0, __tanstack_react_table.getSortedRowModel)(),
3794
- getFacetedRowModel: (0, __tanstack_react_table.getFacetedRowModel)(),
3795
- getFacetedUniqueValues: (0, __tanstack_react_table.getFacetedUniqueValues)(),
3796
- getFacetedMinMaxValues: (0, __tanstack_react_table.getFacetedMinMaxValues)(),
3797
- manualPagination: true,
3798
- manualSorting: true,
3799
- manualFiltering: true,
3800
- meta: {
3801
- ...tableProps.meta,
3802
- queryKeys: {
3803
- page: pageKey,
3804
- perPage: perPageKey,
3805
- sort: sortKey,
3806
- filters: filtersKey,
3807
- joinOperator: joinOperatorKey
3808
- },
3809
- queryStateOptions
3810
- }
3811
- });
3812
- return react.useMemo(() => ({
3813
- table,
3814
- shallow,
3815
- debounceMs,
3816
- throttleMs
3817
- }), [
3818
- table,
3819
- shallow,
3820
- debounceMs,
3821
- throttleMs
3822
- ]);
3823
- }
3824
-
3825
- //#endregion
3826
- //#region src/components/data-table/data-table-column-header.tsx
3827
- function DataTableColumnHeader({ column, label, className,...props }) {
3828
- if (!column.getCanSort() && !column.getCanHide()) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3829
- className: cn(className),
3830
- children: label
3831
- });
3832
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuTrigger, {
3833
- className: cn("-ml-1.5 flex h-8 items-center gap-1.5 rounded-md px-2 py-1.5 hover:bg-accent focus:outline-none focus:ring-1 focus:ring-ring data-[state=open]:bg-accent [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground", className),
3834
- ...props,
3835
- children: [label, column.getCanSort() && (column.getIsSorted() === "desc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {}) : column.getIsSorted() === "asc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronUp, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronsUpDown, {}))]
3836
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuContent, {
3837
- align: "start",
3838
- className: "w-28",
3839
- children: [column.getCanSort() && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3840
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuCheckboxItem, {
3841
- className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3842
- checked: column.getIsSorted() === "asc",
3843
- onClick: () => column.toggleSorting(false),
3844
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronUp, {}), "Asc"]
3845
- }),
3846
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuCheckboxItem, {
3847
- className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3848
- checked: column.getIsSorted() === "desc",
3849
- onClick: () => column.toggleSorting(true),
3850
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {}), "Desc"]
3851
- }),
3852
- column.getIsSorted() && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuItem, {
3853
- className: "pl-2 [&_svg]:text-muted-foreground",
3854
- onClick: () => column.clearSorting(),
3855
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {}), "Reset"]
3856
- })
3857
- ] }), column.getCanHide() && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuCheckboxItem, {
3858
- className: "relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground",
3859
- checked: !column.getIsVisible(),
3860
- onClick: () => column.toggleVisibility(false),
3861
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.EyeOff, {}), "Hide"]
3862
- })]
3863
- })] });
3864
- }
3865
-
3866
- //#endregion
3867
- //#region src/lib/humanize.ts
3868
- /**
3869
- * 将字符串转为人类可读格式
3870
- *
3871
- * @example
3872
- * humanize("createdAt") // "Created At"
3873
- * humanize("user_name") // "User Name"
3874
- * humanize("firstName") // "First Name"
3875
- */
3876
- function humanize(str) {
3877
- return str.replace(/([A-Z])/g, " $1").replace(/[_-]/g, " ").replace(/^./, (s) => s.toUpperCase()).trim();
3878
- }
3879
-
3880
- //#endregion
3881
- //#region src/lib/schema-bridge/types.ts
3882
- /**
3883
- * 字段类型到 Filter Variant 映射
3884
- * - enum 默认 multiSelect(通常需要多选)
3885
- * - date 默认 dateRange(通常需要范围筛选)
3886
- * - number 默认 range(通常需要范围筛选)
3887
- */
3888
- const fieldTypeToFilterVariant = {
3889
- string: "text",
3890
- number: "range",
3891
- boolean: "boolean",
3892
- date: "dateRange",
3893
- enum: "multiSelect",
3894
- array: "multiSelect",
3895
- object: "text"
3896
- };
3897
- /**
3898
- * 字段类型到 Formily 组件映射
3899
- */
3900
- const fieldTypeToFormilyComponent = {
3901
- string: "Input",
3902
- number: "NumberInput",
3903
- boolean: "Switch",
3904
- date: "DatePicker",
3905
- enum: "Select",
3906
- array: "ArrayCards",
3907
- object: "ObjectField"
3908
- };
3909
3642
  /**
3910
- * 字段名模式到 Formily 组件映射
3911
- * 优先级高于 fieldTypeToFormilyComponent
3912
- * 使用正则表达式匹配字段名
3643
+ * 字段名模式到 Filter Variant 映射
3644
+ * 优先级高于 fieldTypeToFilterVariant
3913
3645
  */
3914
- const fieldNamePatternToComponent = [
3646
+ const fieldNamePatternToFilterVariant = [
3915
3647
  {
3916
3648
  pattern: /^(description|content|body|notes|summary|bio|about|comment|message|text)$/i,
3917
- component: "Textarea"
3649
+ variant: "text"
3918
3650
  },
3919
3651
  {
3920
3652
  pattern: /(description|content|body|notes|summary|bio|about|comment|message)$/i,
3921
- component: "Textarea"
3922
- },
3923
- {
3924
- pattern: /^color$/i,
3925
- component: "ColorSelect"
3653
+ variant: "text"
3926
3654
  },
3927
3655
  {
3928
- pattern: /color$/i,
3929
- component: "ColorSelect"
3930
- },
3931
- {
3932
- pattern: /^icon$/i,
3933
- component: "IconPicker"
3934
- },
3935
- {
3936
- pattern: /icon$/i,
3937
- component: "IconPicker"
3938
- }
3939
- ];
3940
- /**
3941
- * 根据字段名推断 Formily 组件
3942
- */
3943
- function inferComponentByFieldName(fieldName) {
3944
- for (const rule of fieldNamePatternToComponent) if (rule.pattern.test(fieldName)) return {
3945
- component: rule.component,
3946
- props: rule.props
3947
- };
3948
- return null;
3949
- }
3950
- /**
3951
- * 字段名模式到 Filter Variant 映射
3952
- * 优先级高于 fieldTypeToFilterVariant
3953
- */
3954
- const fieldNamePatternToFilterVariant = [
3955
- {
3956
- pattern: /^(description|content|body|notes|summary|bio|about|comment|message|text)$/i,
3957
- variant: "text"
3958
- },
3959
- {
3960
- pattern: /(description|content|body|notes|summary|bio|about|comment|message)$/i,
3961
- variant: "text"
3962
- },
3963
- {
3964
- pattern: /^(date|day)$/i,
3965
- variant: "date"
3656
+ pattern: /^(date|day)$/i,
3657
+ variant: "date"
3966
3658
  },
3967
3659
  {
3968
3660
  pattern: /(at|time|timestamp)$/i,
@@ -4161,73 +3853,571 @@ function createTableSchema(schema, options) {
4161
3853
  meta,
4162
3854
  ...restOverride
4163
3855
  });
4164
- }
4165
- return columns;
4166
- }
4167
- /**
4168
- * 创建选择列
4169
- */
4170
- function createSelectColumn() {
4171
- return {
4172
- id: "select",
4173
- header: ({ table }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Checkbox, {
4174
- "aria-label": "Select all",
4175
- className: "translate-y-0.5",
4176
- checked: table.getIsAllPageRowsSelected() || table.getIsSomePageRowsSelected() && "indeterminate",
4177
- onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value)
4178
- }),
4179
- cell: ({ row }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Checkbox, {
4180
- "aria-label": "Select row",
4181
- className: "translate-y-0.5",
4182
- checked: row.getIsSelected(),
4183
- onCheckedChange: (value) => row.toggleSelected(!!value)
4184
- }),
4185
- enableHiding: false,
4186
- enableSorting: false,
4187
- size: 40
4188
- };
4189
- }
4190
- /**
4191
- * 创建操作列
4192
- */
4193
- function createActionsColumn(config) {
4194
- const { onEdit, onDelete, onView, onCopy } = config;
4195
- return {
4196
- id: "actions",
4197
- cell: ({ row }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuTrigger, {
4198
- asChild: true,
4199
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
4200
- "aria-label": "Open menu",
4201
- variant: "ghost",
4202
- className: "flex size-8 p-0 data-[state=open]:bg-muted",
4203
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Ellipsis, { className: "size-4" })
4204
- })
4205
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuContent, {
4206
- align: "end",
4207
- className: "w-40",
4208
- children: [
4209
- onView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
4210
- onClick: () => onView(row.original),
4211
- children: "查看"
4212
- }),
4213
- onEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
4214
- onClick: () => onEdit(row.original),
4215
- children: "编辑"
4216
- }),
4217
- onCopy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
4218
- onClick: () => onCopy(row.original),
4219
- children: "复制"
4220
- }),
4221
- (onView || onEdit || onCopy) && onDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuSeparator, {}),
4222
- onDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
4223
- className: "text-destructive",
4224
- onClick: () => onDelete(row.original),
4225
- children: "删除"
4226
- })
4227
- ]
4228
- })] }),
4229
- size: 40
4230
- };
3856
+ }
3857
+ return columns;
3858
+ }
3859
+ /**
3860
+ * 创建选择列
3861
+ */
3862
+ function createSelectColumn() {
3863
+ return {
3864
+ id: "select",
3865
+ header: ({ table }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Checkbox, {
3866
+ "aria-label": "Select all",
3867
+ className: "translate-y-0.5",
3868
+ checked: table.getIsAllPageRowsSelected() || table.getIsSomePageRowsSelected() && "indeterminate",
3869
+ onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value)
3870
+ }),
3871
+ cell: ({ row }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Checkbox, {
3872
+ "aria-label": "Select row",
3873
+ className: "translate-y-0.5",
3874
+ checked: row.getIsSelected(),
3875
+ onCheckedChange: (value) => row.toggleSelected(!!value)
3876
+ }),
3877
+ enableHiding: false,
3878
+ enableSorting: false,
3879
+ size: 40
3880
+ };
3881
+ }
3882
+ /**
3883
+ * 创建操作列
3884
+ */
3885
+ function createActionsColumn(config) {
3886
+ const { onEdit, onDelete, onView, onCopy } = config;
3887
+ return {
3888
+ id: "actions",
3889
+ cell: ({ row }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuTrigger, {
3890
+ asChild: true,
3891
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
3892
+ "aria-label": "Open menu",
3893
+ variant: "ghost",
3894
+ className: "flex size-8 p-0 data-[state=open]:bg-muted",
3895
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Ellipsis, { className: "size-4" })
3896
+ })
3897
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuContent, {
3898
+ align: "end",
3899
+ className: "w-40",
3900
+ children: [
3901
+ onView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
3902
+ onClick: () => onView(row.original),
3903
+ children: "查看"
3904
+ }),
3905
+ onEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
3906
+ onClick: () => onEdit(row.original),
3907
+ children: "编辑"
3908
+ }),
3909
+ onCopy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
3910
+ onClick: () => onCopy(row.original),
3911
+ children: "复制"
3912
+ }),
3913
+ (onView || onEdit || onCopy) && onDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuSeparator, {}),
3914
+ onDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
3915
+ className: "text-destructive",
3916
+ onClick: () => onDelete(row.original),
3917
+ children: "删除"
3918
+ })
3919
+ ]
3920
+ })] }),
3921
+ size: 40
3922
+ };
3923
+ }
3924
+
3925
+ //#endregion
3926
+ //#region src/lib/import.ts
3927
+ /**
3928
+ * 解析 CSV 字符串为对象数组
3929
+ * 支持:引号包裹、逗号转义、换行符、空值
3930
+ */
3931
+ function parseCSV(content) {
3932
+ const lines = parseCSVLines(content);
3933
+ if (lines.length === 0) return {
3934
+ headers: [],
3935
+ rows: []
3936
+ };
3937
+ const headers = lines[0];
3938
+ const rows = [];
3939
+ for (let i = 1; i < lines.length; i++) {
3940
+ const line = lines[i];
3941
+ if (line.length === 1 && line[0] === "") continue;
3942
+ const row = {};
3943
+ for (let j = 0; j < headers.length; j++) {
3944
+ const header = headers[j];
3945
+ row[header] = line[j] ?? "";
3946
+ }
3947
+ rows.push(row);
3948
+ }
3949
+ return {
3950
+ headers,
3951
+ rows
3952
+ };
3953
+ }
3954
+ /**
3955
+ * 将 CSV 文本解析为二维数组(处理引号转义)
3956
+ */
3957
+ function parseCSVLines(text) {
3958
+ const result = [];
3959
+ let current = "";
3960
+ let inQuotes = false;
3961
+ let row = [];
3962
+ for (let i = 0; i < text.length; i++) {
3963
+ const char = text[i];
3964
+ const next = text[i + 1];
3965
+ if (inQuotes) if (char === "\"" && next === "\"") {
3966
+ current += "\"";
3967
+ i++;
3968
+ } else if (char === "\"") inQuotes = false;
3969
+ else current += char;
3970
+ else if (char === "\"") inQuotes = true;
3971
+ else if (char === ",") {
3972
+ row.push(current);
3973
+ current = "";
3974
+ } else if (char === "\r" && next === "\n") {
3975
+ row.push(current);
3976
+ current = "";
3977
+ result.push(row);
3978
+ row = [];
3979
+ i++;
3980
+ } else if (char === "\n") {
3981
+ row.push(current);
3982
+ current = "";
3983
+ result.push(row);
3984
+ row = [];
3985
+ } else current += char;
3986
+ }
3987
+ if (current !== "" || row.length > 0) {
3988
+ row.push(current);
3989
+ result.push(row);
3990
+ }
3991
+ return result;
3992
+ }
3993
+ /**
3994
+ * 解析 JSON 字符串为对象数组
3995
+ * 支持 JSON 数组或 { data: [...] } 格式
3996
+ */
3997
+ function parseJSON(content) {
3998
+ const parsed = JSON.parse(content);
3999
+ if (Array.isArray(parsed)) return parsed;
4000
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.data)) return parsed.data;
4001
+ throw new Error("Invalid JSON format: expected an array or { data: [...] } object");
4002
+ }
4003
+ /**
4004
+ * 统一解析入口:根据文件扩展名自动选择解析器
4005
+ */
4006
+ async function parseImportFile(file) {
4007
+ const content = await file.text();
4008
+ if (file.name.split(".").pop()?.toLowerCase() === "json") {
4009
+ const rows$1 = parseJSON(content);
4010
+ return {
4011
+ headers: rows$1.length > 0 ? Object.keys(rows$1[0]) : [],
4012
+ rows: rows$1,
4013
+ format: "json"
4014
+ };
4015
+ }
4016
+ const { headers, rows } = parseCSV(content);
4017
+ return {
4018
+ headers,
4019
+ rows,
4020
+ format: "csv"
4021
+ };
4022
+ }
4023
+ /**
4024
+ * 生成 CSV 模板(仅包含表头,用于下载空模板)
4025
+ */
4026
+ function generateCSVTemplate(headers) {
4027
+ return headers.map((h) => escapeCSVField(h)).join(",") + "\n";
4028
+ }
4029
+ /**
4030
+ * 将数据数组转换为 CSV 字符串
4031
+ */
4032
+ function dataToCSV(data, opts = {}) {
4033
+ if (data.length === 0) return "";
4034
+ const allHeaders = Object.keys(data[0]);
4035
+ const headers = (opts.headers ?? allHeaders).filter((h) => !opts.excludeColumns?.includes(h));
4036
+ return [headers.map((h) => escapeCSVField(h)).join(","), ...data.map((row) => headers.map((h) => escapeCSVField(String(row[h] ?? ""))).join(","))].join("\n");
4037
+ }
4038
+ function escapeCSVField(field) {
4039
+ if (field.includes(",") || field.includes("\"") || field.includes("\n") || field.includes("\r")) return `"${field.replace(/"/g, "\"\"")}"`;
4040
+ return field;
4041
+ }
4042
+ /**
4043
+ * 根据 Zod Schema 将 CSV 解析出的 string 值转换为正确类型
4044
+ *
4045
+ * CSV 解析后所有值都是 string,需要根据 schema 字段类型进行转换:
4046
+ * - number: parseFloat
4047
+ * - boolean: "true"/"1"/"yes" → true, 其他 → false
4048
+ * - date: new Date(value)
4049
+ * - 空字符串 → undefined(让 schema 默认值生效)
4050
+ */
4051
+ function coerceRowValues(rows, schema) {
4052
+ const shape = schema.shape;
4053
+ const fieldTypes = /* @__PURE__ */ new Map();
4054
+ for (const [key, fieldSchema] of Object.entries(shape)) {
4055
+ const parsed = parseZodField(fieldSchema);
4056
+ fieldTypes.set(key, parsed.type);
4057
+ }
4058
+ return rows.map((row) => {
4059
+ const coerced = {};
4060
+ for (const [key, value] of Object.entries(row)) {
4061
+ const type = fieldTypes.get(key);
4062
+ if (!type) {
4063
+ coerced[key] = value;
4064
+ continue;
4065
+ }
4066
+ if (value === "" || value === null || value === void 0) {
4067
+ coerced[key] = void 0;
4068
+ continue;
4069
+ }
4070
+ if (typeof value !== "string") {
4071
+ coerced[key] = value;
4072
+ continue;
4073
+ }
4074
+ switch (type) {
4075
+ case "number": {
4076
+ const num = parseFloat(value);
4077
+ coerced[key] = isNaN(num) ? void 0 : num;
4078
+ break;
4079
+ }
4080
+ case "boolean": {
4081
+ const lower = value.toLowerCase().trim();
4082
+ coerced[key] = lower === "true" || lower === "1" || lower === "yes";
4083
+ break;
4084
+ }
4085
+ case "date": {
4086
+ const date = new Date(value);
4087
+ coerced[key] = isNaN(date.getTime()) ? void 0 : date;
4088
+ break;
4089
+ }
4090
+ default: coerced[key] = value;
4091
+ }
4092
+ }
4093
+ return coerced;
4094
+ });
4095
+ }
4096
+
4097
+ //#endregion
4098
+ //#region src/lib/export.ts
4099
+ function exportTableToCSV(table, opts = {}) {
4100
+ const { filename = "table", excludeColumns = [], onlySelected = false } = opts;
4101
+ const headers = table.getAllLeafColumns().map((column) => column.id).filter((id) => !excludeColumns.includes(id));
4102
+ downloadCSV([headers.join(","), ...(onlySelected ? table.getFilteredSelectedRowModel().rows : table.getRowModel().rows).map((row) => headers.map((header) => {
4103
+ const cellValue = row.getValue(header);
4104
+ return typeof cellValue === "string" ? `"${cellValue.replace(/"/g, "\"\"")}"` : cellValue;
4105
+ }).join(","))].join("\n"), filename);
4106
+ }
4107
+ /**
4108
+ * 导出原始数据数组为 CSV(用于服务端全量导出)
4109
+ */
4110
+ function exportAllToCSV(data, opts = {}) {
4111
+ const { filename = "export",...csvOpts } = opts;
4112
+ downloadCSV(dataToCSV(data, csvOpts), filename);
4113
+ }
4114
+ /**
4115
+ * 下载 CSV 模板文件
4116
+ */
4117
+ function downloadCSVTemplate(headers, filename = "template") {
4118
+ downloadCSV(headers.map((h) => {
4119
+ if (h.includes(",") || h.includes("\"") || h.includes("\n")) return `"${h.replace(/"/g, "\"\"")}"`;
4120
+ return h;
4121
+ }).join(",") + "\n", filename);
4122
+ }
4123
+ function downloadCSV(csvContent, filename) {
4124
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
4125
+ const url = URL.createObjectURL(blob);
4126
+ const link = document.createElement("a");
4127
+ link.setAttribute("href", url);
4128
+ link.setAttribute("download", `${filename}.csv`);
4129
+ link.style.visibility = "hidden";
4130
+ document.body.appendChild(link);
4131
+ link.click();
4132
+ document.body.removeChild(link);
4133
+ }
4134
+
4135
+ //#endregion
4136
+ //#region src/components/auto-crud/auto-table-action-bar.tsx
4137
+ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUpdateFields, enableExport = true, enableDelete = true, extraActions }) {
4138
+ const rows = table.getFilteredSelectedRowModel().rows;
4139
+ const onOpenChange = react.useCallback((open) => {
4140
+ if (!open) table.toggleAllRowsSelected(false);
4141
+ }, [table]);
4142
+ const onExport = react.useCallback(() => {
4143
+ exportTableToCSV(table, {
4144
+ excludeColumns: ["select", "actions"],
4145
+ onlySelected: true
4146
+ });
4147
+ }, [table]);
4148
+ const onDelete = react.useCallback(() => {
4149
+ if (onDeleteSelected) onDeleteSelected(rows.map((row) => row.original));
4150
+ }, [rows, onDeleteSelected]);
4151
+ const handleBatchUpdate = react.useCallback((field, value) => {
4152
+ if (onUpdateSelected) {
4153
+ onUpdateSelected(rows.map((row) => row.original), { [field]: value });
4154
+ table.toggleAllRowsSelected(false);
4155
+ }
4156
+ }, [
4157
+ rows,
4158
+ onUpdateSelected,
4159
+ table
4160
+ ]);
4161
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBar, {
4162
+ open: rows.length > 0,
4163
+ onOpenChange,
4164
+ children: [
4165
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBarSelection, { children: [
4166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4167
+ className: "font-medium",
4168
+ children: rows.length
4169
+ }),
4170
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "selected" }),
4171
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionBarSeparator, {}),
4172
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionBarClose, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {}) })
4173
+ ] }),
4174
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionBarSeparator, {}),
4175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBarGroup, { children: [
4176
+ batchUpdateFields?.map((fieldConfig) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuTrigger, {
4177
+ asChild: true,
4178
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
4179
+ variant: "ghost",
4180
+ size: "sm",
4181
+ className: "h-7 gap-1 px-2",
4182
+ children: [fieldConfig.label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "size-3.5" })]
4183
+ })
4184
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuContent, {
4185
+ align: "start",
4186
+ children: fieldConfig.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
4187
+ onClick: () => handleBatchUpdate(fieldConfig.field, option.value),
4188
+ children: option.label
4189
+ }, option.value))
4190
+ })] }, fieldConfig.field)),
4191
+ extraActions,
4192
+ enableExport && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBarItem, {
4193
+ onClick: onExport,
4194
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, {}), "Export"]
4195
+ }),
4196
+ enableDelete && onDeleteSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ActionBarItem, {
4197
+ variant: "destructive",
4198
+ onClick: onDelete,
4199
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Trash2, {}), "Delete"]
4200
+ })
4201
+ ] })
4202
+ ]
4203
+ });
4204
+ }
4205
+
4206
+ //#endregion
4207
+ //#region src/lib/parsers.ts
4208
+ /**
4209
+ * 创建自定义 Parser(替代 nuqs 的 createParser)
4210
+ */
4211
+ function createParser(config) {
4212
+ return {
4213
+ parse: config.parse,
4214
+ serialize: (value) => value === null ? "" : config.serialize(value),
4215
+ withDefault: (defaultValue) => ({
4216
+ parse: (value) => {
4217
+ const parsed = config.parse(value);
4218
+ return parsed === null ? defaultValue : parsed;
4219
+ },
4220
+ serialize: config.serialize,
4221
+ defaultValue
4222
+ })
4223
+ };
4224
+ }
4225
+ const sortingItemSchema = zod.z.object({
4226
+ id: zod.z.string(),
4227
+ desc: zod.z.boolean()
4228
+ });
4229
+ const getSortingStateParser = (columnIds) => {
4230
+ const validKeys = columnIds ? columnIds instanceof Set ? columnIds : new Set(columnIds) : null;
4231
+ return createParser({
4232
+ parse: (value) => {
4233
+ try {
4234
+ const parsed = JSON.parse(value);
4235
+ const result = zod.z.array(sortingItemSchema).safeParse(parsed);
4236
+ if (!result.success) return null;
4237
+ if (validKeys && result.data.some((item) => !validKeys.has(item.id))) return null;
4238
+ return result.data;
4239
+ } catch {
4240
+ return null;
4241
+ }
4242
+ },
4243
+ serialize: (value) => JSON.stringify(value)
4244
+ });
4245
+ };
4246
+ const filterItemSchema = zod.z.object({
4247
+ id: zod.z.string(),
4248
+ value: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]),
4249
+ variant: zod.z.enum(dataTableConfig.filterVariants),
4250
+ operator: zod.z.enum(dataTableConfig.operators),
4251
+ filterId: zod.z.string()
4252
+ });
4253
+
4254
+ //#endregion
4255
+ //#region src/hooks/use-data-table.ts
4256
+ const PAGE_KEY = "page";
4257
+ const PER_PAGE_KEY = "perPage";
4258
+ const SORT_KEY = "sort";
4259
+ const FILTERS_KEY = "filters";
4260
+ const JOIN_OPERATOR_KEY = "joinOperator";
4261
+ const ARRAY_SEPARATOR = ",";
4262
+ const DEBOUNCE_MS = 300;
4263
+ const THROTTLE_MS = 50;
4264
+ function useDataTable(props) {
4265
+ const { columns, pageCount = -1, initialState, queryKeys, history = "replace", debounceMs = DEBOUNCE_MS, throttleMs = THROTTLE_MS, clearOnDefault = false, enableAdvancedFilter = false, scroll = false, shallow = true, startTransition,...tableProps } = props;
4266
+ const pageKey = queryKeys?.page ?? PAGE_KEY;
4267
+ const perPageKey = queryKeys?.perPage ?? PER_PAGE_KEY;
4268
+ const sortKey = queryKeys?.sort ?? SORT_KEY;
4269
+ const filtersKey = queryKeys?.filters ?? FILTERS_KEY;
4270
+ const joinOperatorKey = queryKeys?.joinOperator ?? JOIN_OPERATOR_KEY;
4271
+ const queryStateOptions = react.useMemo(() => ({
4272
+ history,
4273
+ scroll,
4274
+ shallow,
4275
+ throttleMs,
4276
+ debounceMs,
4277
+ clearOnDefault,
4278
+ startTransition
4279
+ }), [
4280
+ history,
4281
+ scroll,
4282
+ shallow,
4283
+ throttleMs,
4284
+ debounceMs,
4285
+ clearOnDefault,
4286
+ startTransition
4287
+ ]);
4288
+ const [rowSelection, setRowSelection] = react.useState(initialState?.rowSelection ?? {});
4289
+ const [columnVisibility, setColumnVisibility] = react.useState(initialState?.columnVisibility ?? {});
4290
+ const [page, setPage] = useQueryState(pageKey, parseAsInteger.withDefault(1));
4291
+ const [perPage, setPerPage] = useQueryState(perPageKey, parseAsInteger.withDefault(initialState?.pagination?.pageSize ?? 10));
4292
+ const pagination = react.useMemo(() => {
4293
+ return {
4294
+ pageIndex: page - 1,
4295
+ pageSize: perPage
4296
+ };
4297
+ }, [page, perPage]);
4298
+ const onPaginationChange = react.useCallback((updaterOrValue) => {
4299
+ if (typeof updaterOrValue === "function") {
4300
+ const newPagination = updaterOrValue(pagination);
4301
+ setPage(newPagination.pageIndex + 1);
4302
+ setPerPage(newPagination.pageSize);
4303
+ } else {
4304
+ setPage(updaterOrValue.pageIndex + 1);
4305
+ setPerPage(updaterOrValue.pageSize);
4306
+ }
4307
+ }, [
4308
+ pagination,
4309
+ setPage,
4310
+ setPerPage
4311
+ ]);
4312
+ const [sorting, setSorting] = useQueryState(sortKey, getSortingStateParser(react.useMemo(() => {
4313
+ return new Set(columns.map((column) => column.id).filter(Boolean));
4314
+ }, [columns])).withDefault(initialState?.sorting ?? []));
4315
+ const onSortingChange = react.useCallback((updaterOrValue) => {
4316
+ if (typeof updaterOrValue === "function") setSorting(updaterOrValue(sorting));
4317
+ else setSorting(updaterOrValue);
4318
+ }, [sorting, setSorting]);
4319
+ const filterableColumns = react.useMemo(() => {
4320
+ if (enableAdvancedFilter) return [];
4321
+ return columns.filter((column) => column.enableColumnFilter);
4322
+ }, [columns, enableAdvancedFilter]);
4323
+ const [filterValues, setFilterValues] = useQueryStates(react.useMemo(() => {
4324
+ if (enableAdvancedFilter) return {};
4325
+ return filterableColumns.reduce((acc, column) => {
4326
+ if (column.meta?.options) acc[column.id ?? ""] = parseAsArrayOf(parseAsString, ARRAY_SEPARATOR).withDefault([]);
4327
+ else acc[column.id ?? ""] = parseAsString.withDefault("");
4328
+ return acc;
4329
+ }, {});
4330
+ }, [filterableColumns, enableAdvancedFilter]));
4331
+ const debouncedSetFilterValues = useDebouncedCallback((values) => {
4332
+ setPage(1);
4333
+ setFilterValues(values);
4334
+ }, debounceMs);
4335
+ const initialColumnFilters = react.useMemo(() => {
4336
+ if (enableAdvancedFilter) return [];
4337
+ return Object.entries(filterValues).reduce((filters, [key, value]) => {
4338
+ if (value !== null) {
4339
+ const processedValue = Array.isArray(value) ? value : typeof value === "string" && /[^a-zA-Z0-9]/.test(value) ? value.split(/[^a-zA-Z0-9]+/).filter(Boolean) : [value];
4340
+ filters.push({
4341
+ id: key,
4342
+ value: processedValue
4343
+ });
4344
+ }
4345
+ return filters;
4346
+ }, []);
4347
+ }, [filterValues, enableAdvancedFilter]);
4348
+ const [columnFilters, setColumnFilters] = react.useState(initialColumnFilters);
4349
+ const onColumnFiltersChange = react.useCallback((updaterOrValue) => {
4350
+ if (enableAdvancedFilter) return;
4351
+ setColumnFilters((prev) => {
4352
+ const next = typeof updaterOrValue === "function" ? updaterOrValue(prev) : updaterOrValue;
4353
+ const filterUpdates = next.reduce((acc, filter) => {
4354
+ if (filterableColumns.find((column) => column.id === filter.id)) acc[filter.id] = filter.value;
4355
+ return acc;
4356
+ }, {});
4357
+ for (const prevFilter of prev) if (!next.some((filter) => filter.id === prevFilter.id)) filterUpdates[prevFilter.id] = null;
4358
+ debouncedSetFilterValues(filterUpdates);
4359
+ return next;
4360
+ });
4361
+ }, [
4362
+ debouncedSetFilterValues,
4363
+ filterableColumns,
4364
+ enableAdvancedFilter
4365
+ ]);
4366
+ const table = (0, __tanstack_react_table.useReactTable)({
4367
+ ...tableProps,
4368
+ columns,
4369
+ initialState,
4370
+ pageCount,
4371
+ state: {
4372
+ pagination,
4373
+ sorting,
4374
+ columnVisibility,
4375
+ rowSelection,
4376
+ columnFilters
4377
+ },
4378
+ defaultColumn: {
4379
+ ...tableProps.defaultColumn,
4380
+ enableColumnFilter: false
4381
+ },
4382
+ enableRowSelection: true,
4383
+ onRowSelectionChange: setRowSelection,
4384
+ onPaginationChange,
4385
+ onSortingChange,
4386
+ onColumnFiltersChange,
4387
+ onColumnVisibilityChange: setColumnVisibility,
4388
+ getCoreRowModel: (0, __tanstack_react_table.getCoreRowModel)(),
4389
+ getFilteredRowModel: (0, __tanstack_react_table.getFilteredRowModel)(),
4390
+ getPaginationRowModel: (0, __tanstack_react_table.getPaginationRowModel)(),
4391
+ getSortedRowModel: (0, __tanstack_react_table.getSortedRowModel)(),
4392
+ getFacetedRowModel: (0, __tanstack_react_table.getFacetedRowModel)(),
4393
+ getFacetedUniqueValues: (0, __tanstack_react_table.getFacetedUniqueValues)(),
4394
+ getFacetedMinMaxValues: (0, __tanstack_react_table.getFacetedMinMaxValues)(),
4395
+ manualPagination: true,
4396
+ manualSorting: true,
4397
+ manualFiltering: true,
4398
+ meta: {
4399
+ ...tableProps.meta,
4400
+ queryKeys: {
4401
+ page: pageKey,
4402
+ perPage: perPageKey,
4403
+ sort: sortKey,
4404
+ filters: filtersKey,
4405
+ joinOperator: joinOperatorKey
4406
+ },
4407
+ queryStateOptions
4408
+ }
4409
+ });
4410
+ return react.useMemo(() => ({
4411
+ table,
4412
+ shallow,
4413
+ debounceMs,
4414
+ throttleMs
4415
+ }), [
4416
+ table,
4417
+ shallow,
4418
+ debounceMs,
4419
+ throttleMs
4420
+ ]);
4231
4421
  }
4232
4422
 
4233
4423
  //#endregion
@@ -4250,7 +4440,7 @@ const filterModeConfig = {
4250
4440
  tooltip: "Linear-style command filters"
4251
4441
  }
4252
4442
  };
4253
- function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, initialSorting, enableExport = true }) {
4443
+ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, initialSorting, enableExport = true, onSelectedCountChange, getSelectedRows }) {
4254
4444
  const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : [
4255
4445
  "simple",
4256
4446
  "advanced",
@@ -4287,6 +4477,17 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
4287
4477
  shallow: false,
4288
4478
  clearOnDefault: true
4289
4479
  });
4480
+ const getSelectedRowsFn = (0, react.useCallback)(() => table.getFilteredSelectedRowModel().rows.map((row) => row.original), [table]);
4481
+ (0, react.useEffect)(() => {
4482
+ if (getSelectedRows) getSelectedRows.current = getSelectedRowsFn;
4483
+ return () => {
4484
+ if (getSelectedRows) getSelectedRows.current = null;
4485
+ };
4486
+ }, [getSelectedRows, getSelectedRowsFn]);
4487
+ const selectedRowCount = table.getFilteredSelectedRowModel().rows.length;
4488
+ (0, react.useEffect)(() => {
4489
+ onSelectedCountChange?.(selectedRowCount);
4490
+ }, [selectedRowCount, onSelectedCountChange]);
4290
4491
  const FilterModeSelect = showToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuTrigger, {
4291
4492
  asChild: true,
4292
4493
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
@@ -4618,6 +4819,385 @@ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubm
4618
4819
  });
4619
4820
  }
4620
4821
 
4822
+ //#endregion
4823
+ //#region src/components/auto-crud/import-dialog.tsx
4824
+ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导入数据" }) {
4825
+ const [step, setStep] = react.useState("upload");
4826
+ const [parsedData, setParsedData] = react.useState(null);
4827
+ const [error, setError] = react.useState(null);
4828
+ const [result, setResult] = react.useState(null);
4829
+ const [isDragging, setIsDragging] = react.useState(false);
4830
+ const fileInputRef = react.useRef(null);
4831
+ const reset = react.useCallback(() => {
4832
+ setStep("upload");
4833
+ setParsedData(null);
4834
+ setError(null);
4835
+ setResult(null);
4836
+ setIsDragging(false);
4837
+ }, []);
4838
+ const handleOpenChange = react.useCallback((value) => {
4839
+ if (!value) reset();
4840
+ onOpenChange(value);
4841
+ }, [onOpenChange, reset]);
4842
+ const handleFile = react.useCallback(async (file) => {
4843
+ setError(null);
4844
+ try {
4845
+ const data = await parseImportFile(file);
4846
+ if (data.rows.length === 0) {
4847
+ setError("文件中没有数据行");
4848
+ return;
4849
+ }
4850
+ setParsedData(data);
4851
+ setStep("preview");
4852
+ } catch (e) {
4853
+ setError(e instanceof Error ? e.message : "文件解析失败");
4854
+ }
4855
+ }, []);
4856
+ const handleDrop = react.useCallback((e) => {
4857
+ e.preventDefault();
4858
+ setIsDragging(false);
4859
+ const file = e.dataTransfer.files[0];
4860
+ if (file) handleFile(file);
4861
+ }, [handleFile]);
4862
+ const handleFileInput = react.useCallback((e) => {
4863
+ const file = e.target.files?.[0];
4864
+ if (file) handleFile(file);
4865
+ }, [handleFile]);
4866
+ const handleImport = react.useCallback(async () => {
4867
+ if (!parsedData) return;
4868
+ setStep("importing");
4869
+ try {
4870
+ setResult(await onImport(parsedData.rows));
4871
+ setStep("result");
4872
+ } catch (e) {
4873
+ setError(e instanceof Error ? e.message : "导入失败");
4874
+ setStep("preview");
4875
+ }
4876
+ }, [parsedData, onImport]);
4877
+ const handleDownloadTemplate = react.useCallback(() => {
4878
+ if (columns.length === 0) return;
4879
+ const csv = generateCSVTemplate(columns);
4880
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
4881
+ const url = URL.createObjectURL(blob);
4882
+ const link = document.createElement("a");
4883
+ link.href = url;
4884
+ link.download = "import-template.csv";
4885
+ link.click();
4886
+ URL.revokeObjectURL(url);
4887
+ }, [columns]);
4888
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Dialog, {
4889
+ open,
4890
+ onOpenChange: handleOpenChange,
4891
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogContent, {
4892
+ className: "sm:max-w-[600px]",
4893
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogTitle, { children: title }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogDescription, { children: [
4894
+ step === "upload" && "上传 CSV 或 JSON 文件以批量导入数据",
4895
+ step === "preview" && `已解析 ${parsedData?.rows.length ?? 0} 条数据,确认后开始导入`,
4896
+ step === "importing" && "正在导入数据...",
4897
+ step === "result" && "导入完成"
4898
+ ] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4899
+ className: "space-y-4",
4900
+ children: [
4901
+ step === "upload" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4902
+ className: `border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-muted-foreground/50"}`,
4903
+ onDragOver: (e) => {
4904
+ e.preventDefault();
4905
+ setIsDragging(true);
4906
+ },
4907
+ onDragLeave: () => setIsDragging(false),
4908
+ onDrop: handleDrop,
4909
+ onClick: () => fileInputRef.current?.click(),
4910
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4911
+ className: "flex flex-col items-center gap-2",
4912
+ children: [
4913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4914
+ className: "h-10 w-10 text-muted-foreground",
4915
+ xmlns: "http://www.w3.org/2000/svg",
4916
+ fill: "none",
4917
+ viewBox: "0 0 24 24",
4918
+ strokeWidth: 1.5,
4919
+ stroke: "currentColor",
4920
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
4921
+ strokeLinecap: "round",
4922
+ strokeLinejoin: "round",
4923
+ d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
4924
+ })
4925
+ }),
4926
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4927
+ className: "text-sm text-muted-foreground",
4928
+ children: "拖拽文件到此处,或点击选择文件"
4929
+ }),
4930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4931
+ className: "text-xs text-muted-foreground/60",
4932
+ children: "支持 CSV、JSON 格式"
4933
+ })
4934
+ ]
4935
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4936
+ ref: fileInputRef,
4937
+ type: "file",
4938
+ accept: ".csv,.json",
4939
+ className: "hidden",
4940
+ onChange: handleFileInput
4941
+ })]
4942
+ }), columns.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
4943
+ variant: "outline",
4944
+ size: "sm",
4945
+ className: "w-full",
4946
+ onClick: handleDownloadTemplate,
4947
+ children: "下载 CSV 模板"
4948
+ })] }),
4949
+ step === "preview" && parsedData && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4951
+ className: "rounded-md border",
4952
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4953
+ className: "max-h-[300px] overflow-auto",
4954
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
4955
+ className: "w-full text-sm",
4956
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", {
4957
+ className: "sticky top-0 bg-muted",
4958
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
4959
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
4960
+ className: "px-3 py-2 text-left font-medium text-muted-foreground",
4961
+ children: "#"
4962
+ }),
4963
+ parsedData.headers.slice(0, 6).map((h) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
4964
+ className: "px-3 py-2 text-left font-medium text-muted-foreground",
4965
+ children: h
4966
+ }, h)),
4967
+ parsedData.headers.length > 6 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("th", {
4968
+ className: "px-3 py-2 text-left font-medium text-muted-foreground",
4969
+ children: [
4970
+ "+",
4971
+ parsedData.headers.length - 6,
4972
+ " 列"
4973
+ ]
4974
+ })
4975
+ ] })
4976
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: parsedData.rows.slice(0, 10).map((row, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
4977
+ className: "border-t",
4978
+ children: [
4979
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
4980
+ className: "px-3 py-2 text-muted-foreground",
4981
+ children: i + 1
4982
+ }),
4983
+ parsedData.headers.slice(0, 6).map((h) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
4984
+ className: "px-3 py-2 max-w-[150px] truncate",
4985
+ children: String(row[h] ?? "")
4986
+ }, h)),
4987
+ parsedData.headers.length > 6 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
4988
+ className: "px-3 py-2 text-muted-foreground",
4989
+ children: "..."
4990
+ })
4991
+ ]
4992
+ }, i)) })]
4993
+ })
4994
+ })
4995
+ }),
4996
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4997
+ className: "text-sm text-muted-foreground",
4998
+ children: [
4999
+ "共 ",
5000
+ parsedData.rows.length,
5001
+ " 条数据",
5002
+ parsedData.rows.length > 10 && "(预览前 10 条)",
5003
+ ",",
5004
+ parsedData.headers.length,
5005
+ " 个字段 ,格式: ",
5006
+ parsedData.format.toUpperCase()
5007
+ ]
5008
+ }),
5009
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5010
+ className: "flex gap-2 justify-end",
5011
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5012
+ variant: "outline",
5013
+ onClick: reset,
5014
+ children: "重新选择"
5015
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5016
+ onClick: handleImport,
5017
+ children: "确认导入"
5018
+ })]
5019
+ })
5020
+ ] }),
5021
+ step === "importing" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5022
+ className: "flex flex-col items-center gap-4 py-8",
5023
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5024
+ className: "text-sm text-muted-foreground",
5025
+ children: [
5026
+ "正在导入 ",
5027
+ parsedData?.rows.length ?? 0,
5028
+ " 条数据..."
5029
+ ]
5030
+ })]
5031
+ }),
5032
+ step === "result" && result && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5033
+ className: "space-y-3",
5034
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5035
+ className: "flex gap-4",
5036
+ children: [
5037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5038
+ className: "flex-1 rounded-lg bg-green-50 dark:bg-green-950/30 p-3 text-center",
5039
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5040
+ className: "text-2xl font-semibold text-green-600",
5041
+ children: result.success
5042
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5043
+ className: "text-xs text-green-600/80",
5044
+ children: "新建"
5045
+ })]
5046
+ }),
5047
+ (result.updated ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5048
+ className: "flex-1 rounded-lg bg-blue-50 dark:bg-blue-950/30 p-3 text-center",
5049
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5050
+ className: "text-2xl font-semibold text-blue-600",
5051
+ children: result.updated
5052
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5053
+ className: "text-xs text-blue-600/80",
5054
+ children: "更新"
5055
+ })]
5056
+ }),
5057
+ result.skipped > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5058
+ className: "flex-1 rounded-lg bg-yellow-50 dark:bg-yellow-950/30 p-3 text-center",
5059
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5060
+ className: "text-2xl font-semibold text-yellow-600",
5061
+ children: result.skipped
5062
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5063
+ className: "text-xs text-yellow-600/80",
5064
+ children: "跳过"
5065
+ })]
5066
+ }),
5067
+ result.failed.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5068
+ className: "flex-1 rounded-lg bg-red-50 dark:bg-red-950/30 p-3 text-center",
5069
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5070
+ className: "text-2xl font-semibold text-red-600",
5071
+ children: result.failed.length
5072
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5073
+ className: "text-xs text-red-600/80",
5074
+ children: "验证失败"
5075
+ })]
5076
+ })
5077
+ ]
5078
+ }), result.failed.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5079
+ className: "rounded-md border border-red-200 dark:border-red-900",
5080
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5081
+ className: "max-h-[200px] overflow-auto",
5082
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
5083
+ className: "w-full text-sm",
5084
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", {
5085
+ className: "sticky top-0 bg-red-50 dark:bg-red-950/30",
5086
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
5087
+ className: "px-3 py-2 text-left font-medium text-red-600",
5088
+ children: "行号"
5089
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
5090
+ className: "px-3 py-2 text-left font-medium text-red-600",
5091
+ children: "错误"
5092
+ })] })
5093
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: result.failed.map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
5094
+ className: "border-t border-red-100 dark:border-red-900",
5095
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
5096
+ className: "px-3 py-2 text-red-600",
5097
+ children: f.row + 1
5098
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
5099
+ className: "px-3 py-2 text-red-600/80",
5100
+ children: f.errors.join("; ")
5101
+ })]
5102
+ }, f.row)) })]
5103
+ })
5104
+ })
5105
+ })]
5106
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5107
+ className: "flex gap-2 justify-end",
5108
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5109
+ variant: "outline",
5110
+ onClick: () => handleOpenChange(false),
5111
+ children: "关闭"
5112
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5113
+ variant: "outline",
5114
+ onClick: reset,
5115
+ children: "继续导入"
5116
+ })]
5117
+ })] }),
5118
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5119
+ className: "rounded-md bg-destructive/10 p-3 text-sm text-destructive",
5120
+ children: error
5121
+ })
5122
+ ]
5123
+ })]
5124
+ })
5125
+ });
5126
+ }
5127
+
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
+
4621
5201
  //#endregion
4622
5202
  //#region src/components/auto-crud/auto-crud-table.tsx
4623
5203
  /**
@@ -4777,9 +5357,43 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
4777
5357
  create: permissions?.can?.create ?? true,
4778
5358
  update: permissions?.can?.update ?? true,
4779
5359
  delete: permissions?.can?.delete ?? true,
4780
- export: permissions?.can?.export ?? true
5360
+ export: permissions?.can?.export ?? true,
5361
+ import: permissions?.can?.import ?? true
4781
5362
  };
4782
5363
  const denyFields = permissions?.deny;
5364
+ const canImport = can.import && !!resource.handlers.import;
5365
+ const canExport = can.export;
5366
+ const [importOpen, setImportOpen] = react.useState(false);
5367
+ const [exportOpen, setExportOpen] = react.useState(false);
5368
+ const [selectedCount, setSelectedCount] = react.useState(0);
5369
+ const getSelectedRowsRef = react.useRef(null);
5370
+ const importColumns = react.useMemo(() => {
5371
+ const shape = schema.shape;
5372
+ const denySet = new Set(denyFields ?? []);
5373
+ return Object.keys(shape).filter((key) => !denySet.has(key));
5374
+ }, [schema, denyFields]);
5375
+ const handleExport = react.useCallback(async (mode) => {
5376
+ const filename = title ?? "export";
5377
+ const excludeColumns = denyFields;
5378
+ if (mode === "selected") {
5379
+ const selectedRows = getSelectedRowsRef.current?.();
5380
+ if (!selectedRows || selectedRows.length === 0) return;
5381
+ exportAllToCSV(selectedRows, {
5382
+ filename,
5383
+ excludeColumns
5384
+ });
5385
+ } else {
5386
+ if (!resource.handlers.export) return;
5387
+ exportAllToCSV(await resource.handlers.export(), {
5388
+ filename,
5389
+ excludeColumns
5390
+ });
5391
+ }
5392
+ }, [
5393
+ resource.handlers.export,
5394
+ title,
5395
+ denyFields
5396
+ ]);
4783
5397
  const tableOverrides = buildTableOverrides(fields, tableConfig?.overrides);
4784
5398
  const formOverrides = buildFormOverrides(fields, formConfig?.overrides, denyFields);
4785
5399
  const hiddenColumns = buildHiddenColumns(fields, tableConfig?.hidden, denyFields);
@@ -4804,10 +5418,25 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
4804
5418
  children: slots?.toolbarStart
4805
5419
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4806
5420
  className: "flex items-center gap-2",
4807
- children: [slots?.toolbarEnd, can.create && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
4808
- onClick: resource.handlers.openCreate,
4809
- children: "新建"
4810
- })]
5421
+ children: [
5422
+ slots?.toolbarEnd,
5423
+ canImport && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
5424
+ variant: "outline",
5425
+ size: "sm",
5426
+ onClick: () => setImportOpen(true),
5427
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, { className: "mr-2 h-4 w-4" }), "导入"]
5428
+ }),
5429
+ canExport && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
5430
+ variant: "outline",
5431
+ size: "sm",
5432
+ onClick: () => setExportOpen(true),
5433
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: "mr-2 h-4 w-4" }), "导出"]
5434
+ }),
5435
+ can.create && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5436
+ onClick: resource.handlers.openCreate,
5437
+ children: "新建"
5438
+ })
5439
+ ]
4811
5440
  })]
4812
5441
  }),
4813
5442
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoTable, {
@@ -4826,8 +5455,10 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
4826
5455
  onDeleteSelected: can.delete ? resource.handlers.deleteMany : void 0,
4827
5456
  onUpdateSelected: can.update ? resource.handlers.updateMany : void 0,
4828
5457
  batchUpdateFields: can.update ? batchFields : void 0,
4829
- enableExport: can.export,
4830
- initialSorting: tableConfig?.defaultSort
5458
+ enableExport: false,
5459
+ initialSorting: tableConfig?.defaultSort,
5460
+ onSelectedCountChange: setSelectedCount,
5461
+ getSelectedRows: getSelectedRowsRef
4831
5462
  }),
4832
5463
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CrudFormModal, {
4833
5464
  open: resource.modal.createOpen || resource.modal.editOpen,
@@ -4860,6 +5491,19 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
4860
5491
  disabled: resource.mutations.isDeleting,
4861
5492
  children: resource.mutations.isDeleting ? "删除中..." : "确认删除"
4862
5493
  })] })] })
5494
+ }),
5495
+ canImport && resource.handlers.import && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImportDialog, {
5496
+ open: importOpen,
5497
+ onOpenChange: setImportOpen,
5498
+ onImport: resource.handlers.import,
5499
+ 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
4863
5507
  })
4864
5508
  ]
4865
5509
  });
@@ -5536,15 +6180,44 @@ function modalReducer(state, action) {
5536
6180
  /**
5537
6181
  * Hook 主函数
5538
6182
  */
5539
- function useAutoCrudResource({ router, queryInput, options = {} }) {
5540
- const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter } = options;
6183
+ function useAutoCrudResource({ router, schema, query: queryTransform, options = {} }) {
6184
+ const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher } = options;
5541
6185
  const toast = (0, react.useMemo)(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
6186
+ const columns = (0, react.useMemo)(() => createTableSchema(schema), [schema]);
6187
+ const [urlParams] = useQueryStates({
6188
+ page: parseAsInteger.withDefault(1),
6189
+ perPage: parseAsInteger.withDefault(10),
6190
+ sort: getSortingStateParser().withDefault([]),
6191
+ joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and")
6192
+ }, { shallow: false });
6193
+ const [filters] = useReadableFilters(columns, { shallow: false });
6194
+ const queryInput = (0, react.useMemo)(() => {
6195
+ const autoParams = {
6196
+ page: urlParams.page,
6197
+ perPage: urlParams.perPage,
6198
+ sort: urlParams.sort,
6199
+ filters,
6200
+ joinOperator: urlParams.joinOperator
6201
+ };
6202
+ return queryTransform ? queryTransform(autoParams) : autoParams;
6203
+ }, [
6204
+ urlParams,
6205
+ filters,
6206
+ queryTransform
6207
+ ]);
5542
6208
  const listQuery = router.list.useQuery(queryInput, { placeholderData: keepPreviousData });
5543
6209
  const createMutation = router.create.useMutation();
5544
6210
  const updateMutation = router.update.useMutation();
5545
6211
  const deleteMutation = router.delete.useMutation();
5546
6212
  const deleteManyMutation = router.deleteMany?.useMutation();
5547
6213
  const updateManyMutation = router.updateMany?.useMutation();
6214
+ const importMutation = router.import?.useMutation();
6215
+ const exportInput = (0, react.useMemo)(() => {
6216
+ if (!queryInput) return {};
6217
+ const { page: _p, perPage: _pp,...rest } = queryInput;
6218
+ return rest;
6219
+ }, [queryInput]);
6220
+ const exportQuery = router.export?.useQuery(exportInput, { enabled: false });
5548
6221
  const [modal, dispatch] = (0, react.useReducer)(modalReducer, {
5549
6222
  createOpen: false,
5550
6223
  editOpen: false,
@@ -5756,6 +6429,33 @@ function useAutoCrudResource({ router, queryInput, options = {} }) {
5756
6429
  payload: row
5757
6430
  });
5758
6431
  }, []);
6432
+ const handleImport = (0, react.useCallback)(async (rows) => {
6433
+ if (!importMutation) throw new Error("Import not supported: router has no import procedure");
6434
+ const coercedRows = coerceRowValues(rows, schema);
6435
+ const result = await importMutation.mutateAsync({
6436
+ rows: coercedRows,
6437
+ onConflict: "upsert"
6438
+ });
6439
+ listQuery.refetch();
6440
+ return result;
6441
+ }, [
6442
+ importMutation,
6443
+ listQuery,
6444
+ schema
6445
+ ]);
6446
+ const handleExport = (0, react.useCallback)(async () => {
6447
+ if (exportFetcher) {
6448
+ const { page: _p, perPage: _pp,...exportParams } = queryInput ?? {};
6449
+ return (await exportFetcher(exportParams))?.data ?? [];
6450
+ }
6451
+ if (exportQuery) return (await exportQuery.refetch())?.data?.data ?? [];
6452
+ throw new Error("Export not supported");
6453
+ }, [
6454
+ exportFetcher,
6455
+ exportQuery,
6456
+ queryInput
6457
+ ]);
6458
+ const canExport = !!(exportFetcher || exportQuery);
5759
6459
  return {
5760
6460
  tableData: {
5761
6461
  data: listQuery.data?.data ?? [],
@@ -5767,7 +6467,8 @@ function useAutoCrudResource({ router, queryInput, options = {} }) {
5767
6467
  mutations: {
5768
6468
  isCreating: createMutation.isPending,
5769
6469
  isUpdating: updateMutation.isPending,
5770
- isDeleting: deleteMutation.isPending
6470
+ isDeleting: deleteMutation.isPending,
6471
+ isImporting: importMutation?.isPending ?? false
5771
6472
  },
5772
6473
  handlers: {
5773
6474
  openCreate,
@@ -5781,7 +6482,9 @@ function useAutoCrudResource({ router, queryInput, options = {} }) {
5781
6482
  confirmDelete,
5782
6483
  deleteMany,
5783
6484
  updateMany,
5784
- setVariant
6485
+ setVariant,
6486
+ import: importMutation ? handleImport : null,
6487
+ export: canExport ? handleExport : null
5785
6488
  }
5786
6489
  };
5787
6490
  }
@@ -6103,8 +6806,11 @@ exports.DataTableFacetedFilter = DataTableFacetedFilter;
6103
6806
  exports.DataTablePagination = DataTablePagination;
6104
6807
  exports.DataTableToolbar = DataTableToolbar;
6105
6808
  exports.DataTableViewOptions = DataTableViewOptions;
6809
+ exports.ExportDialog = ExportDialog;
6810
+ exports.ImportDialog = ImportDialog;
6106
6811
  exports.SchemaAdapter = SchemaAdapter;
6107
6812
  exports.cn = cn;
6813
+ exports.coerceRowValues = coerceRowValues;
6108
6814
  exports.createActionsColumn = createActionsColumn;
6109
6815
  exports.createEditFormSchema = createEditFormSchema;
6110
6816
  exports.createFormSchema = createFormSchema;
@@ -6112,7 +6818,12 @@ exports.createMemoryDataSource = createMemoryDataSource;
6112
6818
  exports.createSelectColumn = createSelectColumn;
6113
6819
  exports.createTRPCDataSource = createTRPCDataSource;
6114
6820
  exports.createTableSchema = createTableSchema;
6821
+ exports.dataToCSV = dataToCSV;
6822
+ exports.downloadCSVTemplate = downloadCSVTemplate;
6823
+ exports.exportAllToCSV = exportAllToCSV;
6824
+ exports.exportTableToCSV = exportTableToCSV;
6115
6825
  exports.formatDate = formatDate;
6826
+ exports.generateCSVTemplate = generateCSVTemplate;
6116
6827
  exports.getUrlParams = getUrlParams;
6117
6828
  exports.humanize = humanize;
6118
6829
  exports.noopToastAdapter = noopToastAdapter;
@@ -6120,6 +6831,9 @@ exports.parseAsArrayOf = parseAsArrayOf;
6120
6831
  exports.parseAsInteger = parseAsInteger;
6121
6832
  exports.parseAsString = parseAsString;
6122
6833
  exports.parseAsStringEnum = parseAsStringEnum;
6834
+ exports.parseCSV = parseCSV;
6835
+ exports.parseImportFile = parseImportFile;
6836
+ exports.parseJSON = parseJSON;
6123
6837
  exports.parseZodField = parseZodField;
6124
6838
  exports.setSearchParams = setSearchParams;
6125
6839
  exports.useAutoCrudResource = useAutoCrudResource;