-
Shipping Rates
-
- {order.customer_name} · {shortId(order.id)}
+
+
+ {/* Perishable warning */}
+ {rates !== null && rates[0]?.isPerishableOnly && (
+
+ Perishable shipment. Only Overnight and 2-Day Air are available
+ for fresh produce (sweet corn, onions, etc.). Ground and Express Saver are not
+ offered for temperature-sensitive items.
+
+ )}
+
+ {loading && (
+
+
+
Fetching FedEx rates...
+
+ )}
+
+ {error && (
+
+ {error}
+
+ Retry
+
+
+ )}
+
+ {rates !== null && rates.length === 0 && !loading && (
+
+ )}
+
+ {rates !== null && rates.length > 0 && (
+
+ {rates.map((rate) => (
+
handleSelectRate(rate)}
+ disabled={creating !== null}
+ className="w-full flex items-center justify-between rounded-xl border px-4 py-3 text-left transition-all duration-150 disabled:opacity-50 disabled:cursor-not-allowed"
+ style={{
+ borderColor: "var(--admin-border)",
+ backgroundColor: "var(--admin-card-bg)",
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.borderColor = "var(--admin-accent)";
+ e.currentTarget.style.backgroundColor = "var(--admin-accent-soft)";
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.borderColor = "var(--admin-border)";
+ e.currentTarget.style.backgroundColor = "var(--admin-card-bg)";
+ }}
+ >
+
+
+ {SERVICE_LABELS[rate.serviceType] ?? rate.serviceType}
+
+
+ {rate.deliveryDateLabel
+ ? `Arrives ${rate.deliveryDateLabel}`
+ : rate.deliveryDayOfWeek}
+
+ {rate.isPerishableOnly && (
+
+ Required for perishable
+
+ )}
+
+
+
+ {formatCurrency(rate.totalCharge / 100)}
+
+ {creating === rate.serviceType && (
+
+ )}
+
+
+ ))}
+
+ )}
+
+ {createError && (
+
+ {createError}
+
+ )}
+
+ {result && (
+
+
+ ✓ Label Created
+
+
+
+ Tracking:{" "}
+
+ {result.trackingNumber}
+
+
+
+
+
+ )}
+
+
+ );
+}
+
+// ── Order Card ────────────────────────────────────────────────────────────────
+
+function OrderCard({
+ order,
+ canManageOrders,
+ updating,
+ trackingInputs,
+ onTrackingInputChange,
+ onStatusChange,
+ onOpenRateModal,
+}: {
+ order: ShippingOrder;
+ canManageOrders: boolean;
+ updating: string | null;
+ trackingInputs: Record;
+ onTrackingInputChange: (orderId: string, value: string) => void;
+ onStatusChange: (orderId: string, status: string) => void;
+ onOpenRateModal: (order: ShippingOrder) => void;
+}) {
+ const tone = STATUS_TONE[order.shipping_status] ?? "neutral";
+ const shipItems = order.order_items.filter((i) => i.fulfillment === "ship");
+ const hasShipItems = shipItems.length > 0;
+ const hasPerishableItems = order.order_items.some(
+ (i) => i.fulfillment === "ship" && i.products?.is_perishable
+ );
+ const isPending =
+ order.shipping_status === "pending" || order.shipping_status === "label_created";
+ const isBusy = updating === order.id;
+
+ return (
+
+
+ {/* Header: customer + status */}
+
+
+
+ {order.customer_name}
+
+ {order.customer_phone && (
+
+ {order.customer_phone}
+
+ )}
+
+ {shortId(order.id)}
-
- ×
-
+
+ {statusLabel(order.shipping_status)}
+
-
- {/* Perishable warning */}
- {rates !== null && rates[0]?.isPerishableOnly && (
-
- Perishable shipment. Only Overnight and 2-Day Air are available
- for fresh produce (sweet corn, onions, etc.). Ground and Express Saver are not
- offered for temperature-sensitive items.
-
- )}
+ {/* Perishable indicator */}
+ {hasPerishableItems && isPending && (
+
+
+ Perishable — Overnight or 2-Day Air only
+
+ )}
- {loading && (
-
-
-
Fetching FedEx rates...
-
- )}
-
- {error && (
-
- {error}
-
+
+ Tracking:
+
- Retry
-
+ {order.tracking_number}
+
- )}
+ {order.shipping_status === "label_created" && canManageOrders && (
+ onStatusChange(order.id, "shipped")}
+ disabled={isBusy}
+ isLoading={isBusy}
+ >
+ Mark Shipped
+
+ )}
+
+ )}
- {rates !== null && rates.length === 0 && !loading && (
-
- No FedEx rates available for this address. Please verify the shipping address.
-
- )}
-
- {rates !== null && rates.length > 0 && (
-
- {rates.map((rate) => (
-
handleSelectRate(rate)}
- disabled={creating !== null}
- className="w-full flex items-center justify-between rounded-xl border border-zinc-800 px-4 py-3 text-left hover:border-blue-400 hover:bg-blue-900/30 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+ {/* Items */}
+ {shipItems.length > 0 && (
+
+
+ {shipItems.map((item) => (
+
-
-
- {SERVICE_LABELS[rate.serviceType] ?? rate.serviceType}
-
-
- {rate.deliveryDateLabel
- ? `Arrives ${rate.deliveryDateLabel}`
- : rate.deliveryDayOfWeek}
-
- {rate.isPerishableOnly && (
-
- Required for perishable
+
+ {item.quantity > 1 && (
+
+ {item.quantity}×{" "}
)}
-
-
-
- ${(rate.totalCharge / 100).toFixed(2)}
-
- {creating === rate.serviceType && (
-
+ {item.products?.name ?? "Unknown Product"}
+ {item.products?.is_perishable && (
+
+
+ perishable
+
)}
-
-
- ))}
-
- )}
-
- {createError && (
-
- {createError}
-
- )}
-
- {result && (
-
-
- ✓ Label Created
-
-
-
Tracking: {result.trackingNumber}
-
-
+ {formatCurrency(Number(item.price) * item.quantity)}
+
+
+ ))}
+
+
+
+ Subtotal
+
+
+ {formatCurrency(Number(order.subtotal))}
+
- )}
-
+
+ )}
+
+ {/* Actions */}
+ {canManageOrders && hasShipItems && isPending && (
+
+ {order.shipping_status === "pending" && (
+
onOpenRateModal(order)}
+ icon={ }
+ iconPosition="left"
+ >
+ Get FedEx Rates
+
+ )}
+
+ {(order.shipping_status === "pending" ||
+ order.shipping_status === "label_created") && (
+
onTrackingInputChange(order.id, e.target.value)}
+ className="flex-1 min-w-[160px] rounded-xl border px-3 py-1.5 text-sm outline-none transition-colors"
+ style={{
+ borderColor: "var(--admin-border)",
+ backgroundColor: "var(--admin-card-bg)",
+ color: "var(--admin-text-primary)",
+ }}
+ onFocus={(e) => {
+ e.currentTarget.style.borderColor = "var(--admin-accent)";
+ }}
+ onBlur={(e) => {
+ e.currentTarget.style.borderColor = "var(--admin-border)";
+ }}
+ />
+ )}
+
+ {SHIPPING_STATUSES.filter(
+ (s) => s.value !== order.shipping_status && s.value !== "label_created"
+ ).map((s) => {
+ const isDanger = s.value === "returned";
+ return (
+
onStatusChange(order.id, s.value)}
+ disabled={isBusy}
+ isLoading={isBusy}
+ >
+ {`Mark ${s.label}`}
+
+ );
+ })}
+
+ )}
+
+ {!canManageOrders && (
+
+ No order management permission
+
+ )}
-
+
);
}
@@ -255,7 +570,7 @@ export default function ShippingFulfillmentPanel({
}: ShippingFulfillmentPanelProps) {
const [orders, setOrders] = useState(initialOrders);
const [search, setSearch] = useState("");
- const [statusFilter, setStatusFilter] = useState("");
+ const [statusFilter, setStatusFilter] = useState("");
const [updating, setUpdating] = useState(null);
const [trackingInputs, setTrackingInputs] = useState>({});
const [rateModalOrder, setRateModalOrder] = useState(null);
@@ -271,13 +586,15 @@ export default function ShippingFulfillmentPanel({
return matchesSearch && matchesStatus;
});
- const counts = SHIPPING_STATUSES.reduce(
- (acc, s) => {
- acc[s.value] = orders.filter((o) => o.shipping_status === s.value).length;
- return acc;
- },
- {} as Record
- );
+ const counts: Record = { all: orders.length };
+ for (const s of SHIPPING_STATUSES) {
+ counts[s.value] = orders.filter((o) => o.shipping_status === s.value).length;
+ }
+
+ const tabs = [
+ { value: "", label: "All", count: counts.all },
+ ...SHIPPING_STATUSES.map((s) => ({ value: s.value, label: s.label, count: counts[s.value] ?? 0 })),
+ ];
async function handleStatusChange(orderId: string, newStatus: string) {
if (!canManageOrders) return;
@@ -292,7 +609,11 @@ export default function ShippingFulfillmentPanel({
setOrders((prev) =>
prev.map((o) =>
o.id === orderId
- ? { ...o, shipping_status: newStatus, tracking_number: trackingNumber ?? o.tracking_number }
+ ? {
+ ...o,
+ shipping_status: newStatus,
+ tracking_number: trackingNumber ?? o.tracking_number,
+ }
: o
)
);
@@ -301,212 +622,118 @@ export default function ShippingFulfillmentPanel({
setUpdating(null);
}
+ function handleTrackingInputChange(orderId: string, value: string) {
+ setTrackingInputs((prev) => ({ ...prev, [orderId]: value }));
+ }
+
return (
-
- {/* Header */}
-
-
-
-
-
Shipping Fulfillment
-
- {orders.length} shipping order{orders.length !== 1 ? "s" : ""}
-
-
-
-
- All Orders
-
-
- Pickup
-
-
+
+ {/* Filter + Search bar */}
+
+
+
setSearch(e.target.value)}
+ onClear={() => setSearch("")}
+ containerClassName="min-w-[12rem]"
+ />
+
+
+ {orders.length} order{orders.length !== 1 ? "s" : ""}
+
+
+
+ All Orders
+
+
+
+
setStatusFilter(v)}
+ tabs={tabs}
+ />
+
-
- {/* Status filter pills */}
-
-
setStatusFilter("")}
- className={`rounded-full px-3 py-1.5 text-sm font-medium ${
- !statusFilter ? "bg-slate-900 text-white" : "bg-zinc-900 text-zinc-400"
- }`}
- >
- All ({orders.length})
-
- {SHIPPING_STATUSES.map((s) => (
-
setStatusFilter(s.value)}
- className={`rounded-full px-3 py-1.5 text-sm font-medium ${
- statusFilter === s.value ? "bg-slate-900 text-white" : "bg-zinc-900 text-zinc-400"
- }`}
- >
- {s.label} ({counts[s.value] ?? 0})
-
+ {/* Orders */}
+ {filtered.length === 0 ? (
+
+
+
+
+ }
+ title={
+ search || statusFilter
+ ? "No matching shipping orders"
+ : "No shipping orders"
+ }
+ description={
+ search || statusFilter
+ ? "Try clearing your search or status filter to see more orders."
+ : "When customers place orders with shipping, they'll show up here for fulfillment."
+ }
+ action={
+ search || statusFilter ? (
+
{
+ setSearch("");
+ setStatusFilter("");
+ }}
+ icon={ }
+ >
+ Clear filters
+
+ ) : undefined
+ }
+ />
+
+ ) : (
+
+ {filtered.map((order) => (
+
))}
-
- {/* Search */}
-
setSearch(e.target.value)}
- className="w-full rounded-xl border border-zinc-600 bg-zinc-900 px-4 py-4 text-base outline-none focus:border-slate-900"
- />
-
- {/* Orders */}
-
- {filtered.length === 0 ? (
-
- No shipping orders
-
- ) : (
- filtered.map((order) => {
- const { cls, label } = statusBadge(order.shipping_status);
- const hasShipItems = order.order_items.some((i) => i.fulfillment === "ship");
- const hasPerishableItems = order.order_items.some(
- (i) => i.fulfillment === "ship" && i.products?.is_perishable
- );
- const isPending = order.shipping_status === "pending" || order.shipping_status === "label_created";
-
- return (
-
- {/* Header */}
-
-
-
{order.customer_name}
- {order.customer_phone && (
-
{order.customer_phone}
- )}
-
{shortId(order.id)}
-
-
- {label}
-
-
-
- {/* Perishable indicator */}
- {hasPerishableItems && isPending && (
-
- 🌽 Perishable — Overnight or 2-Day Air only
-
- )}
-
- {/* Tracking number */}
- {order.tracking_number && (
-
-
- Tracking:
- {order.tracking_number}
-
- {order.shipping_status === "label_created" && (
-
handleStatusChange(order.id, "shipped")}
- disabled={updating === order.id}
- className="text-xs rounded-lg bg-indigo-600 px-3 py-1.5 font-medium text-white hover:bg-indigo-700 disabled:opacity-50"
- >
- {updating === order.id ? "..." : "Mark Shipped"}
-
- )}
-
- )}
-
- {/* Items */}
- {order.order_items.filter((i) => i.fulfillment === "ship").length > 0 && (
-
-
- {order.order_items
- .filter((i) => i.fulfillment === "ship")
- .map((item) => (
-
-
- {item.quantity > 1 && (
- {item.quantity}×
- )}
- {item.products?.name ?? "Unknown Product"}
- {item.products?.is_perishable && (
- 🌽 perishable
- )}
-
-
- ${(Number(item.price) * item.quantity).toFixed(2)}
-
-
- ))}
-
-
- Subtotal
- ${Number(order.subtotal).toFixed(2)}
-
-
- )}
-
- {/* Actions */}
- {canManageOrders && hasShipItems && isPending && (
-
- {/* Get Rates / Create Label */}
- {order.shipping_status === "pending" && (
- setRateModalOrder(order)}
- className="rounded-xl bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
- >
- Get FedEx Rates
-
- )}
-
- {/* Manual tracking input (fallback) */}
- {(order.shipping_status === "pending" ||
- order.shipping_status === "label_created") && (
-
- setTrackingInputs((prev) => ({ ...prev, [order.id]: e.target.value }))
- }
- className="flex-1 min-w-[160px] rounded-lg border border-zinc-600 px-3 py-2 text-sm outline-none focus:border-slate-900"
- />
- )}
-
- {/* Status transitions */}
- {SHIPPING_STATUSES.filter(
- (s) =>
- s.value !== order.shipping_status &&
- s.value !== "label_created" // handled by create label flow
- ).map((s) => (
- handleStatusChange(order.id, s.value)}
- disabled={updating === order.id}
- className="rounded-xl border border-zinc-600 px-4 py-2 text-sm font-medium text-zinc-300 hover:bg-zinc-800 disabled:opacity-50"
- >
- {updating === order.id ? "..." : `Mark ${s.label}`}
-
- ))}
-
- )}
-
- {!canManageOrders && (
-
- No order management permission
-
- )}
-
- );
- })
- )}
-
-
+ )}
{/* Rate Modal */}
{rateModalOrder && (
@@ -514,4 +741,4 @@ export default function ShippingFulfillmentPanel({
)}
);
-}
\ No newline at end of file
+}