@vendure/dashboard 3.4.3-master-202509230228 → 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/_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-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/orders.graphql.ts +4 -0
- 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/registry/registry-types.ts +2 -0
- package/src/lib/index.ts +5 -1
- package/src/lib/components/shared/history-timeline/history-entry.tsx +0 -188
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Button } from '@/vdb/components/ui/button.js';
|
|
2
|
+
import { HistoryEntryItem } from '@/vdb/framework/extension-api/types/history-entries.js';
|
|
3
|
+
import { getCustomHistoryEntryForType } from '@/vdb/framework/history-entry/history-entry-extensions.js';
|
|
4
|
+
import { ChevronDown, ChevronUp } from 'lucide-react';
|
|
5
|
+
import { ReactNode, useState } from 'react';
|
|
6
|
+
import { CustomerHistoryCustomerDetail } from '../../../../app/routes/_authenticated/_customers/components/customer-history/customer-history-types.js';
|
|
7
|
+
import { OrderHistoryOrderDetail } from '../../../../app/routes/_authenticated/_orders/components/order-history/order-history-types.js';
|
|
8
|
+
import { HistoryTimeline } from './history-timeline.js';
|
|
9
|
+
|
|
10
|
+
interface HistoryTimelineWithGroupingProps {
|
|
11
|
+
historyEntries: HistoryEntryItem[];
|
|
12
|
+
entity: OrderHistoryOrderDetail | CustomerHistoryCustomerDetail;
|
|
13
|
+
isPrimaryEvent: (entry: HistoryEntryItem) => boolean;
|
|
14
|
+
renderEntryContent: (entry: HistoryEntryItem) => ReactNode;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type EntryWithIndex = {
|
|
19
|
+
entry: HistoryEntryItem;
|
|
20
|
+
index: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function HistoryTimelineWithGrouping({
|
|
24
|
+
historyEntries,
|
|
25
|
+
entity,
|
|
26
|
+
isPrimaryEvent,
|
|
27
|
+
renderEntryContent,
|
|
28
|
+
children,
|
|
29
|
+
}: Readonly<HistoryTimelineWithGroupingProps>) {
|
|
30
|
+
const [expandedGroups, setExpandedGroups] = useState<Set<number>>(new Set());
|
|
31
|
+
|
|
32
|
+
// Group consecutive secondary events
|
|
33
|
+
const groupedEntries: Array<
|
|
34
|
+
| ({ type: 'primary' } & EntryWithIndex)
|
|
35
|
+
| {
|
|
36
|
+
type: 'secondary-group';
|
|
37
|
+
entries: Array<EntryWithIndex>;
|
|
38
|
+
startIndex: number;
|
|
39
|
+
}
|
|
40
|
+
> = [];
|
|
41
|
+
let currentGroup: Array<EntryWithIndex> = [];
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < historyEntries.length; i++) {
|
|
44
|
+
const entry = historyEntries[i];
|
|
45
|
+
const isSecondary = !isPrimaryEvent(entry);
|
|
46
|
+
|
|
47
|
+
if (isSecondary) {
|
|
48
|
+
currentGroup.push({ entry, index: i });
|
|
49
|
+
} else {
|
|
50
|
+
// If we have accumulated secondary events, add them as a group
|
|
51
|
+
if (currentGroup.length > 0) {
|
|
52
|
+
groupedEntries.push({
|
|
53
|
+
type: 'secondary-group',
|
|
54
|
+
entries: currentGroup,
|
|
55
|
+
startIndex: currentGroup[0].index,
|
|
56
|
+
});
|
|
57
|
+
currentGroup = [];
|
|
58
|
+
}
|
|
59
|
+
// Add the primary event
|
|
60
|
+
groupedEntries.push({ type: 'primary', entry, index: i });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Don't forget the last group if it exists
|
|
65
|
+
if (currentGroup.length > 0) {
|
|
66
|
+
groupedEntries.push({
|
|
67
|
+
type: 'secondary-group',
|
|
68
|
+
entries: currentGroup,
|
|
69
|
+
startIndex: currentGroup[0].index,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const renderEntry = (entry: HistoryEntryItem) => {
|
|
74
|
+
const CustomType = getCustomHistoryEntryForType(entry.type);
|
|
75
|
+
if (CustomType) {
|
|
76
|
+
return <CustomType entry={entry} entity={entity} />;
|
|
77
|
+
} else {
|
|
78
|
+
return renderEntryContent(entry);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const toggleGroup = (groupIndex: number) => {
|
|
83
|
+
const newExpanded = new Set(expandedGroups);
|
|
84
|
+
if (newExpanded.has(groupIndex)) {
|
|
85
|
+
newExpanded.delete(groupIndex);
|
|
86
|
+
} else {
|
|
87
|
+
newExpanded.add(groupIndex);
|
|
88
|
+
}
|
|
89
|
+
setExpandedGroups(newExpanded);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="">
|
|
94
|
+
{children && <div className="mb-4">{children}</div>}
|
|
95
|
+
<HistoryTimeline>
|
|
96
|
+
{groupedEntries.map((group, groupIndex) => {
|
|
97
|
+
if (group.type === 'primary') {
|
|
98
|
+
const entry = group.entry;
|
|
99
|
+
return <div key={entry.id}>{renderEntry(entry)}</div>;
|
|
100
|
+
} else {
|
|
101
|
+
// Secondary group
|
|
102
|
+
const shouldCollapse = group.entries.length > 2;
|
|
103
|
+
const isExpanded = expandedGroups.has(groupIndex);
|
|
104
|
+
const visibleEntries =
|
|
105
|
+
shouldCollapse && !isExpanded ? group.entries.slice(0, 2) : group.entries;
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div key={`group-${groupIndex}`}>
|
|
109
|
+
{visibleEntries.map(({ entry }) => (
|
|
110
|
+
<div key={entry.id}>{renderEntry(entry)}</div>
|
|
111
|
+
))}
|
|
112
|
+
{shouldCollapse && (
|
|
113
|
+
<div className="flex justify-center py-2">
|
|
114
|
+
<Button
|
|
115
|
+
variant="ghost"
|
|
116
|
+
size="sm"
|
|
117
|
+
onClick={() => toggleGroup(groupIndex)}
|
|
118
|
+
className="text-muted-foreground hover:text-foreground h-6 text-xs"
|
|
119
|
+
>
|
|
120
|
+
{isExpanded ? (
|
|
121
|
+
<>
|
|
122
|
+
<ChevronUp className="w-3 h-3 mr-1" />
|
|
123
|
+
Show less
|
|
124
|
+
</>
|
|
125
|
+
) : (
|
|
126
|
+
<>
|
|
127
|
+
<ChevronDown className="w-3 h-3 mr-1" />
|
|
128
|
+
Show all ({group.entries.length - 2})
|
|
129
|
+
</>
|
|
130
|
+
)}
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
})}
|
|
138
|
+
</HistoryTimeline>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useHistoryNoteEditor() {
|
|
4
|
+
const [noteEditorOpen, setNoteEditorOpen] = useState(false);
|
|
5
|
+
const [noteState, setNoteState] = useState<{
|
|
6
|
+
noteId: string;
|
|
7
|
+
note: string;
|
|
8
|
+
isPrivate: boolean;
|
|
9
|
+
}>({
|
|
10
|
+
noteId: '',
|
|
11
|
+
note: '',
|
|
12
|
+
isPrivate: true,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const handleEditNote = (noteId: string, note: string, isPrivate: boolean) => {
|
|
16
|
+
setNoteState({ noteId, note, isPrivate });
|
|
17
|
+
setNoteEditorOpen(true);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
noteEditorOpen,
|
|
22
|
+
setNoteEditorOpen,
|
|
23
|
+
noteState,
|
|
24
|
+
handleEditNote,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
registerDataTableExtensions,
|
|
7
7
|
registerDetailFormExtensions,
|
|
8
8
|
registerFormComponentExtensions,
|
|
9
|
+
registerHistoryEntryComponents,
|
|
9
10
|
registerLayoutExtensions,
|
|
10
11
|
registerLoginExtensions,
|
|
11
12
|
registerNavigationExtensions,
|
|
@@ -40,6 +41,7 @@ export function executeDashboardExtensionCallbacks() {
|
|
|
40
41
|
* - Data tables
|
|
41
42
|
* - Detail forms
|
|
42
43
|
* - Login
|
|
44
|
+
* - Custom history entries
|
|
43
45
|
*
|
|
44
46
|
* @example
|
|
45
47
|
* ```tsx
|
|
@@ -83,6 +85,9 @@ export function defineDashboardExtension(extension: DashboardExtension) {
|
|
|
83
85
|
// Register login extensions
|
|
84
86
|
registerLoginExtensions(extension.login);
|
|
85
87
|
|
|
88
|
+
// Register custom history entry components
|
|
89
|
+
registerHistoryEntryComponents(extension.historyEntries);
|
|
90
|
+
|
|
86
91
|
// Execute extension source change callbacks
|
|
87
92
|
const callbacks = globalRegistry.get('extensionSourceChangeCallbacks');
|
|
88
93
|
if (callbacks.size) {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
DashboardCustomFormComponents,
|
|
6
6
|
DashboardDataTableExtensionDefinition,
|
|
7
7
|
DashboardDetailFormExtensionDefinition,
|
|
8
|
+
DashboardHistoryEntryComponent,
|
|
8
9
|
DashboardLoginExtensions,
|
|
9
10
|
DashboardNavSectionDefinition,
|
|
10
11
|
DashboardPageBlockDefinition,
|
|
@@ -82,4 +83,10 @@ export interface DashboardExtension {
|
|
|
82
83
|
* Allows you to customize the login page with custom components.
|
|
83
84
|
*/
|
|
84
85
|
login?: DashboardLoginExtensions;
|
|
86
|
+
/**
|
|
87
|
+
* @description
|
|
88
|
+
* Allows a custom component to be used to render a history entry item
|
|
89
|
+
* in the Order or Customer history lists.
|
|
90
|
+
*/
|
|
91
|
+
historyEntries?: DashboardHistoryEntryComponent[];
|
|
85
92
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DashboardHistoryEntryComponent } from '@/vdb/framework/extension-api/types/index.js';
|
|
2
|
+
|
|
3
|
+
import { globalRegistry } from '../../registry/global-registry.js';
|
|
4
|
+
|
|
5
|
+
export function registerHistoryEntryComponents(
|
|
6
|
+
historyEntryComponents: DashboardHistoryEntryComponent[] = [],
|
|
7
|
+
) {
|
|
8
|
+
if (historyEntryComponents.length === 0) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
globalRegistry.set('historyEntries', entryMap => {
|
|
12
|
+
for (const entry of historyEntryComponents) {
|
|
13
|
+
const existingEntry = entryMap.get(entry.type);
|
|
14
|
+
if (existingEntry) {
|
|
15
|
+
// eslint-disable-next-line no-console
|
|
16
|
+
console.warn(
|
|
17
|
+
`The history entry type ${entry.type} already has a custom component registered (${String(existingEntry)}`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
entryMap.set(entry.type, entry.component);
|
|
21
|
+
}
|
|
22
|
+
return entryMap;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -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,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
|
+
}
|
|
@@ -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
|
@@ -96,10 +96,10 @@ export * from './components/shared/facet-value-chip.js';
|
|
|
96
96
|
export * from './components/shared/facet-value-selector.js';
|
|
97
97
|
export * from './components/shared/form-field-wrapper.js';
|
|
98
98
|
export * from './components/shared/history-timeline/history-entry-date.js';
|
|
99
|
-
export * from './components/shared/history-timeline/history-entry.js';
|
|
100
99
|
export * from './components/shared/history-timeline/history-note-checkbox.js';
|
|
101
100
|
export * from './components/shared/history-timeline/history-note-editor.js';
|
|
102
101
|
export * from './components/shared/history-timeline/history-note-input.js';
|
|
102
|
+
export * from './components/shared/history-timeline/history-timeline-with-grouping.js';
|
|
103
103
|
export * from './components/shared/history-timeline/history-timeline.js';
|
|
104
104
|
export * from './components/shared/icon-mark.js';
|
|
105
105
|
export * from './components/shared/language-selector.js';
|
|
@@ -198,6 +198,7 @@ export * from './framework/extension-api/logic/alerts.js';
|
|
|
198
198
|
export * from './framework/extension-api/logic/data-table.js';
|
|
199
199
|
export * from './framework/extension-api/logic/detail-forms.js';
|
|
200
200
|
export * from './framework/extension-api/logic/form-components.js';
|
|
201
|
+
export * from './framework/extension-api/logic/history-entries.js';
|
|
201
202
|
export * from './framework/extension-api/logic/layout.js';
|
|
202
203
|
export * from './framework/extension-api/logic/login.js';
|
|
203
204
|
export * from './framework/extension-api/logic/navigation.js';
|
|
@@ -206,6 +207,7 @@ export * from './framework/extension-api/types/alerts.js';
|
|
|
206
207
|
export * from './framework/extension-api/types/data-table.js';
|
|
207
208
|
export * from './framework/extension-api/types/detail-forms.js';
|
|
208
209
|
export * from './framework/extension-api/types/form-components.js';
|
|
210
|
+
export * from './framework/extension-api/types/history-entries.js';
|
|
209
211
|
export * from './framework/extension-api/types/layout.js';
|
|
210
212
|
export * from './framework/extension-api/types/login.js';
|
|
211
213
|
export * from './framework/extension-api/types/navigation.js';
|
|
@@ -222,6 +224,8 @@ export * from './framework/form-engine/overridden-form-component.js';
|
|
|
222
224
|
export * from './framework/form-engine/use-generated-form.js';
|
|
223
225
|
export * from './framework/form-engine/utils.js';
|
|
224
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';
|
|
225
229
|
export * from './framework/layout-engine/dev-mode-button.js';
|
|
226
230
|
export * from './framework/layout-engine/layout-extensions.js';
|
|
227
231
|
export * from './framework/layout-engine/location-wrapper.js';
|