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:
Riqwan Thamir
2024-08-23 13:24:38 +02:00
committed by GitHub
parent 408ed2ee45
commit 531285c1e9
11 changed files with 300 additions and 11 deletions
@@ -912,6 +912,14 @@
"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."
}
},
"toast": {
"canceledSuccessfully": "Return canceled successfully",
"confirmedSuccessfully": "Return confirmed successfully"
},
"panel": {
"title": "Return initiated",
"description": "There is an open return request to be completed"
}
},
"claims": {
@@ -931,6 +939,14 @@
},
"tooltips": {
"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": {
@@ -950,6 +966,14 @@
},
"tooltips": {
"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": {
@@ -307,15 +307,19 @@ export const ClaimCreateForm = ({
const shippingOptionId = form.watch("inbound_option_id")
const handleSubmit = form.handleSubmit(async (data) => {
try {
await confirmClaimRequest({ no_notification: !data.send_notification })
await confirmClaimRequest(
{ no_notification: !data.send_notification },
{
onSuccess: () => {
toast.success(t("orders.claims.toast.confirmedSuccessfully"))
handleSuccess()
} catch (e) {
toast.error(t("general.error"), {
description: e.message,
})
}
handleSuccess()
},
onError: (error) => {
toast.error(error.message)
},
}
)
})
const onItemsSelected = async () => {
@@ -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>
)
}
@@ -0,0 +1 @@
export * from "./active-order-claim-section"
@@ -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>
)
}
@@ -0,0 +1 @@
export * from "./active-order-exchange-section"
@@ -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>
)
}
@@ -0,0 +1 @@
export * from "./active-order-return-section"
@@ -249,6 +249,8 @@ const Header = ({
(i) => !(getReturnableQuantity(i) > 0)
)
const isOrderEditActive = orderPreview?.order_change?.change_type === "edit"
return (
<div className="flex items-center justify-between px-6 py-4">
<Heading level="h2">{t("fields.summary")}</Heading>
@@ -281,6 +283,7 @@ const Header = ({
icon: <ArrowUturnLeft />,
disabled:
shouldDisableReturn ||
isOrderEditActive ||
!!orderPreview?.order_change?.exchange_id ||
!!orderPreview?.order_change?.claim_id,
},
@@ -294,6 +297,7 @@ const Header = ({
icon: <ArrowPath />,
disabled:
shouldDisableReturn ||
isOrderEditActive ||
(!!orderPreview?.order_change?.return_id &&
!!!orderPreview?.order_change?.exchange_id) ||
!!orderPreview?.order_change?.claim_id,
@@ -308,6 +312,7 @@ const Header = ({
icon: <ExclamationCircle />,
disabled:
shouldDisableReturn ||
isOrderEditActive ||
(!!orderPreview?.order_change?.return_id &&
!!!orderPreview?.order_change?.claim_id) ||
!!orderPreview?.order_change?.exchange_id,
@@ -1,7 +1,7 @@
import { Outlet, useLoaderData, useParams } from "react-router-dom"
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 { OrderCustomerSection } from "./components/order-customer-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 sideAfter from "virtual:medusa/widgets/order/details/side/after"
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"
export const OrderDetail = () => {
@@ -31,8 +34,11 @@ export const OrderDetail = () => {
initialData,
}
)
const { order: orderPreview, isLoading: isPreviewLoading } = useOrderPreview(
id!
)
if (isLoading || !order) {
if (isLoading || !order || isPreviewLoading) {
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 w-full flex-col gap-y-3">
<OrderActiveEditSection order={order} />
<ActiveOrderClaimSection orderPreview={orderPreview!} />
<ActiveOrderExchangeSection orderPreview={orderPreview!} />
<ActiveOrderReturnSection orderPreview={orderPreview!} />
<OrderGeneralSection order={order} />
<OrderSummarySection order={order} />
<OrderPaymentSection order={order} />
+1 -1
View File
@@ -321,7 +321,7 @@ export interface BaseOrderChange {
/**
* 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