@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
|
@@ -7,17 +7,46 @@ export type NavMenuSectionPlacement = 'top' | 'bottom';
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @description
|
|
10
|
-
*
|
|
10
|
+
* Defines an items in the navigation menu.
|
|
11
11
|
*
|
|
12
12
|
* @docsCategory extensions-api
|
|
13
13
|
* @docsPage Navigation
|
|
14
14
|
* @since 3.4.0
|
|
15
15
|
*/
|
|
16
|
-
interface
|
|
16
|
+
interface NavMenuItem {
|
|
17
|
+
/**
|
|
18
|
+
* @description
|
|
19
|
+
* A unique ID for this nav menu item
|
|
20
|
+
*/
|
|
17
21
|
id: string;
|
|
22
|
+
/**
|
|
23
|
+
* @description
|
|
24
|
+
* The title that will appear in the nav menu
|
|
25
|
+
*/
|
|
18
26
|
title: string;
|
|
27
|
+
/**
|
|
28
|
+
* @description
|
|
29
|
+
* The url of the route which this nav item links to.
|
|
30
|
+
*/
|
|
31
|
+
url: string;
|
|
32
|
+
/**
|
|
33
|
+
* @description
|
|
34
|
+
* An optional icon component to represent the item,
|
|
35
|
+
* which should be imported from `lucide-react`.
|
|
36
|
+
*/
|
|
19
37
|
icon?: LucideIcon;
|
|
38
|
+
/**
|
|
39
|
+
* @description
|
|
40
|
+
* The order is an number which allows you to control
|
|
41
|
+
* the relative position in relation to other items in the
|
|
42
|
+
* menu.
|
|
43
|
+
* A higher number appears further down the list.
|
|
44
|
+
*/
|
|
20
45
|
order?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Whether this item should appear in the top of bottom section
|
|
48
|
+
* of the nav menu.
|
|
49
|
+
*/
|
|
21
50
|
placement?: NavMenuSectionPlacement;
|
|
22
51
|
/**
|
|
23
52
|
* @description
|
|
@@ -27,23 +56,7 @@ interface NavMenuBaseItem {
|
|
|
27
56
|
requiresPermission?: string | string[];
|
|
28
57
|
}
|
|
29
58
|
|
|
30
|
-
|
|
31
|
-
* @description
|
|
32
|
-
* Defines an items in the navigation menu.
|
|
33
|
-
*
|
|
34
|
-
* @docsCategory extensions-api
|
|
35
|
-
* @docsPage Navigation
|
|
36
|
-
* @since 3.4.0
|
|
37
|
-
*/
|
|
38
|
-
export interface NavMenuItem extends NavMenuBaseItem {
|
|
39
|
-
/**
|
|
40
|
-
* @description
|
|
41
|
-
* The url of the route which this nav item links to.
|
|
42
|
-
*/
|
|
43
|
-
url: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface NavMenuSection extends NavMenuBaseItem {
|
|
59
|
+
export interface NavMenuSection extends Omit<NavMenuItem, 'url'> {
|
|
47
60
|
defaultOpen?: boolean;
|
|
48
61
|
items?: NavMenuItem[];
|
|
49
62
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { DemoRouterProvider } from '../../../../.storybook/providers.js';
|
|
4
|
+
import { DetailPage, DetailPageProps } from './detail-page.js';
|
|
5
|
+
|
|
6
|
+
// Sample GraphQL query for a product detail
|
|
7
|
+
const productFragment = graphql(`
|
|
8
|
+
fragment ProductDetail on Product {
|
|
9
|
+
id
|
|
10
|
+
createdAt
|
|
11
|
+
updatedAt
|
|
12
|
+
name
|
|
13
|
+
slug
|
|
14
|
+
description
|
|
15
|
+
enabled
|
|
16
|
+
featuredAsset {
|
|
17
|
+
id
|
|
18
|
+
}
|
|
19
|
+
assets {
|
|
20
|
+
id
|
|
21
|
+
}
|
|
22
|
+
facetValues {
|
|
23
|
+
id
|
|
24
|
+
}
|
|
25
|
+
translations {
|
|
26
|
+
id
|
|
27
|
+
languageCode
|
|
28
|
+
name
|
|
29
|
+
slug
|
|
30
|
+
description
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
`);
|
|
34
|
+
|
|
35
|
+
const productQuery = graphql(
|
|
36
|
+
`
|
|
37
|
+
query Product($id: ID!) {
|
|
38
|
+
product(id: $id) {
|
|
39
|
+
...ProductDetail
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`,
|
|
43
|
+
[productFragment],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const createProductDocument = graphql(`
|
|
47
|
+
mutation CreateProduct($input: CreateProductInput!) {
|
|
48
|
+
createProduct(input: $input) {
|
|
49
|
+
id
|
|
50
|
+
name
|
|
51
|
+
slug
|
|
52
|
+
description
|
|
53
|
+
enabled
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`);
|
|
57
|
+
|
|
58
|
+
const updateProductDocument = graphql(`
|
|
59
|
+
mutation UpdateProduct($input: UpdateProductInput!) {
|
|
60
|
+
updateProduct(input: $input) {
|
|
61
|
+
id
|
|
62
|
+
name
|
|
63
|
+
slug
|
|
64
|
+
description
|
|
65
|
+
enabled
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`);
|
|
69
|
+
|
|
70
|
+
function DetailPageStoryWrapper(props: Omit<DetailPageProps<any, any, any>, 'route'>) {
|
|
71
|
+
return (
|
|
72
|
+
<DemoRouterProvider
|
|
73
|
+
component={route => <DetailPage {...props} route={route} />}
|
|
74
|
+
path={'/products/$id'}
|
|
75
|
+
initialPath={'/products/1'}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const meta = {
|
|
81
|
+
title: 'Layout/DetailPage',
|
|
82
|
+
component: DetailPageStoryWrapper,
|
|
83
|
+
parameters: {
|
|
84
|
+
layout: 'fullscreen',
|
|
85
|
+
},
|
|
86
|
+
tags: ['autodocs'],
|
|
87
|
+
argTypes: {
|
|
88
|
+
pageId: {
|
|
89
|
+
control: 'text',
|
|
90
|
+
description: 'Unique identifier for the detail page',
|
|
91
|
+
},
|
|
92
|
+
entityName: {
|
|
93
|
+
control: 'text',
|
|
94
|
+
description: 'Name of the entity (inferred from query if not provided)',
|
|
95
|
+
},
|
|
96
|
+
title: {
|
|
97
|
+
control: false,
|
|
98
|
+
description: 'Function that returns the page title based on the entity',
|
|
99
|
+
},
|
|
100
|
+
queryDocument: {
|
|
101
|
+
control: false,
|
|
102
|
+
description: 'GraphQL query document for fetching entity data',
|
|
103
|
+
},
|
|
104
|
+
createDocument: {
|
|
105
|
+
control: false,
|
|
106
|
+
description: 'GraphQL mutation document for creating the entity',
|
|
107
|
+
},
|
|
108
|
+
updateDocument: {
|
|
109
|
+
control: false,
|
|
110
|
+
description: 'GraphQL mutation document for updating the entity',
|
|
111
|
+
},
|
|
112
|
+
setValuesForUpdate: {
|
|
113
|
+
control: false,
|
|
114
|
+
description: 'Function to map entity data to update input',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
} satisfies Meta<typeof DetailPageStoryWrapper>;
|
|
118
|
+
|
|
119
|
+
export default meta;
|
|
120
|
+
type Story = StoryObj<typeof meta>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Basic example of a DetailPage showing a product entity.
|
|
124
|
+
* This demonstrates the minimal configuration needed to render a detail page.
|
|
125
|
+
*/
|
|
126
|
+
export const BasicDetail: Story = {
|
|
127
|
+
args: {
|
|
128
|
+
pageId: 'product-detail',
|
|
129
|
+
queryDocument: productQuery,
|
|
130
|
+
updateDocument: updateProductDocument,
|
|
131
|
+
title: entity => entity?.name || 'Product',
|
|
132
|
+
setValuesForUpdate: entity => ({
|
|
133
|
+
id: entity.id,
|
|
134
|
+
name: entity.name,
|
|
135
|
+
slug: entity.slug,
|
|
136
|
+
description: entity.description,
|
|
137
|
+
enabled: entity.enabled,
|
|
138
|
+
featuredAssetId: entity.featuredAsset?.id,
|
|
139
|
+
assetIds: entity.assets.map(asset => asset.id),
|
|
140
|
+
facetValueIds: entity.facetValues.map(facetValue => facetValue.id),
|
|
141
|
+
translations: entity.translations.map(translation => ({
|
|
142
|
+
id: translation.id,
|
|
143
|
+
languageCode: translation.languageCode,
|
|
144
|
+
name: translation.name,
|
|
145
|
+
slug: translation.slug,
|
|
146
|
+
description: translation.description,
|
|
147
|
+
customFields: (translation as any).customFields,
|
|
148
|
+
})),
|
|
149
|
+
}),
|
|
150
|
+
},
|
|
151
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
|
|
2
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
3
|
+
import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
|
|
4
|
+
import { ListPage, ListPageProps } from '@/vdb/framework/page/list-page.js';
|
|
5
|
+
import { graphql } from '@/vdb/graphql/graphql.js';
|
|
6
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
7
|
+
import { PlusIcon } from 'lucide-react';
|
|
8
|
+
|
|
9
|
+
import { DemoRouterProvider } from '../../../../.storybook/providers.js';
|
|
10
|
+
|
|
11
|
+
// Sample GraphQL query for countries
|
|
12
|
+
const countryItemFragment = graphql(`
|
|
13
|
+
fragment CountryItem on Country {
|
|
14
|
+
id
|
|
15
|
+
createdAt
|
|
16
|
+
updatedAt
|
|
17
|
+
name
|
|
18
|
+
code
|
|
19
|
+
enabled
|
|
20
|
+
}
|
|
21
|
+
`);
|
|
22
|
+
|
|
23
|
+
const countriesListQuery = graphql(
|
|
24
|
+
`
|
|
25
|
+
query CountriesList($options: CountryListOptions) {
|
|
26
|
+
countries(options: $options) {
|
|
27
|
+
items {
|
|
28
|
+
...CountryItem
|
|
29
|
+
}
|
|
30
|
+
totalItems
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
`,
|
|
34
|
+
[countryItemFragment],
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const deleteCountryDocument = graphql(`
|
|
38
|
+
mutation DeleteCountry($id: ID!) {
|
|
39
|
+
deleteCountry(id: $id) {
|
|
40
|
+
result
|
|
41
|
+
message
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
`);
|
|
45
|
+
|
|
46
|
+
function ListPageStoryWrapper(props: Omit<ListPageProps<any, any, any, any>, 'route'>) {
|
|
47
|
+
return <DemoRouterProvider component={route => <ListPage {...props} route={() => route} />} />;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const meta = {
|
|
51
|
+
title: 'Layout/ListPage',
|
|
52
|
+
component: ListPageStoryWrapper,
|
|
53
|
+
parameters: {
|
|
54
|
+
layout: 'fullscreen',
|
|
55
|
+
},
|
|
56
|
+
tags: ['autodocs'],
|
|
57
|
+
argTypes: {
|
|
58
|
+
pageId: {
|
|
59
|
+
control: 'text',
|
|
60
|
+
description: 'Unique identifier for the list page',
|
|
61
|
+
},
|
|
62
|
+
title: {
|
|
63
|
+
control: 'text',
|
|
64
|
+
description: 'Page title displayed in the header',
|
|
65
|
+
},
|
|
66
|
+
listQuery: {
|
|
67
|
+
control: false,
|
|
68
|
+
description: 'GraphQL query document for fetching list data',
|
|
69
|
+
},
|
|
70
|
+
deleteMutation: {
|
|
71
|
+
control: false,
|
|
72
|
+
description: 'GraphQL mutation document for deleting items',
|
|
73
|
+
},
|
|
74
|
+
customizeColumns: {
|
|
75
|
+
control: false,
|
|
76
|
+
description: 'Customize column rendering and behavior',
|
|
77
|
+
},
|
|
78
|
+
defaultVisibility: {
|
|
79
|
+
control: 'object',
|
|
80
|
+
description: 'Default visible columns',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
} satisfies Meta<typeof ListPageStoryWrapper>;
|
|
84
|
+
|
|
85
|
+
export default meta;
|
|
86
|
+
type Story = StoryObj<typeof meta>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Basic example of a ListPage showing countries from the demo API.
|
|
90
|
+
* This demonstrates the minimal configuration needed to render a list.
|
|
91
|
+
*/
|
|
92
|
+
export const BasicList: Story = {
|
|
93
|
+
args: {
|
|
94
|
+
pageId: 'country-list',
|
|
95
|
+
listQuery: countriesListQuery,
|
|
96
|
+
title: 'Countries',
|
|
97
|
+
defaultVisibility: {
|
|
98
|
+
name: true,
|
|
99
|
+
code: true,
|
|
100
|
+
enabled: true,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* ListPage with custom column rendering and delete mutation.
|
|
107
|
+
* Shows how to customize the name column to render as a detail page button.
|
|
108
|
+
*/
|
|
109
|
+
export const WithCustomColumns: Story = {
|
|
110
|
+
args: {
|
|
111
|
+
pageId: 'country-list-custom',
|
|
112
|
+
listQuery: countriesListQuery,
|
|
113
|
+
deleteMutation: deleteCountryDocument,
|
|
114
|
+
title: 'Countries',
|
|
115
|
+
defaultVisibility: {
|
|
116
|
+
name: true,
|
|
117
|
+
code: true,
|
|
118
|
+
enabled: true,
|
|
119
|
+
},
|
|
120
|
+
customizeColumns: {
|
|
121
|
+
name: {
|
|
122
|
+
cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* ListPage with search functionality.
|
|
130
|
+
* Demonstrates how to configure search to filter on multiple fields.
|
|
131
|
+
*/
|
|
132
|
+
export const WithSearch: Story = {
|
|
133
|
+
args: {
|
|
134
|
+
pageId: 'country-list-search',
|
|
135
|
+
listQuery: countriesListQuery,
|
|
136
|
+
title: 'Countries',
|
|
137
|
+
defaultVisibility: {
|
|
138
|
+
name: true,
|
|
139
|
+
code: true,
|
|
140
|
+
enabled: true,
|
|
141
|
+
},
|
|
142
|
+
onSearchTermChange: (searchTerm: string) => {
|
|
143
|
+
return {
|
|
144
|
+
name: {
|
|
145
|
+
contains: searchTerm,
|
|
146
|
+
},
|
|
147
|
+
code: {
|
|
148
|
+
contains: searchTerm,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
transformVariables: (variables: any) => {
|
|
153
|
+
return {
|
|
154
|
+
...variables,
|
|
155
|
+
options: {
|
|
156
|
+
...variables.options,
|
|
157
|
+
filterOperator: 'OR',
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
customizeColumns: {
|
|
162
|
+
name: {
|
|
163
|
+
cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Complete example with action bar and all features.
|
|
171
|
+
* Shows the full ListPage configuration including custom action buttons.
|
|
172
|
+
*/
|
|
173
|
+
export const Complete: Story = {
|
|
174
|
+
args: {
|
|
175
|
+
pageId: 'country-list-complete',
|
|
176
|
+
listQuery: countriesListQuery,
|
|
177
|
+
deleteMutation: deleteCountryDocument,
|
|
178
|
+
title: 'Countries',
|
|
179
|
+
defaultVisibility: {
|
|
180
|
+
name: true,
|
|
181
|
+
code: true,
|
|
182
|
+
enabled: true,
|
|
183
|
+
},
|
|
184
|
+
onSearchTermChange: (searchTerm: string) => {
|
|
185
|
+
return {
|
|
186
|
+
name: {
|
|
187
|
+
contains: searchTerm,
|
|
188
|
+
},
|
|
189
|
+
code: {
|
|
190
|
+
contains: searchTerm,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
transformVariables: (variables: any) => {
|
|
195
|
+
return {
|
|
196
|
+
...variables,
|
|
197
|
+
options: {
|
|
198
|
+
...variables.options,
|
|
199
|
+
filterOperator: 'OR',
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
customizeColumns: {
|
|
204
|
+
name: {
|
|
205
|
+
cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
children: (
|
|
209
|
+
<PageActionBarRight>
|
|
210
|
+
<Button>
|
|
211
|
+
<PlusIcon />
|
|
212
|
+
Add Country
|
|
213
|
+
</Button>
|
|
214
|
+
</PageActionBarRight>
|
|
215
|
+
),
|
|
216
|
+
},
|
|
217
|
+
};
|
|
@@ -17,7 +17,13 @@ import { ColumnFiltersState, SortingState, Table } from '@tanstack/react-table';
|
|
|
17
17
|
import { TableOptions } from '@tanstack/table-core';
|
|
18
18
|
|
|
19
19
|
import { BulkAction } from '@/vdb/framework/extension-api/types/index.js';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
FullWidthPageBlock,
|
|
22
|
+
Page,
|
|
23
|
+
PageActionBar,
|
|
24
|
+
PageLayout,
|
|
25
|
+
PageTitle,
|
|
26
|
+
} from '../layout-engine/page-layout.js';
|
|
21
27
|
|
|
22
28
|
/**
|
|
23
29
|
* @description
|
|
@@ -393,6 +399,7 @@ export interface ListPageProps<
|
|
|
393
399
|
* body
|
|
394
400
|
* customFields
|
|
395
401
|
* }
|
|
402
|
+
* totalItems
|
|
396
403
|
* }
|
|
397
404
|
* }
|
|
398
405
|
* `);
|
package/src/lib/graphql/api.ts
CHANGED
|
@@ -69,6 +69,20 @@ const awesomeClient = new AwesomeGraphQLClient({
|
|
|
69
69
|
},
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* @description
|
|
74
|
+
* Handles the scenario where there's an invalid channel token in local storage.
|
|
75
|
+
* Most often seen in local development when testing multiple backends on the same
|
|
76
|
+
* localhost origin.
|
|
77
|
+
*/
|
|
78
|
+
function handleInvalidChannelToken(err: unknown) {
|
|
79
|
+
if (err instanceof Error) {
|
|
80
|
+
if ((err as any).extensions?.code === 'CHANNEL_NOT_FOUND') {
|
|
81
|
+
localStorage.removeItem(LS_KEY_SELECTED_CHANNEL_TOKEN);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
export type VariablesAndRequestHeadersArgs<V extends Variables> =
|
|
73
87
|
V extends Record<any, never>
|
|
74
88
|
? [variables?: V, requestHeaders?: HeadersInit]
|
|
@@ -79,7 +93,10 @@ function query<T, V extends Variables = Variables>(
|
|
|
79
93
|
variables?: V,
|
|
80
94
|
): Promise<T> {
|
|
81
95
|
const documentString = typeof document === 'string' ? document : print(document);
|
|
82
|
-
return awesomeClient.request(documentString, variables)
|
|
96
|
+
return awesomeClient.request(documentString, variables).catch(err => {
|
|
97
|
+
handleInvalidChannelToken(err);
|
|
98
|
+
throw err;
|
|
99
|
+
}) as any;
|
|
83
100
|
}
|
|
84
101
|
|
|
85
102
|
function mutate<T, V extends Variables = Variables>(
|
|
@@ -416,7 +416,7 @@ export type introspection_types = {
|
|
|
416
416
|
'TaxCategorySortParameter': { kind: 'INPUT_OBJECT'; name: 'TaxCategorySortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
|
|
417
417
|
'TaxLine': { kind: 'OBJECT'; name: 'TaxLine'; fields: { 'description': { name: 'description'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'taxRate': { name: 'taxRate'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; }; };
|
|
418
418
|
'TaxRate': { kind: 'OBJECT'; name: 'TaxRate'; fields: { 'category': { name: 'category'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TaxCategory'; ofType: null; }; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'customFields': { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; } }; 'customerGroup': { name: 'customerGroup'; type: { kind: 'OBJECT'; name: 'CustomerGroup'; ofType: null; } }; 'enabled': { name: 'enabled'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'value': { name: 'value'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; 'zone': { name: 'zone'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Zone'; ofType: null; }; } }; }; };
|
|
419
|
-
'TaxRateFilterParameter': { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanOperators'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }]; };
|
|
419
|
+
'TaxRateFilterParameter': { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; isOneOf: false; inputFields: [{ name: 'zoneId'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'categoryId'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanOperators'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }]; };
|
|
420
420
|
'TaxRateList': { kind: 'OBJECT'; name: 'TaxRateList'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TaxRate'; ofType: null; }; }; }; } }; 'totalItems': { name: 'totalItems'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
|
|
421
421
|
'TaxRateListOptions': { kind: 'INPUT_OBJECT'; name: 'TaxRateListOptions'; isOneOf: false; inputFields: [{ name: 'skip'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'take'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'sort'; type: { kind: 'INPUT_OBJECT'; name: 'TaxRateSortParameter'; ofType: null; }; defaultValue: null }, { name: 'filter'; type: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; defaultValue: null }, { name: 'filterOperator'; type: { kind: 'ENUM'; name: 'LogicalOperator'; ofType: null; }; defaultValue: null }]; };
|
|
422
422
|
'TaxRateSortParameter': { kind: 'INPUT_OBJECT'; name: 'TaxRateSortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { AlertSeverity, DashboardAlertDefinition } from '@/vdb/framework/extension-api/types/alerts.js';
|
|
2
|
+
import { useAlertsContext } from '@/vdb/providers/alerts-provider.js';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* An individual Alert item.
|
|
8
|
+
*
|
|
9
|
+
* @docsCategory hooks
|
|
10
|
+
* @docsPage useAlerts
|
|
11
|
+
* @since 3.5.0
|
|
12
|
+
*/
|
|
13
|
+
export interface AlertEntry {
|
|
14
|
+
definition: DashboardAlertDefinition;
|
|
15
|
+
active: boolean;
|
|
16
|
+
currentSeverity?: AlertSeverity;
|
|
17
|
+
lastData: any;
|
|
18
|
+
dismiss: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @description
|
|
23
|
+
* Returns information about all registered Alerts, including how many are
|
|
24
|
+
* active and at what severity.
|
|
25
|
+
*
|
|
26
|
+
* @docsCategory hooks
|
|
27
|
+
* @docsPage useAlerts
|
|
28
|
+
* @docsWeight 0
|
|
29
|
+
* @since 3.5.0
|
|
30
|
+
*/
|
|
31
|
+
export function useAlerts(): { alerts: AlertEntry[]; activeCount: number; highestSeverity: AlertSeverity } {
|
|
32
|
+
const { alertDefs, rawResults, dismissedAlerts, setDismissedAlerts } = useAlertsContext();
|
|
33
|
+
|
|
34
|
+
const alerts = useMemo(() => {
|
|
35
|
+
return rawResults.map((result, idx) => {
|
|
36
|
+
const alertDef = alertDefs[idx];
|
|
37
|
+
const dismissedAt = dismissedAlerts.get(alertDef.id);
|
|
38
|
+
const isDismissed =
|
|
39
|
+
dismissedAt !== undefined &&
|
|
40
|
+
result.dataUpdatedAt !== undefined &&
|
|
41
|
+
dismissedAt > result.dataUpdatedAt;
|
|
42
|
+
const active = alertDef.shouldShow(result.data) && !isDismissed;
|
|
43
|
+
const currentSeverity = getSeverity(alertDef, result.data);
|
|
44
|
+
return {
|
|
45
|
+
definition: alertDef,
|
|
46
|
+
active,
|
|
47
|
+
lastData: result.data,
|
|
48
|
+
currentSeverity,
|
|
49
|
+
dismiss: () => {
|
|
50
|
+
setDismissedAlerts(prev => new Map(prev).set(alertDef.id, Date.now()));
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}, [rawResults, alertDefs, dismissedAlerts]);
|
|
55
|
+
|
|
56
|
+
const activeCount = useMemo(() => alerts.filter(alert => alert.active).length, [alerts]);
|
|
57
|
+
const highestSeverity: AlertSeverity =
|
|
58
|
+
alerts.length > 0
|
|
59
|
+
? alerts.reduce((highest, a) => {
|
|
60
|
+
if (highest === 'warning' && a.currentSeverity === 'error') {
|
|
61
|
+
return 'error';
|
|
62
|
+
}
|
|
63
|
+
if (
|
|
64
|
+
highest === 'info' &&
|
|
65
|
+
(a.currentSeverity === 'warning' || a.currentSeverity === 'error')
|
|
66
|
+
) {
|
|
67
|
+
return a.currentSeverity;
|
|
68
|
+
}
|
|
69
|
+
return highest;
|
|
70
|
+
}, 'info' as AlertSeverity)
|
|
71
|
+
: 'info';
|
|
72
|
+
|
|
73
|
+
return { alerts, activeCount, highestSeverity };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getSeverity(alertDef: DashboardAlertDefinition, data: any) {
|
|
77
|
+
if (typeof alertDef.severity === 'string') {
|
|
78
|
+
return alertDef.severity;
|
|
79
|
+
} else if (typeof alertDef.severity === 'function') {
|
|
80
|
+
return alertDef.severity(data);
|
|
81
|
+
} else {
|
|
82
|
+
return 'info';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -32,9 +32,8 @@ export function useFloatingBulkActions({
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const updatePosition = () => {
|
|
35
|
-
// Find the container
|
|
36
|
-
const
|
|
37
|
-
const container = currentElement.closest(containerSelector) as HTMLElement;
|
|
35
|
+
// Find the container in the document
|
|
36
|
+
const container = document.querySelector(containerSelector) as HTMLElement;
|
|
38
37
|
if (!container) return;
|
|
39
38
|
|
|
40
39
|
const containerRect = container.getBoundingClientRect();
|