@vendure/dashboard 3.4.3-master-202509200226 → 3.4.3-master-202509240228
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/_countries/countries.graphql.ts +2 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-container.tsx +2 -2
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-types.ts +5 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx +124 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history.tsx +91 -59
- package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +176 -0
- package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +4 -2
- package/src/app/routes/_authenticated/_customers/customers.graphql.ts +2 -0
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +302 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx +98 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx +9 -7
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-types.ts +5 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +173 -0
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx +64 -408
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +16 -0
- package/src/app/routes/_authenticated/_orders/components/seller-orders-card.tsx +61 -0
- package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +17 -10
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +35 -0
- package/src/app/routes/_authenticated/_orders/orders_.$aggregateOrderId_.seller-orders.$sellerOrderId.tsx +50 -0
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +17 -290
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +7 -39
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +4 -26
- package/src/app/routes/_authenticated/_orders/utils/order-detail-loaders.tsx +129 -0
- package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +8 -0
- package/src/app/routes/_authenticated/_roles/components/permissions-table-grid.tsx +251 -0
- package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +5 -3
- package/src/lib/components/shared/detail-page-button.tsx +6 -4
- package/src/lib/components/shared/history-timeline/history-note-entry.tsx +65 -0
- package/src/lib/components/shared/history-timeline/history-timeline-with-grouping.tsx +141 -0
- package/src/lib/components/shared/history-timeline/use-history-note-editor.ts +26 -0
- package/src/lib/framework/extension-api/define-dashboard-extension.ts +5 -0
- package/src/lib/framework/extension-api/extension-api-types.ts +7 -0
- package/src/lib/framework/extension-api/logic/history-entries.ts +24 -0
- package/src/lib/framework/extension-api/logic/index.ts +1 -0
- package/src/lib/framework/extension-api/types/history-entries.ts +120 -0
- package/src/lib/framework/extension-api/types/index.ts +1 -0
- package/src/lib/framework/history-entry/history-entry-extensions.ts +11 -0
- package/src/lib/framework/history-entry/history-entry.tsx +129 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +96 -9
- package/src/lib/framework/registry/registry-types.ts +2 -0
- package/src/lib/index.ts +12 -1
- package/src/app/routes/_authenticated/_roles/components/permissions-grid.tsx +0 -120
- package/src/lib/components/shared/history-timeline/history-entry.tsx +0 -188
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { CustomerHistoryCustomerDetail } from '../../../../app/routes/_authenticated/_customers/components/customer-history/customer-history-types.js';
|
|
4
|
+
import { OrderHistoryOrderDetail } from '../../../../app/routes/_authenticated/_orders/components/order-history/order-history-types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @description
|
|
8
|
+
* This object contains the information about the history entry.
|
|
9
|
+
*
|
|
10
|
+
* @docsCategory extensions-api
|
|
11
|
+
* @docsPage HistoryEntries
|
|
12
|
+
* @since 3.4.3
|
|
13
|
+
*/
|
|
14
|
+
export interface HistoryEntryItem {
|
|
15
|
+
id: string;
|
|
16
|
+
/**
|
|
17
|
+
* @description
|
|
18
|
+
* The `HistoryEntryType`, such as `ORDER_STATE_TRANSITION`.
|
|
19
|
+
*/
|
|
20
|
+
type: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
/**
|
|
23
|
+
* @description
|
|
24
|
+
* Whether this entry is visible to customers via the Shop API
|
|
25
|
+
*/
|
|
26
|
+
isPublic: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* @description
|
|
29
|
+
* If an Administrator created this entry, their details will
|
|
30
|
+
* be available here.
|
|
31
|
+
*/
|
|
32
|
+
administrator?: {
|
|
33
|
+
id: string;
|
|
34
|
+
firstName: string;
|
|
35
|
+
lastName: string;
|
|
36
|
+
} | null;
|
|
37
|
+
/**
|
|
38
|
+
* @description
|
|
39
|
+
* The entry payload data. This will be an object, which is different
|
|
40
|
+
* for each type of history entry.
|
|
41
|
+
*
|
|
42
|
+
* For example, the `CUSTOMER_ADDED_TO_GROUP` data looks like this:
|
|
43
|
+
* ```json
|
|
44
|
+
* {
|
|
45
|
+
* groupName: 'Some Group',
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* and the `ORDER_STATE_TRANSITION` data looks like this:
|
|
50
|
+
* ```json
|
|
51
|
+
* {
|
|
52
|
+
* from: 'ArrangingPayment',
|
|
53
|
+
* to: 'PaymentSettled',
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
data: any;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @description
|
|
62
|
+
* A definition of a custom component that will be used to render the given
|
|
63
|
+
* type of history entry.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* import { defineDashboardExtension, HistoryEntry } from '\@vendure/dashboard';
|
|
68
|
+
* import { IdCard } from 'lucide-react';
|
|
69
|
+
*
|
|
70
|
+
* defineDashboardExtension({
|
|
71
|
+
* historyEntries: [
|
|
72
|
+
* {
|
|
73
|
+
* type: 'CUSTOMER_TAX_ID_APPROVAL',
|
|
74
|
+
* component: ({ entry, entity }) => {
|
|
75
|
+
* return (
|
|
76
|
+
* <HistoryEntry
|
|
77
|
+
* entry={entry}
|
|
78
|
+
* title={'Tax ID verified'}
|
|
79
|
+
* timelineIconClassName={'bg-success text-success-foreground'}
|
|
80
|
+
* timelineIcon={<IdCard />}
|
|
81
|
+
* >
|
|
82
|
+
* <div className="text-xs">Approval reference: {entry.data.ref}</div>
|
|
83
|
+
* </HistoryEntry>
|
|
84
|
+
* );
|
|
85
|
+
* },
|
|
86
|
+
* },
|
|
87
|
+
* ],
|
|
88
|
+
* });
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @docsCategory extensions-api
|
|
92
|
+
* @docsPage HistoryEntries
|
|
93
|
+
* @since 3.4.3
|
|
94
|
+
* @docsWeight 0
|
|
95
|
+
*/
|
|
96
|
+
export interface DashboardHistoryEntryComponent {
|
|
97
|
+
/**
|
|
98
|
+
* @description
|
|
99
|
+
* The `type` should correspond to a valid `HistoryEntryType`, such as
|
|
100
|
+
*
|
|
101
|
+
* - `CUSTOMER_REGISTERED`
|
|
102
|
+
* - `ORDER_STATE_TRANSITION`
|
|
103
|
+
* - some custom type - see the {@link HistoryService} docs for a guide on
|
|
104
|
+
* how to define custom history entry types.
|
|
105
|
+
*/
|
|
106
|
+
type: string;
|
|
107
|
+
/**
|
|
108
|
+
* @description
|
|
109
|
+
* The component which is used to render the timeline entry. It should use the
|
|
110
|
+
* {@link HistoryEntry} component and pass the appropriate props to configure
|
|
111
|
+
* how it will be displayed.
|
|
112
|
+
*
|
|
113
|
+
* The `entity` prop will be a subset of the Order object for Order history entries,
|
|
114
|
+
* or a subset of the Customer object for customer history entries.
|
|
115
|
+
*/
|
|
116
|
+
component: React.ComponentType<{
|
|
117
|
+
entry: HistoryEntryItem;
|
|
118
|
+
entity: OrderHistoryOrderDetail | CustomerHistoryCustomerDetail;
|
|
119
|
+
}>;
|
|
120
|
+
}
|
|
@@ -3,6 +3,7 @@ export * from './alerts.js';
|
|
|
3
3
|
export * from './data-table.js';
|
|
4
4
|
export * from './detail-forms.js';
|
|
5
5
|
export * from './form-components.js';
|
|
6
|
+
export * from './history-entries.js';
|
|
6
7
|
export * from './layout.js';
|
|
7
8
|
export * from './login.js';
|
|
8
9
|
export * from './navigation.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DashboardHistoryEntryComponent } from '@/vdb/framework/extension-api/types/index.js';
|
|
2
|
+
|
|
3
|
+
import { globalRegistry } from '../registry/global-registry.js';
|
|
4
|
+
|
|
5
|
+
globalRegistry.register('historyEntries', new Map<string, DashboardHistoryEntryComponent['component']>());
|
|
6
|
+
|
|
7
|
+
export function getCustomHistoryEntryForType(
|
|
8
|
+
type: string,
|
|
9
|
+
): DashboardHistoryEntryComponent['component'] | undefined {
|
|
10
|
+
return globalRegistry.get('historyEntries').get(type);
|
|
11
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { HistoryEntryItem } from '@/vdb/framework/extension-api/types/index.js';
|
|
2
|
+
import { cn } from '@/vdb/lib/utils.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { HistoryEntryDate } from '../../components/shared/history-timeline/history-entry-date.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @description
|
|
8
|
+
* The props for the {@link HistoryEntry} component.
|
|
9
|
+
*
|
|
10
|
+
* @docsCategory extensions-api
|
|
11
|
+
* @docsPage HistoryEntries
|
|
12
|
+
* @since 3.4.3
|
|
13
|
+
*/
|
|
14
|
+
export interface HistoryEntryProps {
|
|
15
|
+
/**
|
|
16
|
+
* @description
|
|
17
|
+
* The entry itself, which will get passed down to your custom component
|
|
18
|
+
*/
|
|
19
|
+
entry: HistoryEntryItem;
|
|
20
|
+
/**
|
|
21
|
+
* @description
|
|
22
|
+
* The title of the entry
|
|
23
|
+
*/
|
|
24
|
+
title: string | React.ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* @description
|
|
27
|
+
* An icon which is used to represent the entry. Note that this will only
|
|
28
|
+
* display if `isPrimary` is `true`.
|
|
29
|
+
*/
|
|
30
|
+
timelineIcon?: React.ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* @description
|
|
33
|
+
* Optional tailwind classes to apply to the icon. For instance
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* const success = 'bg-success text-success-foreground';
|
|
37
|
+
* const destructive = 'bg-danger text-danger-foreground';
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
timelineIconClassName?: string;
|
|
41
|
+
/**
|
|
42
|
+
* @description
|
|
43
|
+
* The name to display of "who did the action". For instance:
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* const getActorName = (entry: HistoryEntryItem) => {
|
|
47
|
+
* if (entry.administrator) {
|
|
48
|
+
* return `${entry.administrator.firstName} ${entry.administrator.lastName}`;
|
|
49
|
+
* } else if (entity?.customer) {
|
|
50
|
+
* return `${entity.customer.firstName} ${entity.customer.lastName}`;
|
|
51
|
+
* }
|
|
52
|
+
* return '';
|
|
53
|
+
* };
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
actorName?: string;
|
|
57
|
+
children: React.ReactNode;
|
|
58
|
+
/**
|
|
59
|
+
* @description
|
|
60
|
+
* When set to `true`, the timeline entry will feature the specified icon and will not
|
|
61
|
+
* be collapsible.
|
|
62
|
+
*/
|
|
63
|
+
isPrimary?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @description
|
|
68
|
+
* A component which is used to display a history entry in the order/customer history timeline.
|
|
69
|
+
*
|
|
70
|
+
* @docsCategory extensions-api
|
|
71
|
+
* @docsPage HistoryEntries
|
|
72
|
+
* @since 3.4.3
|
|
73
|
+
*/
|
|
74
|
+
export function HistoryEntry({
|
|
75
|
+
entry,
|
|
76
|
+
timelineIcon,
|
|
77
|
+
timelineIconClassName,
|
|
78
|
+
actorName,
|
|
79
|
+
title,
|
|
80
|
+
children,
|
|
81
|
+
isPrimary = true,
|
|
82
|
+
}: Readonly<HistoryEntryProps>) {
|
|
83
|
+
return (
|
|
84
|
+
<div key={entry.id} className="relative group">
|
|
85
|
+
<div
|
|
86
|
+
className={`flex gap-3 p-3 rounded-lg hover:bg-muted/30 transition-colors ${!isPrimary ? 'opacity-90' : ''}`}
|
|
87
|
+
>
|
|
88
|
+
<div className={cn(`relative z-10 flex-shrink-0`, isPrimary ? 'ml-0' : 'ml-2 mt-1')}>
|
|
89
|
+
<div
|
|
90
|
+
className={`rounded-full flex items-center justify-center ${isPrimary ? 'h-6 w-6' : 'h-2 w-2 border'} ${timelineIconClassName ?? ''} ${isPrimary ? 'shadow-sm' : 'shadow-none'}`}
|
|
91
|
+
>
|
|
92
|
+
<div className={isPrimary ? 'text-current scale-80' : 'text-current scale-0'}>
|
|
93
|
+
{timelineIcon ?? ''}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div className="flex-1 min-w-0">
|
|
99
|
+
<div className="flex items-start justify-between">
|
|
100
|
+
<div className="flex-1 min-w-0">
|
|
101
|
+
<h4
|
|
102
|
+
className={`text-sm ${isPrimary ? 'font-medium text-foreground' : 'font-normal text-muted-foreground'}`}
|
|
103
|
+
>
|
|
104
|
+
{title}
|
|
105
|
+
</h4>
|
|
106
|
+
<div className="mt-1">{children}</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div className="flex items-center gap-2 ml-4 flex-shrink-0">
|
|
110
|
+
<div className="text-right">
|
|
111
|
+
<HistoryEntryDate
|
|
112
|
+
date={entry.createdAt}
|
|
113
|
+
className={`text-xs cursor-help ${isPrimary ? 'text-muted-foreground' : 'text-muted-foreground/70'}`}
|
|
114
|
+
/>
|
|
115
|
+
{actorName && (
|
|
116
|
+
<div
|
|
117
|
+
className={`text-xs ${isPrimary ? 'text-muted-foreground' : 'text-muted-foreground/70'}`}
|
|
118
|
+
>
|
|
119
|
+
{actorName}
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -6,17 +6,24 @@ import { Form } from '@/vdb/components/ui/form.js';
|
|
|
6
6
|
import { useCustomFieldConfig } from '@/vdb/hooks/use-custom-field-config.js';
|
|
7
7
|
import { usePage } from '@/vdb/hooks/use-page.js';
|
|
8
8
|
import { cn } from '@/vdb/lib/utils.js';
|
|
9
|
-
import { useMediaQuery } from '@uidotdev/usehooks';
|
|
10
|
-
import { EllipsisVerticalIcon } from 'lucide-react';
|
|
11
|
-
import React, { ComponentProps, useMemo } from 'react';
|
|
9
|
+
import { useCopyToClipboard, useMediaQuery } from '@uidotdev/usehooks';
|
|
10
|
+
import { CheckIcon, CopyIcon, EllipsisVerticalIcon, InfoIcon } from 'lucide-react';
|
|
11
|
+
import React, { ComponentProps, useMemo, useState } from 'react';
|
|
12
12
|
import { Control, UseFormReturn } from 'react-hook-form';
|
|
13
13
|
|
|
14
14
|
import { DashboardActionBarItem } from '../extension-api/types/layout.js';
|
|
15
15
|
|
|
16
16
|
import { Button } from '@/vdb/components/ui/button.js';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
DropdownMenu,
|
|
19
|
+
DropdownMenuContent,
|
|
20
|
+
DropdownMenuLabel,
|
|
21
|
+
DropdownMenuSeparator,
|
|
22
|
+
DropdownMenuTrigger,
|
|
23
|
+
} from '@/vdb/components/ui/dropdown-menu.js';
|
|
18
24
|
import { PageBlockContext } from '@/vdb/framework/layout-engine/page-block-provider.js';
|
|
19
25
|
import { PageContext, PageContextValue } from '@/vdb/framework/layout-engine/page-provider.js';
|
|
26
|
+
import { Trans } from '@/vdb/lib/trans.js';
|
|
20
27
|
import { getDashboardActionBarItems, getDashboardPageBlocks } from './layout-extensions.js';
|
|
21
28
|
import { LocationWrapper } from './location-wrapper.js';
|
|
22
29
|
|
|
@@ -50,13 +57,13 @@ export interface PageProps extends ComponentProps<'div'> {
|
|
|
50
57
|
* - {@link PageTitle}
|
|
51
58
|
* - {@link PageActionBar}
|
|
52
59
|
* - {@link PageLayout}
|
|
53
|
-
*
|
|
60
|
+
*
|
|
54
61
|
* @example
|
|
55
62
|
* ```tsx
|
|
56
63
|
* import { Page, PageTitle, PageActionBar, PageLayout, PageBlock, Button } from '\@vendure/dashboard';
|
|
57
|
-
*
|
|
64
|
+
*
|
|
58
65
|
* const pageId = 'my-page';
|
|
59
|
-
*
|
|
66
|
+
*
|
|
60
67
|
* export function MyPage() {
|
|
61
68
|
* return (
|
|
62
69
|
* <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
@@ -319,7 +326,7 @@ export function PageActionBar({ children }: Readonly<{ children: React.ReactNode
|
|
|
319
326
|
/**
|
|
320
327
|
* @description
|
|
321
328
|
* The PageActionBarLeft component should be used to display the left content of the action bar.
|
|
322
|
-
*
|
|
329
|
+
*
|
|
323
330
|
* @docsCategory page-layout
|
|
324
331
|
* @docsPage PageActionBar
|
|
325
332
|
* @since 3.3.0
|
|
@@ -330,6 +337,85 @@ export function PageActionBarLeft({ children }: Readonly<{ children: React.React
|
|
|
330
337
|
|
|
331
338
|
type InlineDropdownItem = Omit<DashboardActionBarItem, 'type' | 'pageId'>;
|
|
332
339
|
|
|
340
|
+
function EntityInfoDropdown({ entity }: Readonly<{ entity: any }>) {
|
|
341
|
+
const [copiedField, setCopiedField] = useState<string | null>(null);
|
|
342
|
+
const [, copy] = useCopyToClipboard();
|
|
343
|
+
|
|
344
|
+
const handleCopy = async (text: string, field: string) => {
|
|
345
|
+
await copy(text);
|
|
346
|
+
setCopiedField(field);
|
|
347
|
+
setTimeout(() => setCopiedField(null), 2000);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const formatDate = (dateString: string) => {
|
|
351
|
+
return new Date(dateString).toLocaleString();
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
if (!entity || !entity.id) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
<DropdownMenu>
|
|
360
|
+
<DropdownMenuTrigger asChild>
|
|
361
|
+
<Button variant="ghost" size="icon" className="text-muted-foreground">
|
|
362
|
+
<InfoIcon className="w-4 h-4" />
|
|
363
|
+
</Button>
|
|
364
|
+
</DropdownMenuTrigger>
|
|
365
|
+
<DropdownMenuContent align="end" className="w-64">
|
|
366
|
+
<DropdownMenuLabel>
|
|
367
|
+
<Trans>Entity Information</Trans>
|
|
368
|
+
</DropdownMenuLabel>
|
|
369
|
+
<DropdownMenuSeparator />
|
|
370
|
+
<div className="px-3 py-2">
|
|
371
|
+
<div className="flex items-center justify-between">
|
|
372
|
+
<span className="text-sm font-medium">ID</span>
|
|
373
|
+
<div className="flex items-center gap-1">
|
|
374
|
+
<span className="text-sm font-mono">{entity.id}</span>
|
|
375
|
+
<button
|
|
376
|
+
onClick={() => handleCopy(entity.id, 'id')}
|
|
377
|
+
className="p-1 hover:bg-muted rounded-sm transition-colors"
|
|
378
|
+
>
|
|
379
|
+
{copiedField === 'id' ? (
|
|
380
|
+
<CheckIcon className="h-3 w-3 text-green-500" />
|
|
381
|
+
) : (
|
|
382
|
+
<CopyIcon className="h-3 w-3" />
|
|
383
|
+
)}
|
|
384
|
+
</button>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
{entity.createdAt && (
|
|
389
|
+
<>
|
|
390
|
+
<DropdownMenuSeparator />
|
|
391
|
+
<div className="px-3 py-2">
|
|
392
|
+
<div className="text-sm">
|
|
393
|
+
<div className="font-medium text-muted-foreground">
|
|
394
|
+
<Trans>Created</Trans>
|
|
395
|
+
</div>
|
|
396
|
+
<div className="text-xs">{formatDate(entity.createdAt)}</div>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
</>
|
|
400
|
+
)}
|
|
401
|
+
{entity.updatedAt && (
|
|
402
|
+
<>
|
|
403
|
+
<DropdownMenuSeparator />
|
|
404
|
+
<div className="px-3 py-2">
|
|
405
|
+
<div className="text-sm">
|
|
406
|
+
<div className="font-medium text-muted-foreground">
|
|
407
|
+
<Trans>Updated</Trans>
|
|
408
|
+
</div>
|
|
409
|
+
<div className="text-xs">{formatDate(entity.updatedAt)}</div>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
</>
|
|
413
|
+
)}
|
|
414
|
+
</DropdownMenuContent>
|
|
415
|
+
</DropdownMenu>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
333
419
|
/**
|
|
334
420
|
* @description
|
|
335
421
|
* The PageActionBarRight component should be used to display the right content of the action bar.
|
|
@@ -366,6 +452,7 @@ export function PageActionBarRight({
|
|
|
366
452
|
{actionBarDropdownItems.length > 0 && (
|
|
367
453
|
<PageActionBarDropdown items={actionBarDropdownItems} page={page} />
|
|
368
454
|
)}
|
|
455
|
+
<EntityInfoDropdown entity={page.entity} />
|
|
369
456
|
</div>
|
|
370
457
|
);
|
|
371
458
|
}
|
|
@@ -449,7 +536,7 @@ export type PageBlockProps = {
|
|
|
449
536
|
* A component for displaying a block of content on a page. This should be used inside the {@link PageLayout} component.
|
|
450
537
|
* It should be provided with a `column` prop to determine which column it should appear in, and a `blockId` prop
|
|
451
538
|
* to identify the block.
|
|
452
|
-
*
|
|
539
|
+
*
|
|
453
540
|
* @example
|
|
454
541
|
* ```tsx
|
|
455
542
|
* <PageBlock column="main" blockId="my-block">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BulkAction,
|
|
3
3
|
DashboardActionBarItem,
|
|
4
|
+
DashboardHistoryEntryComponent,
|
|
4
5
|
DashboardLoginExtensions,
|
|
5
6
|
DashboardPageBlockDefinition,
|
|
6
7
|
DashboardWidgetDefinition,
|
|
@@ -26,4 +27,5 @@ export interface GlobalRegistryContents {
|
|
|
26
27
|
listQueryDocumentRegistry: Map<string, DocumentNode[]>;
|
|
27
28
|
detailQueryDocumentRegistry: Map<string, DocumentNode[]>;
|
|
28
29
|
loginExtensions: DashboardLoginExtensions;
|
|
30
|
+
historyEntries: Map<string, DashboardHistoryEntryComponent['component']>;
|
|
29
31
|
}
|
package/src/lib/index.ts
CHANGED
|
@@ -44,6 +44,7 @@ export * from './components/data-table/filters/data-table-string-filter.js';
|
|
|
44
44
|
export * from './components/data-table/human-readable-operator.js';
|
|
45
45
|
export * from './components/data-table/refresh-button.js';
|
|
46
46
|
export * from './components/data-table/types.js';
|
|
47
|
+
export * from './components/data-table/use-all-bulk-actions.js';
|
|
47
48
|
export * from './components/data-table/use-generated-columns.js';
|
|
48
49
|
export * from './components/labeled-data.js';
|
|
49
50
|
export * from './components/layout/app-layout.js';
|
|
@@ -95,10 +96,10 @@ export * from './components/shared/facet-value-chip.js';
|
|
|
95
96
|
export * from './components/shared/facet-value-selector.js';
|
|
96
97
|
export * from './components/shared/form-field-wrapper.js';
|
|
97
98
|
export * from './components/shared/history-timeline/history-entry-date.js';
|
|
98
|
-
export * from './components/shared/history-timeline/history-entry.js';
|
|
99
99
|
export * from './components/shared/history-timeline/history-note-checkbox.js';
|
|
100
100
|
export * from './components/shared/history-timeline/history-note-editor.js';
|
|
101
101
|
export * from './components/shared/history-timeline/history-note-input.js';
|
|
102
|
+
export * from './components/shared/history-timeline/history-timeline-with-grouping.js';
|
|
102
103
|
export * from './components/shared/history-timeline/history-timeline.js';
|
|
103
104
|
export * from './components/shared/icon-mark.js';
|
|
104
105
|
export * from './components/shared/language-selector.js';
|
|
@@ -110,6 +111,12 @@ export * from './components/shared/paginated-list-data-table.js';
|
|
|
110
111
|
export * from './components/shared/permission-guard.js';
|
|
111
112
|
export * from './components/shared/product-variant-selector.js';
|
|
112
113
|
export * from './components/shared/remove-from-channel-bulk-action.js';
|
|
114
|
+
export * from './components/shared/rich-text-editor/image-dialog.js';
|
|
115
|
+
export * from './components/shared/rich-text-editor/link-dialog.js';
|
|
116
|
+
export * from './components/shared/rich-text-editor/responsive-toolbar.js';
|
|
117
|
+
export * from './components/shared/rich-text-editor/rich-text-editor.js';
|
|
118
|
+
export * from './components/shared/rich-text-editor/table-delete-menu.js';
|
|
119
|
+
export * from './components/shared/rich-text-editor/table-edit-icons.js';
|
|
113
120
|
export * from './components/shared/role-code-label.js';
|
|
114
121
|
export * from './components/shared/role-selector.js';
|
|
115
122
|
export * from './components/shared/seller-selector.js';
|
|
@@ -191,6 +198,7 @@ export * from './framework/extension-api/logic/alerts.js';
|
|
|
191
198
|
export * from './framework/extension-api/logic/data-table.js';
|
|
192
199
|
export * from './framework/extension-api/logic/detail-forms.js';
|
|
193
200
|
export * from './framework/extension-api/logic/form-components.js';
|
|
201
|
+
export * from './framework/extension-api/logic/history-entries.js';
|
|
194
202
|
export * from './framework/extension-api/logic/layout.js';
|
|
195
203
|
export * from './framework/extension-api/logic/login.js';
|
|
196
204
|
export * from './framework/extension-api/logic/navigation.js';
|
|
@@ -199,6 +207,7 @@ export * from './framework/extension-api/types/alerts.js';
|
|
|
199
207
|
export * from './framework/extension-api/types/data-table.js';
|
|
200
208
|
export * from './framework/extension-api/types/detail-forms.js';
|
|
201
209
|
export * from './framework/extension-api/types/form-components.js';
|
|
210
|
+
export * from './framework/extension-api/types/history-entries.js';
|
|
202
211
|
export * from './framework/extension-api/types/layout.js';
|
|
203
212
|
export * from './framework/extension-api/types/login.js';
|
|
204
213
|
export * from './framework/extension-api/types/navigation.js';
|
|
@@ -215,6 +224,8 @@ export * from './framework/form-engine/overridden-form-component.js';
|
|
|
215
224
|
export * from './framework/form-engine/use-generated-form.js';
|
|
216
225
|
export * from './framework/form-engine/utils.js';
|
|
217
226
|
export * from './framework/form-engine/value-transformers.js';
|
|
227
|
+
export * from './framework/history-entry/history-entry-extensions.js';
|
|
228
|
+
export * from './framework/history-entry/history-entry.js';
|
|
218
229
|
export * from './framework/layout-engine/dev-mode-button.js';
|
|
219
230
|
export * from './framework/layout-engine/layout-extensions.js';
|
|
220
231
|
export * from './framework/layout-engine/location-wrapper.js';
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Accordion,
|
|
3
|
-
AccordionContent,
|
|
4
|
-
AccordionItem,
|
|
5
|
-
AccordionTrigger,
|
|
6
|
-
} from '@/vdb/components/ui/accordion.js';
|
|
7
|
-
import { Button } from '@/vdb/components/ui/button.js';
|
|
8
|
-
import { Switch } from '@/vdb/components/ui/switch.js';
|
|
9
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/vdb/components/ui/tooltip.js';
|
|
10
|
-
import { useGroupedPermissions } from '@/vdb/hooks/use-grouped-permissions.js';
|
|
11
|
-
import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
12
|
-
import { ServerConfig } from '@/vdb/providers/server-config.js';
|
|
13
|
-
import { useState } from 'react';
|
|
14
|
-
|
|
15
|
-
interface PermissionsGridProps {
|
|
16
|
-
value: string[];
|
|
17
|
-
onChange: (permissions: string[]) => void;
|
|
18
|
-
readonly?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function PermissionsGrid({ value, onChange, readonly = false }: Readonly<PermissionsGridProps>) {
|
|
22
|
-
const { i18n } = useLingui();
|
|
23
|
-
const groupedPermissions = useGroupedPermissions();
|
|
24
|
-
|
|
25
|
-
const setPermission = (permission: string, checked: boolean) => {
|
|
26
|
-
if (readonly) return;
|
|
27
|
-
|
|
28
|
-
const newPermissions = checked ? [...value, permission] : value.filter(p => p !== permission);
|
|
29
|
-
onChange(newPermissions);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const toggleAll = (defs: ServerConfig['permissions']) => {
|
|
33
|
-
if (readonly) return;
|
|
34
|
-
|
|
35
|
-
const shouldEnable = defs.some(d => !value.includes(d.name));
|
|
36
|
-
const newPermissions = shouldEnable
|
|
37
|
-
? [...new Set([...value, ...defs.map(d => d.name)])]
|
|
38
|
-
: value.filter(p => !defs.some(d => d.name === p));
|
|
39
|
-
onChange(newPermissions);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Get default expanded sections based on which ones have active permissions
|
|
43
|
-
const defaultExpandedSections = groupedPermissions
|
|
44
|
-
.map(section => ({
|
|
45
|
-
section,
|
|
46
|
-
hasActivePermissions: section.permissions.some(permission => value.includes(permission.name)),
|
|
47
|
-
}))
|
|
48
|
-
.filter(({ hasActivePermissions }) => hasActivePermissions)
|
|
49
|
-
.map(({ section }) => section.id);
|
|
50
|
-
|
|
51
|
-
const [accordionValue, setAccordionValue] = useState<string[]>(defaultExpandedSections);
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<div className="w-full">
|
|
55
|
-
<Accordion
|
|
56
|
-
type="multiple"
|
|
57
|
-
value={accordionValue.length ? accordionValue : defaultExpandedSections}
|
|
58
|
-
onValueChange={setAccordionValue}
|
|
59
|
-
className="space-y-4"
|
|
60
|
-
>
|
|
61
|
-
{groupedPermissions.map((section, index) => (
|
|
62
|
-
<AccordionItem key={index} value={section.id} className="border rounded-lg px-6">
|
|
63
|
-
<AccordionTrigger className="hover:no-underline">
|
|
64
|
-
<div className="flex flex-col items-start gap-1 text-sm py-2">
|
|
65
|
-
<div>{i18n.t(section.label)}</div>
|
|
66
|
-
<div className="text-muted-foreground text-sm font-normal">
|
|
67
|
-
{i18n.t(section.description)}
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
</AccordionTrigger>
|
|
71
|
-
<AccordionContent>
|
|
72
|
-
<div className="pb-4 space-y-4">
|
|
73
|
-
{section.permissions.length > 1 && !readonly && (
|
|
74
|
-
<Button
|
|
75
|
-
variant="outline"
|
|
76
|
-
type="button"
|
|
77
|
-
size="sm"
|
|
78
|
-
onClick={() => toggleAll(section.permissions)}
|
|
79
|
-
className="w-fit"
|
|
80
|
-
>
|
|
81
|
-
<Trans>Toggle all</Trans>
|
|
82
|
-
</Button>
|
|
83
|
-
)}
|
|
84
|
-
<div className="md:grid md:grid-cols-4 md:gap-2 space-y-2">
|
|
85
|
-
{section.permissions.map(permission => (
|
|
86
|
-
<div key={permission.name} className="flex items-center space-x-2">
|
|
87
|
-
<Switch
|
|
88
|
-
id={permission.name}
|
|
89
|
-
checked={value.includes(permission.name)}
|
|
90
|
-
onCheckedChange={checked =>
|
|
91
|
-
setPermission(permission.name, checked)
|
|
92
|
-
}
|
|
93
|
-
disabled={readonly}
|
|
94
|
-
/>
|
|
95
|
-
<TooltipProvider>
|
|
96
|
-
<Tooltip>
|
|
97
|
-
<TooltipTrigger asChild>
|
|
98
|
-
<label
|
|
99
|
-
htmlFor={permission.name}
|
|
100
|
-
className="text-sm whitespace-nowrap"
|
|
101
|
-
>
|
|
102
|
-
{i18n.t(permission.name)}
|
|
103
|
-
</label>
|
|
104
|
-
</TooltipTrigger>
|
|
105
|
-
<TooltipContent align="end">
|
|
106
|
-
<p>{i18n.t(permission.description)}</p>
|
|
107
|
-
</TooltipContent>
|
|
108
|
-
</Tooltip>
|
|
109
|
-
</TooltipProvider>
|
|
110
|
-
</div>
|
|
111
|
-
))}
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
</AccordionContent>
|
|
115
|
-
</AccordionItem>
|
|
116
|
-
))}
|
|
117
|
-
</Accordion>
|
|
118
|
-
</div>
|
|
119
|
-
);
|
|
120
|
-
}
|