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:
Riqwan Thamir
2024-08-29 16:04:00 +02:00
committed by GitHub
parent 4e6e81f445
commit 2a6c6fe590
5 changed files with 236 additions and 47 deletions

View File

@@ -924,8 +924,12 @@
"inboundShippingHint": "Choose which method you want to use.",
"returnableQuantityLabel": "Returnable quantity",
"refundableAmountLabel": "Refundable amount",
"returnRequestedInfo": "{{requestedItemsCount}}x item return requested",
"returnReceivedInfo": "{{requestedItemsCount}}x item return received",
"returnRequestedInfo": "{{requestedItemsCount}}x items return requested",
"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.",
"cancel": {
"title": "Cancel Return",

View File

@@ -44,6 +44,7 @@ function ActivityItems(props: ActivityItemsProps) {
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
autoFocus={false}
className="focus-visible:outline-none"
>
<Text size="small" leading="compact" weight="plus">
{title}
@@ -53,7 +54,7 @@ function ActivityItems(props: ActivityItemsProps) {
<Popover.Content
align="center"
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">
{!!itemsToSend?.length && (

View File

@@ -52,6 +52,7 @@ import {
import { getTotalCaptured } from "../../../../../lib/payment"
import { getReturnableQuantity } from "../../../../../lib/rma"
import { CopyPaymentLink } from "../copy-payment-link/copy-payment-link"
import ReturnInfoPopover from "./return-info-popover"
type OrderSummarySectionProps = {
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 = ({
orderReturn,
itemId,
@@ -585,52 +641,72 @@ const ReturnBreakdown = ({
const isRequested = orderReturn.status === "requested"
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>
{t(
`orders.returns.${
isRequested ? "returnRequestedInfo" : "returnReceivedInfo"
}`,
{
requestedItemsCount:
item?.[isRequested ? "quantity" : "received_quantity"],
}
)}
</Text>
<>
{damagedQuantity > 0 && (
<ReturnBreakdownWithDamages
orderReturn={orderReturn}
itemId={itemId}
/>
)}
<div
key={item.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.${
isRequested ? "returnRequestedInfo" : "returnReceivedInfo"
}`,
{
requestedItemsCount:
item?.[isRequested ? "quantity" : "received_quantity"],
}
)}
</Text>
{item?.note && (
<Tooltip content={item.note}>
<DocumentText className="text-ui-tag-neutral-icon ml-1 inline" />
</Tooltip>
{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>
{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 && (
<Badge
size="2xsmall"
className="cursor-default select-none capitalize"
rounded="full"
>
{item?.reason?.label}
</Badge>
{orderReturn && !isRequested && (
<Text size="small" leading="compact" className="text-ui-fg-muted">
{t(`orders.returns.itemReceived`)}
<span className="ml-2">
<ReturnInfoPopover orderReturn={orderReturn} />
</span>
</Text>
)}
</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">
<ArrowDownRightMini className="text-ui-fg-muted" />
<Text>
<Text size="small">
{t(`orders.claims.outboundItemAdded`, {
itemsCount: items.reduce(
(acc, item) => (acc = acc + item.quantity),
@@ -696,7 +772,7 @@ const ExchangeBreakdown = ({
>
<div className="flex items-center gap-2">
<ArrowDownRightMini className="text-ui-fg-muted" />
<Text>
<Text size="small">
{t(`orders.exchanges.outboundItemAdded`, {
itemsCount: items.reduce(
(acc, item) => (acc = acc + item.quantity),
@@ -720,19 +796,39 @@ const Total = ({ order }: { order: AdminOrder }) => {
return (
<div className=" flex flex-col gap-y-2 px-6 py-4">
<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")}
</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)}
</Text>
</div>
<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")}
</Text>
<Text className="text-ui-fg-subtle" size="small" leading="compact">
<Text
weight="plus"
className="text-ui-fg-subtle"
size="small"
leading="compact"
>
{getStylizedAmount(
getTotalCaptured(order.payment_collections || []),
order.currency_code
@@ -745,6 +841,7 @@ const Total = ({ order }: { order: AdminOrder }) => {
className="text-ui-fg-subtle text-semibold"
size="small"
leading="compact"
weight="plus"
>
{t("orders.returns.outstandingAmount")}
</Text>
@@ -752,6 +849,7 @@ const Total = ({ order }: { order: AdminOrder }) => {
className="text-ui-fg-subtle text-bold"
size="small"
leading="compact"
weight="plus"
>
{getStylizedAmount(
order.summary.pending_difference || 0,

View File

@@ -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

View File

@@ -2,6 +2,7 @@ export interface BaseReturnItem {
id: string
quantity: number
received_quantity: number
damaged_quantity: number
reason_id?: string
note?: string
item_id: string