>({
+ defaultValues: {
+ value: "",
+ },
+ resolver: zodResolver(OrderNoteSchema),
+ })
+
+ const { mutateAsync, isLoading } = useAdminCreateNote()
+
+ const handleSubmit = form.handleSubmit(async (values) => {
+ mutateAsync(
+ {
+ resource_id: order.id,
+ resource_type: "order",
+ value: values.value,
+ },
+ {
+ onSuccess: () => {
+ form.reset()
+ handleResetSize()
+ },
+ }
+ )
+ })
+
+ const handleResize = () => {
+ const textarea = textareaRef.current
+ if (textarea) {
+ textarea.style.height = "auto"
+ textarea.style.height = textarea.scrollHeight + "px"
+ }
+ }
+
+ const handleResetSize = () => {
+ const textarea = textareaRef.current
+ if (textarea) {
+ textarea.style.height = "auto"
+ }
+ }
+
+ return (
+
+ )
+}
diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-timeline.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-timeline.tsx
new file mode 100644
index 0000000000..9b50016bc6
--- /dev/null
+++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-activity-section/order-timeline.tsx
@@ -0,0 +1,405 @@
+import { Fulfillment, Note, Order } from "@medusajs/medusa"
+import { IconButton, Text, Tooltip, clx, usePrompt } from "@medusajs/ui"
+import * as Collapsible from "@radix-ui/react-collapsible"
+import {
+ useAdminDeleteNote,
+ useAdminNotes,
+ useAdminStockLocation,
+} from "medusa-react"
+import { PropsWithChildren, ReactNode, useMemo, useState } from "react"
+import { Link } from "react-router-dom"
+
+import { XMarkMini } from "@medusajs/icons"
+import { useTranslation } from "react-i18next"
+import { Skeleton } from "../../../../../components/common/skeleton"
+import { useDate } from "../../../../../hooks/use-date"
+import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
+
+type OrderTimelineProps = {
+ order: Order
+}
+
+/**
+ * Arbitrary high limit to ensure all notes are fetched
+ */
+const NOTE_LIMIT = 9999
+
+export const OrderTimeline = ({ order }: OrderTimelineProps) => {
+ const items = useActivityItems(order)
+
+ if (items.length <= 3) {
+ return (
+
+ {items.map((item, index) => {
+ return (
+
+ {item.children}
+
+ )
+ })}
+
+ )
+ }
+
+ const lastItems = items.slice(0, 2)
+ const collapsibleItems = items.slice(2, items.length - 1)
+ const firstItem = items[items.length - 1]
+
+ return (
+
+ {lastItems.map((item, index) => {
+ return (
+
+ {item.children}
+
+ )
+ })}
+
+
+ {firstItem.children}
+
+
+ )
+}
+
+type Activity = {
+ title: string
+ timestamp: string | Date
+ children?: ReactNode
+}
+
+const useActivityItems = (order: Order) => {
+ const { t } = useTranslation()
+
+ const { notes, isLoading, isError, error } = useAdminNotes(
+ {
+ resource_id: order.id,
+ limit: NOTE_LIMIT,
+ offset: 0,
+ },
+ {
+ keepPreviousData: true,
+ }
+ )
+
+ if (isError) {
+ throw error
+ }
+
+ return useMemo(() => {
+ if (isLoading) {
+ return []
+ }
+
+ const items: Activity[] = []
+
+ for (const payment of order.payments) {
+ items.push({
+ title: t("orders.activity.events.payment.awaiting"),
+ timestamp: payment.created_at,
+ children: (
+
+ {getStylizedAmount(payment.amount, payment.currency_code)}
+
+ ),
+ })
+
+ if (payment.canceled_at) {
+ items.push({
+ title: t("orders.activity.events.payment.canceled"),
+ timestamp: payment.canceled_at,
+ children: (
+
+ {getStylizedAmount(payment.amount, payment.currency_code)}
+
+ ),
+ })
+ }
+
+ if (payment.captured_at) {
+ items.push({
+ title: t("orders.activity.events.payment.captured"),
+ timestamp: payment.captured_at,
+ children: (
+
+ {getStylizedAmount(payment.amount, payment.currency_code)}
+
+ ),
+ })
+ }
+ }
+
+ for (const fulfillment of order.fulfillments) {
+ items.push({
+ title: t("orders.activity.events.fulfillment.created"),
+ timestamp: fulfillment.created_at,
+ children: ,
+ })
+
+ if (fulfillment.shipped_at) {
+ items.push({
+ title: t("orders.activity.events.fulfillment.shipped"),
+ timestamp: fulfillment.shipped_at,
+ })
+ }
+ }
+
+ for (const ret of order.returns) {
+ items.push({
+ title: t("orders.activity.events.return.created"),
+ timestamp: ret.created_at,
+ })
+ }
+
+ for (const note of notes || []) {
+ items.push({
+ title: t("orders.activity.events.note.comment"),
+ timestamp: note.created_at,
+ children: ,
+ })
+ }
+
+ if (order.canceled_at) {
+ items.push({
+ title: t("orders.activity.events.canceled.title"),
+ timestamp: order.canceled_at,
+ })
+ }
+
+ const sortedActivities = items.sort((a, b) => {
+ return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
+ })
+
+ const createdAt = {
+ title: t("orders.activity.events.placed.title"),
+ timestamp: order.created_at,
+ children: (
+
+ {t("orders.activity.events.placed.fromSalesChannel", {
+ salesChannel: order.sales_channel.name,
+ })}
+
+ ),
+ }
+
+ return [...sortedActivities, createdAt]
+ }, [order, notes, isLoading, t])
+}
+
+type OrderActivityItemProps = PropsWithChildren<{
+ title: string
+ timestamp: string | Date
+ isFirst?: boolean
+}>
+
+const OrderActivityItem = ({
+ title,
+ timestamp,
+ isFirst = false,
+ children,
+}: OrderActivityItemProps) => {
+ const { getFullDate, getRelativeDate } = useDate()
+
+ return (
+
+
+
+
+
+ {title}
+
+
+
+ {getRelativeDate(timestamp)}
+
+
+
+
{children}
+
+
+ )
+}
+
+const OrderActivityCollapsible = ({
+ activities,
+}: {
+ activities: Activity[]
+}) => {
+ const [open, setOpen] = useState(false)
+
+ const { t } = useTranslation()
+
+ if (!activities.length) {
+ return null
+ }
+
+ return (
+
+ {!open && (
+
+
+
+
+
+ {t("orders.activity.showMoreActivities", {
+ count: activities.length,
+ })}
+
+
+
+
+ )}
+
+
+ {activities.map((item, index) => {
+ return (
+
+ {item.children}
+
+ )
+ })}
+
+
+
+ )
+}
+
+const NoteBody = ({ note }: { note: Note }) => {
+ const { t } = useTranslation()
+ const prompt = usePrompt()
+
+ const { first_name, last_name, email } = note.author || {}
+ const name = [first_name, last_name].filter(Boolean).join(" ")
+
+ const byLine = t("orders.activity.events.note.byLine", {
+ author: name || email,
+ })
+
+ const { mutateAsync } = useAdminDeleteNote(note.id)
+
+ const handleDelete = async () => {
+ const res = await prompt({
+ title: t("general.areYouSure"),
+ description: "This action cannot be undone",
+ confirmText: t("actions.delete"),
+ cancelText: t("actions.cancel"),
+ })
+
+ if (!res) {
+ return
+ }
+
+ await mutateAsync()
+ }
+
+ return (
+
+
+
+
+ {note.value}
+
+
+
+
+ {t("orders.activity.comment.deleteButtonText")}
+
+
+
+
+
+
{byLine}
+
+
+ )
+}
+
+const FulfillmentCreatedBody = ({
+ fulfillment,
+}: {
+ fulfillment: Fulfillment
+}) => {
+ const { t } = useTranslation()
+
+ const { stock_location, isLoading, isError, error } = useAdminStockLocation(
+ fulfillment.location_id!,
+ {
+ enabled: !!fulfillment.location_id,
+ }
+ )
+
+ const numberOfItems = fulfillment.items.reduce((acc, item) => {
+ return acc + item.quantity
+ }, 0)
+
+ const triggerText = stock_location
+ ? t("orders.activity.events.fulfillment.itemsFulfilledFrom", {
+ count: numberOfItems,
+ location: stock_location.name,
+ })
+ : t("orders.activity.events.fulfillment.itemsFulfilled", {
+ count: numberOfItems,
+ })
+
+ if (isError) {
+ throw error
+ }
+
+ return (
+
+ {isLoading ? (
+
+ ) : (
+
+ {triggerText}
+
+ )}
+
+ )
+}
diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx
index 0e2b878b43..6ac93b3466 100644
--- a/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx
+++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/components/order-fulfillment-section/order-fulfillment-section.tsx
@@ -20,6 +20,7 @@ import { Link } from "react-router-dom"
import { ActionMenu } from "../../../../../components/common/action-menu"
import { Skeleton } from "../../../../../components/common/skeleton"
import { Thumbnail } from "../../../../../components/common/thumbnail"
+import { formatProvider } from "../../../../../lib/format-provider"
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
type OrderFulfillmentSectionProps = {
@@ -265,6 +266,15 @@ const Fulfillment = ({
)}
)}
+
+
+ {t("fields.provider")}
+
+
+
+ {formatProvider(fulfillment.provider_id)}
+
+
{t("orders.fulfillment.trackingLabel")}
diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/constants.ts b/packages/admin-next/dashboard/src/routes/orders/order-detail/constants.ts
index 23e80b0a8e..8523d53a16 100644
--- a/packages/admin-next/dashboard/src/routes/orders/order-detail/constants.ts
+++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/constants.ts
@@ -1,2 +1,2 @@
export const orderExpand =
- "items,items.variant,items.variant.options,sales_channel,shipping_methods,shipping_methods.shipping_option,discounts,payments,customer,shipping_address,shipping_address.country,billing_address,billing_address.country,fulfillments,fulfillments.items,fulfillments.items.item,fulfillments.tracking_links,refunds,edits,edits.items,edits.items.variant,edits.items.variant.product"
+ "items,items.variant,items.variant.options,sales_channel,shipping_methods,shipping_methods.shipping_option,discounts,payments,customer,shipping_address,shipping_address.country,billing_address,billing_address.country,fulfillments,fulfillments.items,fulfillments.items.item,fulfillments.tracking_links,refunds,edits,edits.items,edits.items.variant,edits.items.variant.product,returns"
diff --git a/packages/admin-next/dashboard/src/routes/orders/order-detail/order-detail.tsx b/packages/admin-next/dashboard/src/routes/orders/order-detail/order-detail.tsx
index 26f01b5442..6a044a3356 100644
--- a/packages/admin-next/dashboard/src/routes/orders/order-detail/order-detail.tsx
+++ b/packages/admin-next/dashboard/src/routes/orders/order-detail/order-detail.tsx
@@ -1,6 +1,7 @@
import { useAdminOrder } from "medusa-react"
import { Outlet, useLoaderData, useParams } from "react-router-dom"
import { JsonViewSection } from "../../../components/common/json-view-section"
+import { OrderActivitySection } from "./components/order-activity-section"
import { OrderCustomerSection } from "./components/order-customer-section"
import { OrderFulfillmentSection } from "./components/order-fulfillment-section"
import { OrderGeneralSection } from "./components/order-general-section"
@@ -41,11 +42,13 @@ export const OrderDetail = () => {
+
+