Selaa lähdekoodia

fix(dashboard): Fix editing of order history entries

Michael Bromley 5 kuukautta sitten
vanhempi
sitoutus
74f149a269

+ 67 - 86
packages/dashboard/src/app/routes/_authenticated/_orders/components/order-history/order-history.tsx

@@ -1,18 +1,9 @@
 import { HistoryEntry, HistoryEntryItem } from '@/vdb/components/shared/history-timeline/history-entry.js';
+import { HistoryNoteEditor } from '@/vdb/components/shared/history-timeline/history-note-editor.js';
 import { HistoryNoteInput } from '@/vdb/components/shared/history-timeline/history-note-input.js';
-import {
-    HistoryTimeline,
-    useHistoryTimeline,
-} from '@/vdb/components/shared/history-timeline/history-timeline.js';
+import { HistoryTimeline } from '@/vdb/components/shared/history-timeline/history-timeline.js';
 import { Badge } from '@/vdb/components/ui/badge.js';
 import { Button } from '@/vdb/components/ui/button.js';
-import {
-    DropdownMenu,
-    DropdownMenuContent,
-    DropdownMenuItem,
-    DropdownMenuTrigger,
-} from '@/vdb/components/ui/dropdown-menu.js';
-import { Separator } from '@/vdb/components/ui/separator.js';
 import { Trans } from '@/vdb/lib/trans.js';
 import {
     ArrowRightToLine,
@@ -22,11 +13,7 @@ import {
     ChevronUp,
     CreditCardIcon,
     Edit3,
-    MoreVerticalIcon,
-    PencilIcon,
-    RefreshCcw,
     SquarePen,
-    TrashIcon,
     Truck,
     UserX,
 } from 'lucide-react';
@@ -56,7 +43,29 @@ export function OrderHistory({
     onDeleteNote,
 }: Readonly<OrderHistoryProps>) {
     const [expandedGroups, setExpandedGroups] = useState<Set<number>>(new Set());
-    const { editNote, deleteNote } = useHistoryTimeline();
+    const [noteEditorOpen, setNoteEditorOpen] = useState(false);
+    const [noteEditorNote, setNoteEditorNote] = useState<{
+        noteId: string;
+        note: string;
+        isPrivate: boolean;
+    }>({
+        noteId: '',
+        note: '',
+        isPrivate: true,
+    });
+
+    const handleEditNote = (noteId: string, note: string, isPrivate: boolean) => {
+        setNoteEditorNote({ noteId, note, isPrivate });
+        setNoteEditorOpen(true);
+    };
+
+    const handleDeleteNote = (noteId: string) => {
+        onDeleteNote?.(noteId);
+    };
+
+    const handleNoteEditorSave = (noteId: string, note: string, isPrivate: boolean) => {
+        onUpdateNote?.(noteId, note, isPrivate);
+    };
 
     const isPrimaryEvent = (entry: HistoryEntryItem) => {
         // Based on Angular component's isFeatured method
@@ -229,7 +238,7 @@ export function OrderHistory({
             <div className="mb-4">
                 <HistoryNoteInput onAddNote={onAddNote} />
             </div>
-            <HistoryTimeline onEditNote={onUpdateNote} onDeleteNote={onDeleteNote}>
+            <HistoryTimeline>
                 {groupedEntries.map((group, groupIndex) => {
                     if (group.type === 'primary') {
                         const entry = group.entry;
@@ -242,6 +251,8 @@ export function OrderHistory({
                                 title={getTitle(entry)}
                                 isPrimary={true}
                                 customer={order.customer}
+                                onEditNote={handleEditNote}
+                                onDeleteNote={handleDeleteNote}
                             >
                                 {entry.type === 'ORDER_NOTE' && (
                                     <div className="space-y-2">
@@ -253,36 +264,6 @@ export function OrderHistory({
                                             >
                                                 {entry.isPublic ? 'Public' : 'Private'}
                                             </Badge>
-                                            <DropdownMenu>
-                                                <DropdownMenuTrigger asChild>
-                                                    <Button variant="ghost" size="sm" className="h-6 w-6 p-0">
-                                                        <MoreVerticalIcon className="h-3 w-3" />
-                                                    </Button>
-                                                </DropdownMenuTrigger>
-                                                <DropdownMenuContent align="end">
-                                                    <DropdownMenuItem
-                                                        onClick={() =>
-                                                            editNote(
-                                                                entry.id,
-                                                                entry.data.note,
-                                                                !entry.isPublic,
-                                                            )
-                                                        }
-                                                        className="cursor-pointer"
-                                                    >
-                                                        <PencilIcon className="mr-2 h-4 w-4" />
-                                                        <Trans>Edit</Trans>
-                                                    </DropdownMenuItem>
-                                                    <Separator className="my-1" />
-                                                    <DropdownMenuItem
-                                                        onClick={() => deleteNote(entry.id)}
-                                                        className="cursor-pointer text-red-600 focus:text-red-600"
-                                                    >
-                                                        <TrashIcon className="mr-2 h-4 w-4" />
-                                                        <span>Delete</span>
-                                                    </DropdownMenuItem>
-                                                </DropdownMenuContent>
-                                            </DropdownMenu>
                                         </div>
                                     </div>
                                 )}
@@ -307,39 +288,33 @@ export function OrderHistory({
                                         </Trans>
                                     </p>
                                 )}
-                                {entry.type === 'ORDER_FULFILLMENT_TRANSITION' && entry.data.from !== 'Created' && (
-                                    <p className="text-xs text-muted-foreground">
-                                        <Trans>
-                                            Fulfillment #{entry.data.fulfillmentId} from {entry.data.from} to {entry.data.to}
-                                        </Trans>
-                                    </p>
-                                )}
+                                {entry.type === 'ORDER_FULFILLMENT_TRANSITION' &&
+                                    entry.data.from !== 'Created' && (
+                                        <p className="text-xs text-muted-foreground">
+                                            <Trans>
+                                                Fulfillment #{entry.data.fulfillmentId} from {entry.data.from}{' '}
+                                                to {entry.data.to}
+                                            </Trans>
+                                        </p>
+                                    )}
                                 {entry.type === 'ORDER_FULFILLMENT' && (
                                     <p className="text-xs text-muted-foreground">
-                                        <Trans>
-                                            Fulfillment #{entry.data.fulfillmentId} created
-                                        </Trans>
+                                        <Trans>Fulfillment #{entry.data.fulfillmentId} created</Trans>
                                     </p>
                                 )}
                                 {entry.type === 'ORDER_MODIFIED' && (
                                     <p className="text-xs text-muted-foreground">
-                                        <Trans>
-                                            Order modification #{entry.data.modificationId}
-                                        </Trans>
+                                        <Trans>Order modification #{entry.data.modificationId}</Trans>
                                     </p>
                                 )}
                                 {entry.type === 'ORDER_CUSTOMER_UPDATED' && (
                                     <p className="text-xs text-muted-foreground">
-                                        <Trans>
-                                            Customer information updated
-                                        </Trans>
+                                        <Trans>Customer information updated</Trans>
                                     </p>
                                 )}
                                 {entry.type === 'ORDER_CANCELLATION' && (
                                     <p className="text-xs text-muted-foreground">
-                                        <Trans>
-                                            Order cancelled
-                                        </Trans>
+                                        <Trans>Order cancelled</Trans>
                                     </p>
                                 )}
                             </HistoryEntry>
@@ -362,6 +337,8 @@ export function OrderHistory({
                                         title={getTitle(entry)}
                                         isPrimary={false}
                                         customer={order.customer}
+                                        onEditNote={handleEditNote}
+                                        onDeleteNote={handleDeleteNote}
                                     >
                                         {entry.type === 'ORDER_NOTE' && (
                                             <div className="space-y-1">
@@ -393,43 +370,38 @@ export function OrderHistory({
                                         {entry.type === 'ORDER_REFUND_TRANSITION' && (
                                             <p className="text-xs text-muted-foreground">
                                                 <Trans>
-                                                    Refund #{entry.data.refundId} transitioned to {entry.data.to}
-                                                </Trans>
-                                            </p>
-                                        )}
-                                        {entry.type === 'ORDER_FULFILLMENT_TRANSITION' && entry.data.from !== 'Created' && (
-                                            <p className="text-xs text-muted-foreground">
-                                                <Trans>
-                                                    Fulfillment #{entry.data.fulfillmentId} from {entry.data.from} to {entry.data.to}
+                                                    Refund #{entry.data.refundId} transitioned to{' '}
+                                                    {entry.data.to}
                                                 </Trans>
                                             </p>
                                         )}
+                                        {entry.type === 'ORDER_FULFILLMENT_TRANSITION' &&
+                                            entry.data.from !== 'Created' && (
+                                                <p className="text-xs text-muted-foreground">
+                                                    <Trans>
+                                                        Fulfillment #{entry.data.fulfillmentId} from{' '}
+                                                        {entry.data.from} to {entry.data.to}
+                                                    </Trans>
+                                                </p>
+                                            )}
                                         {entry.type === 'ORDER_FULFILLMENT' && (
                                             <p className="text-xs text-muted-foreground">
-                                                <Trans>
-                                                    Fulfillment #{entry.data.fulfillmentId} created
-                                                </Trans>
+                                                <Trans>Fulfillment #{entry.data.fulfillmentId} created</Trans>
                                             </p>
                                         )}
                                         {entry.type === 'ORDER_MODIFIED' && (
                                             <p className="text-xs text-muted-foreground">
-                                                <Trans>
-                                                    Order modification #{entry.data.modificationId}
-                                                </Trans>
+                                                <Trans>Order modification #{entry.data.modificationId}</Trans>
                                             </p>
                                         )}
                                         {entry.type === 'ORDER_CUSTOMER_UPDATED' && (
                                             <p className="text-xs text-muted-foreground">
-                                                <Trans>
-                                                    Customer information updated
-                                                </Trans>
+                                                <Trans>Customer information updated</Trans>
                                             </p>
                                         )}
                                         {entry.type === 'ORDER_CANCELLATION' && (
                                             <p className="text-xs text-muted-foreground">
-                                                <Trans>
-                                                    Order cancelled
-                                                </Trans>
+                                                <Trans>Order cancelled</Trans>
                                             </p>
                                         )}
                                     </HistoryEntry>
@@ -462,6 +434,15 @@ export function OrderHistory({
                     }
                 })}
             </HistoryTimeline>
+            <HistoryNoteEditor
+                key={noteEditorNote.noteId}
+                note={noteEditorNote.note}
+                onNoteChange={handleNoteEditorSave}
+                open={noteEditorOpen}
+                onOpenChange={setNoteEditorOpen}
+                noteId={noteEditorNote.noteId}
+                isPrivate={noteEditorNote.isPrivate}
+            />
         </div>
     );
 }

+ 66 - 1
packages/dashboard/src/lib/components/shared/history-timeline/history-entry.tsx

@@ -1,4 +1,15 @@
+import { Badge } from '@/vdb/components/ui/badge.js';
+import { Button } from '@/vdb/components/ui/button.js';
+import {
+    DropdownMenu,
+    DropdownMenuContent,
+    DropdownMenuItem,
+    DropdownMenuTrigger,
+} from '@/vdb/components/ui/dropdown-menu.js';
+import { Separator } from '@/vdb/components/ui/separator.js';
+import { Trans } from '@/vdb/lib/trans.js';
 import { cn } from '@/vdb/lib/utils.js';
+import { MoreVerticalIcon, PencilIcon, TrashIcon } from 'lucide-react';
 import { HistoryEntryDate } from './history-entry-date.js';
 
 export interface HistoryEntryItem {
@@ -27,6 +38,8 @@ interface HistoryEntryProps {
     children: React.ReactNode;
     isPrimary?: boolean;
     customer?: OrderCustomer | null;
+    onEditNote?: (noteId: string, note: string, isPrivate: boolean) => void;
+    onDeleteNote?: (noteId: string) => void;
 }
 
 export function HistoryEntry({
@@ -37,6 +50,8 @@ export function HistoryEntry({
     children,
     isPrimary = true,
     customer,
+    onEditNote,
+    onDeleteNote,
 }: Readonly<HistoryEntryProps>) {
     const getIconColor = (type: string) => {
         // Check for success states (payment settled, order delivered)
@@ -97,7 +112,57 @@ export function HistoryEntry({
                             >
                                 {title}
                             </h4>
-                            <div className="mt-1">{children}</div>
+                            <div className="mt-1">
+                                {entry.type === 'ORDER_NOTE' ? (
+                                    <div className={`space-y-${isPrimary ? '2' : '1'}`}>
+                                        <p className={`${isPrimary ? 'text-sm' : 'text-xs'} text-foreground`}>
+                                            {entry.data.note}
+                                        </p>
+                                        <div className="flex items-center gap-2">
+                                            <Badge
+                                                variant={entry.isPublic ? 'outline' : 'secondary'}
+                                                className="text-xs"
+                                            >
+                                                {entry.isPublic ? 'Public' : 'Private'}
+                                            </Badge>
+                                            {isPrimary && onEditNote && onDeleteNote && (
+                                                <DropdownMenu>
+                                                    <DropdownMenuTrigger asChild>
+                                                        <Button variant="ghost" size="sm" className="h-6 w-6 p-0">
+                                                            <MoreVerticalIcon className="h-3 w-3" />
+                                                        </Button>
+                                                    </DropdownMenuTrigger>
+                                                    <DropdownMenuContent align="end">
+                                                        <DropdownMenuItem
+                                                            onClick={() => {
+                                                                onEditNote(
+                                                                    entry.id,
+                                                                    entry.data.note,
+                                                                    !entry.isPublic,
+                                                                );
+                                                            }}
+                                                            className="cursor-pointer"
+                                                        >
+                                                            <PencilIcon className="mr-2 h-4 w-4" />
+                                                            <Trans>Edit</Trans>
+                                                        </DropdownMenuItem>
+                                                        <Separator className="my-1" />
+                                                        <DropdownMenuItem
+                                                            onClick={() => onDeleteNote(entry.id)}
+                                                            className="cursor-pointer text-red-600 focus:text-red-600"
+                                                        >
+                                                            <TrashIcon className="mr-2 h-4 w-4" />
+                                                            <span>Delete</span>
+                                                        </DropdownMenuItem>
+                                                    </DropdownMenuContent>
+                                                </DropdownMenu>
+                                            )}
+                                        </div>
+                                    </div>
+                                ) : (
+                                    children
+                                )}
+                            </div>
                         </div>
 
                         <div className="flex items-center gap-2 ml-4 flex-shrink-0">

+ 1 - 1
packages/dashboard/src/lib/components/shared/history-timeline/history-note-input.tsx

@@ -25,7 +25,7 @@ export function HistoryNoteInput({ onAddNote }: Readonly<HistoryNoteInputProps>)
                     placeholder="Add a note..."
                     value={note}
                     onChange={e => setNote(e.target.value)}
-                    className="min-h-[50px] resize-none border-0 bg-background/50 focus:bg-background transition-colors text-sm"
+                    className="min-h-[50px] resize-none bg-background/50 focus:bg-background transition-colors text-sm"
                 />
                 <div className="flex items-center justify-between">
                     <HistoryNoteCheckbox value={noteIsPrivate} onChange={setNoteIsPrivate} />

+ 7 - 56
packages/dashboard/src/lib/components/shared/history-timeline/history-timeline.tsx

@@ -1,65 +1,16 @@
 import { ScrollArea } from '@/vdb/components/ui/scroll-area.js';
-import { createContext, useContext, useState } from 'react';
-import { HistoryNoteEditor } from './history-note-editor.js';
 
 interface HistoryTimelineProps {
     children: React.ReactNode;
-    onEditNote?: (entryId: string, note: string, isPublic: boolean) => void;
-    onDeleteNote?: (entryId: string) => void;
 }
 
-// Use context to make the note editing functions available to the child
-// HistoryEntry component
-const HistoryTimelineContext = createContext<{
-    editNote: (noteId: string, note: string, isPrivate: boolean) => void;
-    deleteNote: (noteId: string) => void;
-}>({
-    editNote: () => {},
-    deleteNote: () => {},
-});
-
-type NoteEditorNote = { noteId: string; note: string; isPrivate: boolean };
-
-export function useHistoryTimeline() {
-    return useContext(HistoryTimelineContext);
-}
-
-export function HistoryTimeline({ children, onEditNote, onDeleteNote }: Readonly<HistoryTimelineProps>) {
-    const [noteEditorOpen, setNoteEditorOpen] = useState(false);
-    const [noteEditorNote, setNoteEditorNote] = useState<NoteEditorNote>({
-        noteId: '',
-        note: '',
-        isPrivate: true,
-    });
-
-    const editNote = (noteId: string, note: string, isPrivate: boolean) => {
-        setNoteEditorNote({ noteId, note, isPrivate });
-        setNoteEditorOpen(true);
-    };
-
-    const deleteNote = (noteId: string) => {
-        setNoteEditorNote({ noteId, note: '', isPrivate: true });
-    };
-
+export function HistoryTimeline({ children }: Readonly<HistoryTimelineProps>) {
     return (
-        <HistoryTimelineContext.Provider value={{ editNote, deleteNote }}>
-            <ScrollArea className="pr-2">
-                <div className="relative">
-                    <div className="absolute left-6 top-6 bottom-0 w-px bg-gradient-to-b from-border via-border/50 to-transparent" />
-                    <div className="space-y-0.5">
-                        {children}
-                    </div>
-                </div>
-            </ScrollArea>
-            <HistoryNoteEditor
-                key={noteEditorNote.noteId}
-                note={noteEditorNote.note}
-                onNoteChange={(...args) => onEditNote?.(...args)}
-                open={noteEditorOpen}
-                onOpenChange={setNoteEditorOpen}
-                noteId={noteEditorNote.noteId}
-                isPrivate={noteEditorNote.isPrivate}
-            />
-        </HistoryTimelineContext.Provider>
+        <ScrollArea className="pr-2">
+            <div className="relative">
+                <div className="absolute left-6 top-6 bottom-0 w-px bg-gradient-to-b from-border via-border/50 to-transparent" />
+                <div className="space-y-0.5">{children}</div>
+            </div>
+        </ScrollArea>
     );
 }