@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.
Files changed (28) hide show
  1. package/package.json +4 -4
  2. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-container.tsx +2 -2
  3. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-types.ts +5 -0
  4. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx +124 -0
  5. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history.tsx +91 -59
  6. package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +176 -0
  7. package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +4 -2
  8. package/src/app/routes/_authenticated/_customers/customers.graphql.ts +2 -0
  9. package/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx +98 -0
  10. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx +9 -7
  11. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-types.ts +5 -0
  12. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +173 -0
  13. package/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx +64 -408
  14. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +4 -0
  15. package/src/lib/components/shared/history-timeline/history-note-entry.tsx +65 -0
  16. package/src/lib/components/shared/history-timeline/history-timeline-with-grouping.tsx +141 -0
  17. package/src/lib/components/shared/history-timeline/use-history-note-editor.ts +26 -0
  18. package/src/lib/framework/extension-api/define-dashboard-extension.ts +5 -0
  19. package/src/lib/framework/extension-api/extension-api-types.ts +7 -0
  20. package/src/lib/framework/extension-api/logic/history-entries.ts +24 -0
  21. package/src/lib/framework/extension-api/logic/index.ts +1 -0
  22. package/src/lib/framework/extension-api/types/history-entries.ts +120 -0
  23. package/src/lib/framework/extension-api/types/index.ts +1 -0
  24. package/src/lib/framework/history-entry/history-entry-extensions.ts +11 -0
  25. package/src/lib/framework/history-entry/history-entry.tsx +129 -0
  26. package/src/lib/framework/registry/registry-types.ts +2 -0
  27. package/src/lib/index.ts +5 -1
  28. package/src/lib/components/shared/history-timeline/history-entry.tsx +0 -188
@@ -1,34 +1,25 @@
1
- import { HistoryEntry, HistoryEntryItem } from '@/vdb/components/shared/history-timeline/history-entry.js';
2
1
  import { HistoryNoteEditor } from '@/vdb/components/shared/history-timeline/history-note-editor.js';
2
+ import { HistoryNoteEntry } from '@/vdb/components/shared/history-timeline/history-note-entry.js';
3
3
  import { HistoryNoteInput } from '@/vdb/components/shared/history-timeline/history-note-input.js';
4
- import { HistoryTimeline } from '@/vdb/components/shared/history-timeline/history-timeline.js';
5
- import { Badge } from '@/vdb/components/ui/badge.js';
6
- import { Button } from '@/vdb/components/ui/button.js';
7
- import { Trans } from '@/vdb/lib/trans.js';
4
+ import { HistoryTimelineWithGrouping } from '@/vdb/components/shared/history-timeline/history-timeline-with-grouping.js';
5
+ import { useHistoryNoteEditor } from '@/vdb/components/shared/history-timeline/use-history-note-editor.js';
6
+ import { HistoryEntryItem } from '@/vdb/framework/extension-api/types/index.js';
7
+ import { HistoryEntryProps } from '@/vdb/framework/history-entry/history-entry.js';
8
8
  import {
9
- ArrowRightToLine,
10
- Ban,
11
- CheckIcon,
12
- ChevronDown,
13
- ChevronUp,
14
- CreditCardIcon,
15
- Edit3,
16
- SquarePen,
17
- Truck,
18
- UserX,
19
- } from 'lucide-react';
20
- import { useState } from 'react';
9
+ OrderCancellationComponent,
10
+ OrderCustomerUpdatedComponent,
11
+ OrderFulfillmentComponent,
12
+ OrderFulfillmentTransitionComponent,
13
+ OrderModifiedComponent,
14
+ OrderPaymentTransitionComponent,
15
+ OrderRefundTransitionComponent,
16
+ OrderStateTransitionComponent,
17
+ } from './default-order-history-components.js';
18
+ import { OrderHistoryOrderDetail } from './order-history-types.js';
19
+ import { orderHistoryUtils } from './order-history-utils.js';
21
20
 
