@vendure/dashboard 3.5.0-minor-202510071456 → 3.5.0-minor-202510201346
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/plugin/dashboard.plugin.js +1 -1
- package/dist/vite/utils/ast-utils.spec.js +3 -3
- package/dist/vite/vite-plugin-hmr.d.ts +8 -0
- package/dist/vite/vite-plugin-hmr.js +34 -0
- package/dist/vite/vite-plugin-theme.js +6 -6
- package/dist/vite/vite-plugin-transform-index.js +6 -1
- package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
- package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
- package/package.json +17 -5
- package/src/app/app-providers.tsx +4 -1
- package/src/app/common/map-faceted-filter-fields.ts +21 -0
- package/src/app/main.tsx +3 -1
- package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
- package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
- package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
- package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
- package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
- package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
- package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
- package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
- package/src/app/routes/_authenticated/_orders/orders.tsx +2 -0
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
- package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
- package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
- package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +2 -0
- package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
- package/src/app/routes/_authenticated/_products/products.tsx +6 -2
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
- package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
- package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -2
- package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
- package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
- package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
- package/src/app/routes/login.tsx +2 -2
- package/src/i18n/locales/ar.po +420 -289
- package/src/i18n/locales/cs.po +420 -289
- package/src/i18n/locales/de.po +420 -289
- package/src/i18n/locales/en.po +420 -289
- package/src/i18n/locales/es.po +420 -289
- package/src/i18n/locales/fa.po +420 -289
- package/src/i18n/locales/fr.po +468 -337
- package/src/i18n/locales/he.po +420 -289
- package/src/i18n/locales/hr.po +420 -289
- package/src/i18n/locales/it.po +420 -289
- package/src/i18n/locales/ja.po +420 -289
- package/src/i18n/locales/nb.po +420 -289
- package/src/i18n/locales/ne.po +420 -289
- package/src/i18n/locales/pl.po +420 -289
- package/src/i18n/locales/pt_BR.po +420 -289
- package/src/i18n/locales/pt_PT.po +420 -289
- package/src/i18n/locales/ru.po +420 -289
- package/src/i18n/locales/sv.po +420 -289
- package/src/i18n/locales/tr.po +420 -289
- package/src/i18n/locales/uk.po +420 -289
- package/src/i18n/locales/zh_Hans.po +420 -289
- package/src/i18n/locales/zh_Hant.po +420 -289
- package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
- package/src/lib/components/data-input/affixed-input.tsx +5 -2
- package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
- package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
- package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
- package/src/lib/components/data-input/datetime-input.tsx +27 -13
- package/src/lib/components/data-input/default-relation-input.tsx +18 -12
- package/src/lib/components/data-input/money-input.stories.tsx +88 -0
- package/src/lib/components/data-input/number-input.stories.tsx +103 -0
- package/src/lib/components/data-input/number-input.tsx +10 -4
- package/src/lib/components/data-input/password-form-input.stories.tsx +65 -0
- package/src/lib/components/data-input/{password-input.tsx → password-form-input.tsx} +1 -1
- package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
- package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
- package/src/lib/components/data-input/slug-input.tsx +9 -10
- package/src/lib/components/data-input/text-input.stories.tsx +52 -0
- package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
- package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
- package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
- package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
- package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
- package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
- package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
- package/src/lib/components/data-table/data-table-filter-badge.tsx +23 -16
- package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
- package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
- package/src/lib/components/data-table/data-table.stories.tsx +249 -0
- package/src/lib/components/data-table/data-table.tsx +37 -9
- package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
- package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
- package/src/lib/components/layout/nav-user.tsx +19 -13
- package/src/lib/components/login/login-form.tsx +39 -123
- package/src/lib/components/shared/alerts.tsx +29 -17
- package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
- package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
- package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
- package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
- package/src/lib/components/shared/customer-group-selector.tsx +5 -2
- package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
- package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
- package/src/lib/components/shared/facet-value-selector.tsx +130 -34
- package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
- package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
- package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
- package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
- package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
- package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
- package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
- package/src/lib/components/shared/vendure-image.tsx +6 -7
- package/src/lib/components/ui/accordion.stories.tsx +33 -0
- package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
- package/src/lib/components/ui/alert.stories.tsx +35 -0
- package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
- package/src/lib/components/ui/badge.stories.tsx +28 -0
- package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
- package/src/lib/components/ui/button.stories.tsx +38 -0
- package/src/lib/components/ui/calendar.stories.tsx +22 -0
- package/src/lib/components/ui/card.stories.tsx +28 -0
- package/src/lib/components/ui/carousel.stories.tsx +34 -0
- package/src/lib/components/ui/checkbox.stories.tsx +31 -0
- package/src/lib/components/ui/collapsible.stories.tsx +39 -0
- package/src/lib/components/ui/command.stories.tsx +44 -0
- package/src/lib/components/ui/context-menu.stories.tsx +38 -0
- package/src/lib/components/ui/dialog.stories.tsx +52 -0
- package/src/lib/components/ui/drawer.stories.tsx +50 -0
- package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
- package/src/lib/components/ui/hover-card.stories.tsx +38 -0
- package/src/lib/components/ui/input-group.tsx +148 -0
- package/src/lib/components/ui/input-otp.stories.tsx +30 -0
- package/src/lib/components/ui/input.stories.tsx +38 -0
- package/src/lib/components/ui/label.stories.tsx +24 -0
- package/src/lib/components/ui/menubar.stories.tsx +53 -0
- package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
- package/src/lib/components/ui/pagination.stories.tsx +51 -0
- package/src/lib/components/ui/password-input.stories.tsx +32 -0
- package/src/lib/components/ui/password-input.tsx +29 -0
- package/src/lib/components/ui/popover.stories.tsx +33 -0
- package/src/lib/components/ui/progress.stories.tsx +27 -0
- package/src/lib/components/ui/radio-group.stories.tsx +34 -0
- package/src/lib/components/ui/resizable.stories.tsx +32 -0
- package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
- package/src/lib/components/ui/select.stories.tsx +36 -0
- package/src/lib/components/ui/separator.stories.tsx +35 -0
- package/src/lib/components/ui/sheet.stories.tsx +50 -0
- package/src/lib/components/ui/sidebar-context.ts +16 -0
- package/src/lib/components/ui/sidebar.tsx +2 -13
- package/src/lib/components/ui/skeleton.stories.tsx +26 -0
- package/src/lib/components/ui/slider.stories.tsx +37 -0
- package/src/lib/components/ui/switch.stories.tsx +31 -0
- package/src/lib/components/ui/table.stories.tsx +52 -0
- package/src/lib/components/ui/tabs.stories.tsx +29 -0
- package/src/lib/components/ui/textarea.stories.tsx +32 -0
- package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
- package/src/lib/components/ui/toggle.stories.tsx +39 -0
- package/src/lib/components/ui/tooltip.stories.tsx +30 -0
- package/src/lib/components/ui/tooltip.tsx +2 -2
- package/src/lib/framework/alert/alert-extensions.tsx +0 -11
- package/src/lib/framework/alert/alert-item.tsx +14 -19
- package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
- package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
- package/src/lib/framework/component-registry/component-registry.tsx +3 -14
- package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
- package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
- package/src/lib/framework/defaults.ts +9 -13
- package/src/lib/framework/extension-api/input-component-extensions.tsx +8 -3
- package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
- package/src/lib/framework/extension-api/types/alerts.ts +12 -6
- package/src/lib/framework/extension-api/types/data-table.ts +5 -2
- package/src/lib/framework/extension-api/types/login.ts +0 -21
- package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +11 -9
- package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
- package/src/lib/framework/page/detail-page.stories.tsx +151 -0
- package/src/lib/framework/page/list-page.stories.tsx +217 -0
- package/src/lib/framework/page/list-page.tsx +8 -1
- package/src/lib/graphql/api.ts +18 -1
- package/src/lib/graphql/graphql-env.d.ts +1 -1
- package/src/lib/hooks/use-alerts.ts +84 -0
- package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
- package/src/lib/index.ts +14 -1
- package/src/lib/providers/alerts-provider.tsx +60 -0
- package/src/lib/providers/theme-provider.tsx +6 -3
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { Badge } from '@/vdb/components/ui/badge.js';
|
|
2
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
3
|
+
import { FullWidthPageBlock, Page, PageLayout } from '@/vdb/framework/layout-engine/page-layout.js';
|
|
4
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
5
|
+
import { ColumnDef, ColumnFiltersState, SortingState } from '@tanstack/react-table';
|
|
6
|
+
import { useState } from 'react';
|
|
7
|
+
import { withDescription } from '../../../../.storybook/with-description.js';
|
|
8
|
+
import { DataTable } from './data-table.js';
|
|
9
|
+
|
|
10
|
+
// Sample data type
|
|
11
|
+
interface Product {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
category: string;
|
|
15
|
+
price: number;
|
|
16
|
+
stock: number;
|
|
17
|
+
status: 'active' | 'inactive' | 'discontinued';
|
|
18
|
+
createdAt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Sample data
|
|
22
|
+
const sampleData: Product[] = Array.from({ length: 100 }, (_, i) => ({
|
|
23
|
+
id: `product-${i + 1}`,
|
|
24
|
+
name: `Product ${i + 1}`,
|
|
25
|
+
category: ['Electronics', 'Clothing', 'Home & Garden', 'Sports', 'Books'][i % 5],
|
|
26
|
+
price: Math.floor(Math.random() * 1000) + 10,
|
|
27
|
+
stock: Math.floor(Math.random() * 100),
|
|
28
|
+
status: (['active', 'inactive', 'discontinued'] as const)[i % 3],
|
|
29
|
+
createdAt: new Date(Date.now() - Math.random() * 10000000000).toISOString(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const meta = {
|
|
33
|
+
title: 'Components/DataTable',
|
|
34
|
+
component: DataTable,
|
|
35
|
+
...withDescription(import.meta.url, './data-table.js'),
|
|
36
|
+
parameters: {
|
|
37
|
+
layout: 'fullscreen',
|
|
38
|
+
},
|
|
39
|
+
tags: ['autodocs'],
|
|
40
|
+
} satisfies Meta<typeof DataTable>;
|
|
41
|
+
|
|
42
|
+
export default meta;
|
|
43
|
+
type Story = StoryObj<typeof meta>;
|
|
44
|
+
|
|
45
|
+
export const Playground: Story = {
|
|
46
|
+
render: () => {
|
|
47
|
+
const [page, setPage] = useState(1);
|
|
48
|
+
const [pageSize, setPageSize] = useState(10);
|
|
49
|
+
const [sorting, setSorting] = useState<SortingState>([{ id: 'name', desc: false }]);
|
|
50
|
+
const [filters, setFilters] = useState<ColumnFiltersState>([]);
|
|
51
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
52
|
+
|
|
53
|
+
// Define columns
|
|
54
|
+
const columns: ColumnDef<Product>[] = [
|
|
55
|
+
{
|
|
56
|
+
id: 'name',
|
|
57
|
+
accessorKey: 'name',
|
|
58
|
+
header: 'Product Name',
|
|
59
|
+
cell: ({ row }) => <span className="font-medium">{row.original.name}</span>,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'category',
|
|
63
|
+
accessorKey: 'category',
|
|
64
|
+
header: 'Category',
|
|
65
|
+
cell: ({ row }) => <span>{row.original.category}</span>,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'price',
|
|
69
|
+
accessorKey: 'price',
|
|
70
|
+
header: 'Price',
|
|
71
|
+
cell: ({ row }) => <span>${row.original.price.toFixed(2)}</span>,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'stock',
|
|
75
|
+
accessorKey: 'stock',
|
|
76
|
+
header: 'Stock',
|
|
77
|
+
cell: ({ row }) => {
|
|
78
|
+
const stock = row.original.stock;
|
|
79
|
+
return (
|
|
80
|
+
<Badge variant={stock > 50 ? 'default' : stock > 20 ? 'secondary' : 'destructive'}>
|
|
81
|
+
{stock}
|
|
82
|
+
</Badge>
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'status',
|
|
88
|
+
accessorKey: 'status',
|
|
89
|
+
header: 'Status',
|
|
90
|
+
cell: ({ row }) => {
|
|
91
|
+
const status = row.original.status;
|
|
92
|
+
return (
|
|
93
|
+
<Badge
|
|
94
|
+
variant={
|
|
95
|
+
status === 'active'
|
|
96
|
+
? 'default'
|
|
97
|
+
: status === 'inactive'
|
|
98
|
+
? 'secondary'
|
|
99
|
+
: 'outline'
|
|
100
|
+
}
|
|
101
|
+
>
|
|
102
|
+
{status}
|
|
103
|
+
</Badge>
|
|
104
|
+
);
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'createdAt',
|
|
109
|
+
accessorKey: 'createdAt',
|
|
110
|
+
header: 'Created',
|
|
111
|
+
cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString(),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'actions',
|
|
115
|
+
header: 'Actions',
|
|
116
|
+
cell: ({ row }) => (
|
|
117
|
+
<div className="flex gap-2">
|
|
118
|
+
<Button
|
|
119
|
+
variant="ghost"
|
|
120
|
+
size="sm"
|
|
121
|
+
onClick={() => console.log('Edit', row.original.id)}
|
|
122
|
+
>
|
|
123
|
+
Edit
|
|
124
|
+
</Button>
|
|
125
|
+
<Button
|
|
126
|
+
variant="ghost"
|
|
127
|
+
size="sm"
|
|
128
|
+
onClick={() => console.log('Delete', row.original.id)}
|
|
129
|
+
>
|
|
130
|
+
Delete
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
// Filter and sort data based on state
|
|
138
|
+
let filteredData = [...sampleData];
|
|
139
|
+
|
|
140
|
+
// Apply search filter
|
|
141
|
+
if (searchTerm) {
|
|
142
|
+
filteredData = filteredData.filter(item =>
|
|
143
|
+
item.name.toLowerCase().includes(searchTerm.toLowerCase()),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Apply column filters
|
|
148
|
+
filters.forEach(filter => {
|
|
149
|
+
if (filter.id === 'status' && Array.isArray(filter.value)) {
|
|
150
|
+
filteredData = filteredData.filter(item => filter.value.includes(item.status));
|
|
151
|
+
}
|
|
152
|
+
if (filter.id === 'category' && Array.isArray(filter.value)) {
|
|
153
|
+
filteredData = filteredData.filter(item => filter.value.includes(item.category));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Apply sorting
|
|
158
|
+
if (sorting.length > 0) {
|
|
159
|
+
const sort = sorting[0];
|
|
160
|
+
filteredData.sort((a, b) => {
|
|
161
|
+
const aValue = a[sort.id as keyof Product];
|
|
162
|
+
const bValue = b[sort.id as keyof Product];
|
|
163
|
+
if (aValue < bValue) return sort.desc ? 1 : -1;
|
|
164
|
+
if (aValue > bValue) return sort.desc ? -1 : 1;
|
|
165
|
+
return 0;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const totalItems = filteredData.length;
|
|
170
|
+
const paginatedData = filteredData.slice((page - 1) * pageSize, page * pageSize);
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div className="p-6">
|
|
174
|
+
<Page pageId="test-page">
|
|
175
|
+
<PageLayout>
|
|
176
|
+
<FullWidthPageBlock blockId="test-block">
|
|
177
|
+
<DataTable
|
|
178
|
+
columns={columns}
|
|
179
|
+
data={paginatedData}
|
|
180
|
+
totalItems={totalItems}
|
|
181
|
+
page={page}
|
|
182
|
+
itemsPerPage={pageSize}
|
|
183
|
+
sorting={sorting}
|
|
184
|
+
columnFilters={filters}
|
|
185
|
+
defaultColumnVisibility={{
|
|
186
|
+
createdAt: false,
|
|
187
|
+
}}
|
|
188
|
+
onPageChange={(_, newPage, newPageSize) => {
|
|
189
|
+
setPage(newPage);
|
|
190
|
+
setPageSize(newPageSize);
|
|
191
|
+
}}
|
|
192
|
+
onSortChange={(_, newSorting) => {
|
|
193
|
+
setSorting(newSorting);
|
|
194
|
+
}}
|
|
195
|
+
onFilterChange={(_, newFilters) => {
|
|
196
|
+
setFilters(newFilters);
|
|
197
|
+
setPage(1); // Reset to first page when filters change
|
|
198
|
+
}}
|
|
199
|
+
onSearchTermChange={term => {
|
|
200
|
+
setSearchTerm(term);
|
|
201
|
+
setPage(1); // Reset to first page when search changes
|
|
202
|
+
}}
|
|
203
|
+
facetedFilters={{
|
|
204
|
+
status: {
|
|
205
|
+
title: 'Status',
|
|
206
|
+
options: [
|
|
207
|
+
{ label: 'Active', value: 'active' },
|
|
208
|
+
{ label: 'Inactive', value: 'inactive' },
|
|
209
|
+
{ label: 'Discontinued', value: 'discontinued' },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
category: {
|
|
213
|
+
title: 'Category',
|
|
214
|
+
options: [
|
|
215
|
+
{ label: 'Electronics', value: 'Electronics' },
|
|
216
|
+
{ label: 'Clothing', value: 'Clothing' },
|
|
217
|
+
{ label: 'Home & Garden', value: 'Home & Garden' },
|
|
218
|
+
{ label: 'Sports', value: 'Sports' },
|
|
219
|
+
{ label: 'Books', value: 'Books' },
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
}}
|
|
223
|
+
bulkActions={[
|
|
224
|
+
{
|
|
225
|
+
label: 'Delete selected',
|
|
226
|
+
icon: 'trash',
|
|
227
|
+
onClick: (selectedItems: Product[]) => {
|
|
228
|
+
console.log('Delete selected:', selectedItems);
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
label: 'Export selected',
|
|
233
|
+
icon: 'download',
|
|
234
|
+
onClick: (selectedItems: Product[]) => {
|
|
235
|
+
console.log('Export selected:', selectedItems);
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
]}
|
|
239
|
+
onRefresh={() => {
|
|
240
|
+
console.log('Refresh data');
|
|
241
|
+
}}
|
|
242
|
+
/>
|
|
243
|
+
</FullWidthPageBlock>
|
|
244
|
+
</PageLayout>
|
|
245
|
+
</Page>
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
},
|
|
249
|
+
};
|
|
@@ -29,12 +29,12 @@ import {
|
|
|
29
29
|
VisibilityState,
|
|
30
30
|
} from '@tanstack/react-table';
|
|
31
31
|
import { RowSelectionState, TableOptions } from '@tanstack/table-core';
|
|
32
|
-
import React, { Suspense, useEffect } from 'react';
|
|
32
|
+
import React, { Suspense, useEffect, useRef } from 'react';
|
|
33
33
|
import { AddFilterMenu } from './add-filter-menu.js';
|
|
34
34
|
import { DataTableBulkActions } from './data-table-bulk-actions.js';
|
|
35
35
|
import { DataTableProvider } from './data-table-context.js';
|
|
36
36
|
import { DataTableFacetedFilter, DataTableFacetedFilterOption } from './data-table-faceted-filter.js';
|
|
37
|
-
import {
|
|
37
|
+
import { DataTableFilterBadgeEditable } from './data-table-filter-badge-editable.js';
|
|
38
38
|
|
|
39
39
|
export interface FacetedFilter {
|
|
40
40
|
title: string;
|
|
@@ -128,6 +128,8 @@ export function DataTable<TData>({
|
|
|
128
128
|
defaultColumnVisibility ?? {},
|
|
129
129
|
);
|
|
130
130
|
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
|
|
131
|
+
const prevSearchTermRef = useRef(searchTerm);
|
|
132
|
+
const prevColumnFiltersRef = useRef(columnFilters);
|
|
131
133
|
|
|
132
134
|
useEffect(() => {
|
|
133
135
|
// If the defaultColumnVisibility changes externally (e.g. the user reset the table settings),
|
|
@@ -179,21 +181,46 @@ export function DataTable<TData>({
|
|
|
179
181
|
onSortChange?.(table, sorting);
|
|
180
182
|
}, [sorting]);
|
|
181
183
|
|
|
182
|
-
useEffect(() => {
|
|
183
|
-
onFilterChange?.(table, columnFilters);
|
|
184
|
-
}, [columnFilters]);
|
|
185
|
-
|
|
186
184
|
useEffect(() => {
|
|
187
185
|
onColumnVisibilityChange?.(table, columnVisibility);
|
|
188
186
|
}, [columnVisibility]);
|
|
189
187
|
|
|
190
|
-
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
if (page && page > 1 && itemsPerPage && prevSearchTermRef.current !== searchTerm) {
|
|
190
|
+
// Set the page back to 1 when searchTerm changes
|
|
191
|
+
setPagination({
|
|
192
|
+
...pagination,
|
|
193
|
+
pageIndex: 0,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
prevSearchTermRef.current = searchTerm;
|
|
197
|
+
}, [onPageChange, searchTerm]);
|
|
198
|
+
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
onFilterChange?.(table, columnFilters);
|
|
201
|
+
if (
|
|
202
|
+
page &&
|
|
203
|
+
page > 1 &&
|
|
204
|
+
itemsPerPage &&
|
|
205
|
+
JSON.stringify(prevColumnFiltersRef.current) !== JSON.stringify(columnFilters)
|
|
206
|
+
) {
|
|
207
|
+
// Set the page back to 1 when filters change
|
|
208
|
+
setPagination({
|
|
209
|
+
...pagination,
|
|
210
|
+
pageIndex: 0,
|
|
211
|
+
});
|
|
212
|
+
pagination.pageIndex;
|
|
213
|
+
}
|
|
214
|
+
prevColumnFiltersRef.current = columnFilters;
|
|
215
|
+
}, [columnFilters]);
|
|
191
216
|
|
|
192
217
|
const handleSearchChange = (value: string) => {
|
|
193
218
|
setSearchTerm(value);
|
|
194
219
|
onSearchTermChange?.(value);
|
|
195
220
|
};
|
|
196
221
|
|
|
222
|
+
const visibleColumnCount = Object.values(columnVisibility).filter(Boolean).length;
|
|
223
|
+
|
|
197
224
|
return (
|
|
198
225
|
<DataTableProvider
|
|
199
226
|
columnFilters={columnFilters}
|
|
@@ -254,9 +281,10 @@ export function DataTable<TData>({
|
|
|
254
281
|
const column = table.getColumn(f.id);
|
|
255
282
|
const currency = activeChannel?.defaultCurrencyCode ?? 'USD';
|
|
256
283
|
return (
|
|
257
|
-
<
|
|
284
|
+
<DataTableFilterBadgeEditable
|
|
258
285
|
key={f.id}
|
|
259
286
|
filter={f}
|
|
287
|
+
column={column}
|
|
260
288
|
currencyCode={currency}
|
|
261
289
|
dataType={
|
|
262
290
|
(column?.columnDef.meta as any)?.fieldInfo?.type ?? 'String'
|
|
@@ -303,7 +331,7 @@ export function DataTable<TData>({
|
|
|
303
331
|
</TableHeader>
|
|
304
332
|
<TableBody>
|
|
305
333
|
{isLoading && !data?.length ? (
|
|
306
|
-
Array.from({ length: pagination.pageSize }).map((_, index) => (
|
|
334
|
+
Array.from({ length: Math.min(pagination.pageSize, 100) }).map((_, index) => (
|
|
307
335
|
<TableRow
|
|
308
336
|
key={`skeleton-${index}`}
|
|
309
337
|
className="animate-in fade-in duration-100"
|
|
@@ -10,47 +10,92 @@ export interface DataTableDateTimeFilterProps {
|
|
|
10
10
|
|
|
11
11
|
export const DATETIME_OPERATORS = ['eq', 'before', 'after', 'between', 'isNull'] as const;
|
|
12
12
|
|
|
13
|
+
export interface DateTimeFilterResult {
|
|
14
|
+
filter: Record<string, any>;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ParsedDateTimeFilter {
|
|
19
|
+
operator: string;
|
|
20
|
+
value?: Date;
|
|
21
|
+
startDate?: Date;
|
|
22
|
+
endDate?: Date;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseDateTimeFilter(incomingValue: Record<string, any> | undefined): ParsedDateTimeFilter {
|
|
26
|
+
if (!incomingValue || Object.keys(incomingValue).length === 0) {
|
|
27
|
+
return { operator: 'eq' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const operator = Object.keys(incomingValue)[0];
|
|
31
|
+
const value = Object.values(incomingValue)[0];
|
|
32
|
+
|
|
33
|
+
if (operator === 'isNull') {
|
|
34
|
+
return { operator };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (operator === 'between' && typeof value === 'object' && value !== null) {
|
|
38
|
+
return {
|
|
39
|
+
operator,
|
|
40
|
+
startDate: value.start ? new Date(value.start) : undefined,
|
|
41
|
+
endDate: value.end ? new Date(value.end) : undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// For eq, before, after operators
|
|
46
|
+
return {
|
|
47
|
+
operator,
|
|
48
|
+
value: typeof value === 'string' ? new Date(value) : undefined,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildDateTimeFilter(
|
|
53
|
+
operator: string,
|
|
54
|
+
value?: Date,
|
|
55
|
+
startDate?: Date,
|
|
56
|
+
endDate?: Date,
|
|
57
|
+
): DateTimeFilterResult {
|
|
58
|
+
if (operator === 'isNull') {
|
|
59
|
+
return { filter: { [operator]: true } };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (operator === 'between') {
|
|
63
|
+
if (!startDate && !endDate) {
|
|
64
|
+
return { filter: {} };
|
|
65
|
+
}
|
|
66
|
+
if (!startDate || !endDate) {
|
|
67
|
+
return { filter: {}, error: 'Please enter both start and end dates' };
|
|
68
|
+
}
|
|
69
|
+
if (startDate > endDate) {
|
|
70
|
+
return { filter: {}, error: 'Start date must be before end date' };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
filter: { [operator]: { start: startDate.toISOString(), end: endDate.toISOString() } },
|
|
74
|
+
};
|
|
75
|
+
} else {
|
|
76
|
+
if (!value) {
|
|
77
|
+
return { filter: {} };
|
|
78
|
+
}
|
|
79
|
+
return { filter: { [operator]: value.toISOString() } };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
13
83
|
export function DataTableDateTimeFilter({
|
|
14
84
|
value: incomingValue,
|
|
15
85
|
onChange,
|
|
16
86
|
}: Readonly<DataTableDateTimeFilterProps>) {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const [
|
|
20
|
-
const [
|
|
21
|
-
const [
|
|
22
|
-
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
|
|
87
|
+
const parsed = parseDateTimeFilter(incomingValue);
|
|
88
|
+
const [operator, setOperator] = useState<string>(parsed.operator);
|
|
89
|
+
const [value, setValue] = useState<Date | undefined>(parsed.value);
|
|
90
|
+
const [startDate, setStartDate] = useState<Date | undefined>(parsed.startDate);
|
|
91
|
+
const [endDate, setEndDate] = useState<Date | undefined>(parsed.endDate);
|
|
23
92
|
const [error, setError] = useState<string>('');
|
|
24
93
|
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (operator === 'isNull') {
|
|
27
|
-
onChange({ [operator]: true });
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
94
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
if (!startDate || !endDate) {
|
|
37
|
-
setError('Please enter both start and end dates');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
if (startDate > endDate) {
|
|
41
|
-
setError('Start date must be before end date');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
setError('');
|
|
45
|
-
onChange({ [operator]: { start: startDate.toISOString(), end: endDate.toISOString() } });
|
|
46
|
-
} else {
|
|
47
|
-
if (!value) {
|
|
48
|
-
onChange({});
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
setError('');
|
|
52
|
-
onChange({ [operator]: value.toISOString() });
|
|
53
|
-
}
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
const result = buildDateTimeFilter(operator, value, startDate, endDate);
|
|
97
|
+
onChange(result.filter);
|
|
98
|
+
setError(result.error ?? '');
|
|
54
99
|
}, [operator, value, startDate, endDate]);
|
|
55
100
|
|
|
56
101
|
const parseToDate = (input: unknown): Date | undefined => {
|
|
@@ -5,8 +5,14 @@ import {
|
|
|
5
5
|
getOperationVariablesFields,
|
|
6
6
|
getTypeFieldInfo,
|
|
7
7
|
} from '@/vdb/framework/document-introspection/get-document-structure.js';
|
|
8
|
+
import {
|
|
9
|
+
generateDisplayComponentKey,
|
|
10
|
+
getDisplayComponent,
|
|
11
|
+
} from '@/vdb/framework/extension-api/display-component-extensions.js';
|
|
8
12
|
import { BulkAction } from '@/vdb/framework/extension-api/types/index.js';
|
|
9
13
|
import { api } from '@/vdb/graphql/api.js';
|
|
14
|
+
import { usePageBlock } from '@/vdb/hooks/use-page-block.js';
|
|
15
|
+
import { usePage } from '@/vdb/hooks/use-page.js';
|
|
10
16
|
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
11
17
|
import { Trans, useLingui } from '@lingui/react/macro';
|
|
12
18
|
import { useMutation } from '@tanstack/react-query';
|
|
@@ -36,7 +42,12 @@ import {
|
|
|
36
42
|
} from '../ui/alert-dialog.js';
|
|
37
43
|
import { Button } from '../ui/button.js';
|
|
38
44
|
import { Checkbox } from '../ui/checkbox.js';
|
|
39
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
DropdownMenu,
|
|
47
|
+
DropdownMenuContent,
|
|
48
|
+
DropdownMenuItem,
|
|
49
|
+
DropdownMenuTrigger,
|
|
50
|
+
} from '../ui/dropdown-menu.js';
|
|
40
51
|
import { DataTableColumnHeader } from './data-table-column-header.js';
|
|
41
52
|
|
|
42
53
|
/**
|
|
@@ -77,6 +88,8 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
|
|
|
77
88
|
columns: Array<AccessorKeyColumnDef<any> | AccessorFnColumnDef<any>>;
|
|
78
89
|
customFieldColumnNames: string[];
|
|
79
90
|
} {
|
|
91
|
+
const { pageId } = usePage();
|
|
92
|
+
const pageBlock = usePageBlock();
|
|
80
93
|
const columnHelper = createColumnHelper<PaginatedListItemFields<T>>();
|
|
81
94
|
const allBulkActions = useAllBulkActions(bulkActions ?? []);
|
|
82
95
|
|
|
@@ -101,47 +114,39 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
|
|
|
101
114
|
|
|
102
115
|
const queryBasedColumns = columnConfigs.map(({ fieldInfo, isCustomField }) => {
|
|
103
116
|
const customConfig = customizeColumns?.[fieldInfo.name as unknown as AllItemFieldKeys<T>] ?? {};
|
|
104
|
-
const { header, ...customConfigRest } = customConfig;
|
|
117
|
+
const { header, meta, cell: customCell, ...customConfigRest } = customConfig;
|
|
105
118
|
const enableColumnFilter = fieldInfo.isScalar && !facetedFilters?.[fieldInfo.name];
|
|
106
119
|
|
|
107
120
|
return columnHelper.accessor(fieldInfo.name as any, {
|
|
108
121
|
id: fieldInfo.name,
|
|
109
|
-
meta: { fieldInfo, isCustomField },
|
|
122
|
+
meta: { fieldInfo, isCustomField, ...(meta ?? {}) },
|
|
110
123
|
enableColumnFilter,
|
|
111
|
-
enableSorting: fieldInfo.isScalar && enableSorting,
|
|
124
|
+
enableSorting: fieldInfo.isScalar && fieldInfo.type !== 'Boolean' && enableSorting,
|
|
112
125
|
// Filtering is done on the server side, but we set this to 'equalsString' because
|
|
113
126
|
// otherwise the TanStack Table with apply an "auto" function which somehow
|
|
114
127
|
// prevents certain filters from working.
|
|
115
128
|
filterFn: 'equalsString',
|
|
116
|
-
cell:
|
|
129
|
+
cell: cellContext => {
|
|
130
|
+
const { cell, row } = cellContext;
|
|
117
131
|
const cellValue = cell.getValue();
|
|
118
132
|
const value =
|
|
119
133
|
cellValue ??
|
|
120
134
|
(isCustomField ? row.original?.customFields?.[fieldInfo.name] : undefined);
|
|
135
|
+
const displayComponentId =
|
|
136
|
+
pageId && pageBlock?.blockId
|
|
137
|
+
? generateDisplayComponentKey(pageId, pageBlock.blockId, fieldInfo.name)
|
|
138
|
+
: undefined;
|
|
121
139
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
value instanceof Date
|
|
128
|
-
) {
|
|
129
|
-
return <DisplayComponent id="vendure:dateTime" value={value} />;
|
|
130
|
-
}
|
|
131
|
-
if (fieldInfo.type === 'Boolean') {
|
|
132
|
-
if (cell.column.id === 'enabled') {
|
|
133
|
-
return <DisplayComponent id="vendure:booleanBadge" value={value} />;
|
|
134
|
-
} else {
|
|
135
|
-
return <DisplayComponent id="vendure:booleanCheckbox" value={value} />;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (fieldInfo.type === 'Asset') {
|
|
139
|
-
return <DisplayComponent id="vendure:asset" value={value} />;
|
|
140
|
+
const CustomDisplayComponent =
|
|
141
|
+
displayComponentId && getDisplayComponent(displayComponentId);
|
|
142
|
+
|
|
143
|
+
if (CustomDisplayComponent) {
|
|
144
|
+
return <CustomDisplayComponent value={value} {...cellContext} />;
|
|
140
145
|
}
|
|
141
|
-
if (
|
|
142
|
-
return
|
|
146
|
+
if (typeof customCell === 'function') {
|
|
147
|
+
return customCell(cellContext);
|
|
143
148
|
}
|
|
144
|
-
return value
|
|
149
|
+
return <DefaultDisplayComponent value={value} fieldInfo={fieldInfo} />;
|
|
145
150
|
},
|
|
146
151
|
header: headerContext => {
|
|
147
152
|
return (
|
|
@@ -158,7 +163,7 @@ export function useGeneratedColumns<T extends TypedDocumentNode<any, any>>({
|
|
|
158
163
|
if (!id) {
|
|
159
164
|
throw new Error('Column id is required');
|
|
160
165
|
}
|
|
161
|
-
finalColumns.push(columnHelper.accessor(id as any, { ...column, id }));
|
|
166
|
+
finalColumns.push(columnHelper.accessor(id as any, { ...column, id, enableColumnFilter: false }));
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
if (defaultColumnOrder) {
|
|
@@ -259,6 +264,29 @@ function getRowActions(
|
|
|
259
264
|
};
|
|
260
265
|
}
|
|
261
266
|
|
|
267
|
+
function DefaultDisplayComponent({ value, fieldInfo }: { value: any; fieldInfo: FieldInfo }) {
|
|
268
|
+
if (fieldInfo.list && Array.isArray(value)) {
|
|
269
|
+
return value.join(', ');
|
|
270
|
+
}
|
|
271
|
+
if ((fieldInfo.type === 'DateTime' && typeof value === 'string') || value instanceof Date) {
|
|
272
|
+
return <DisplayComponent id="vendure:dateTime" value={value} />;
|
|
273
|
+
}
|
|
274
|
+
if (fieldInfo.type === 'Boolean') {
|
|
275
|
+
if (fieldInfo.name === 'enabled') {
|
|
276
|
+
return <DisplayComponent id="vendure:booleanBadge" value={value} />;
|
|
277
|
+
} else {
|
|
278
|
+
return <DisplayComponent id="vendure:booleanCheckbox" value={value} />;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (fieldInfo.type === 'Asset') {
|
|
282
|
+
return <DisplayComponent id="vendure:asset" value={value} />;
|
|
283
|
+
}
|
|
284
|
+
if (value !== null && typeof value === 'object') {
|
|
285
|
+
return <DisplayComponent id="vendure:json" value={value} />;
|
|
286
|
+
}
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
|
|
262
290
|
function DeleteMutationRowAction({
|
|
263
291
|
deleteMutation,
|
|
264
292
|
row,
|