feat(dashboard,types): add active order change panel - claims, exchanges & returns (#8738)
* feat(dashboard,types): add active order change panel - claims, exchanges & returns * Apply suggestions from code review
This commit is contained in:
1 parent
408ed2ee45
commit
531285c1e9
11 files changed
+300
-11
No files matched your search
@@ -912,6 +912,14 @@
|
|||||||
"errorNegativeValue": "Quantity cannot be a negative value.",
|
"errorNegativeValue": "Quantity cannot be a negative value.",
|
||||||
"errorLargeDamagedValue": "Damaged items quantity + non damaged received quantity exceeds total item quantity on the return. Please decrease quantity of non damaged items."
|
"errorLargeDamagedValue": "Damaged items quantity + non damaged received quantity exceeds total item quantity on the return. Please decrease quantity of non damaged items."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"canceledSuccessfully": "Return canceled successfully",
|
||||||
|
"confirmedSuccessfully": "Return confirmed successfully"
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"title": "Return initiated",
|
||||||
|
"description": "There is an open return request to be completed"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"claims": {
|
"claims": {
|
||||||
@@ -931,6 +939,14 @@
|
|||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"onlyReturnShippingOptions": "This list will consist of only return shipping options."
|
"onlyReturnShippingOptions": "This list will consist of only return shipping options."
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"canceledSuccessfully": "Claim canceled successfully",
|
||||||
|
"confirmedSuccessfully": "Claim confirmed successfully"
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"title": "Claim initiated",
|
||||||
|
"description": "There is an open claim request to be completed"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exchanges": {
|
"exchanges": {
|
||||||
@@ -950,6 +966,14 @@
|
|||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"onlyReturnShippingOptions": "This list will consist of only return shipping options."
|
"onlyReturnShippingOptions": "This list will consist of only return shipping options."
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"canceledSuccessfully": "Exchange canceled successfully",
|
||||||
|
"confirmedSuccessfully": "Exchange confirmed successfully"
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"title": "Exchange initiated",
|
||||||
|
"description": "There is an open exchange request to be completed"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reservations": {
|
"reservations": {
|
||||||
|
|||||||
+12
-8
@@ -307,15 +307,19 @@ export const ClaimCreateForm = ({
|
|||||||
const shippingOptionId = form.watch("inbound_option_id")
|
const shippingOptionId = form.watch("inbound_option_id")
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
try {
|
await confirmClaimRequest(
|
||||||
await confirmClaimRequest({ no_notification: !data.send_notification })
|
{ no_notification: !data.send_notification },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("orders.claims.toast.confirmedSuccessfully"))
|
||||||
|
|
||||||
handleSuccess()
|
handleSuccess()
|
||||||
} catch (e) {
|
},
|
||||||
toast.error(t("general.error"), {
|
onError: (error) => {
|
||||||
description: e.message,
|
toast.error(error.message)
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const onItemsSelected = async () => {
|
const onItemsSelected = async () => {
|
||||||
|
|||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
import { ExclamationCircle } from "@medusajs/icons"
|
||||||
|
import { Button, Container, Heading, Text, toast } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useCancelClaimRequest } from "../../../../../hooks/api/claims"
|
||||||
|
|
||||||
|
type ActiveOrderClaimSectionProps = {
|
||||||
|
orderPreview: HttpTypes.AdminOrderPreview
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ActiveOrderClaimSection = ({
|
||||||
|
orderPreview,
|
||||||
|
}: ActiveOrderClaimSectionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const claimId = orderPreview?.order_change?.claim_id
|
||||||
|
|
||||||
|
const { mutateAsync: cancelClaim } = useCancelClaimRequest(
|
||||||
|
claimId,
|
||||||
|
orderPreview.id
|
||||||
|
)
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const onContinueClaim = async () => {
|
||||||
|
navigate(`/orders/${orderPreview.id}/claims`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancelClaim = async () => {
|
||||||
|
await cancelClaim(undefined, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("orders.claims.toast.canceledSuccessfully"))
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!claimId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"repeating-linear-gradient(-45deg, rgb(212, 212, 216, 0.15), rgb(212, 212, 216,.15) 10px, transparent 10px, transparent 20px)",
|
||||||
|
}}
|
||||||
|
className="-m-4 mb-1 border-b p-4"
|
||||||
|
>
|
||||||
|
<Container className="flex items-center justify-between p-0">
|
||||||
|
<div className="flex w-full flex-row divide-y divide-dashed justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 px-6 pt-4 mb-2">
|
||||||
|
<ExclamationCircle className="text-ui-fg-subtle" />
|
||||||
|
<Heading level="h2">{t("orders.claims.panel.title")}</Heading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="gap-2 px-6 pb-4">
|
||||||
|
<Text>{t("orders.claims.panel.description")}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end gap-x-2 rounded-b-xl px-4 py-4">
|
||||||
|
<Button size="small" variant="secondary" onClick={onCancelClaim}>
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button size="small" variant="secondary" onClick={onContinueClaim}>
|
||||||
|
{t("actions.continue")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./active-order-claim-section"
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
import { ArrowPath } from "@medusajs/icons"
|
||||||
|
import { Button, Container, Heading, Text, toast } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useCancelExchangeRequest } from "../../../../../hooks/api/exchanges"
|
||||||
|
|
||||||
|
type ActiveOrderExchangeSectionProps = {
|
||||||
|
orderPreview: HttpTypes.AdminOrderPreview
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ActiveOrderExchangeSection = ({
|
||||||
|
orderPreview,
|
||||||
|
}: ActiveOrderExchangeSectionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const exchangeId = orderPreview?.order_change?.exchange_id
|
||||||
|
|
||||||
|
const { mutateAsync: cancelExchange } = useCancelExchangeRequest(
|
||||||
|
exchangeId,
|
||||||
|
orderPreview.id
|
||||||
|
)
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const onContinueExchange = async () => {
|
||||||
|
navigate(`/orders/${orderPreview.id}/exchanges`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancelExchange = async () => {
|
||||||
|
await cancelExchange(undefined, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("orders.exchanges.toast.canceledSuccessfully"))
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exchangeId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"repeating-linear-gradient(-45deg, rgb(212, 212, 216, 0.15), rgb(212, 212, 216,.15) 10px, transparent 10px, transparent 20px)",
|
||||||
|
}}
|
||||||
|
className="-m-4 mb-1 border-b p-4"
|
||||||
|
>
|
||||||
|
<Container className="flex items-center justify-between p-0">
|
||||||
|
<div className="flex w-full flex-row divide-y divide-dashed justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 px-6 pt-4 mb-2">
|
||||||
|
<ArrowPath className="text-ui-fg-subtle" />
|
||||||
|
<Heading level="h2">{t("orders.exchanges.panel.title")}</Heading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="gap-2 px-6 pb-4">
|
||||||
|
<Text>{t("orders.exchanges.panel.description")}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end gap-x-2 rounded-b-xl px-4 py-4">
|
||||||
|
<Button size="small" variant="secondary" onClick={onCancelExchange}>
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={onContinueExchange}
|
||||||
|
>
|
||||||
|
{t("actions.continue")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./active-order-exchange-section"
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
import { ArrowUturnLeft } from "@medusajs/icons"
|
||||||
|
import { Button, Container, Heading, Text, toast } from "@medusajs/ui"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { useCancelReturnRequest } from "../../../../../hooks/api/returns"
|
||||||
|
|
||||||
|
type ActiveOrderReturnSectionProps = {
|
||||||
|
orderPreview: HttpTypes.AdminOrderPreview
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ActiveOrderReturnSection = ({
|
||||||
|
orderPreview,
|
||||||
|
}: ActiveOrderReturnSectionProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const orderChange = orderPreview?.order_change
|
||||||
|
const returnId = orderChange?.return_id
|
||||||
|
const isReturnRequest =
|
||||||
|
orderChange?.change_type === "return_request" && !!orderChange.return_id
|
||||||
|
|
||||||
|
const { mutateAsync: cancelReturn } = useCancelReturnRequest(
|
||||||
|
returnId,
|
||||||
|
orderPreview.id
|
||||||
|
)
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const onContinueReturn = async () => {
|
||||||
|
navigate(`/orders/${orderPreview.id}/returns`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancelReturn = async () => {
|
||||||
|
await cancelReturn(undefined, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("orders.returns.toast.canceledSuccessfully"))
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!returnId || !isReturnRequest) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"repeating-linear-gradient(-45deg, rgb(212, 212, 216, 0.15), rgb(212, 212, 216,.15) 10px, transparent 10px, transparent 20px)",
|
||||||
|
}}
|
||||||
|
className="-m-4 mb-1 border-b p-4"
|
||||||
|
>
|
||||||
|
<Container className="flex items-center justify-between p-0">
|
||||||
|
<div className="flex w-full flex-row divide-y divide-dashed justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 px-6 pt-4 mb-2">
|
||||||
|
<ArrowUturnLeft className="text-ui-fg-subtle" />
|
||||||
|
<Heading level="h2">{t("orders.returns.panel.title")}</Heading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="gap-2 px-6 pb-4">
|
||||||
|
<Text>{t("orders.returns.panel.description")}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-end gap-x-2 rounded-b-xl px-4 py-4">
|
||||||
|
<Button size="small" variant="secondary" onClick={onCancelReturn}>
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button size="small" variant="secondary" onClick={onContinueReturn}>
|
||||||
|
{t("actions.continue")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./active-order-return-section"
|
||||||
+5
@@ -249,6 +249,8 @@ const Header = ({
|
|||||||
(i) => !(getReturnableQuantity(i) > 0)
|
(i) => !(getReturnableQuantity(i) > 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isOrderEditActive = orderPreview?.order_change?.change_type === "edit"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
<div className="flex items-center justify-between px-6 py-4">
|
||||||
<Heading level="h2">{t("fields.summary")}</Heading>
|
<Heading level="h2">{t("fields.summary")}</Heading>
|
||||||
@@ -281,6 +283,7 @@ const Header = ({
|
|||||||
icon: <ArrowUturnLeft />,
|
icon: <ArrowUturnLeft />,
|
||||||
disabled:
|
disabled:
|
||||||
shouldDisableReturn ||
|
shouldDisableReturn ||
|
||||||
|
isOrderEditActive ||
|
||||||
!!orderPreview?.order_change?.exchange_id ||
|
!!orderPreview?.order_change?.exchange_id ||
|
||||||
!!orderPreview?.order_change?.claim_id,
|
!!orderPreview?.order_change?.claim_id,
|
||||||
},
|
},
|
||||||
@@ -294,6 +297,7 @@ const Header = ({
|
|||||||
icon: <ArrowPath />,
|
icon: <ArrowPath />,
|
||||||
disabled:
|
disabled:
|
||||||
shouldDisableReturn ||
|
shouldDisableReturn ||
|
||||||
|
isOrderEditActive ||
|
||||||
(!!orderPreview?.order_change?.return_id &&
|
(!!orderPreview?.order_change?.return_id &&
|
||||||
!!!orderPreview?.order_change?.exchange_id) ||
|
!!!orderPreview?.order_change?.exchange_id) ||
|
||||||
!!orderPreview?.order_change?.claim_id,
|
!!orderPreview?.order_change?.claim_id,
|
||||||
@@ -308,6 +312,7 @@ const Header = ({
|
|||||||
icon: <ExclamationCircle />,
|
icon: <ExclamationCircle />,
|
||||||
disabled:
|
disabled:
|
||||||
shouldDisableReturn ||
|
shouldDisableReturn ||
|
||||||
|
isOrderEditActive ||
|
||||||
(!!orderPreview?.order_change?.return_id &&
|
(!!orderPreview?.order_change?.return_id &&
|
||||||
!!!orderPreview?.order_change?.claim_id) ||
|
!!!orderPreview?.order_change?.claim_id) ||
|
||||||
!!orderPreview?.order_change?.exchange_id,
|
!!orderPreview?.order_change?.exchange_id,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Outlet, useLoaderData, useParams } from "react-router-dom"
|
import { Outlet, useLoaderData, useParams } from "react-router-dom"
|
||||||
|
|
||||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||||
import { useOrder } from "../../../hooks/api/orders"
|
import { useOrder, useOrderPreview } from "../../../hooks/api/orders"
|
||||||
import { OrderActivitySection } from "./components/order-activity-section"
|
import { OrderActivitySection } from "./components/order-activity-section"
|
||||||
import { OrderCustomerSection } from "./components/order-customer-section"
|
import { OrderCustomerSection } from "./components/order-customer-section"
|
||||||
import { OrderFulfillmentSection } from "./components/order-fulfillment-section"
|
import { OrderFulfillmentSection } from "./components/order-fulfillment-section"
|
||||||
@@ -15,6 +15,9 @@ import after from "virtual:medusa/widgets/order/details/after"
|
|||||||
import before from "virtual:medusa/widgets/order/details/before"
|
import before from "virtual:medusa/widgets/order/details/before"
|
||||||
import sideAfter from "virtual:medusa/widgets/order/details/side/after"
|
import sideAfter from "virtual:medusa/widgets/order/details/side/after"
|
||||||
import sideBefore from "virtual:medusa/widgets/order/details/side/before"
|
import sideBefore from "virtual:medusa/widgets/order/details/side/before"
|
||||||
|
import { ActiveOrderClaimSection } from "./components/active-order-claim-section"
|
||||||
|
import { ActiveOrderExchangeSection } from "./components/active-order-exchange-section"
|
||||||
|
import { ActiveOrderReturnSection } from "./components/active-order-return-section"
|
||||||
import { OrderActiveEditSection } from "./components/order-active-edit-section"
|
import { OrderActiveEditSection } from "./components/order-active-edit-section"
|
||||||
|
|
||||||
export const OrderDetail = () => {
|
export const OrderDetail = () => {
|
||||||
@@ -31,8 +34,11 @@ export const OrderDetail = () => {
|
|||||||
initialData,
|
initialData,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
const { order: orderPreview, isLoading: isPreviewLoading } = useOrderPreview(
|
||||||
|
id!
|
||||||
|
)
|
||||||
|
|
||||||
if (isLoading || !order) {
|
if (isLoading || !order || isPreviewLoading) {
|
||||||
return <div>Loading...</div>
|
return <div>Loading...</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +58,9 @@ export const OrderDetail = () => {
|
|||||||
<div className="flex flex-col gap-x-4 lg:flex-row xl:items-start">
|
<div className="flex flex-col gap-x-4 lg:flex-row xl:items-start">
|
||||||
<div className="flex w-full flex-col gap-y-3">
|
<div className="flex w-full flex-col gap-y-3">
|
||||||
<OrderActiveEditSection order={order} />
|
<OrderActiveEditSection order={order} />
|
||||||
|
<ActiveOrderClaimSection orderPreview={orderPreview!} />
|
||||||
|
<ActiveOrderExchangeSection orderPreview={orderPreview!} />
|
||||||
|
<ActiveOrderReturnSection orderPreview={orderPreview!} />
|
||||||
<OrderGeneralSection order={order} />
|
<OrderGeneralSection order={order} />
|
||||||
<OrderSummarySection order={order} />
|
<OrderSummarySection order={order} />
|
||||||
<OrderPaymentSection order={order} />
|
<OrderPaymentSection order={order} />
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export interface BaseOrderChange {
|
|||||||
/**
|
/**
|
||||||
* The type of the order change
|
* The type of the order change
|
||||||
*/
|
*/
|
||||||
change_type?: "return" | "exchange" | "claim" | "edit"
|
change_type?: "return" | "exchange" | "claim" | "edit" | "return_request"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the associated order
|
* The ID of the associated order
|
||||||
|
|||||||
Reference in new issue
Block a user