import { Address, ClaimOrder, Fulfillment, LineItem, Swap, } from "@medusajs/medusa" import { useAdminCancelOrder, useAdminCapturePayment, useAdminOrder, useAdminRegion, useAdminReservations, useAdminUpdateOrder, } from "medusa-react" import { useNavigate, useParams } from "react-router-dom" import OrderEditProvider, { OrderEditContext } from "../edit/context" import { DisplayTotal, FormattedAddress, FormattedFulfillment, FulfillmentStatusComponent, OrderStatusComponent, PaymentActionables, PaymentStatusComponent, } from "./templates" import { capitalize } from "lodash" import moment from "moment" import { useEffect, useMemo, useState } from "react" import { useHotkeys } from "react-hotkeys-hook" import { useTranslation } from "react-i18next" import Avatar from "../../../components/atoms/avatar" import BackButton from "../../../components/atoms/back-button" import Spacer from "../../../components/atoms/spacer" import Spinner from "../../../components/atoms/spinner" import Tooltip from "../../../components/atoms/tooltip" import WidgetContainer from "../../../components/extensions/widget-container" import Button from "../../../components/fundamentals/button" import DetailsIcon from "../../../components/fundamentals/details-icon" import CancelIcon from "../../../components/fundamentals/icons/cancel-icon" import ClipboardCopyIcon from "../../../components/fundamentals/icons/clipboard-copy-icon" import CornerDownRightIcon from "../../../components/fundamentals/icons/corner-down-right-icon" import DollarSignIcon from "../../../components/fundamentals/icons/dollar-sign-icon" import MailIcon from "../../../components/fundamentals/icons/mail-icon" import RefreshIcon from "../../../components/fundamentals/icons/refresh-icon" import TruckIcon from "../../../components/fundamentals/icons/truck-icon" import { ActionType } from "../../../components/molecules/actionables" import JSONView from "../../../components/molecules/json-view" import BodyCard from "../../../components/organisms/body-card" import RawJSON from "../../../components/organisms/raw-json" import Timeline from "../../../components/organisms/timeline" import { AddressType } from "../../../components/templates/address-form" import TransferOrdersModal from "../../../components/templates/transfer-orders-modal" import useClipboard from "../../../hooks/use-clipboard" import useImperativeDialog from "../../../hooks/use-imperative-dialog" import useNotification from "../../../hooks/use-notification" import useToggleState from "../../../hooks/use-toggle-state" import { useFeatureFlag } from "../../../providers/feature-flag-provider" import { useWidgets } from "../../../providers/widget-provider" import { isoAlpha2Countries } from "../../../utils/countries" import { getErrorMessage } from "../../../utils/error-messages" import extractCustomerName from "../../../utils/extract-customer-name" import { formatAmountWithSymbol } from "../../../utils/prices" import OrderEditModal from "../edit/modal" import AddressModal from "./address-modal" import CreateFulfillmentModal from "./create-fulfillment" import SummaryCard from "./detail-cards/summary" import EmailModal from "./email-modal" import MarkShippedModal from "./mark-shipped" import CreateRefundModal from "./refund" type OrderDetailFulfillment = { title: string type: string fulfillment: Fulfillment swap?: Swap claim?: ClaimOrder } const gatherAllFulfillments = (order) => { if (!order) { return [] } const all: OrderDetailFulfillment[] = [] order.fulfillments.forEach((f, index) => { all.push({ title: `Fulfillment #${index + 1}`, type: "default", fulfillment: f, }) }) if (order.claims?.length) { order.claims.forEach((claim) => { if (claim.fulfillment_status !== "not_fulfilled") { claim.fulfillments.forEach((fulfillment, index) => { all.push({ title: `Claim fulfillment #${index + 1}`, type: "claim", fulfillment, claim, }) }) } }) } if (order.swaps?.length) { order.swaps.forEach((swap) => { if (swap.fulfillment_status !== "not_fulfilled") { swap.fulfillments.forEach((fulfillment, index) => { all.push({ title: `Swap fulfillment #${index + 1}`, type: "swap", fulfillment, swap, }) }) } }) } return all } const OrderDetails = () => { const { id } = useParams() const { t } = useTranslation() const dialog = useImperativeDialog() const [addressModal, setAddressModal] = useState(null) const [emailModal, setEmailModal] = useState(null) const { state: showTransferOrderModal, toggle: toggleTransferOrderModal } = useToggleState() const [showFulfillment, setShowFulfillment] = useState(false) const [showRefund, setShowRefund] = useState(false) const [fullfilmentToShip, setFullfilmentToShip] = useState(null) const { order, isLoading } = useAdminOrder(id!) const capturePayment = useAdminCapturePayment(id!) const cancelOrder = useAdminCancelOrder(id!) const { state: addressModalState, close: closeAddressModal, open: openAddressModal, } = useToggleState() const { mutate: updateOrder } = useAdminUpdateOrder(id!) const { region } = useAdminRegion(order?.region_id!, { enabled: !!order?.region_id, }) const { isFeatureEnabled } = useFeatureFlag() const inventoryEnabled = useMemo(() => { return isFeatureEnabled("inventoryService") }, [isFeatureEnabled]) const { reservations, refetch: refetchReservations } = useAdminReservations( { line_item_id: order?.items.map((item) => item.id), }, { enabled: inventoryEnabled, } ) useEffect(() => { if (inventoryEnabled) { refetchReservations() } }, [inventoryEnabled, refetchReservations]) const navigate = useNavigate() const notification = useNotification() const [, handleCopy] = useClipboard(`${order?.display_id!}`, { successDuration: 5500, onCopied: () => notification( t("details-success", "Success"), t("details-order-id-copied", "Order ID copied"), "success" ), }) const [, handleCopyEmail] = useClipboard(order?.email!, { successDuration: 5500, onCopied: () => notification( t("details-success", "Success"), t("details-email-copied", "Email copied"), "success" ), }) // @ts-ignore useHotkeys("esc", () => navigate("/a/orders")) useHotkeys("command+i", handleCopy) const { getWidgets } = useWidgets() const handleDeleteOrder = async () => { const shouldDelete = await dialog({ heading: t("details-cancel-order-heading", "Cancel order"), text: t( "details-are-you-sure-you-want-to-cancel-the-order", "Are you sure you want to cancel the order?" ), extraConfirmation: true, entityName: t("order-details-display-id", "order #{{display_id}}", { display_id: order.display_id, }), }) if (!shouldDelete) { return } return cancelOrder.mutate(undefined, { onSuccess: () => notification( t("details-success", "Success"), t( "details-successfully-canceled-order", "Successfully canceled order" ), "success" ), onError: (err) => notification( t("details-error", "Error"), getErrorMessage(err), "error" ), }) } const allFulfillments = gatherAllFulfillments(order) const customerActionables: ActionType[] = [ { label: t("details-go-to-customer", "Go to Customer"), icon: , onClick: () => navigate(`/a/customers/${order?.customer.id}`), }, { label: t("details-transfer-ownership", "Transfer ownership"), icon: , onClick: () => toggleTransferOrderModal(), }, ] customerActionables.push({ label: t("details-edit-shipping-address", "Edit Shipping Address"), icon: , onClick: () => { setAddressModal({ address: order?.shipping_address, type: AddressType.SHIPPING, }) openAddressModal() }, }) customerActionables.push({ label: t("details-edit-billing-address", "Edit Billing Address"), icon: , onClick: () => { setAddressModal({ address: order?.billing_address, type: AddressType.BILLING, }) openAddressModal() }, }) if (order?.email) { customerActionables.push({ label: t("details-edit-email-address", "Edit Email Address"), icon: , onClick: () => { setEmailModal({ email: order?.email, }) }, }) } if (!order && isLoading) { return (
) } if (!order && !isLoading) { navigate("/404") } const anyItemsToFulfil = order.items.some( (item: LineItem) => item.quantity > (item.fulfilled_quantity ?? 0) ) return (
{isLoading || !order ? ( ) : ( <>
{getWidgets("order.details.before").map((widget, i) => { return ( ) })}
} subtitle={moment(order.created_at).format( "D MMMM YYYY hh:mm a" )} status={} forceDropdown={true} actionables={[ { label: t("details-cancel-order", "Cancel Order"), icon: , variant: "danger", onClick: () => handleDeleteOrder(), }, ]} >
{t("details-email", "Email")}
{t("details-phone", "Phone")}
{order.shipping_address?.phone || "N/A"}
{t("details-payment", "Payment")}
{order.payments ?.map((p) => capitalize(p.provider_id)) .join(", ")}
} customActionable={ setShowRefund(true)} /> } >
{order.payments.map((payment) => (
{!!payment.amount_refunded && (
{t("details-refunded", "Refunded")}
- {formatAmountWithSymbol({ amount: payment.amount_refunded, currency: order.currency_code, })}
{order.currency_code.toUpperCase()}
)}
))}
{t("details-total-paid", "Total Paid")}
{formatAmountWithSymbol({ amount: order.paid_total - order.refunded_total, currency: order.currency_code, })}
{order.currency_code.toUpperCase()}
} customActionable={ order.status !== "canceled" && anyItemsToFulfil && ( ) } >
{order.shipping_methods.map((method) => (
{t("details-shipping-method", "Shipping Method")} {method?.shipping_option?.name || ""}
))}
{allFulfillments.map((fulfillmentObj, i) => ( ))}

{extractCustomerName(order)}

{order.shipping_address && ( {order.shipping_address.city},{" "} { isoAlpha2Countries[ order.shipping_address.country_code?.toUpperCase() ] } )}
{t("details-contact", "Contact")}
{order.email} {order.shipping_address?.phone || ""}
{getWidgets("order.details.after").map((widget, i) => { return ( ) })}
{emailModal && ( setEmailModal(null)} email={emailModal.email} orderId={order.id} /> )} {showFulfillment && ( setShowFulfillment(false)} orderId={order.id} onComplete={inventoryEnabled ? refetchReservations : () => {}} /> )} {showRefund && ( setShowRefund(false)} /> )} {showTransferOrderModal && ( )} {fullfilmentToShip && ( setFullfilmentToShip(null)} fulfillment={fullfilmentToShip} orderId={order.id} /> )} {({ isModalVisible }) => isModalVisible && } )}
) } export default OrderDetails