feat(dashboard,types): split damaged activity from received (#8859)
what: - split damaged activity from received - adds a popover to view return details <img width="1661" alt="Screenshot 2024-08-28 at 23 23 10" src="https://github.com/user-attachments/assets/064e982c-f850-452d-a60d-e5c267d0075a"> RESOLVES CC-363
This commit is contained in:
@@ -924,8 +924,12 @@
|
|||||||
"inboundShippingHint": "Choose which method you want to use.",
|
"inboundShippingHint": "Choose which method you want to use.",
|
||||||
"returnableQuantityLabel": "Returnable quantity",
|
"returnableQuantityLabel": "Returnable quantity",
|
||||||
"refundableAmountLabel": "Refundable amount",
|
"refundableAmountLabel": "Refundable amount",
|
||||||
"returnRequestedInfo": "{{requestedItemsCount}}x item return requested",
|
"returnRequestedInfo": "{{requestedItemsCount}}x items return requested",
|
||||||
"returnReceivedInfo": "{{requestedItemsCount}}x item return received",
|
"returnReceivedInfo": "{{requestedItemsCount}}x items return received",
|
||||||
|
"itemReceived": "Items received",
|
||||||
|
"returnRequested": "Return requested",
|
||||||
|
"damagedItemReceived": "Damaged items received",
|
||||||
|
"damagedItemsReturned": "{{quantity}}x damaged items returned",
|
||||||
"activeChangeError": "There is an active order change in progress on this order. Please finish or discard the change first.",
|
"activeChangeError": "There is an active order change in progress on this order. Please finish or discard the change first.",
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"title": "Cancel Return",
|
"title": "Cancel Return",
|
||||||
|
|||||||
+2
-1
@@ -44,6 +44,7 @@ function ActivityItems(props: ActivityItemsProps) {
|
|||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
autoFocus={false}
|
autoFocus={false}
|
||||||
|
className="focus-visible:outline-none"
|
||||||
>
|
>
|
||||||
<Text size="small" leading="compact" weight="plus">
|
<Text size="small" leading="compact" weight="plus">
|
||||||
{title}
|
{title}
|
||||||
@@ -53,7 +54,7 @@ function ActivityItems(props: ActivityItemsProps) {
|
|||||||
<Popover.Content
|
<Popover.Content
|
||||||
align="center"
|
align="center"
|
||||||
side="top"
|
side="top"
|
||||||
className="bg-ui-bg-component p-0 max-w-[200px]"
|
className="bg-ui-bg-component p-0 max-w-[200px] focus-visible:outline-none"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{!!itemsToSend?.length && (
|
{!!itemsToSend?.length && (
|
||||||
|
|||||||
+142
-44
@@ -52,6 +52,7 @@ import {
|
|||||||
import { getTotalCaptured } from "../../../../../lib/payment"
|
import { getTotalCaptured } from "../../../../../lib/payment"
|
||||||
import { getReturnableQuantity } from "../../../../../lib/rma"
|
import { getReturnableQuantity } from "../../../../../lib/rma"
|
||||||
import { CopyPaymentLink } from "../copy-payment-link/copy-payment-link"
|
import { CopyPaymentLink } from "../copy-payment-link/copy-payment-link"
|
||||||
|
import ReturnInfoPopover from "./return-info-popover"
|
||||||
|
|
||||||
type OrderSummarySectionProps = {
|
type OrderSummarySectionProps = {
|
||||||
order: AdminOrder
|
order: AdminOrder
|
||||||
@@ -565,6 +566,61 @@ const CostBreakdown = ({ order }: { order: AdminOrder }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ReturnBreakdownWithDamages = ({
|
||||||
|
orderReturn,
|
||||||
|
itemId,
|
||||||
|
}: {
|
||||||
|
orderReturn: AdminReturn
|
||||||
|
itemId: string
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const item = orderReturn?.items?.find((ri) => ri.item_id === itemId)
|
||||||
|
const damagedQuantity = item?.damaged_quantity || 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
item && (
|
||||||
|
<div
|
||||||
|
key={orderReturn.id}
|
||||||
|
className="txt-compact-small-plus text-ui-fg-subtle bg-ui-bg-subtle flex flex-row justify-between gap-y-2 border-t-2 border-dotted px-6 py-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ArrowDownRightMini className="text-ui-fg-muted" />
|
||||||
|
<Text size="small">
|
||||||
|
{t(`orders.returns.damagedItemsReturned`, {
|
||||||
|
quantity: damagedQuantity,
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{item?.note && (
|
||||||
|
<Tooltip content={item.note}>
|
||||||
|
<DocumentText className="text-ui-tag-neutral-icon ml-1 inline" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item?.reason && (
|
||||||
|
<Badge
|
||||||
|
size="2xsmall"
|
||||||
|
className="cursor-default select-none capitalize"
|
||||||
|
rounded="full"
|
||||||
|
>
|
||||||
|
{item?.reason?.label}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text size="small" leading="compact" className="text-ui-fg-muted">
|
||||||
|
{t(`orders.returns.damagedItemReceived`)}
|
||||||
|
|
||||||
|
<span className="ml-2">
|
||||||
|
<ReturnInfoPopover orderReturn={orderReturn} />
|
||||||
|
</span>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const ReturnBreakdown = ({
|
const ReturnBreakdown = ({
|
||||||
orderReturn,
|
orderReturn,
|
||||||
itemId,
|
itemId,
|
||||||
@@ -585,52 +641,72 @@ const ReturnBreakdown = ({
|
|||||||
|
|
||||||
const isRequested = orderReturn.status === "requested"
|
const isRequested = orderReturn.status === "requested"
|
||||||
const item = orderReturn?.items?.find((ri) => ri.item_id === itemId)
|
const item = orderReturn?.items?.find((ri) => ri.item_id === itemId)
|
||||||
|
const damagedQuantity = item?.damaged_quantity || 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
item && (
|
item && (
|
||||||
<div
|
<>
|
||||||
key={orderReturn.id}
|
{damagedQuantity > 0 && (
|
||||||
className="txt-compact-small-plus text-ui-fg-subtle bg-ui-bg-subtle flex flex-row justify-between gap-y-2 border-t-2 border-dotted px-6 py-4"
|
<ReturnBreakdownWithDamages
|
||||||
>
|
orderReturn={orderReturn}
|
||||||
<div className="flex items-center gap-2">
|
itemId={itemId}
|
||||||
<ArrowDownRightMini className="text-ui-fg-muted" />
|
/>
|
||||||
<Text>
|
)}
|
||||||
{t(
|
<div
|
||||||
`orders.returns.${
|
key={item.id}
|
||||||
isRequested ? "returnRequestedInfo" : "returnReceivedInfo"
|
className="txt-compact-small-plus text-ui-fg-subtle bg-ui-bg-subtle flex flex-row justify-between gap-y-2 border-t-2 border-dotted px-6 py-4"
|
||||||
}`,
|
>
|
||||||
{
|
<div className="flex items-center gap-2">
|
||||||
requestedItemsCount:
|
<ArrowDownRightMini className="text-ui-fg-muted" />
|
||||||
item?.[isRequested ? "quantity" : "received_quantity"],
|
<Text size="small">
|
||||||
}
|
{t(
|
||||||
)}
|
`orders.returns.${
|
||||||
</Text>
|
isRequested ? "returnRequestedInfo" : "returnReceivedInfo"
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
requestedItemsCount:
|
||||||
|
item?.[isRequested ? "quantity" : "received_quantity"],
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
{item?.note && (
|
{item?.note && (
|
||||||
<Tooltip content={item.note}>
|
<Tooltip content={item.note}>
|
||||||
<DocumentText className="text-ui-tag-neutral-icon ml-1 inline" />
|
<DocumentText className="text-ui-tag-neutral-icon ml-1 inline" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item?.reason && (
|
||||||
|
<Badge
|
||||||
|
size="2xsmall"
|
||||||
|
className="cursor-default select-none capitalize"
|
||||||
|
rounded="full"
|
||||||
|
>
|
||||||
|
{item?.reason?.label}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{orderReturn && isRequested && (
|
||||||
|
<Text size="small" leading="compact" className="text-ui-fg-muted">
|
||||||
|
{getRelativeDate(orderReturn.created_at)}
|
||||||
|
<span className="ml-2">
|
||||||
|
<ReturnInfoPopover orderReturn={orderReturn} />
|
||||||
|
</span>
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{item?.reason && (
|
{orderReturn && !isRequested && (
|
||||||
<Badge
|
<Text size="small" leading="compact" className="text-ui-fg-muted">
|
||||||
size="2xsmall"
|
{t(`orders.returns.itemReceived`)}
|
||||||
className="cursor-default select-none capitalize"
|
|
||||||
rounded="full"
|
<span className="ml-2">
|
||||||
>
|
<ReturnInfoPopover orderReturn={orderReturn} />
|
||||||
{item?.reason?.label}
|
</span>
|
||||||
</Badge>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
{orderReturn && (
|
|
||||||
<Text size="small" leading="compact" className="text-ui-fg-muted">
|
|
||||||
{getRelativeDate(
|
|
||||||
isRequested ? orderReturn.created_at : orderReturn.received_at
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -657,7 +733,7 @@ const ClaimBreakdown = ({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ArrowDownRightMini className="text-ui-fg-muted" />
|
<ArrowDownRightMini className="text-ui-fg-muted" />
|
||||||
|
|
||||||
<Text>
|
<Text size="small">
|
||||||
{t(`orders.claims.outboundItemAdded`, {
|
{t(`orders.claims.outboundItemAdded`, {
|
||||||
itemsCount: items.reduce(
|
itemsCount: items.reduce(
|
||||||
(acc, item) => (acc = acc + item.quantity),
|
(acc, item) => (acc = acc + item.quantity),
|
||||||
@@ -696,7 +772,7 @@ const ExchangeBreakdown = ({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ArrowDownRightMini className="text-ui-fg-muted" />
|
<ArrowDownRightMini className="text-ui-fg-muted" />
|
||||||
<Text>
|
<Text size="small">
|
||||||
{t(`orders.exchanges.outboundItemAdded`, {
|
{t(`orders.exchanges.outboundItemAdded`, {
|
||||||
itemsCount: items.reduce(
|
itemsCount: items.reduce(
|
||||||
(acc, item) => (acc = acc + item.quantity),
|
(acc, item) => (acc = acc + item.quantity),
|
||||||
@@ -720,19 +796,39 @@ const Total = ({ order }: { order: AdminOrder }) => {
|
|||||||
return (
|
return (
|
||||||
<div className=" flex flex-col gap-y-2 px-6 py-4">
|
<div className=" flex flex-col gap-y-2 px-6 py-4">
|
||||||
<div className="text-ui-fg-base flex items-center justify-between">
|
<div className="text-ui-fg-base flex items-center justify-between">
|
||||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
<Text
|
||||||
|
weight="plus"
|
||||||
|
className="text-ui-fg-subtle"
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
>
|
||||||
{t("fields.total")}
|
{t("fields.total")}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
<Text
|
||||||
|
weight="plus"
|
||||||
|
className="text-ui-fg-subtle"
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
>
|
||||||
{getStylizedAmount(order.total, order.currency_code)}
|
{getStylizedAmount(order.total, order.currency_code)}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-ui-fg-base flex items-center justify-between">
|
<div className="text-ui-fg-base flex items-center justify-between">
|
||||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
<Text
|
||||||
|
weight="plus"
|
||||||
|
className="text-ui-fg-subtle"
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
>
|
||||||
{t("fields.paidTotal")}
|
{t("fields.paidTotal")}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
<Text
|
||||||
|
weight="plus"
|
||||||
|
className="text-ui-fg-subtle"
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
>
|
||||||
{getStylizedAmount(
|
{getStylizedAmount(
|
||||||
getTotalCaptured(order.payment_collections || []),
|
getTotalCaptured(order.payment_collections || []),
|
||||||
order.currency_code
|
order.currency_code
|
||||||
@@ -745,6 +841,7 @@ const Total = ({ order }: { order: AdminOrder }) => {
|
|||||||
className="text-ui-fg-subtle text-semibold"
|
className="text-ui-fg-subtle text-semibold"
|
||||||
size="small"
|
size="small"
|
||||||
leading="compact"
|
leading="compact"
|
||||||
|
weight="plus"
|
||||||
>
|
>
|
||||||
{t("orders.returns.outstandingAmount")}
|
{t("orders.returns.outstandingAmount")}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -752,6 +849,7 @@ const Total = ({ order }: { order: AdminOrder }) => {
|
|||||||
className="text-ui-fg-subtle text-bold"
|
className="text-ui-fg-subtle text-bold"
|
||||||
size="small"
|
size="small"
|
||||||
leading="compact"
|
leading="compact"
|
||||||
|
weight="plus"
|
||||||
>
|
>
|
||||||
{getStylizedAmount(
|
{getStylizedAmount(
|
||||||
order.summary.pending_difference || 0,
|
order.summary.pending_difference || 0,
|
||||||
|
|||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
import { InformationCircleSolid } from "@medusajs/icons"
|
||||||
|
import { AdminReturn } from "@medusajs/types"
|
||||||
|
import { Badge, Popover, Text } from "@medusajs/ui"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { formatDate } from "../../../../../components/common/date"
|
||||||
|
|
||||||
|
type ReturnInfoPopoverProps = {
|
||||||
|
orderReturn: AdminReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReturnInfoPopover({ orderReturn }: ReturnInfoPopoverProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
setOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
let returnType = "Return"
|
||||||
|
let returnTypeId = orderReturn.id
|
||||||
|
|
||||||
|
if (orderReturn.claim_id) {
|
||||||
|
returnType = "Claim"
|
||||||
|
returnTypeId = orderReturn.claim_id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderReturn.exchange_id) {
|
||||||
|
returnType = "Exchange"
|
||||||
|
returnTypeId = orderReturn.exchange_id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof orderReturn !== "object") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover open={open}>
|
||||||
|
<Popover.Trigger
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
autoFocus={false}
|
||||||
|
className="focus-visible:outline-none align-sub"
|
||||||
|
>
|
||||||
|
<InformationCircleSolid />
|
||||||
|
</Popover.Trigger>
|
||||||
|
|
||||||
|
<Popover.Content
|
||||||
|
align="center"
|
||||||
|
side="top"
|
||||||
|
className="bg-ui-bg-component focus-visible:outline-none p-2"
|
||||||
|
>
|
||||||
|
<div className="">
|
||||||
|
<Badge size="2xsmall" className="mb-2" rounded="full">
|
||||||
|
{returnType}: #{returnTypeId.slice(-7)}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<Text size="xsmall">
|
||||||
|
<span className="text-ui-fg-subtle">
|
||||||
|
{t(`orders.returns.returnRequested`)}
|
||||||
|
</span>
|
||||||
|
{" · "}
|
||||||
|
{formatDate(orderReturn.requested_at)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="xsmall">
|
||||||
|
<span className="text-ui-fg-subtle">
|
||||||
|
{t(`orders.returns.itemReceived`)}
|
||||||
|
</span>
|
||||||
|
{" · "}
|
||||||
|
{orderReturn.received_at
|
||||||
|
? formatDate(orderReturn.received_at)
|
||||||
|
: "-"}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReturnInfoPopover
|
||||||
@@ -2,6 +2,7 @@ export interface BaseReturnItem {
|
|||||||
id: string
|
id: string
|
||||||
quantity: number
|
quantity: number
|
||||||
received_quantity: number
|
received_quantity: number
|
||||||
|
damaged_quantity: number
|
||||||
reason_id?: string
|
reason_id?: string
|
||||||
note?: string
|
note?: string
|
||||||
item_id: string
|
item_id: string
|
||||||
|
|||||||
Reference in New Issue
Block a user