22
21
  interface OrderHistoryProps {
23
- order: {
24
- id: string;
25
- createdAt: string;
26
- currencyCode: string;
27
- customer?: {
28
- firstName: string;
29
- lastName: string;
30
- } | null;
31
- };
22
+ order: OrderHistoryOrderDetail;
32
23
  historyEntries: Array<HistoryEntryItem>;
33
24
  onAddNote: (note: string, isPrivate: boolean) => void;
34
25
  onUpdateNote?: (entryId: string, note: string, isPrivate: boolean) => void;
@@ -42,22 +33,7 @@ export function OrderHistory({
42
33
  onUpdateNote,
43
34
  onDeleteNote,
44
35
  }: Readonly<OrderHistoryProps>) {
45
- const [expandedGroups, setExpandedGroups] = useState<Set<number>>(new Set());
46
- const [noteEditorOpen, setNoteEditorOpen] = useState(false);
47
- const [noteEditorNote, setNoteEditorNote] = useState<{
48
- noteId: string;
49
- note: string;
50
- isPrivate: boolean;
51
- }>({
52
- noteId: '',
53
- note: '',
54
- isPrivate: true,
55
- });
56
-
57
- const handleEditNote = (noteId: string, note: string, isPrivate: boolean) => {
58
- setNoteEditorNote({ noteId, note, isPrivate });
59
- setNoteEditorOpen(true);
60
- };
36
+ const { noteState, noteEditorOpen, handleEditNote, setNoteEditorOpen } = useHistoryNoteEditor();
61
37
 
62
38
  const handleDeleteNote = (noteId: string) => {
63
39
  onDeleteNote?.(noteId);
@@ -67,382 +43,62 @@ export function OrderHistory({
67
43
  onUpdateNote?.(noteId, note, isPrivate);
68
44
  };
69
45
 
70
- const isPrimaryEvent = (entry: HistoryEntryItem) => {
71
- // Based on Angular component's isFeatured method
72
- switch (entry.type) {
73
- case 'ORDER_STATE_TRANSITION':
74
- return (
75
- entry.data.to === 'Delivered' ||
76
- entry.data.to === 'Cancelled' ||
77
- entry.data.to === 'Settled' ||
78
- entry.data.from === 'Created'
79
- );
80
- case 'ORDER_REFUND_TRANSITION':
81
- return entry.data.to === 'Settled';
82
- case 'ORDER_PAYMENT_TRANSITION':
83
- return entry.data.to === 'Settled' || entry.data.to === 'Cancelled';
84
- case 'ORDER_FULFILLMENT_TRANSITION':
85
- return entry.data.to === 'Delivered' || entry.data.to === 'Shipped';
86
- case 'ORDER_NOTE':
87
- case 'ORDER_MODIFIED':
88
- case 'ORDER_CUSTOMER_UPDATED':
89
- case 'ORDER_CANCELLATION':
90
- return true;
91
- default:
92
- return false; // All other events are secondary
93
- }
94
- };
46
+ const { getTimelineIcon, getTitle, getIconColor, getActorName, isPrimaryEvent } =
47
+ orderHistoryUtils(order);
95
48
 
96
- // Group consecutive secondary events
97
- const groupedEntries: Array<
98
- | { type: 'primary'; entry: HistoryEntryItem; index: number }
99
- | {
100
- type: 'secondary-group';
101
- entries: Array<{ entry: HistoryEntryItem; index: number }>;
102
- startIndex: number;
103
- }
104
- > = [];
105
- let currentGroup: Array<{ entry: HistoryEntryItem; index: number }> = [];
106
-
107
- for (let i = 0; i < historyEntries.length; i++) {
108
- const entry = historyEntries[i];
109
- const isSecondary = !isPrimaryEvent(entry);
110
-
111
- if (isSecondary) {
112
- currentGroup.push({ entry, index: i });
113
- } else {
114
- // If we have accumulated secondary events, add them as a group
115
- if (currentGroup.length > 0) {
116
- groupedEntries.push({
117
- type: 'secondary-group',
118
- entries: currentGroup,
119
- startIndex: currentGroup[0].index,
120
- });
121
- currentGroup = [];
122
- }
123
- // Add the primary event
124
- groupedEntries.push({ type: 'primary', entry, index: i });
125
- }
126
- }
127
-
128
- // Don't forget the last group if it exists
129
- if (currentGroup.length > 0) {
130
- groupedEntries.push({
131
- type: 'secondary-group',
132
- entries: currentGroup,
133
- startIndex: currentGroup[0].index,
134
- });
135
- }
136
-
137
- const toggleGroup = (groupIndex: number) => {
138
- const newExpanded = new Set(expandedGroups);
139
- if (newExpanded.has(groupIndex)) {
140
- newExpanded.delete(groupIndex);
141
- } else {
142
- newExpanded.add(groupIndex);
143
- }
144
- setExpandedGroups(newExpanded);
145
- };
146
- const getTimelineIcon = (entry: OrderHistoryProps['historyEntries'][0]) => {
147
- switch (entry.type) {
148
- case 'ORDER_PAYMENT_TRANSITION':
149
- return <CreditCardIcon className="h-4 w-4" />;
150
- case 'ORDER_REFUND_TRANSITION':
151
- return <CreditCardIcon className="h-4 w-4" />;
152
- case 'ORDER_NOTE':
153
- return <SquarePen className="h-4 w-4" />;
154
- case 'ORDER_STATE_TRANSITION':
155
- if (entry.data.to === 'Delivered') {
156
- return <CheckIcon className="h-4 w-4" />;
157
- }
158
- if (entry.data.to === 'Cancelled') {
159
- return <Ban className="h-4 w-4" />;
160
- }
161
- return <ArrowRightToLine className="h-4 w-4" />;
162
- case 'ORDER_FULFILLMENT_TRANSITION':
163
- if (entry.data.to === 'Shipped' || entry.data.to === 'Delivered') {
164
- return <Truck className="h-4 w-4" />;
165
- }
166
- return <ArrowRightToLine className="h-4 w-4" />;
167
- case 'ORDER_FULFILLMENT':
168
- return <Truck className="h-4 w-4" />;
169
- case 'ORDER_MODIFIED':
170
- return <Edit3 className="h-4 w-4" />;
171
- case 'ORDER_CUSTOMER_UPDATED':
172
- return <UserX className="h-4 w-4" />;
173
- case 'ORDER_CANCELLATION':
174
- return <Ban className="h-4 w-4" />;
175
- default:
176
- return <CheckIcon className="h-4 w-4" />;
177
- }
178
- };
179
-
180
- const getTitle = (entry: OrderHistoryProps['historyEntries'][0]) => {
181
- switch (entry.type) {
182
- case 'ORDER_PAYMENT_TRANSITION':
183
- if (entry.data.to === 'Settled') {
184
- return <Trans>Payment settled</Trans>;
185
- }
186
- if (entry.data.to === 'Authorized') {
187
- return <Trans>Payment authorized</Trans>;
188
- }
189
- if (entry.data.to === 'Declined' || entry.data.to === 'Cancelled') {
190
- return <Trans>Payment failed</Trans>;
191
- }
192
- return <Trans>Payment transitioned</Trans>;
193
- case 'ORDER_REFUND_TRANSITION':
194
- if (entry.data.to === 'Settled') {
195
- return <Trans>Refund settled</Trans>;
196
- }
197
- return <Trans>Refund transitioned</Trans>;
198
- case 'ORDER_NOTE':
199
- return <Trans>Note added</Trans>;
200
- case 'ORDER_STATE_TRANSITION': {
201
- if (entry.data.from === 'Created') {
202
- return <Trans>Order placed</Trans>;
203
- }
204
- if (entry.data.to === 'Delivered') {
205
- return <Trans>Order fulfilled</Trans>;
206
- }
207
- if (entry.data.to === 'Cancelled') {
208
- return <Trans>Order cancelled</Trans>;
209
- }
210
- if (entry.data.to === 'Shipped') {
211
- return <Trans>Order shipped</Trans>;
212
- }
213
- return <Trans>Order transitioned</Trans>;
214
- }
215
- case 'ORDER_FULFILLMENT_TRANSITION':
216
- if (entry.data.to === 'Shipped') {
217
- return <Trans>Order shipped</Trans>;
218
- }
219
- if (entry.data.to === 'Delivered') {
220
- return <Trans>Order delivered</Trans>;
221
- }
222
- return <Trans>Fulfillment transitioned</Trans>;
223
- case 'ORDER_FULFILLMENT':
224
- return <Trans>Fulfillment created</Trans>;
225
- case 'ORDER_MODIFIED':
226
- return <Trans>Order modified</Trans>;
227
- case 'ORDER_CUSTOMER_UPDATED':
228
- return <Trans>Customer updated</Trans>;
229
- case 'ORDER_CANCELLATION':
230
- return <Trans>Order cancelled</Trans>;
231
- default:
232
- return <Trans>{entry.type.replace(/_/g, ' ').toLowerCase()}</Trans>;
49
+ const renderEntryContent = (entry: HistoryEntryItem) => {
50
+ const props: HistoryEntryProps = {
51
+ entry,
52
+ title: getTitle(entry),
53
+ actorName: getActorName(entry),
54
+ timelineIcon: getTimelineIcon(entry),
55
+ timelineIconClassName: getIconColor(entry),
56
+ isPrimary: isPrimaryEvent(entry),
57
+ children: null,
58
+ };
59
+ if (entry.type === 'ORDER_NOTE') {
60
+ return (
61
+ <HistoryNoteEntry {...props} onEditNote={handleEditNote} onDeleteNote={handleDeleteNote} />
62
+ );
63
+ } else if (entry.type === 'ORDER_STATE_TRANSITION') {
64
+ return <OrderStateTransitionComponent {...props} />;
65
+ } else if (entry.type === 'ORDER_PAYMENT_TRANSITION') {
66
+ return <OrderPaymentTransitionComponent {...props} />;
67
+ } else if (entry.type === 'ORDER_REFUND_TRANSITION') {
68
+ return <OrderRefundTransitionComponent {...props} />;
69
+ } else if (entry.type === 'ORDER_FULFILLMENT_TRANSITION') {
70
+ return <OrderFulfillmentTransitionComponent {...props} />;
71
+ } else if (entry.type === 'ORDER_FULFILLMENT') {
72
+ return <OrderFulfillmentComponent {...props} />;
73
+ } else if (entry.type === 'ORDER_MODIFIED') {
74
+ return <OrderModifiedComponent {...props} />;
75
+ } else if (entry.type === 'ORDER_CUSTOMER_UPDATED') {
76
+ return <OrderCustomerUpdatedComponent {...props} />;
77
+ } else if (entry.type === 'ORDER_CANCELLATION') {
78
+ return <OrderCancellationComponent {...props} />;
233
79
  }
80
+ return null;
234
81
  };
235
82
 
236
83
  return (
237
- <div className="">
238
- <div className="mb-4">
84
+ <>
85
+ <HistoryTimelineWithGrouping
86
+ historyEntries={historyEntries}
87
+ isPrimaryEvent={isPrimaryEvent}
88
+ renderEntryContent={renderEntryContent}
89
+ entity={order}
90
+ >
239
91
  <HistoryNoteInput onAddNote={onAddNote} />
240
- </div>
241
- <HistoryTimeline>
242
- {groupedEntries.map((group, groupIndex) => {
243
- if (group.type === 'primary') {
244
- const entry = group.entry;
245
- return (
246
- <HistoryEntry
247
- key={entry.id}
248
- entry={entry}
249
- isNoteEntry={entry.type === 'ORDER_NOTE'}
250
- timelineIcon={getTimelineIcon(entry)}
251
- title={getTitle(entry)}
252
- isPrimary={true}
253
- customer={order.customer}
254
- onEditNote={handleEditNote}
255
- onDeleteNote={handleDeleteNote}
256
- >
257
- {entry.type === 'ORDER_NOTE' && (
258
- <div className="space-y-2">
259
- <p className="text-sm text-foreground">{entry.data.note}</p>
260
- <div className="flex items-center gap-2">
261
- <Badge
262
- variant={entry.isPublic ? 'outline' : 'secondary'}
263
- className="text-xs"
264
- >
265
- {entry.isPublic ? 'Public' : 'Private'}
266
- </Badge>
267
- </div>
268
- </div>
269
- )}
270
- {entry.type === 'ORDER_STATE_TRANSITION' && entry.data.from !== 'Created' && (
271
- <p className="text-xs text-muted-foreground">
272
- <Trans>
273
- From {entry.data.from} to {entry.data.to}
274
- </Trans>
275
- </p>
276
- )}
277
- {entry.type === 'ORDER_PAYMENT_TRANSITION' && (
278
- <p className="text-xs text-muted-foreground">
279
- <Trans>
280
- Payment #{entry.data.paymentId} transitioned to {entry.data.to}
281
- </Trans>
282
- </p>
283
- )}
284
- {entry.type === 'ORDER_REFUND_TRANSITION' && (
285
- <p className="text-xs text-muted-foreground">
286
- <Trans>
287
- Refund #{entry.data.refundId} transitioned to {entry.data.to}
288
- </Trans>
289
- </p>
290
- )}
291
- {entry.type === 'ORDER_FULFILLMENT_TRANSITION' &&
292
- entry.data.from !== 'Created' && (
293
- <p className="text-xs text-muted-foreground">
294
- <Trans>
295
- Fulfillment #{entry.data.fulfillmentId} from {entry.data.from}{' '}
296
- to {entry.data.to}
297
- </Trans>
298
- </p>
299
- )}
300
- {entry.type === 'ORDER_FULFILLMENT' && (
301
- <p className="text-xs text-muted-foreground">
302
- <Trans>Fulfillment #{entry.data.fulfillmentId} created</Trans>
303
- </p>
304
- )}
305
- {entry.type === 'ORDER_MODIFIED' && (
306
- <p className="text-xs text-muted-foreground">
307
- <Trans>Order modification #{entry.data.modificationId}</Trans>
308
- </p>
309
- )}
310
- {entry.type === 'ORDER_CUSTOMER_UPDATED' && (
311
- <p className="text-xs text-muted-foreground">
312
- <Trans>Customer information updated</Trans>
313
- </p>
314
- )}
315
- {entry.type === 'ORDER_CANCELLATION' && (
316
- <p className="text-xs text-muted-foreground">
317
- <Trans>Order cancelled</Trans>
318
- </p>
319
- )}
320
- </HistoryEntry>
321
- );
322
- } else {
323
- // Secondary group
324
- const shouldCollapse = group.entries.length > 2;
325
- const isExpanded = expandedGroups.has(groupIndex);
326
- const visibleEntries =
327
- shouldCollapse && !isExpanded ? group.entries.slice(0, 2) : group.entries;
328
-
329
- return (
330
- <div key={`group-${groupIndex}`}>
331
- {visibleEntries.map(({ entry }) => (
332
- <HistoryEntry
333
- key={entry.id}
334
- entry={entry}
335
- isNoteEntry={entry.type === 'ORDER_NOTE'}
336
- timelineIcon={getTimelineIcon(entry)}
337
- title={getTitle(entry)}
338
- isPrimary={false}
339
- customer={order.customer}
340
- onEditNote={handleEditNote}
341
- onDeleteNote={handleDeleteNote}
342
- >
343
- {entry.type === 'ORDER_NOTE' && (
344
- <div className="space-y-1">
345
- <p className="text-xs text-foreground">{entry.data.note}</p>
346
- <Badge
347
- variant={entry.isPublic ? 'outline' : 'secondary'}
348
- className="text-xs"
349
- >
350
- {entry.isPublic ? 'Public' : 'Private'}
351
- </Badge>
352
- </div>
353
- )}
354
- {entry.type === 'ORDER_STATE_TRANSITION' &&
355
- entry.data.from !== 'Created' && (
356
- <p className="text-xs text-muted-foreground">
357
- <Trans>
358
- From {entry.data.from} to {entry.data.to}
359
- </Trans>
360
- </p>
361
- )}
362
- {entry.type === 'ORDER_PAYMENT_TRANSITION' && (
363
- <p className="text-xs text-muted-foreground">
364
- <Trans>
365
- Payment #{entry.data.paymentId} transitioned to{' '}
366
- {entry.data.to}
367
- </Trans>
368
- </p>
369
- )}
370
- {entry.type === 'ORDER_REFUND_TRANSITION' && (
371
- <p className="text-xs text-muted-foreground">
372
- <Trans>
373
- Refund #{entry.data.refundId} transitioned to{' '}
374
- {entry.data.to}
375
- </Trans>
376
- </p>
377
- )}
378
- {entry.type === 'ORDER_FULFILLMENT_TRANSITION' &&
379
- entry.data.from !== 'Created' && (
380
- <p className="text-xs text-muted-foreground">
381
- <Trans>
382
- Fulfillment #{entry.data.fulfillmentId} from{' '}
383
- {entry.data.from} to {entry.data.to}
384
- </Trans>
385
- </p>
386
- )}
387
- {entry.type === 'ORDER_FULFILLMENT' && (
388
- <p className="text-xs text-muted-foreground">
389
- <Trans>Fulfillment #{entry.data.fulfillmentId} created</Trans>
390
- </p>
391
- )}
392
- {entry.type === 'ORDER_MODIFIED' && (
393
- <p className="text-xs text-muted-foreground">
394
- <Trans>Order modification #{entry.data.modificationId}</Trans>
395
- </p>
396
- )}
397
- {entry.type === 'ORDER_CUSTOMER_UPDATED' && (
398
- <p className="text-xs text-muted-foreground">
399
- <Trans>Customer information updated</Trans>
400
- </p>
401
- )}
402
- {entry.type === 'ORDER_CANCELLATION' && (
403
- <p className="text-xs text-muted-foreground">
404
- <Trans>Order cancelled</Trans>
405
- </p>
406
- )}
407
- </HistoryEntry>
408
- ))}
409
-
410
- {shouldCollapse && (
411
- <div className="flex justify-center py-2">
412
- <Button
413
- variant="ghost"
414
- size="sm"
415
- onClick={() => toggleGroup(groupIndex)}
416
- className="text-muted-foreground hover:text-foreground h-6 text-xs"
417
- >
418
- {isExpanded ? (
419
- <>
420
- <ChevronUp className="w-3 h-3 mr-1" />
421
- <Trans>Show less</Trans>
422
- </>
423
- ) : (
424
- <>
425
- <ChevronDown className="w-3 h-3 mr-1" />
426
- <Trans>Show all ({group.entries.length})</Trans>
427
- </>
428
- )}
429
- </Button>
430
- </div>
431
- )}
432
- </div>
433
- );
434
- }
435
- })}
436
- </HistoryTimeline>
92
+ </HistoryTimelineWithGrouping>
437
93
  <HistoryNoteEditor
438
- key={noteEditorNote.noteId}
439
- note={noteEditorNote.note}
94
+ key={noteState.noteId}
95
+ note={noteState.note}
440
96
  onNoteChange={handleNoteEditorSave}
441
97
  open={noteEditorOpen}
442
98
  onOpenChange={setNoteEditorOpen}
443
- noteId={noteEditorNote.noteId}
444
- isPrivate={noteEditorNote.isPrivate}
99
+ noteId={noteState.noteId}
100
+ isPrivate={noteState.isPrivate}
445
101
  />
446
- </div>
102
+ </>
447
103
  );
448
104
  }
@@ -315,6 +315,10 @@ export const orderHistoryDocument = graphql(`
315
315
  updatedAt
316
316
  code
317
317
  currencyCode
318
+ customer {
319
+ firstName
320
+ lastName
321
+ }
318
322
  history(options: $options) {
319
323
  totalItems
320
324
  items {
@@ -0,0 +1,65 @@
1
+ import { Badge } from '@/vdb/components/ui/badge.js';
2
+ import { Button } from '@/vdb/components/ui/button.js';
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from '@/vdb/components/ui/dropdown-menu.js';
9
+ import { Separator } from '@/vdb/components/ui/separator.js';
10
+ import { HistoryEntry, HistoryEntryProps } from '@/vdb/framework/history-entry/history-entry.js';
11
+ import { Trans } from '@/vdb/lib/trans.js';
12
+ import { MoreVerticalIcon, PencilIcon, TrashIcon } from 'lucide-react';
13
+
14
+ interface HistoryNoteEntryProps extends Readonly<HistoryEntryProps> {
15
+ onEditNote?: (noteId: string, note: string, isPrivate: boolean) => void;
16
+ onDeleteNote?: (noteId: string) => void;
17
+ }
18
+
19
+ export function HistoryNoteEntry(props: HistoryNoteEntryProps) {
20
+ const { entry, isPrimary, onEditNote, onDeleteNote } = props;
21
+ return (
22
+ <HistoryEntry {...props}>
23
+ <div className={isPrimary ? 'space-y-2' : 'space-y-1'}>
24
+ <div className="space-y-1">
25
+ <p className={`${isPrimary ? 'text-sm' : 'text-xs'} text-foreground`}>
26
+ {entry.data.note}
27
+ </p>
28
+ </div>
29
+ {onEditNote && onDeleteNote && (
30
+ <div className="flex items-center gap-2">
31
+ <Badge variant={entry.isPublic ? 'outline' : 'secondary'} className="text-xs">
32
+ {entry.isPublic ? 'Public' : 'Private'}
33
+ </Badge>
34
+ <DropdownMenu>
35
+ <DropdownMenuTrigger asChild>
36
+ <Button variant="ghost" size="sm" className="h-6 w-6 p-0">
37
+ <MoreVerticalIcon className="h-3 w-3" />
38
+ </Button>
39
+ </DropdownMenuTrigger>
40
+ <DropdownMenuContent align="end">
41
+ <DropdownMenuItem
42
+ onClick={() => {
43
+ onEditNote(entry.id, entry.data.note, !entry.isPublic);
44
+ }}
45
+ className="cursor-pointer"
46
+ >
47
+ <PencilIcon className="mr-2 h-4 w-4" />
48
+ <Trans>Edit</Trans>
49
+ </DropdownMenuItem>
50
+ <Separator className="my-1" />
51
+ <DropdownMenuItem
52
+ onClick={() => onDeleteNote(entry.id)}
53
+ className="cursor-pointer text-red-600 focus:text-red-600"
54
+ >
55
+ <TrashIcon className="mr-2 h-4 w-4" />
56
+ <span>Delete</span>
57
+ </DropdownMenuItem>
58
+ </DropdownMenuContent>
59
+ </DropdownMenu>
60
+ </div>
61
+ )}
62
+ </div>
63
+ </HistoryEntry>
64
+ );
65
+ }