feat(admin-ui): Implement allocations on draft orders (#3753)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/admin-ui": patch
|
||||
---
|
||||
|
||||
feat(admin): Implement allocations on draft orders
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
import React from "react"
|
||||
import { LineItem } from "@medusajs/medusa"
|
||||
import { sum } from "lodash"
|
||||
import { ReservationItemDTO } from "@medusajs/types"
|
||||
import { useAdminStockLocations, useAdminVariantsInventory } from "medusa-react"
|
||||
import Tooltip from "../../../../components/atoms/tooltip"
|
||||
import CircleQuarterSolid from "../../../../components/fundamentals/icons/circle-quarter-solid"
|
||||
import WarningCircleIcon from "../../../../components/fundamentals/icons/warning-circle"
|
||||
import CheckCircleFillIcon from "../../../../components/fundamentals/icons/check-circle-fill-icon"
|
||||
import EditAllocationDrawer from "../../details/allocations/edit-allocation-modal"
|
||||
import Button from "../../../../components/fundamentals/button"
|
||||
|
||||
const ReservationIndicator = ({
|
||||
reservations,
|
||||
lineItem,
|
||||
}: {
|
||||
reservations?: ReservationItemDTO[]
|
||||
lineItem: LineItem
|
||||
}) => {
|
||||
const { variant, isLoading, isFetching } = useAdminVariantsInventory(
|
||||
lineItem.variant_id!,
|
||||
{
|
||||
enabled: !!lineItem?.variant_id,
|
||||
}
|
||||
)
|
||||
|
||||
const { stock_locations } = useAdminStockLocations({
|
||||
id: reservations?.map((r) => r.location_id) || [],
|
||||
})
|
||||
|
||||
const [reservation, setReservation] =
|
||||
React.useState<ReservationItemDTO | null>(null)
|
||||
|
||||
const locationMap = new Map(stock_locations?.map((l) => [l.id, l.name]) || [])
|
||||
|
||||
const reservationsSum = sum(reservations?.map((r) => r.quantity) || [])
|
||||
|
||||
const allocatableSum = lineItem.quantity - (lineItem?.fulfilled_quantity || 0)
|
||||
|
||||
const awaitingAllocation = allocatableSum - reservationsSum
|
||||
|
||||
if (
|
||||
isLoading ||
|
||||
isFetching ||
|
||||
!lineItem.variant_id ||
|
||||
!variant?.inventory.length
|
||||
) {
|
||||
return <div className="w-[20px]" />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={awaitingAllocation ? "text-rose-50" : "text-grey-40"}>
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="inter-small-regular flex flex-col items-center px-1 pt-1 pb-2">
|
||||
{reservationsSum || awaitingAllocation ? (
|
||||
<div className="gap-y-base grid grid-cols-1 divide-y">
|
||||
{!!awaitingAllocation && (
|
||||
<span className="flex w-full items-center">
|
||||
{awaitingAllocation} items await allocation
|
||||
</span>
|
||||
)}
|
||||
{reservations?.map((reservation) => (
|
||||
<EditAllocationButton
|
||||
key={reservation.id}
|
||||
locationName={locationMap.get(reservation.location_id)}
|
||||
totalReservedQuantity={reservationsSum}
|
||||
reservation={reservation}
|
||||
lineItem={lineItem}
|
||||
onClick={() => setReservation(reservation)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="flex w-full items-center">
|
||||
This item has been fulfilled.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
side="bottom"
|
||||
>
|
||||
{awaitingAllocation ? (
|
||||
reservationsSum ? (
|
||||
<CircleQuarterSolid size={20} />
|
||||
) : (
|
||||
<WarningCircleIcon fillType="solid" size={20} />
|
||||
)
|
||||
) : (
|
||||
<CheckCircleFillIcon size={20} />
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
{reservation && (
|
||||
<EditAllocationDrawer
|
||||
totalReservedQuantity={reservationsSum}
|
||||
close={() => setReservation(null)}
|
||||
reservation={reservation}
|
||||
item={lineItem}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const EditAllocationButton = ({
|
||||
reservation,
|
||||
locationName,
|
||||
onClick,
|
||||
}: {
|
||||
reservation: ReservationItemDTO
|
||||
totalReservedQuantity: number
|
||||
locationName?: string
|
||||
lineItem: LineItem
|
||||
onClick: () => void
|
||||
}) => {
|
||||
return (
|
||||
<div className="pt-base first:pt-0">
|
||||
{`${reservation.quantity} item: ${locationName}`}
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="mt-2 w-full border"
|
||||
>
|
||||
Edit Allocation
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReservationIndicator
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Controller, useForm, useWatch } from "react-hook-form"
|
||||
import { LineItem, Order, ReservationItemDTO } from "@medusajs/medusa"
|
||||
import { LineItem } from "@medusajs/medusa"
|
||||
import { NestedForm, nestedForm } from "../../../../utils/nested-form"
|
||||
import React, { useEffect, useMemo } from "react"
|
||||
import {
|
||||
@@ -20,6 +20,7 @@ import { getErrorMessage } from "../../../../utils/error-messages"
|
||||
import { getFulfillableQuantity } from "../create-fulfillment/item-table"
|
||||
import { sum } from "lodash"
|
||||
import useNotification from "../../../../hooks/use-notification"
|
||||
import { ReservationItemDTO } from "@medusajs/types"
|
||||
|
||||
type AllocationModalFormData = {
|
||||
location?: { label: string; value: string }
|
||||
@@ -27,13 +28,13 @@ type AllocationModalFormData = {
|
||||
}
|
||||
|
||||
type AllocateItemsModalProps = {
|
||||
order: Order
|
||||
items: LineItem[]
|
||||
reservationItemsMap: Record<string, ReservationItemDTO[]>
|
||||
close: () => void
|
||||
}
|
||||
|
||||
const AllocateItemsModal: React.FC<AllocateItemsModalProps> = ({
|
||||
order,
|
||||
items,
|
||||
close,
|
||||
reservationItemsMap,
|
||||
}) => {
|
||||
@@ -175,7 +176,7 @@ const AllocateItemsModal: React.FC<AllocateItemsModalProps> = ({
|
||||
<p className="inter-base-regular">
|
||||
Select the number of items that you wish to allocate.
|
||||
</p>
|
||||
{order.items?.map((item, i) => {
|
||||
{items?.map((item, i) => {
|
||||
return (
|
||||
<AllocationLineItem
|
||||
form={nestedForm(form, `items.${i}` as "items.0")}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { sum } from "lodash"
|
||||
import {
|
||||
AdminGetVariantsVariantInventoryRes,
|
||||
DraftOrder,
|
||||
VariantInventory,
|
||||
} from "@medusajs/medusa"
|
||||
import Badge from "../../../../components/fundamentals/badge"
|
||||
import ImagePlaceholder from "../../../../components/fundamentals/image-placeholder"
|
||||
import BodyCard from "../../../../components/organisms/body-card"
|
||||
import { formatAmountWithSymbol } from "../../../../utils/prices"
|
||||
import { DisplayTotal } from "../templates"
|
||||
import { ActionType } from "../../../../components/molecules/actionables"
|
||||
import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
|
||||
import useToggleState from "../../../../hooks/use-toggle-state"
|
||||
import { useAdminReservations, useMedusa } from "medusa-react"
|
||||
import { ReservationItemDTO } from "@medusajs/types"
|
||||
import ReservationIndicator from "../../components/reservation-indicator/reservation-indicator"
|
||||
import AllocateItemsModal from "../allocations/allocate-items-modal"
|
||||
import { Response } from "@medusajs/medusa-js"
|
||||
import StatusIndicator from "../../../../components/fundamentals/status-indicator"
|
||||
|
||||
type DraftSummaryCardProps = {
|
||||
order: DraftOrder
|
||||
}
|
||||
|
||||
const DraftSummaryCard: React.FC<DraftSummaryCardProps> = ({ order }) => {
|
||||
const { client } = useMedusa()
|
||||
const { isFeatureEnabled } = useFeatureFlag()
|
||||
const inventoryEnabled = useMemo(() => {
|
||||
return isFeatureEnabled("inventoryService")
|
||||
}, [isFeatureEnabled])
|
||||
|
||||
const { cart } = order
|
||||
const { region } = cart
|
||||
|
||||
const [variantInventoryMap, setVariantInventoryMap] = React.useState<
|
||||
Map<string, VariantInventory>
|
||||
>(new Map())
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!inventoryEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const fetchInventory = async () => {
|
||||
const inventory = await Promise.all(
|
||||
cart.items.map(async (item) => {
|
||||
if (!item.variant_id) {
|
||||
return
|
||||
}
|
||||
return await client.admin.variants.getInventory(item.variant_id)
|
||||
})
|
||||
)
|
||||
|
||||
setVariantInventoryMap(
|
||||
new Map(
|
||||
inventory
|
||||
.filter(
|
||||
(
|
||||
inventoryItem
|
||||
// eslint-disable-next-line max-len
|
||||
): inventoryItem is Response<AdminGetVariantsVariantInventoryRes> =>
|
||||
!!inventoryItem
|
||||
)
|
||||
.map((i) => {
|
||||
return [i.variant.id, i.variant]
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fetchInventory()
|
||||
}, [cart.items, inventoryEnabled, client.admin.variants])
|
||||
|
||||
const { reservations } = useAdminReservations(
|
||||
{
|
||||
line_item_id: cart?.items.map((item) => item.id),
|
||||
},
|
||||
{
|
||||
enabled: inventoryEnabled,
|
||||
}
|
||||
)
|
||||
|
||||
const reservationItemsMap = useMemo(() => {
|
||||
if (!reservations?.length || !inventoryEnabled) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return reservations.reduce(
|
||||
(acc: Record<string, ReservationItemDTO[]>, item: ReservationItemDTO) => {
|
||||
if (!item.line_item_id) {
|
||||
return acc
|
||||
}
|
||||
acc[item.line_item_id] = acc[item.line_item_id]
|
||||
? [...acc[item.line_item_id], item]
|
||||
: [item]
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
}, [reservations, inventoryEnabled])
|
||||
|
||||
const {
|
||||
state: allocationModalIsOpen,
|
||||
open: showAllocationModal,
|
||||
close: closeAllocationModal,
|
||||
} = useToggleState()
|
||||
|
||||
const allItemsReserved = useMemo(() => {
|
||||
return cart.items.every((item) => {
|
||||
if (
|
||||
!item.variant_id ||
|
||||
!variantInventoryMap.get(item.variant_id)?.inventory.length
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
const reservations = reservationItemsMap[item.id]
|
||||
|
||||
return (
|
||||
item.quantity === item.fulfilled_quantity ||
|
||||
(reservations &&
|
||||
sum(reservations.map((r) => r.quantity)) ===
|
||||
item.quantity - (item.fulfilled_quantity || 0))
|
||||
)
|
||||
})
|
||||
}, [cart.items, variantInventoryMap, reservationItemsMap])
|
||||
|
||||
const actionables = useMemo(() => {
|
||||
const actionables: ActionType[] = []
|
||||
if (inventoryEnabled && !allItemsReserved) {
|
||||
actionables.push({
|
||||
label: "Allocate",
|
||||
onClick: showAllocationModal,
|
||||
})
|
||||
}
|
||||
return actionables
|
||||
}, [inventoryEnabled, showAllocationModal, allItemsReserved])
|
||||
|
||||
return (
|
||||
<BodyCard
|
||||
className={"mb-4 h-auto min-h-0 w-full"}
|
||||
title="Summary"
|
||||
status={
|
||||
isFeatureEnabled("inventoryService") &&
|
||||
Array.isArray(reservations) && (
|
||||
<StatusIndicator
|
||||
onClick={allItemsReserved ? undefined : showAllocationModal}
|
||||
variant={allItemsReserved ? "success" : "danger"}
|
||||
title={allItemsReserved ? "Allocated" : "Awaits allocation"}
|
||||
className="rounded-rounded border px-3 py-1.5"
|
||||
/>
|
||||
)
|
||||
}
|
||||
actionables={actionables}
|
||||
>
|
||||
<div className="mt-6">
|
||||
{cart?.items?.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="hover:bg-grey-5 rounded-rounded mx-[-5px] mb-1 flex h-[64px] justify-between py-2 px-[5px]"
|
||||
>
|
||||
<div className="flex justify-center space-x-4">
|
||||
<div className="rounded-rounded flex h-[48px] w-[36px] items-center justify-center">
|
||||
{item?.thumbnail ? (
|
||||
<img
|
||||
src={item.thumbnail}
|
||||
className="rounded-rounded object-cover"
|
||||
/>
|
||||
) : (
|
||||
<ImagePlaceholder />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col justify-center">
|
||||
<span className="inter-small-regular text-grey-90 max-w-[225px] truncate">
|
||||
{item.title}
|
||||
</span>
|
||||
{item?.variant && (
|
||||
<span className="inter-small-regular text-grey-50">
|
||||
{item.variant.sku}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="small:space-x-2 medium:space-x-4 large:space-x-6 mr-3 flex">
|
||||
<div className="inter-small-regular text-grey-50">
|
||||
{formatAmountWithSymbol({
|
||||
amount: (item?.total ?? 0) / item.quantity,
|
||||
currency: region?.currency_code ?? "",
|
||||
digits: 2,
|
||||
tax: [],
|
||||
})}
|
||||
</div>
|
||||
<div className="inter-small-regular text-grey-50">
|
||||
x {item.quantity}
|
||||
</div>
|
||||
{inventoryEnabled && (
|
||||
<ReservationIndicator
|
||||
reservations={reservationItemsMap[item.id]}
|
||||
lineItem={item}
|
||||
/>
|
||||
)}
|
||||
<div className="inter-small-regular text-grey-90">
|
||||
{formatAmountWithSymbol({
|
||||
amount: item.total ?? 0,
|
||||
currency: region?.currency_code ?? "",
|
||||
digits: 2,
|
||||
tax: [],
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="inter-small-regular text-grey-50">
|
||||
{region?.currency_code.toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<DisplayTotal
|
||||
currency={region?.currency_code}
|
||||
totalAmount={order?.cart?.subtotal}
|
||||
totalTitle={"Subtotal"}
|
||||
/>
|
||||
{cart?.discounts?.map((discount, index) => (
|
||||
<div key={index} className="mt-4 flex items-center justify-between">
|
||||
<div className="inter-small-regular text-grey-90 flex items-center">
|
||||
Discount:{" "}
|
||||
<Badge className="ml-3" variant="default">
|
||||
{discount.code}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="inter-small-regular text-grey-90">
|
||||
-
|
||||
{formatAmountWithSymbol({
|
||||
amount: cart?.discount_total,
|
||||
currency: region?.currency_code || "",
|
||||
digits: 2,
|
||||
tax: region?.tax_rate,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<DisplayTotal
|
||||
currency={region?.currency_code}
|
||||
totalAmount={cart?.shipping_total}
|
||||
totalTitle={"Shipping"}
|
||||
/>
|
||||
<DisplayTotal
|
||||
currency={region?.currency_code}
|
||||
totalAmount={cart?.tax_total}
|
||||
totalTitle={`Tax`}
|
||||
/>
|
||||
<DisplayTotal
|
||||
currency={region?.currency_code}
|
||||
variant="large"
|
||||
totalAmount={cart?.total}
|
||||
totalTitle={`Total`}
|
||||
/>
|
||||
</div>
|
||||
{allocationModalIsOpen && (
|
||||
<AllocateItemsModal
|
||||
reservationItemsMap={reservationItemsMap}
|
||||
items={cart.items}
|
||||
close={closeAllocationModal}
|
||||
/>
|
||||
)}
|
||||
</BodyCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default DraftSummaryCard
|
||||
@@ -261,7 +261,7 @@ const SummaryCard: React.FC<SummaryCardProps> = ({ order, reservations }) => {
|
||||
{allocationModalIsOpen && (
|
||||
<AllocateItemsModal
|
||||
reservationItemsMap={reservationItemsMap}
|
||||
order={order}
|
||||
items={order.items}
|
||||
close={closeAllocationModal}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { LineItem, ReservationItemDTO } from "@medusajs/medusa"
|
||||
import { useAdminStockLocations, useAdminVariantsInventory } from "medusa-react"
|
||||
import { LineItem } from "@medusajs/medusa"
|
||||
import { ReservationItemDTO } from "@medusajs/types"
|
||||
|
||||
import Button from "../../../../components/fundamentals/button"
|
||||
import CheckCircleFillIcon from "../../../../components/fundamentals/icons/check-circle-fill-icon"
|
||||
import CircleQuarterSolid from "../../../../components/fundamentals/icons/circle-quarter-solid"
|
||||
import EditAllocationDrawer from "../allocations/edit-allocation-modal"
|
||||
import ImagePlaceholder from "../../../../components/fundamentals/image-placeholder"
|
||||
import React from "react"
|
||||
import Tooltip from "../../../../components/atoms/tooltip"
|
||||
import WarningCircleIcon from "../../../../components/fundamentals/icons/warning-circle"
|
||||
import { formatAmountWithSymbol } from "../../../../utils/prices"
|
||||
import { sum } from "lodash"
|
||||
import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
|
||||
import ReservationIndicator from "../../components/reservation-indicator/reservation-indicator"
|
||||
|
||||
type OrderLineProps = {
|
||||
item: LineItem
|
||||
@@ -77,123 +70,4 @@ const OrderLine = ({ item, currencyCode, reservations }: OrderLineProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
const ReservationIndicator = ({
|
||||
reservations,
|
||||
lineItem,
|
||||
}: {
|
||||
reservations?: ReservationItemDTO[]
|
||||
lineItem: LineItem
|
||||
}) => {
|
||||
const { variant, isLoading, isFetching } = useAdminVariantsInventory(
|
||||
lineItem.variant_id!,
|
||||
{
|
||||
enabled: !!lineItem?.variant_id,
|
||||
}
|
||||
)
|
||||
|
||||
const { stock_locations } = useAdminStockLocations({
|
||||
id: reservations?.map((r) => r.location_id) || [],
|
||||
})
|
||||
|
||||
const [reservation, setReservation] =
|
||||
React.useState<ReservationItemDTO | null>(null)
|
||||
|
||||
const locationMap = new Map(stock_locations?.map((l) => [l.id, l.name]) || [])
|
||||
|
||||
const reservationsSum = sum(reservations?.map((r) => r.quantity) || [])
|
||||
|
||||
const allocatableSum = lineItem.quantity - (lineItem?.fulfilled_quantity || 0)
|
||||
|
||||
const awaitingAllocation = allocatableSum - reservationsSum
|
||||
|
||||
if (
|
||||
isLoading ||
|
||||
isFetching ||
|
||||
!lineItem.variant_id ||
|
||||
!variant?.inventory.length
|
||||
) {
|
||||
return <div className="w-[20px]" />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={awaitingAllocation ? "text-rose-50" : "text-grey-40"}>
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="inter-small-regular flex flex-col items-center px-1 pt-1 pb-2">
|
||||
{reservationsSum || awaitingAllocation ? (
|
||||
<div className="gap-y-base grid grid-cols-1 divide-y">
|
||||
{!!awaitingAllocation && (
|
||||
<span className="flex w-full items-center">
|
||||
{awaitingAllocation} items await allocation
|
||||
</span>
|
||||
)}
|
||||
{reservations?.map((reservation) => (
|
||||
<EditAllocationButton
|
||||
key={reservation.id}
|
||||
locationName={locationMap.get(reservation.location_id)}
|
||||
totalReservedQuantity={reservationsSum}
|
||||
reservation={reservation}
|
||||
lineItem={lineItem}
|
||||
onClick={() => setReservation(reservation)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="flex w-full items-center">
|
||||
This item has been fulfilled.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
side="bottom"
|
||||
>
|
||||
{awaitingAllocation ? (
|
||||
reservationsSum ? (
|
||||
<CircleQuarterSolid size={20} />
|
||||
) : (
|
||||
<WarningCircleIcon fillType="solid" size={20} />
|
||||
)
|
||||
) : (
|
||||
<CheckCircleFillIcon size={20} />
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
{reservation && (
|
||||
<EditAllocationDrawer
|
||||
totalReservedQuantity={reservationsSum}
|
||||
close={() => setReservation(null)}
|
||||
reservation={reservation}
|
||||
item={lineItem}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const EditAllocationButton = ({
|
||||
reservation,
|
||||
locationName,
|
||||
onClick,
|
||||
}: {
|
||||
reservation: ReservationItemDTO
|
||||
totalReservedQuantity: number
|
||||
locationName?: string
|
||||
lineItem: LineItem
|
||||
onClick: () => void
|
||||
}) => {
|
||||
return (
|
||||
<div className="pt-base first:pt-0">
|
||||
{`${reservation.quantity} item: ${locationName}`}
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="mt-2 w-full border"
|
||||
>
|
||||
Edit Allocation
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderLine
|
||||
|
||||
@@ -13,12 +13,10 @@ import Avatar from "../../../components/atoms/avatar"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import CopyToClipboard from "../../../components/atoms/copy-to-clipboard"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
import Badge from "../../../components/fundamentals/badge"
|
||||
import Button from "../../../components/fundamentals/button"
|
||||
import DetailsIcon from "../../../components/fundamentals/details-icon"
|
||||
import DollarSignIcon from "../../../components/fundamentals/icons/dollar-sign-icon"
|
||||
import TruckIcon from "../../../components/fundamentals/icons/truck-icon"
|
||||
import ImagePlaceholder from "../../../components/fundamentals/image-placeholder"
|
||||
import StatusDot from "../../../components/fundamentals/status-indicator"
|
||||
import JSONView from "../../../components/molecules/json-view"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
@@ -31,6 +29,7 @@ import { getErrorMessage } from "../../../utils/error-messages"
|
||||
import extractCustomerName from "../../../utils/extract-customer-name"
|
||||
import { formatAmountWithSymbol } from "../../../utils/prices"
|
||||
import AddressModal from "../details/address-modal"
|
||||
import DraftSummaryCard from "../details/detail-cards/draft-summary"
|
||||
import { DisplayTotal, FormattedAddress } from "../details/templates"
|
||||
|
||||
type DeletePromptData = {
|
||||
@@ -211,108 +210,7 @@ const DraftOrderDetails = () => {
|
||||
</div>
|
||||
</div>
|
||||
</BodyCard>
|
||||
<BodyCard className={"mb-4 h-auto min-h-0 w-full"} title="Summary">
|
||||
<div className="mt-6">
|
||||
{cart?.items?.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="hover:bg-grey-5 rounded-rounded mx-[-5px] mb-1 flex h-[64px] justify-between py-2 px-[5px]"
|
||||
>
|
||||
<div className="flex justify-center space-x-4">
|
||||
<div className="rounded-rounded flex h-[48px] w-[36px] items-center justify-center">
|
||||
{item?.thumbnail ? (
|
||||
<img
|
||||
src={item.thumbnail}
|
||||
className="rounded-rounded object-cover"
|
||||
/>
|
||||
) : (
|
||||
<ImagePlaceholder />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col justify-center">
|
||||
<span className="inter-small-regular text-grey-90 max-w-[225px] truncate">
|
||||
{item.title}
|
||||
</span>
|
||||
{item?.variant && (
|
||||
<span className="inter-small-regular text-grey-50">
|
||||
{item.variant.sku}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="small:space-x-2 medium:space-x-4 large:space-x-6 mr-3 flex">
|
||||
<div className="inter-small-regular text-grey-50">
|
||||
{formatAmountWithSymbol({
|
||||
amount: (item?.total ?? 0) / item.quantity,
|
||||
currency: region?.currency_code ?? "",
|
||||
digits: 2,
|
||||
tax: [],
|
||||
})}
|
||||
</div>
|
||||
<div className="inter-small-regular text-grey-50">
|
||||
x {item.quantity}
|
||||
</div>
|
||||
<div className="inter-small-regular text-grey-90">
|
||||
{formatAmountWithSymbol({
|
||||
amount: item.total ?? 0,
|
||||
currency: region?.currency_code ?? "",
|
||||
digits: 2,
|
||||
tax: [],
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="inter-small-regular text-grey-50">
|
||||
{region?.currency_code.toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<DisplayTotal
|
||||
currency={region?.currency_code}
|
||||
totalAmount={draft_order?.cart?.subtotal}
|
||||
totalTitle={"Subtotal"}
|
||||
/>
|
||||
{cart?.discounts?.map((discount, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="mt-4 flex items-center justify-between"
|
||||
>
|
||||
<div className="inter-small-regular text-grey-90 flex items-center">
|
||||
Discount:{" "}
|
||||
<Badge className="ml-3" variant="default">
|
||||
{discount.code}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="inter-small-regular text-grey-90">
|
||||
-
|
||||
{formatAmountWithSymbol({
|
||||
amount: cart?.discount_total,
|
||||
currency: region?.currency_code || "",
|
||||
digits: 2,
|
||||
tax: region?.tax_rate,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<DisplayTotal
|
||||
currency={region?.currency_code}
|
||||
totalAmount={cart?.shipping_total}
|
||||
totalTitle={"Shipping"}
|
||||
/>
|
||||
<DisplayTotal
|
||||
currency={region?.currency_code}
|
||||
totalAmount={cart?.tax_total}
|
||||
totalTitle={`Tax`}
|
||||
/>
|
||||
<DisplayTotal
|
||||
currency={region?.currency_code}
|
||||
variant="large"
|
||||
totalAmount={cart?.total}
|
||||
totalTitle={`Total`}
|
||||
/>
|
||||
</div>
|
||||
</BodyCard>
|
||||
<DraftSummaryCard order={draft_order} />
|
||||
<BodyCard
|
||||
className={"mb-4 h-auto min-h-0 w-full"}
|
||||
title="Payment"
|
||||
|
||||
Reference in New Issue
Block a user