@vendure/dashboard 3.4.2-master-202509081248 → 3.4.2-master-202509110229
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/package.json +4 -4
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_assets/assets.tsx +1 -0
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +5 -5
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +5 -5
- package/src/app/routes/_authenticated/_facets/facets.tsx +5 -8
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +2 -2
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +5 -1
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +5 -5
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx +2 -2
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +5 -5
- package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_system/healthchecks.tsx +1 -0
- package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +4 -4
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +5 -4
- package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +4 -4
- package/src/lib/components/data-input/affixed-input.tsx +18 -0
- package/src/lib/components/data-input/boolean-input.tsx +7 -0
- package/src/lib/components/data-input/checkbox-input.tsx +7 -0
- package/src/lib/components/data-input/datetime-input.tsx +7 -0
- package/src/lib/components/data-input/money-input.tsx +8 -0
- package/src/lib/components/data-input/number-input.tsx +7 -0
- package/src/lib/components/data-input/password-input.tsx +7 -0
- package/src/lib/components/data-input/rich-text-input.tsx +7 -0
- package/src/lib/components/data-input/text-input.tsx +7 -0
- package/src/lib/components/data-input/textarea-input.tsx +7 -0
- package/src/lib/components/data-table/data-table-view-options.tsx +29 -26
- package/src/lib/components/data-table/data-table.tsx +20 -0
- package/src/lib/components/data-table/types.ts +39 -0
- package/src/lib/components/layout/channel-switcher.tsx +1 -3
- package/src/lib/components/layout/generated-breadcrumbs.tsx +103 -43
- package/src/lib/components/shared/asset/asset-gallery.tsx +58 -0
- package/src/lib/components/shared/asset/asset-picker-dialog.tsx +39 -0
- package/src/lib/components/shared/detail-page-button.tsx +8 -22
- package/src/lib/components/shared/facet-value-chip.tsx +7 -0
- package/src/lib/components/shared/facet-value-selector.tsx +55 -0
- package/src/lib/components/shared/form-field-wrapper.tsx +51 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +128 -16
- package/src/lib/components/shared/permission-guard.tsx +30 -0
- package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +1 -1
- package/src/lib/components/shared/translatable-form-field.tsx +52 -0
- package/src/lib/components/shared/vendure-image.tsx +114 -2
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +25 -3
- package/src/lib/framework/extension-api/extension-api-types.ts +12 -3
- package/src/lib/framework/extension-api/types/alerts.ts +2 -3
- package/src/lib/framework/extension-api/types/data-table.ts +2 -2
- package/src/lib/framework/extension-api/types/detail-forms.ts +2 -2
- package/src/lib/framework/extension-api/types/form-components.ts +2 -2
- package/src/lib/framework/extension-api/types/layout.ts +24 -13
- package/src/lib/framework/extension-api/types/login.ts +6 -5
- package/src/lib/framework/extension-api/types/navigation.ts +3 -3
- package/src/lib/framework/extension-api/types/widgets.ts +7 -3
- package/src/lib/framework/form-engine/form-engine-types.ts +13 -7
- package/src/lib/framework/form-engine/use-generated-form.tsx +44 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +94 -31
- package/src/lib/framework/page/detail-page.tsx +3 -5
- package/src/lib/framework/page/list-page.tsx +87 -5
- package/src/lib/framework/page/use-detail-page.ts +4 -5
- package/src/lib/graphql/api.ts +2 -2
- package/src/lib/graphql/graphql-env.d.ts +7 -16
- package/src/lib/hooks/use-auth.tsx +1 -3
- package/src/lib/hooks/use-channel.ts +4 -2
- package/src/lib/hooks/use-page-block.tsx +9 -0
- package/src/lib/hooks/use-permissions.ts +6 -2
- package/src/lib/index.ts +2 -0
- package/src/lib/providers/auth.tsx +34 -2
- package/src/lib/providers/channel-provider.tsx +22 -1
- package/src/lib/components/shared/table-cell/table-cell-types.ts +0 -33
|
@@ -28,7 +28,7 @@ export const Route = createFileRoute('/_authenticated/_sellers/sellers_/$id')({
|
|
|
28
28
|
pageId,
|
|
29
29
|
queryDocument: sellerDetailDocument,
|
|
30
30
|
breadcrumb: (isNew, entity) => [
|
|
31
|
-
{ path: '/sellers', label:
|
|
31
|
+
{ path: '/sellers', label: <Trans>Sellers</Trans> },
|
|
32
32
|
isNew ? <Trans>New seller</Trans> : entity?.name,
|
|
33
33
|
],
|
|
34
34
|
}),
|
|
@@ -55,14 +55,14 @@ function SellerDetailPage() {
|
|
|
55
55
|
},
|
|
56
56
|
params: { id: params.id },
|
|
57
57
|
onSuccess: async data => {
|
|
58
|
-
toast(i18n.t('Successfully updated seller'));
|
|
58
|
+
toast(i18n.t(creatingNewEntity ? 'Successfully created seller' : 'Successfully updated seller'));
|
|
59
59
|
form.reset(form.getValues());
|
|
60
60
|
if (creatingNewEntity) {
|
|
61
61
|
await navigate({ to: `../$id`, params: { id: data.id } });
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
64
|
onError: err => {
|
|
65
|
-
toast(i18n.t('Failed to update seller'), {
|
|
65
|
+
toast(i18n.t(creatingNewEntity ? 'Failed to create seller' : 'Failed to update seller'), {
|
|
66
66
|
description: err instanceof Error ? err.message : 'Unknown error',
|
|
67
67
|
});
|
|
68
68
|
},
|
|
@@ -78,7 +78,7 @@ function SellerDetailPage() {
|
|
|
78
78
|
type="submit"
|
|
79
79
|
disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
|
|
80
80
|
>
|
|
81
|
-
<Trans>Update</Trans>
|
|
81
|
+
{creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
|
|
82
82
|
</Button>
|
|
83
83
|
</PermissionGuard>
|
|
84
84
|
</PageActionBarRight>
|
|
@@ -39,7 +39,7 @@ export const Route = createFileRoute('/_authenticated/_shipping-methods/shipping
|
|
|
39
39
|
queryDocument: shippingMethodDetailDocument,
|
|
40
40
|
breadcrumb(isNew, entity) {
|
|
41
41
|
return [
|
|
42
|
-
{ path: '/shipping-methods', label:
|
|
42
|
+
{ path: '/shipping-methods', label: <Trans>Shipping Methods</Trans> },
|
|
43
43
|
isNew ? <Trans>New shipping method</Trans> : entity?.name,
|
|
44
44
|
];
|
|
45
45
|
},
|
|
@@ -84,14 +84,14 @@ function ShippingMethodDetailPage() {
|
|
|
84
84
|
},
|
|
85
85
|
params: { id: params.id },
|
|
86
86
|
onSuccess: async data => {
|
|
87
|
-
toast.success(i18n.t('Successfully updated shipping method'));
|
|
87
|
+
toast.success(i18n.t(creatingNewEntity ? 'Successfully created shipping method' : 'Successfully updated shipping method'));
|
|
88
88
|
resetForm();
|
|
89
89
|
if (creatingNewEntity) {
|
|
90
90
|
await navigate({ to: `../$id`, params: { id: data.id } });
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
onError: err => {
|
|
94
|
-
toast.error(i18n.t('Failed to update shipping method'), {
|
|
94
|
+
toast.error(i18n.t(creatingNewEntity ? 'Failed to create shipping method' : 'Failed to update shipping method'), {
|
|
95
95
|
description: err instanceof Error ? err.message : 'Unknown error',
|
|
96
96
|
});
|
|
97
97
|
},
|
|
@@ -109,7 +109,7 @@ function ShippingMethodDetailPage() {
|
|
|
109
109
|
type="submit"
|
|
110
110
|
disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
|
|
111
111
|
>
|
|
112
|
-
<Trans>Update</Trans>
|
|
112
|
+
{creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
|
|
113
113
|
</Button>
|
|
114
114
|
</PermissionGuard>
|
|
115
115
|
</PageActionBarRight>
|
|
@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_stock-locations/stock-loc
|
|
|
35
35
|
queryDocument: stockLocationDetailQuery,
|
|
36
36
|
breadcrumb(isNew, entity) {
|
|
37
37
|
return [
|
|
38
|
-
{ path: '/stock-locations', label:
|
|
38
|
+
{ path: '/stock-locations', label: <Trans>Stock Locations</Trans> },
|
|
39
39
|
isNew ? <Trans>New stock location</Trans> : entity?.name,
|
|
40
40
|
];
|
|
41
41
|
},
|
|
@@ -64,14 +64,14 @@ function StockLocationDetailPage() {
|
|
|
64
64
|
},
|
|
65
65
|
params: { id: params.id },
|
|
66
66
|
onSuccess: async data => {
|
|
67
|
-
toast.success(i18n.t('Successfully updated stock location'));
|
|
67
|
+
toast.success(i18n.t(creatingNewEntity ? 'Successfully created stock location' : 'Successfully updated stock location'));
|
|
68
68
|
resetForm();
|
|
69
69
|
if (creatingNewEntity) {
|
|
70
70
|
await navigate({ to: `../$id`, params: { id: data.id } });
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
73
|
onError: err => {
|
|
74
|
-
toast.error(i18n.t('Failed to update stock location'), {
|
|
74
|
+
toast.error(i18n.t(creatingNewEntity ? 'Failed to create stock location' : 'Failed to update stock location'), {
|
|
75
75
|
description: err instanceof Error ? err.message : 'Unknown error',
|
|
76
76
|
});
|
|
77
77
|
},
|
|
@@ -89,7 +89,7 @@ function StockLocationDetailPage() {
|
|
|
89
89
|
type="submit"
|
|
90
90
|
disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
|
|
91
91
|
>
|
|
92
|
-
<Trans>Update</Trans>
|
|
92
|
+
{creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
|
|
93
93
|
</Button>
|
|
94
94
|
</PermissionGuard>
|
|
95
95
|
</PageActionBarRight>
|
|
@@ -10,6 +10,7 @@ import { uiConfig } from 'virtual:vendure-ui-config';
|
|
|
10
10
|
|
|
11
11
|
export const Route = createFileRoute('/_authenticated/_system/healthchecks')({
|
|
12
12
|
component: HealthchecksPage,
|
|
13
|
+
loader: () => ({ breadcrumb: () => <Trans>Healthchecks</Trans> }),
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
interface HealthcheckItem {
|
|
@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_tax-categories/tax-catego
|
|
|
35
35
|
queryDocument: taxCategoryDetailDocument,
|
|
36
36
|
breadcrumb(isNew, entity) {
|
|
37
37
|
return [
|
|
38
|
-
{ path: '/tax-categories', label:
|
|
38
|
+
{ path: '/tax-categories', label: <Trans>Tax Categories</Trans> },
|
|
39
39
|
isNew ? <Trans>New tax category</Trans> : entity?.name,
|
|
40
40
|
];
|
|
41
41
|
},
|
|
@@ -63,14 +63,14 @@ function TaxCategoryDetailPage() {
|
|
|
63
63
|
},
|
|
64
64
|
params: { id: params.id },
|
|
65
65
|
onSuccess: async data => {
|
|
66
|
-
toast.success(i18n.t('Successfully updated tax category'));
|
|
66
|
+
toast.success(i18n.t(creatingNewEntity ? 'Successfully created tax category' : 'Successfully updated tax category'));
|
|
67
67
|
form.reset(form.getValues());
|
|
68
68
|
if (creatingNewEntity) {
|
|
69
69
|
await navigate({ to: `../$id`, params: { id: data.id } });
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
onError: err => {
|
|
73
|
-
toast.error(i18n.t('Failed to update tax category'), {
|
|
73
|
+
toast.error(i18n.t(creatingNewEntity ? 'Failed to create tax category' : 'Failed to update tax category'), {
|
|
74
74
|
description: err instanceof Error ? err.message : 'Unknown error',
|
|
75
75
|
});
|
|
76
76
|
},
|
|
@@ -88,7 +88,7 @@ function TaxCategoryDetailPage() {
|
|
|
88
88
|
type="submit"
|
|
89
89
|
disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
|
|
90
90
|
>
|
|
91
|
-
<Trans>Update</Trans>
|
|
91
|
+
{creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
|
|
92
92
|
</Button>
|
|
93
93
|
</PermissionGuard>
|
|
94
94
|
</PageActionBarRight>
|
|
@@ -34,7 +34,7 @@ export const Route = createFileRoute('/_authenticated/_tax-rates/tax-rates_/$id'
|
|
|
34
34
|
queryDocument: taxRateDetailDocument,
|
|
35
35
|
breadcrumb(isNew, entity) {
|
|
36
36
|
return [
|
|
37
|
-
{ path: '/tax-rates', label:
|
|
37
|
+
{ path: '/tax-rates', label: <Trans>Tax Rates</Trans> },
|
|
38
38
|
isNew ? <Trans>New tax rate</Trans> : entity?.name,
|
|
39
39
|
];
|
|
40
40
|
},
|
|
@@ -67,14 +67,14 @@ function TaxRateDetailPage() {
|
|
|
67
67
|
},
|
|
68
68
|
params: { id: params.id },
|
|
69
69
|
onSuccess: async data => {
|
|
70
|
-
toast.success(i18n.t('Successfully updated tax rate'));
|
|
70
|
+
toast.success(i18n.t(creatingNewEntity ? 'Successfully created tax rate' : 'Successfully updated tax rate'));
|
|
71
71
|
resetForm();
|
|
72
72
|
if (creatingNewEntity) {
|
|
73
73
|
await navigate({ to: `../$id`, params: { id: data.id } });
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
76
|
onError: err => {
|
|
77
|
-
toast.error(i18n.t('Failed to update tax rate'), {
|
|
77
|
+
toast.error(i18n.t(creatingNewEntity ? 'Failed to create tax rate' : 'Failed to update tax rate'), {
|
|
78
78
|
description: err instanceof Error ? err.message : 'Unknown error',
|
|
79
79
|
});
|
|
80
80
|
},
|
|
@@ -90,7 +90,7 @@ function TaxRateDetailPage() {
|
|
|
90
90
|
type="submit"
|
|
91
91
|
disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
|
|
92
92
|
>
|
|
93
|
-
<Trans>Update</Trans>
|
|
93
|
+
{creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
|
|
94
94
|
</Button>
|
|
95
95
|
</PermissionGuard>
|
|
96
96
|
</PageActionBarRight>
|
|
@@ -120,6 +120,7 @@ function TaxRateDetailPage() {
|
|
|
120
120
|
label={<Trans>Rate</Trans>}
|
|
121
121
|
render={({ field }) => (
|
|
122
122
|
<AffixedInput
|
|
123
|
+
{...field}
|
|
123
124
|
type="number"
|
|
124
125
|
suffix="%"
|
|
125
126
|
value={field.value}
|
|
@@ -30,7 +30,7 @@ export const Route = createFileRoute('/_authenticated/_zones/zones_/$id')({
|
|
|
30
30
|
pageId,
|
|
31
31
|
queryDocument: zoneDetailDocument,
|
|
32
32
|
breadcrumb(isNew, entity) {
|
|
33
|
-
return [{ path: '/zones', label:
|
|
33
|
+
return [{ path: '/zones', label: <Trans>Zones</Trans> }, isNew ? <Trans>New zone</Trans> : entity?.name];
|
|
34
34
|
},
|
|
35
35
|
}),
|
|
36
36
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
@@ -56,14 +56,14 @@ function ZoneDetailPage() {
|
|
|
56
56
|
},
|
|
57
57
|
params: { id: params.id },
|
|
58
58
|
onSuccess: async data => {
|
|
59
|
-
toast.success(i18n.t('Successfully updated zone'));
|
|
59
|
+
toast.success(i18n.t(creatingNewEntity ? 'Successfully created zone' : 'Successfully updated zone'));
|
|
60
60
|
resetForm();
|
|
61
61
|
if (creatingNewEntity) {
|
|
62
62
|
await navigate({ to: `../$id`, params: { id: data.id } });
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
onError: err => {
|
|
66
|
-
toast.error(i18n.t('Failed to update zone'), {
|
|
66
|
+
toast.error(i18n.t(creatingNewEntity ? 'Failed to create zone' : 'Failed to update zone'), {
|
|
67
67
|
description: err instanceof Error ? err.message : 'Unknown error',
|
|
68
68
|
});
|
|
69
69
|
},
|
|
@@ -79,7 +79,7 @@ function ZoneDetailPage() {
|
|
|
79
79
|
type="submit"
|
|
80
80
|
disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
|
|
81
81
|
>
|
|
82
|
-
<Trans>Update</Trans>
|
|
82
|
+
{creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
|
|
83
83
|
</Button>
|
|
84
84
|
</PermissionGuard>
|
|
85
85
|
</PageActionBarRight>
|
|
@@ -10,6 +10,24 @@ export type AffixedInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>
|
|
|
10
10
|
suffix?: ReactNode;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @description
|
|
15
|
+
* A component for displaying an input with a prefix and/or a suffix.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <AffixedInput
|
|
20
|
+
* {...field}
|
|
21
|
+
* type="number"
|
|
22
|
+
* suffix="%"
|
|
23
|
+
* value={field.value}
|
|
24
|
+
* onChange={e => field.onChange(e.target.valueAsNumber)}
|
|
25
|
+
* />
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @docsCategory form-components
|
|
29
|
+
* @docsPage AffixedInput
|
|
30
|
+
*/
|
|
13
31
|
export function AffixedInput({ prefix, suffix, className = '', ...props }: Readonly<AffixedInputProps>) {
|
|
14
32
|
const readOnly = props.disabled || isReadonlyField(props.fieldDef);
|
|
15
33
|
const prefixRef = useRef<HTMLSpanElement>(null);
|
|
@@ -2,6 +2,13 @@ import { Switch } from '@/vdb/components/ui/switch.js';
|
|
|
2
2
|
import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
3
3
|
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* Displays a boolean value as a switch toggle.
|
|
8
|
+
*
|
|
9
|
+
* @docsCategory form-components
|
|
10
|
+
* @docsPage BooleanInput
|
|
11
|
+
*/
|
|
5
12
|
export function BooleanInput({ value, onChange, fieldDef }: Readonly<DashboardFormComponentProps>) {
|
|
6
13
|
const checked = typeof value === 'string' ? value === 'true' : value;
|
|
7
14
|
const readOnly = isReadonlyField(fieldDef);
|
|
@@ -2,6 +2,13 @@ import { Checkbox } from '../ui/checkbox.js';
|
|
|
2
2
|
import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
3
3
|
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* Displays a boolean value as a checkbox.
|
|
8
|
+
*
|
|
9
|
+
* @docsCategory form-components
|
|
10
|
+
* @docsPage CheckboxInput
|
|
11
|
+
*/
|
|
5
12
|
export function CheckboxInput({ value, onChange, fieldDef }: Readonly<DashboardFormComponentProps>) {
|
|
6
13
|
const readOnly = isReadonlyField(fieldDef);
|
|
7
14
|
return <Checkbox checked={value} onCheckedChange={onChange} disabled={readOnly} />;
|
|
@@ -12,6 +12,13 @@ import { cn } from '@/vdb/lib/utils.js';
|
|
|
12
12
|
import { CalendarClock } from 'lucide-react';
|
|
13
13
|
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @description
|
|
17
|
+
* A component for selecting a date and time.
|
|
18
|
+
*
|
|
19
|
+
* @docsCategory form-components
|
|
20
|
+
* @docsPage DateTimeInput
|
|
21
|
+
*/
|
|
15
22
|
export function DateTimeInput({ value, onChange, fieldDef }: Readonly<DashboardFormComponentProps>) {
|
|
16
23
|
const readOnly = isReadonlyField(fieldDef);
|
|
17
24
|
const date = value && value instanceof Date ? value.toISOString() : (value ?? '');
|
|
@@ -11,6 +11,14 @@ export interface MoneyInputProps extends DashboardFormComponentProps {
|
|
|
11
11
|
currency?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @description
|
|
16
|
+
* A component for displaying a money value. The `currency` can be specified, but otherwise
|
|
17
|
+
* will be taken from the active channel's default currency.
|
|
18
|
+
*
|
|
19
|
+
* @docsCategory form-components
|
|
20
|
+
* @docsPage MoneyInput
|
|
21
|
+
*/
|
|
14
22
|
export function MoneyInput(props: Readonly<MoneyInputProps>) {
|
|
15
23
|
const { value, onChange, currency, ...rest } = props;
|
|
16
24
|
const { activeChannel } = useChannel();
|
|
@@ -4,6 +4,13 @@ import { Input } from '@/vdb/components/ui/input.js';
|
|
|
4
4
|
import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
5
5
|
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @description
|
|
9
|
+
* A component for displaying a numeric value.
|
|
10
|
+
*
|
|
11
|
+
* @docsCategory form-components
|
|
12
|
+
* @docsPage NumberInput
|
|
13
|
+
*/
|
|
7
14
|
export function NumberInput({ fieldDef, onChange, ...fieldProps }: Readonly<DashboardFormComponentProps>) {
|
|
8
15
|
const readOnly = fieldProps.disabled || isReadonlyField(fieldDef);
|
|
9
16
|
const isFloat = fieldDef ? fieldDef.type === 'float' : false;
|
|
@@ -2,6 +2,13 @@ import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-en
|
|
|
2
2
|
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
3
3
|
import { Input } from '../ui/input.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* A component for displaying a password input.
|
|
8
|
+
*
|
|
9
|
+
* @docsCategory form-components
|
|
10
|
+
* @docsPage PasswordInput
|
|
11
|
+
*/
|
|
5
12
|
export function PasswordInput(props: Readonly<DashboardFormComponentProps>) {
|
|
6
13
|
const readOnly = props.disabled || isReadonlyField(props.fieldDef);
|
|
7
14
|
return (
|
|
@@ -22,6 +22,13 @@ const extensions = [
|
|
|
22
22
|
}),
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @description
|
|
27
|
+
* A component for displaying a rich text editor. Internally uses ProseMirror (rich text editor) under the hood.
|
|
28
|
+
*
|
|
29
|
+
* @docsCategory form-components
|
|
30
|
+
* @docsPage RichTextInput
|
|
31
|
+
*/
|
|
25
32
|
export function RichTextInput({ value, onChange, fieldDef }: Readonly<DashboardFormComponentProps>) {
|
|
26
33
|
const readOnly = isReadonlyField(fieldDef);
|
|
27
34
|
const isInternalUpdate = useRef(false);
|
|
@@ -3,6 +3,13 @@ import { Input } from '@/vdb/components/ui/input.js';
|
|
|
3
3
|
import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
4
4
|
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @description
|
|
8
|
+
* A component for displaying a text input.
|
|
9
|
+
*
|
|
10
|
+
* @docsCategory form-components
|
|
11
|
+
* @docsPage TextInput
|
|
12
|
+
*/
|
|
6
13
|
export const TextInput: DashboardFormComponent = ({ value, onChange, fieldDef }) => {
|
|
7
14
|
const readOnly = isReadonlyField(fieldDef);
|
|
8
15
|
return <Input value={value || ''} onChange={e => onChange(e.target.value)} disabled={readOnly} />;
|
|
@@ -2,6 +2,13 @@ import { Textarea } from '@/vdb/components/ui/textarea.js';
|
|
|
2
2
|
import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
|
|
3
3
|
import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* A component for displaying a textarea input.
|
|
8
|
+
*
|
|
9
|
+
* @docsCategory form-components
|
|
10
|
+
* @docsPage TextareaInput
|
|
11
|
+
*/
|
|
5
12
|
export function TextareaInput(props: Readonly<DashboardFormComponentProps>) {
|
|
6
13
|
const readOnly = props.disabled || isReadonlyField(props.fieldDef);
|
|
7
14
|
return (
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
DropdownMenuSeparator,
|
|
17
17
|
DropdownMenuTrigger,
|
|
18
18
|
} from '@/vdb/components/ui/dropdown-menu.js';
|
|
19
|
+
import { ScrollArea } from '@/vdb/components/ui/scroll-area.js';
|
|
19
20
|
import { usePage } from '@/vdb/hooks/use-page.js';
|
|
20
21
|
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
21
22
|
import { Trans } from '@/vdb/lib/trans.js';
|
|
@@ -76,39 +77,41 @@ export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps
|
|
|
76
77
|
|
|
77
78
|
return (
|
|
78
79
|
<div className="flex items-center gap-2">
|
|
79
|
-
<DropdownMenu>
|
|
80
|
+
<DropdownMenu modal={false}>
|
|
80
81
|
<DropdownMenuTrigger asChild>
|
|
81
82
|
<Button variant="ghost" size="sm" className="ml-auto hidden h-8 lg:flex">
|
|
82
83
|
<Settings2 />
|
|
83
84
|
<Trans>Columns</Trans>
|
|
84
85
|
</Button>
|
|
85
86
|
</DropdownMenuTrigger>
|
|
86
|
-
<DropdownMenuContent align="end">
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<SortableContext
|
|
93
|
-
items={columns.map(col => col.id)}
|
|
94
|
-
strategy={verticalListSortingStrategy}
|
|
87
|
+
<DropdownMenuContent align="end" className="overflow-auto">
|
|
88
|
+
<ScrollArea className="max-h-[60vh]" type="always">
|
|
89
|
+
<DndContext
|
|
90
|
+
collisionDetection={closestCenter}
|
|
91
|
+
onDragEnd={handleDragEnd}
|
|
92
|
+
modifiers={[restrictToVerticalAxis]}
|
|
95
93
|
>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
94
|
+
<SortableContext
|
|
95
|
+
items={columns.map(col => col.id)}
|
|
96
|
+
strategy={verticalListSortingStrategy}
|
|
97
|
+
>
|
|
98
|
+
{columns.map(column => (
|
|
99
|
+
<SortableItem key={column.id} id={column.id}>
|
|
100
|
+
<DropdownMenuCheckboxItem
|
|
101
|
+
className="capitalize"
|
|
102
|
+
checked={column.getIsVisible()}
|
|
103
|
+
onCheckedChange={value => column.toggleVisibility(!!value)}
|
|
104
|
+
onSelect={e => e.preventDefault()}
|
|
105
|
+
>
|
|
106
|
+
{column.id}
|
|
107
|
+
</DropdownMenuCheckboxItem>
|
|
108
|
+
</SortableItem>
|
|
109
|
+
))}
|
|
110
|
+
</SortableContext>
|
|
111
|
+
</DndContext>
|
|
112
|
+
<DropdownMenuSeparator />
|
|
113
|
+
<DropdownMenuItem onClick={handleReset}>Reset</DropdownMenuItem>
|
|
114
|
+
</ScrollArea>
|
|
112
115
|
</DropdownMenuContent>
|
|
113
116
|
</DropdownMenu>
|
|
114
117
|
</div>
|
|
@@ -35,6 +35,14 @@ export interface FacetedFilter {
|
|
|
35
35
|
options?: DataTableFacetedFilterOption[];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* @description
|
|
40
|
+
* Props for configuring the {@link DataTable}.
|
|
41
|
+
*
|
|
42
|
+
* @docsCategory list-views
|
|
43
|
+
* @docsPage DataTable
|
|
44
|
+
* @since 3.4.0
|
|
45
|
+
*/
|
|
38
46
|
interface DataTableProps<TData> {
|
|
39
47
|
children?: React.ReactNode;
|
|
40
48
|
columns: ColumnDef<TData, any>[];
|
|
@@ -55,6 +63,7 @@ interface DataTableProps<TData> {
|
|
|
55
63
|
disableViewOptions?: boolean;
|
|
56
64
|
bulkActions?: BulkAction[];
|
|
57
65
|
/**
|
|
66
|
+
* @description
|
|
58
67
|
* This property allows full control over _all_ features of TanStack Table
|
|
59
68
|
* when needed.
|
|
60
69
|
*/
|
|
@@ -62,6 +71,17 @@ interface DataTableProps<TData> {
|
|
|
62
71
|
onRefresh?: () => void;
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @description
|
|
76
|
+
* A data table which includes sorting, filtering, pagination, bulk actions, column controls etc.
|
|
77
|
+
*
|
|
78
|
+
* This is the building block of all data tables in the Dashboard.
|
|
79
|
+
*
|
|
80
|
+
* @docsCategory list-views
|
|
81
|
+
* @docsPage DataTable
|
|
82
|
+
* @since 3.4.0
|
|
83
|
+
* @docsWeight 0
|
|
84
|
+
*/
|
|
65
85
|
export function DataTable<TData>({
|
|
66
86
|
children,
|
|
67
87
|
columns,
|
|
@@ -1 +1,40 @@
|
|
|
1
|
+
import { CellContext } from '@tanstack/table-core';
|
|
2
|
+
|
|
1
3
|
export type ColumnDataType = 'String' | 'Int' | 'Float' | 'DateTime' | 'Boolean' | 'ID' | 'Money';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @description
|
|
7
|
+
* This type is used to define re-usable components that can render a table cell in a
|
|
8
|
+
* DataTable.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { DataTableCellComponent } from '\@vendure/dashboard';
|
|
13
|
+
*
|
|
14
|
+
* type CustomerCellData = {
|
|
15
|
+
* customer: {
|
|
16
|
+
* id: string;
|
|
17
|
+
* firstName: string;
|
|
18
|
+
* lastName: string;
|
|
19
|
+
* } | null;
|
|
20
|
+
* };
|
|
21
|
+
*
|
|
22
|
+
* export const CustomerCell: DataTableCellComponent<CustomerCellData> = ({ row }) => {
|
|
23
|
+
* const value = row.original.customer;
|
|
24
|
+
* if (!value) {
|
|
25
|
+
* return null;
|
|
26
|
+
* }
|
|
27
|
+
* return (
|
|
28
|
+
* <Button asChild variant="ghost">
|
|
29
|
+
* <Link to={`/customers/${value.id}`}>
|
|
30
|
+
* {value.firstName} {value.lastName}
|
|
31
|
+
* </Link>
|
|
32
|
+
* </Button>
|
|
33
|
+
* );
|
|
34
|
+
* };
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @docsCategory list-views
|
|
38
|
+
* @since 3.4.0
|
|
39
|
+
*/
|
|
40
|
+
export type DataTableCellComponent<T> = <Data extends T>(context: CellContext<Data, any>) => any;
|
|
@@ -54,9 +54,7 @@ export function ChannelSwitcher() {
|
|
|
54
54
|
setContentLanguage,
|
|
55
55
|
} = useUserSettings();
|
|
56
56
|
const [showManageLanguagesDialog, setShowManageLanguagesDialog] = useState(false);
|
|
57
|
-
|
|
58
|
-
// Use the selected channel if available, otherwise fall back to the active channel
|
|
59
|
-
const displayChannel = activeChannel || activeChannel;
|
|
57
|
+
const displayChannel = activeChannel;
|
|
60
58
|
|
|
61
59
|
// Get available languages from server config
|
|
62
60
|
const availableLanguages = serverConfig?.availableLanguages || [];
|