feat(admin-ui): Add location names to fulfilment rows and timeline events (#3481)
Adds location information to fulfilment rows and timeline events   Resolves CORE-1234
This commit is contained in:
1 parent
02c77d7059
commit
4213326fe8
8 files changed
+84
-5
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/admin-ui": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(admin-ui): Add location names to fulfilment rows and timeline events
|
||||||
@@ -25,6 +25,7 @@ export type EventContainerProps = {
|
|||||||
isFirst?: boolean
|
isFirst?: boolean
|
||||||
expandable?: boolean
|
expandable?: boolean
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
|
detail?: string | React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventContainer: React.FC<EventContainerProps> = ({
|
const EventContainer: React.FC<EventContainerProps> = ({
|
||||||
@@ -38,6 +39,7 @@ const EventContainer: React.FC<EventContainerProps> = ({
|
|||||||
isFirst = false,
|
isFirst = false,
|
||||||
expandable = false,
|
expandable = false,
|
||||||
children,
|
children,
|
||||||
|
detail,
|
||||||
}) => {
|
}) => {
|
||||||
const [isExpanded, setIsExpanded] = useState<boolean>(!expandable)
|
const [isExpanded, setIsExpanded] = useState<boolean>(!expandable)
|
||||||
|
|
||||||
@@ -92,6 +94,7 @@ const EventContainer: React.FC<EventContainerProps> = ({
|
|||||||
{children && isExpanded && (
|
{children && isExpanded && (
|
||||||
<div className="mt-small pb-base w-full">{children}</div>
|
<div className="mt-small pb-base w-full">{children}</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="text-grey-50">{detail}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+10
-2
@@ -9,14 +9,20 @@ type EventItemContainerProps = {
|
|||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
detail?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventItemContainer: React.FC<EventItemContainerProps> = ({ item }) => {
|
const EventItemContainer: React.FC<EventItemContainerProps> = ({
|
||||||
|
item,
|
||||||
|
detail,
|
||||||
|
}) => {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gap-x-small mb-base flex items-center last:mb-0">
|
<div className="mb-base flex flex-col last:mb-0 ">
|
||||||
|
<div className="gap-x-small flex items-center">
|
||||||
{item.thumbnail && (
|
{item.thumbnail && (
|
||||||
<div className="rounded-base h-10 w-[30px] overflow-hidden">
|
<div className="rounded-base h-10 w-[30px] overflow-hidden">
|
||||||
<img
|
<img
|
||||||
@@ -34,6 +40,8 @@ const EventItemContainer: React.FC<EventItemContainerProps> = ({ item }) => {
|
|||||||
<p className="text-grey-50">{item.variant.title}</p>
|
<p className="text-grey-50">{item.variant.title}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{detail && <div className="text-grey-50">{detail}</div>}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ const ItemsFulfilled: React.FC<ItemsFulfilledProps> = ({ event }) => {
|
|||||||
? "Exchange Items Fulfilled"
|
? "Exchange Items Fulfilled"
|
||||||
: "Items Fulfilled"
|
: "Items Fulfilled"
|
||||||
|
|
||||||
|
const detail = event.locationName
|
||||||
|
? `Shipping from ${event.locationName}`
|
||||||
|
: undefined
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
icon: <PackageIcon size={20} />,
|
icon: <PackageIcon size={20} />,
|
||||||
time: event.time,
|
time: event.time,
|
||||||
@@ -25,7 +29,9 @@ const ItemsFulfilled: React.FC<ItemsFulfilledProps> = ({ event }) => {
|
|||||||
)),
|
)),
|
||||||
noNotification: event.noNotification,
|
noNotification: event.noNotification,
|
||||||
isFirst: event.first,
|
isFirst: event.first,
|
||||||
|
detail,
|
||||||
}
|
}
|
||||||
|
|
||||||
return <EventContainer {...args} />
|
return <EventContainer {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ const ItemsShipped: React.FC<ItemsShippedProps> = ({ event }) => {
|
|||||||
? "Exchange Items Shipped"
|
? "Exchange Items Shipped"
|
||||||
: "Items Shipped"
|
: "Items Shipped"
|
||||||
|
|
||||||
|
const detail = event.locationName
|
||||||
|
? `Shipped from ${event.locationName}`
|
||||||
|
: undefined
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
icon: <TruckIcon size={20} />,
|
icon: <TruckIcon size={20} />,
|
||||||
time: event.time,
|
time: event.time,
|
||||||
@@ -25,6 +29,7 @@ const ItemsShipped: React.FC<ItemsShippedProps> = ({ event }) => {
|
|||||||
)),
|
)),
|
||||||
noNotification: event.noNotification,
|
noNotification: event.noNotification,
|
||||||
isFirst: event.first,
|
isFirst: event.first,
|
||||||
|
detail,
|
||||||
}
|
}
|
||||||
return <EventContainer {...args} />
|
return <EventContainer {...args} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import {
|
|||||||
useAdminCancelFulfillment,
|
useAdminCancelFulfillment,
|
||||||
useAdminCancelSwapFulfillment,
|
useAdminCancelSwapFulfillment,
|
||||||
} from "medusa-react"
|
} from "medusa-react"
|
||||||
|
import IconBadge from "../../../../components/fundamentals/icon-badge"
|
||||||
|
import BuildingsIcon from "../../../../components/fundamentals/icons/buildings-icon"
|
||||||
import CancelIcon from "../../../../components/fundamentals/icons/cancel-icon"
|
import CancelIcon from "../../../../components/fundamentals/icons/cancel-icon"
|
||||||
import PackageIcon from "../../../../components/fundamentals/icons/package-icon"
|
import PackageIcon from "../../../../components/fundamentals/icons/package-icon"
|
||||||
import Actionables from "../../../../components/molecules/actionables"
|
import Actionables from "../../../../components/molecules/actionables"
|
||||||
import useImperativeDialog from "../../../../hooks/use-imperative-dialog"
|
import useImperativeDialog from "../../../../hooks/use-imperative-dialog"
|
||||||
import useNotification from "../../../../hooks/use-notification"
|
import useNotification from "../../../../hooks/use-notification"
|
||||||
|
import useStockLocations from "../../../../hooks/use-stock-locations"
|
||||||
import { getErrorMessage } from "../../../../utils/error-messages"
|
import { getErrorMessage } from "../../../../utils/error-messages"
|
||||||
import { TrackingLink } from "./tracking-link"
|
import { TrackingLink } from "./tracking-link"
|
||||||
|
|
||||||
@@ -23,6 +26,7 @@ export const FormattedFulfillment = ({
|
|||||||
const cancelFulfillment = useAdminCancelFulfillment(order.id)
|
const cancelFulfillment = useAdminCancelFulfillment(order.id)
|
||||||
const cancelSwapFulfillment = useAdminCancelSwapFulfillment(order.id)
|
const cancelSwapFulfillment = useAdminCancelSwapFulfillment(order.id)
|
||||||
const cancelClaimFulfillment = useAdminCancelClaimFulfillment(order.id)
|
const cancelClaimFulfillment = useAdminCancelClaimFulfillment(order.id)
|
||||||
|
const { getLocationNameById } = useStockLocations()
|
||||||
|
|
||||||
const { fulfillment } = fulfillmentObj
|
const { fulfillment } = fulfillmentObj
|
||||||
const hasLinks = !!fulfillment.tracking_links?.length
|
const hasLinks = !!fulfillment.tracking_links?.length
|
||||||
@@ -89,7 +93,7 @@ export const FormattedFulfillment = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full justify-between">
|
<div className="flex w-full justify-between">
|
||||||
<div className="flex flex-col space-y-1 py-2">
|
<div className="flex flex-col space-y-1 py-4">
|
||||||
<div className="text-grey-90">
|
<div className="text-grey-90">
|
||||||
{fulfillment.canceled_at
|
{fulfillment.canceled_at
|
||||||
? "Fulfillment has been canceled"
|
? "Fulfillment has been canceled"
|
||||||
@@ -104,6 +108,19 @@ export const FormattedFulfillment = ({
|
|||||||
<TrackingLink key={j} trackingLink={tl} />
|
<TrackingLink key={j} trackingLink={tl} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{!fulfillment.canceled_at && fulfillment.location_id && (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="text-grey-50 font-semibold">
|
||||||
|
{fulfillment.shipped_at ? "Shipped" : "Shipping"} from{" "}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center pt-2">
|
||||||
|
<IconBadge className="mr-2">
|
||||||
|
<BuildingsIcon />
|
||||||
|
</IconBadge>
|
||||||
|
{getLocationNameById(fulfillment.location_id)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!fulfillment.canceled_at && !fulfillment.shipped_at && (
|
{!fulfillment.canceled_at && !fulfillment.shipped_at && (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import useOrdersExpandParam from "../domain/orders/details/utils/use-admin-expand-paramter"
|
import useOrdersExpandParam from "../domain/orders/details/utils/use-admin-expand-paramter"
|
||||||
import { useFeatureFlag } from "../providers/feature-flag-provider"
|
import { useFeatureFlag } from "../providers/feature-flag-provider"
|
||||||
|
import useStockLocations from "./use-stock-locations"
|
||||||
|
|
||||||
export interface TimelineEvent {
|
export interface TimelineEvent {
|
||||||
id: string
|
id: string
|
||||||
@@ -92,10 +93,12 @@ interface FulfillmentEvent extends TimelineEvent {
|
|||||||
|
|
||||||
export interface ItemsFulfilledEvent extends FulfillmentEvent {
|
export interface ItemsFulfilledEvent extends FulfillmentEvent {
|
||||||
items: OrderItem[]
|
items: OrderItem[]
|
||||||
|
locationName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemsShippedEvent extends FulfillmentEvent {
|
export interface ItemsShippedEvent extends FulfillmentEvent {
|
||||||
items: OrderItem[]
|
items: OrderItem[]
|
||||||
|
locationName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefundEvent extends TimelineEvent {
|
export interface RefundEvent extends TimelineEvent {
|
||||||
@@ -175,6 +178,8 @@ export const useBuildTimeline = (orderId: string) => {
|
|||||||
|
|
||||||
const { notifications } = useAdminNotifications({ resource_id: orderId })
|
const { notifications } = useAdminNotifications({ resource_id: orderId })
|
||||||
|
|
||||||
|
const { getLocationNameById } = useStockLocations()
|
||||||
|
|
||||||
const events: TimelineEvent[] | undefined = useMemo(() => {
|
const events: TimelineEvent[] | undefined = useMemo(() => {
|
||||||
if (!order) {
|
if (!order) {
|
||||||
return undefined
|
return undefined
|
||||||
@@ -318,9 +323,10 @@ export const useBuildTimeline = (orderId: string) => {
|
|||||||
id: event.id,
|
id: event.id,
|
||||||
time: event.created_at,
|
time: event.created_at,
|
||||||
type: "fulfilled",
|
type: "fulfilled",
|
||||||
items: event.items.map((item) => getLineItem(allItems, item.item_id)),
|
items: event.items.map((item) => getFulfilmentItem(allItems, item)),
|
||||||
noNotification: event.no_notification,
|
noNotification: event.no_notification,
|
||||||
orderId: order.id,
|
orderId: order.id,
|
||||||
|
locationName: getLocationNameById(event.location_id),
|
||||||
} as ItemsFulfilledEvent)
|
} as ItemsFulfilledEvent)
|
||||||
|
|
||||||
if (event.shipped_at) {
|
if (event.shipped_at) {
|
||||||
@@ -328,9 +334,10 @@ export const useBuildTimeline = (orderId: string) => {
|
|||||||
id: event.id,
|
id: event.id,
|
||||||
time: event.shipped_at,
|
time: event.shipped_at,
|
||||||
type: "shipped",
|
type: "shipped",
|
||||||
items: event.items.map((item) => getLineItem(allItems, item.item_id)),
|
items: event.items.map((item) => getFulfilmentItem(allItems, item)),
|
||||||
noNotification: event.no_notification,
|
noNotification: event.no_notification,
|
||||||
orderId: order.id,
|
orderId: order.id,
|
||||||
|
locationName: getLocationNameById(event.location_id),
|
||||||
} as ItemsShippedEvent)
|
} as ItemsShippedEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,3 +599,18 @@ function getWasRefundClaim(claimId, order) {
|
|||||||
|
|
||||||
return claim.type === "refund"
|
return claim.type === "refund"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFulfilmentItem(allItems, item) {
|
||||||
|
const line = allItems.find((line) => line.id === item.item_id)
|
||||||
|
|
||||||
|
if (!line) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: line.title,
|
||||||
|
quantity: item.quantity,
|
||||||
|
thumbnail: line.thumbnail,
|
||||||
|
variant: { title: line?.variant?.title || "-" },
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { useAdminStockLocations } from "medusa-react"
|
||||||
|
|
||||||
|
const useStockLocations = () => {
|
||||||
|
const { stock_locations } = useAdminStockLocations()
|
||||||
|
|
||||||
|
const getLocationNameById = (locationId: string | null) =>
|
||||||
|
stock_locations?.find((stock_location) => stock_location.id === locationId)
|
||||||
|
?.name
|
||||||
|
|
||||||
|
return { stock_locations, getLocationNameById }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useStockLocations
|
||||||
Reference in new issue
Block a user