feat(dashboard,types): added inbound shipping placeholder + shipping summary details (#9150)
what: - shipping summary <img width="1018" alt="Screenshot 2024-09-16 at 18 05 37" src="https://github.com/user-attachments/assets/d7e3d73c-bf11-42e5-90fb-03069437e96c"> - No Shipping options placeholder <img width="781" alt="Screenshot 2024-09-16 at 18 06 11" src="https://github.com/user-attachments/assets/05ed682a-97cd-4e66-9af1-f1e77d3d47cb">
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
ComponentPropsWithoutRef,
|
||||
ForwardedRef,
|
||||
Fragment,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useDeferredValue,
|
||||
useImperativeHandle,
|
||||
@@ -50,6 +51,7 @@ interface ComboboxProps<T extends Value = Value>
|
||||
fetchNextPage?: () => void
|
||||
isFetchingNextPage?: boolean
|
||||
onCreateOption?: (value: string) => void
|
||||
noResultsPlaceholder?: ReactNode
|
||||
}
|
||||
|
||||
const ComboboxImpl = <T extends Value = string>(
|
||||
@@ -64,6 +66,7 @@ const ComboboxImpl = <T extends Value = string>(
|
||||
fetchNextPage,
|
||||
isFetchingNextPage,
|
||||
onCreateOption,
|
||||
noResultsPlaceholder,
|
||||
...inputProps
|
||||
}: ComboboxProps<T>,
|
||||
ref: ForwardedRef<HTMLInputElement>
|
||||
@@ -182,7 +185,7 @@ const ComboboxImpl = <T extends Value = string>(
|
||||
setOpen(open)
|
||||
}
|
||||
|
||||
const hasValue = selectedValues.length > 0
|
||||
const hasValue = selectedValues?.length > 0
|
||||
|
||||
const showTag = hasValue && isArrayValue
|
||||
const showSelected = showTag && !searchValue && !open
|
||||
@@ -321,13 +324,20 @@ const ComboboxImpl = <T extends Value = string>(
|
||||
<div className="bg-ui-bg-component size-full h-5 w-full animate-pulse rounded-[4px]" />
|
||||
</div>
|
||||
)}
|
||||
{!results.length && (
|
||||
<div className="flex items-center gap-x-2 rounded-[4px] px-2 py-1.5">
|
||||
<Text size="small" leading="compact" className="text-ui-fg-subtle">
|
||||
{t("general.noResultsTitle")}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
{!results.length &&
|
||||
(noResultsPlaceholder && !searchValue?.length ? (
|
||||
noResultsPlaceholder
|
||||
) : (
|
||||
<div className="flex items-center gap-x-2 rounded-[4px] px-2 py-1.5">
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
{t("general.noResultsTitle")}
|
||||
</Text>
|
||||
</div>
|
||||
))}
|
||||
{!results.length && onCreateOption && (
|
||||
<Fragment>
|
||||
<PrimitiveSeparator className="bg-ui-border-base -mx-1" />
|
||||
|
||||
@@ -835,6 +835,9 @@
|
||||
},
|
||||
"orders": {
|
||||
"domain": "Orders",
|
||||
"claim": "Claim",
|
||||
"exchange": "Exchange",
|
||||
"return": "Return",
|
||||
"cancelWarning": "You are about to cancel the order {{id}}. This action cannot be undone.",
|
||||
"onDateFromSalesChannel": "{{date}} from {{salesChannel}}",
|
||||
"list": {
|
||||
@@ -929,7 +932,7 @@
|
||||
"noteHint": "You can type freely if you want to specify something.",
|
||||
"location": "Location",
|
||||
"locationHint": "Choose which location you want to return the items to.",
|
||||
"inboundShipping": "Inbound shipping",
|
||||
"inboundShipping": "Return shipping",
|
||||
"inboundShippingHint": "Choose which method you want to use.",
|
||||
"returnableQuantityLabel": "Returnable quantity",
|
||||
"refundableAmountLabel": "Refundable amount",
|
||||
@@ -944,6 +947,16 @@
|
||||
"title": "Cancel Return",
|
||||
"description": "Are you sure you want to cancel the return request?"
|
||||
},
|
||||
"placeholders": {
|
||||
"noReturnShippingOptions": {
|
||||
"title": "No return shipping options found",
|
||||
"hint": "No return shipping options were created for the location. You can create one at <LinkComponent>Location & Shipping</LinkComponent>."
|
||||
},
|
||||
"outboundShippingOptions": {
|
||||
"title": "No outbound shipping options found",
|
||||
"hint": "No outbound shipping options were created for the location. You can create one at <LinkComponent>Location & Shipping</LinkComponent>."
|
||||
}
|
||||
},
|
||||
"receive": {
|
||||
"action": "Receive items",
|
||||
"receiveItems": "{{ returnType }} {{ id }}",
|
||||
@@ -2561,6 +2574,8 @@
|
||||
"totalExclTax": "Total excl. tax",
|
||||
"subtotal": "Subtotal",
|
||||
"shipping": "Shipping",
|
||||
"outboundShipping": "Outbound Shipping",
|
||||
"returnShipping": "Return Shipping",
|
||||
"tax": "Tax",
|
||||
"created": "Created",
|
||||
"key": "Key",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
export const ReturnShippingPlaceholder = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex h-[120px] flex-col items-center justify-center gap-2 p-2 text-center">
|
||||
<span className="txt-small text-ui-fg-subtle font-medium">
|
||||
{t("orders.returns.placeholders.noReturnShippingOptions.title")}
|
||||
</span>
|
||||
|
||||
<span className="txt-small text-ui-fg-muted">
|
||||
<Trans
|
||||
i18nKey="orders.returns.placeholders.noReturnShippingOptions.hint"
|
||||
components={{
|
||||
LinkComponent: (
|
||||
<Link to={`/settings/locations`} className="text-blue-500" />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const OutboundShippingPlaceholder = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex h-[120px] flex-col items-center justify-center gap-2 p-2 text-center">
|
||||
<span className="txt-small text-ui-fg-subtle font-medium">
|
||||
{t("orders.returns.placeholders.outboundShippingOptions.title")}
|
||||
</span>
|
||||
|
||||
<span className="txt-small text-ui-fg-muted">
|
||||
<Trans
|
||||
i18nKey="orders.returns.placeholders.outboundShippingOptions.hint"
|
||||
components={{
|
||||
LinkComponent: (
|
||||
<Link to={`/settings/locations`} className="text-blue-500" />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
import { useUpdateReturn } from "../../../../../hooks/api/returns.tsx"
|
||||
import { sdk } from "../../../../../lib/client"
|
||||
import { currencies } from "../../../../../lib/data/currencies"
|
||||
import { ReturnShippingPlaceholder } from "../../../common/placeholders.tsx"
|
||||
import { ClaimOutboundSection } from "./claim-outbound-section"
|
||||
import { ItemPlaceholder } from "./item-placeholder"
|
||||
|
||||
@@ -715,12 +716,15 @@ export const ClaimCreateForm = ({
|
||||
{/*INBOUND SHIPPING*/}
|
||||
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
|
||||
<div>
|
||||
<Form.Label
|
||||
tooltip={t(
|
||||
"orders.claims.tooltips.onlyReturnShippingOptions"
|
||||
)}
|
||||
>
|
||||
<Form.Label>
|
||||
{t("orders.returns.inboundShipping")}
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-muted inline ml-1"
|
||||
>
|
||||
({t("fields.optional")})
|
||||
</Text>
|
||||
</Form.Label>
|
||||
|
||||
<Form.Hint className="!mt-1">
|
||||
@@ -748,6 +752,9 @@ export const ClaimCreateForm = ({
|
||||
value: so.id,
|
||||
}))}
|
||||
disabled={!locationId}
|
||||
noResultsPlaceholder={
|
||||
<ReturnShippingPlaceholder />
|
||||
}
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from "../../../../../hooks/api/claims"
|
||||
import { useShippingOptions } from "../../../../../hooks/api/shipping-options"
|
||||
import { sdk } from "../../../../../lib/client"
|
||||
import { OutboundShippingPlaceholder } from "../../../common/placeholders"
|
||||
import { AddClaimOutboundItemsTable } from "../add-claim-outbound-items-table"
|
||||
import { ClaimOutboundItem } from "./claim-outbound-item"
|
||||
import { ItemPlaceholder } from "./item-placeholder"
|
||||
@@ -405,6 +406,7 @@ export const ClaimOutboundSection = ({
|
||||
value: so.id,
|
||||
}))}
|
||||
disabled={!shipping_options.length}
|
||||
noResultsPlaceholder={<OutboundShippingPlaceholder />}
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from "../../../../../hooks/api/exchanges"
|
||||
import { useUpdateReturn } from "../../../../../hooks/api/returns"
|
||||
import { sdk } from "../../../../../lib/client"
|
||||
import { ReturnShippingPlaceholder } from "../../../common/placeholders"
|
||||
import { ItemPlaceholder } from "../../../order-create-claim/components/claim-create-form/item-placeholder"
|
||||
import { AddExchangeInboundItemsTable } from "../add-exchange-inbound-items-table"
|
||||
import { ExchangeInboundItem } from "./exchange-inbound-item"
|
||||
@@ -480,12 +481,15 @@ export const ExchangeInboundSection = ({
|
||||
{/*INBOUND SHIPPING*/}
|
||||
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
|
||||
<div>
|
||||
<Form.Label
|
||||
tooltip={t(
|
||||
"orders.exchanges.tooltips.onlyReturnShippingOptions"
|
||||
)}
|
||||
>
|
||||
<Form.Label>
|
||||
{t("orders.returns.inboundShipping")}
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-muted inline ml-1"
|
||||
>
|
||||
({t("fields.optional")})
|
||||
</Text>
|
||||
</Form.Label>
|
||||
|
||||
<Form.Hint className="!mt-1">
|
||||
@@ -512,6 +516,7 @@ export const ExchangeInboundSection = ({
|
||||
value: so.id,
|
||||
}))}
|
||||
disabled={!locationId}
|
||||
noResultsPlaceholder={<ReturnShippingPlaceholder />}
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "../../../../../hooks/api/exchanges"
|
||||
import { useShippingOptions } from "../../../../../hooks/api/shipping-options"
|
||||
import { sdk } from "../../../../../lib/client"
|
||||
import { OutboundShippingPlaceholder } from "../../../common/placeholders"
|
||||
import { ItemPlaceholder } from "../../../order-create-claim/components/claim-create-form/item-placeholder"
|
||||
import { AddExchangeOutboundItemsTable } from "../add-exchange-outbound-items-table"
|
||||
import { ExchangeOutboundItem } from "./exchange-outbound-item"
|
||||
@@ -402,6 +403,7 @@ export const ExchangeOutboundSection = ({
|
||||
<Form.Item>
|
||||
<Form.Control>
|
||||
<Combobox
|
||||
noResultsPlaceholder={<OutboundShippingPlaceholder />}
|
||||
value={value ?? undefined}
|
||||
onChange={(val) => {
|
||||
onChange(val)
|
||||
|
||||
@@ -46,6 +46,7 @@ import { useStockLocations } from "../../../../../hooks/api/stock-locations"
|
||||
import { sdk } from "../../../../../lib/client"
|
||||
import { currencies } from "../../../../../lib/data/currencies"
|
||||
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { ReturnShippingPlaceholder } from "../../../common/placeholders"
|
||||
import { AddReturnItemsTable } from "../add-return-items-table"
|
||||
import { ReturnItem } from "./return-item"
|
||||
import { ReturnCreateSchema, ReturnCreateSchemaType } from "./schema"
|
||||
@@ -549,7 +550,15 @@ export const ReturnCreateForm = ({
|
||||
<div>
|
||||
<Form.Label>
|
||||
{t("orders.returns.inboundShipping")}
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-muted inline ml-1"
|
||||
>
|
||||
({t("fields.optional")})
|
||||
</Text>
|
||||
</Form.Label>
|
||||
|
||||
<Form.Hint className="!mt-1">
|
||||
{t("orders.returns.inboundShippingHint")}
|
||||
</Form.Hint>
|
||||
@@ -588,6 +597,9 @@ export const ReturnCreateForm = ({
|
||||
value: so.id,
|
||||
}))}
|
||||
disabled={!locationId}
|
||||
noResultsPlaceholder={
|
||||
<ReturnShippingPlaceholder />
|
||||
}
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from "react"
|
||||
import { ReactNode, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
@@ -54,6 +54,7 @@ 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"
|
||||
import ShippingInfoPopover from "./shipping-info-popover"
|
||||
|
||||
type OrderSummarySectionProps = {
|
||||
order: AdminOrder
|
||||
@@ -523,14 +524,16 @@ const Cost = ({
|
||||
label,
|
||||
value,
|
||||
secondaryValue,
|
||||
tooltip,
|
||||
}: {
|
||||
label: string
|
||||
value: string | number
|
||||
secondaryValue: string
|
||||
tooltip?: ReactNode
|
||||
}) => (
|
||||
<div className="grid grid-cols-3 items-center">
|
||||
<Text size="small" leading="compact">
|
||||
{label}
|
||||
{label} {tooltip}
|
||||
</Text>
|
||||
<div className="text-right">
|
||||
<Text size="small" leading="compact">
|
||||
@@ -568,14 +571,23 @@ const CostBreakdown = ({ order }: { order: AdminOrder }) => {
|
||||
.sort((m1, m2) =>
|
||||
(m1.created_at as string).localeCompare(m2.created_at as string)
|
||||
)
|
||||
.map((sm, i) => (
|
||||
<Cost
|
||||
key={sm.id}
|
||||
label={t("fields.shipping") + (i ? ` ${i + 1}` : "")}
|
||||
secondaryValue={sm.name}
|
||||
value={getLocaleAmount(sm.total, order.currency_code)}
|
||||
/>
|
||||
))}
|
||||
.map((sm, i) => {
|
||||
return (
|
||||
<div>
|
||||
<Cost
|
||||
key={sm.id}
|
||||
label={
|
||||
sm.detail.return_id
|
||||
? t("fields.returnShipping")
|
||||
: t("fields.outboundShipping")
|
||||
}
|
||||
secondaryValue={sm.name}
|
||||
value={getLocaleAmount(sm.total, order.currency_code)}
|
||||
tooltip={<ShippingInfoPopover key={i} shippingMethod={sm} />}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { InformationCircleSolid } from "@medusajs/icons"
|
||||
import { AdminOrderShippingMethod } from "@medusajs/types"
|
||||
import { Badge, Tooltip } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type ShippingInfoPopoverProps = {
|
||||
shippingMethod: AdminOrderShippingMethod
|
||||
}
|
||||
|
||||
function ShippingInfoPopover({ shippingMethod }: ShippingInfoPopoverProps) {
|
||||
const { t } = useTranslation()
|
||||
const shippingDetail = shippingMethod?.detail
|
||||
|
||||
if (!shippingDetail) {
|
||||
return
|
||||
}
|
||||
|
||||
let rmaType = t("orders.return")
|
||||
let rmaId = shippingDetail.return_id
|
||||
|
||||
if (shippingDetail.claim_id) {
|
||||
rmaType = t("orders.claim")
|
||||
rmaId = shippingDetail.claim_id
|
||||
}
|
||||
|
||||
if (shippingDetail.exchange_id) {
|
||||
rmaType = t("orders.exchange")
|
||||
rmaId = shippingDetail.exchange_id
|
||||
}
|
||||
|
||||
if (!rmaId) {
|
||||
return
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
<Badge size="2xsmall" rounded="full">
|
||||
{rmaType}: #{rmaId.slice(-7)}
|
||||
</Badge>
|
||||
}
|
||||
>
|
||||
<InformationCircleSolid className="inline-block text-ui-fg-muted ml-1" />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShippingInfoPopover
|
||||
@@ -105,6 +105,7 @@ export interface BaseOrderShippingMethod {
|
||||
original_subtotal: BigNumberValue
|
||||
original_tax_total: BigNumberValue
|
||||
total: BigNumberValue
|
||||
detail?: BaseOrderShippingDetail
|
||||
subtotal: BigNumberValue
|
||||
tax_total: BigNumberValue
|
||||
discount_total: BigNumberValue
|
||||
@@ -176,6 +177,17 @@ export interface BaseOrderItemDetail {
|
||||
updated_at: Date
|
||||
}
|
||||
|
||||
export interface BaseOrderShippingDetail {
|
||||
id: string
|
||||
shipping_method_id: string
|
||||
shipping_method: BaseOrderShippingMethod
|
||||
claim_id: string
|
||||
exchange_id: string
|
||||
return_id: string
|
||||
created_at: Date
|
||||
updated_at: Date
|
||||
}
|
||||
|
||||
export interface BaseOrderChange {
|
||||
id: string
|
||||
order_id: string
|
||||
|
||||
Reference in New Issue
Block a user