Files
ai-wave-02/apps/web/src/orders/order-page.tsx
T
derrick 698a49ae82
Runner Test / test (push) Successful in 17s
Runner Test / test (pull_request) Successful in 16s
add:order notes ui component
2026-09-24 22:35:55 +09:00

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>
);
}