96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import type { Address, AuditEvent, OrderDetail } from "@orderops/shared";
|
|
import { Link, useParams } from "react-router-dom";
|
|
import { Toast } from "../ui/toast";
|
|
import { cancelOrder, getOrder, listAuditEvents, updateOrderItemQuantity, updateOrderNotes, updateShippingAddress } from "./order-api";
|
|
import { OrderDetailPage } from "./order-detail-page";
|
|
|
|
interface ToastState {
|
|
message: string;
|
|
variant: "success" | "error";
|
|
}
|
|
|
|
export function OrderPage() {
|
|
const { orderId } = useParams();
|
|
const [order, setOrder] = useState<OrderDetail | null>(null);
|
|
const [auditEvents, setAuditEvents] = useState<AuditEvent[]>([]);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [toast, setToast] = useState<ToastState | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
async function refreshOrder(nextOrderId: string) {
|
|
const [nextOrder, nextEvents] = await Promise.all([getOrder(nextOrderId), listAuditEvents(nextOrderId)]);
|
|
setOrder(nextOrder);
|
|
setAuditEvents(nextEvents);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!toast) {
|
|
return;
|
|
}
|
|
|
|
const timeout = window.setTimeout(() => setToast(null), 3000);
|
|
return () => window.clearTimeout(timeout);
|
|
}, [toast]);
|
|
|
|
useEffect(() => {
|
|
if (!orderId) {
|
|
setError("Order not found.");
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
setToast(null);
|
|
|
|
void refreshOrder(orderId)
|
|
.catch((caught: unknown) => {
|
|
setOrder(null);
|
|
setAuditEvents([]);
|
|
setError(caught instanceof Error ? caught.message : "Failed to load order.");
|
|
})
|
|
.finally(() => setIsLoading(false));
|
|
}, [orderId]);
|
|
|
|
async function runAction(action: () => Promise<OrderDetail>, successMessage: string) {
|
|
if (!orderId) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setError(null);
|
|
const nextOrder = await action();
|
|
setOrder(nextOrder);
|
|
await refreshOrder(orderId);
|
|
setToast({ message: successMessage, variant: "success" });
|
|
} catch (caught) {
|
|
setToast({
|
|
message: caught instanceof Error ? caught.message : "Request failed.",
|
|
variant: "error"
|
|
});
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<Link className="inline-flex text-sm font-medium text-blue-600 hover:text-blue-700" to="/orders">
|
|
← Back to orders
|
|
</Link>
|
|
{toast ? <Toast message={toast.message} onDismiss={() => setToast(null)} variant={toast.variant} /> : null}
|
|
{error ? <div className="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error}</div> : null}
|
|
{isLoading ? <div className="rounded-xl border border-slate-200 bg-white p-8 text-sm text-slate-600 shadow-sm">Loading order...</div> : null}
|
|
{!isLoading && order ? (
|
|
<OrderDetailPage
|
|
order={order}
|
|
auditEvents={auditEvents}
|
|
onUpdateQuantity={(itemId, quantity) => runAction(() => updateOrderItemQuantity(order.id, itemId, quantity), "Quantity saved.")}
|
|
onSaveAddress={(address: Address) => runAction(() => updateShippingAddress(order.id, address), "Shipping address saved.")}
|
|
onSaveNotes={(notes) => runAction(() => updateOrderNotes(order.id, notes), "Notes saved.")}
|
|
onCancel={() => runAction(() => cancelOrder(order.id), "Order cancelled.")}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|