@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.
Files changed (44) hide show
  1. package/package.json +4 -4
  2. package/src/app/routes/_authenticated/_countries/countries.graphql.ts +2 -0
  3. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-container.tsx +2 -2
  4. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-types.ts +5 -0
  5. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx +124 -0
  6. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history.tsx +91 -59
  7. package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +176 -0
  8. package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +4 -2
  9. package/src/app/routes/_authenticated/_customers/customers.graphql.ts +2 -0
  10. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +302 -0
  11. package/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx +98 -0
  12. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx +9 -7
  13. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-types.ts +5 -0
  14. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +173 -0
  15. package/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx +64 -408
  16. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +16 -0
  17. package/src/app/routes/_authenticated/_orders/components/seller-orders-card.tsx +61 -0
  18. package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +17 -10
  19. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +35 -0
  20. package/src/app/routes/_authenticated/_orders/orders_.$aggregateOrderId_.seller-orders.$sellerOrderId.tsx +50 -0
  21. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +17 -290
  22. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +7 -39
  23. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +4 -26
  24. package/src/app/routes/_authenticated/_orders/utils/order-detail-loaders.tsx +129 -0
  25. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +8 -0
  26. package/src/app/routes/_authenticated/_roles/components/permissions-table-grid.tsx +251 -0
  27. package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +5 -3
  28. package/src/lib/components/shared/detail-page-button.tsx +6 -4
  29. package/src/lib/components/shared/history-timeline/history-note-entry.tsx +65 -0
  30. package/src/lib/components/shared/history-timeline/history-timeline-with-grouping.tsx +141 -0
  31. package/src/lib/components/shared/history-timeline/use-history-note-editor.ts +26 -0
  32. package/src/lib/framework/extension-api/define-dashboard-extension.ts +5 -0
  33. package/src/lib/framework/extension-api/extension-api-types.ts +7 -0
  34. package/src/lib/framework/extension-api/logic/history-entries.ts +24 -0
  35. package/src/lib/framework/extension-api/logic/index.ts +1 -0
  36. package/src/lib/framework/extension-api/types/history-entries.ts +120 -0
  37. package/src/lib/framework/extension-api/types/index.ts +1 -0
  38. package/src/lib/framework/history-entry/history-entry-extensions.ts +11 -0
  39. package/src/lib/framework/history-entry/history-entry.tsx +129 -0
  40. package/src/lib/framework/layout-engine/page-layout.tsx +96 -9
  41. package/src/lib/framework/registry/registry-types.ts +2 -0
  42. package/src/lib/index.ts +12 -1
  43. package/src/app/routes/_authenticated/_roles/components/permissions-grid.tsx +0 -120
  44. 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 { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/vdb/components/ui/dropdown-menu.js';
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
- }