From dfba4f08fb38c7a107d4dd00f06ac37703a71cff Mon Sep 17 00:00:00 2001 From: notshop Date: Sun, 26 Apr 2026 16:36:07 +0000 Subject: [PATCH] chore: add client/src/pages/admin/orders.tsx --- client/src/pages/admin/orders.tsx | 183 ++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 client/src/pages/admin/orders.tsx diff --git a/client/src/pages/admin/orders.tsx b/client/src/pages/admin/orders.tsx new file mode 100644 index 0000000..59fafa0 --- /dev/null +++ b/client/src/pages/admin/orders.tsx @@ -0,0 +1,183 @@ +import { useState } from "react"; +import { useQuery, useMutation } from "@tanstack/react-query"; +import { queryClient, apiRequest } from "../../lib/queryClient"; +import { AdminNav } from "./dashboard"; +import { toast } from "../../hooks/use-toast"; +import { Search, ChevronDown, ChevronUp } from "lucide-react"; + +interface Order { + id: number; + orderNumber: string; + email: string; + status: string; + paymentStatus: string; + fulfillmentStatus: string; + total: string; + subtotal: string; + discountAmount?: string; + discountCode?: string; + shipping: string; + tax: string; + paymentProcessor: string; + trackingNumber?: string; + trackingCarrier?: string; + shippingAddress?: any; + notes?: string; + createdAt: string; +} + +const STATUS_OPTIONS = ["", "pending", "confirmed", "fulfilled", "cancelled", "refunded"]; +const FULFILLMENT_OPTIONS = ["unfulfilled", "partial", "fulfilled", "returned"]; + +function OrderRow({ order }: { order: Order }) { + const [expanded, setExpanded] = useState(false); + const [tracking, setTracking] = useState(order.trackingNumber || ""); + const [carrier, setCarrier] = useState(order.trackingCarrier || ""); + const [status, setStatus] = useState(order.status); + const [saving, setSaving] = useState(false); + + const updateMutation = useMutation({ + mutationFn: (body: object) => apiRequest("PATCH", `/api/admin/orders/${order.id}`, body), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/admin/orders"] }); + toast({ title: "Order updated" }); + setSaving(false); + }, + onError: (err: any) => { + toast({ title: "Error", description: err.message, variant: "destructive" }); + setSaving(false); + }, + }); + + const addr = order.shippingAddress; + + return ( +
+
setExpanded((v) => !v)}> +
+
+ #{order.orderNumber} + {order.status} + {order.fulfillmentStatus} +
+

{order.email}

+
+
+

${parseFloat(order.total).toFixed(2)}

+

{new Date(order.createdAt).toLocaleDateString()}

+
+ {expanded ? : } +
+ + {expanded && ( +
+
+
+

Ship to

+ {addr ? ( +

{addr.firstName} {addr.lastName}
{addr.address1}{addr.address2 ? `, ${addr.address2}` : ""}
{addr.city}, {addr.state} {addr.zip}

+ ) :

No address

} +
+
+

Payment

+

{order.paymentProcessor} — {order.paymentStatus}

+
+
+
+
+ + +
+
+
+ + setTracking(e.target.value)} placeholder="e.g. 1Z..." + className="w-full px-3 py-1.5 border border-border rounded-md text-sm bg-background focus:outline-none" + data-testid={`input-tracking-${order.id}`} /> +
+
+ + +
+
+ +
+
+ )} +
+ ); +} + +export default function AdminOrders() { + const [q, setQ] = useState(""); + const [search, setSearch] = useState(""); + const [statusFilter, setStatusFilter] = useState(""); + const [page, setPage] = useState(1); + + const { data, isLoading } = useQuery<{ orders: Order[]; total: number }>({ + queryKey: ["/api/admin/orders", { q: search, status: statusFilter, page }], + queryFn: async () => { + const params = new URLSearchParams({ page: String(page), limit: "20" }); + if (search) params.set("q", search); + if (statusFilter) params.set("status", statusFilter); + const r = await fetch(`/api/admin/orders?${params}`, { credentials: "include" }); + if (r.status === 401) { window.location.href = "/admin/login"; return { orders: [], total: 0 }; } + return r.json(); + }, + }); + + return ( +
+ +
+
+

Orders {data?.total !== undefined && ({data.total})}

+
+ +
{ e.preventDefault(); setSearch(q); setPage(1); }} className="flex gap-2"> +
+ + setQ(e.target.value)} placeholder="Search orders..." + className="pl-8 pr-3 py-2 border border-border rounded-md text-sm bg-background focus:outline-none w-52" /> +
+ +
+
+
+ + {isLoading ? ( +
{[1,2,3,4,5].map((i) =>
)}
+ ) : ( +
+ {data?.orders.map((o) => )} + {!data?.orders.length &&

No orders found

} +
+ )} + + {data && data.total > 20 && ( +
+ + Page {page} + +
+ )} +
+
+ ); +}