Skip to content

Commit

Permalink
Add ability to create order returns on order detail Sitecore#179
Browse files Browse the repository at this point in the history
  • Loading branch information
Crhistian committed Jul 18, 2023
1 parent 70417c6 commit 4f84bac
Show file tree
Hide file tree
Showing 6 changed files with 537 additions and 14 deletions.
42 changes: 39 additions & 3 deletions src/components/orders/detail/OrderDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import {Card, CardBody, Container, Stack, VStack, Text, CardHeader, Heading, Flex} from "@chakra-ui/react"
import {
Card,
CardBody,
Container,
Stack,
VStack,
Text,
CardHeader,
Heading,
Flex,
HStack,
Button
} from "@chakra-ui/react"
import {dateHelper} from "utils"
import {OrderSummary} from "./order-summary/OrderSummary"
import {OrderCustomer} from "./order-customer/OrderCustomer"
Expand All @@ -15,6 +27,7 @@ import {Shipments} from "ordercloud-javascript-sdk"
import {OrderShipments} from "./order-shipments/OrderShipments"
import {OrderHeaderItem} from "./OrderHeaderItem"
import {OrderReturns} from "./order-returns/OrderReturns"
import {ReturnModal} from "./order-returns/return-modal/ReturnModal"

type OrderDetailProps = ReturnType<typeof useOrderDetail>

Expand All @@ -29,7 +42,8 @@ export function OrderDetail({
returns,
fetchOrder,
fetchShipments,
fetchLineItems
fetchLineItems,
fetchReturns
}: OrderDetailProps) {
const {isAdmin} = useAuth()
const shippingAddress = lineItems?.length ? lineItems[0].ShippingAddress : null
Expand All @@ -43,6 +57,10 @@ export function OrderDetail({
])
}

const handleReturnUpdate = async () => {
await fetchReturns(order)
}

const handleShipmentDelete = async (shipmentId) => {
await Shipments.Delete(shipmentId)
await handleShipmentUpdate()
Expand All @@ -57,6 +75,10 @@ export function OrderDetail({
return !lineItem.SupplierID
})

const refundableLineItems = lineItems.filter((lineItem) => {
return lineItem.QuantityShipped && lineItem.Product.Returnable
})

return (
<Container maxW="100%" bgColor="st.mainBackgroundColor" flexGrow={1} p={[4, 6, 8]}>
<Heading size="md" marginBottom={7}>
Expand Down Expand Up @@ -160,7 +182,21 @@ export function OrderDetail({
)}
<Card width="full">
<CardHeader>
<Heading size="md">Returns</Heading>
<Stack direction={["column", "column", "row"]} justifyContent="space-between">
<Heading size="md">Returns</Heading>
{(order.Status === "Open" || order.Status === "Completed") && refundableLineItems?.length && (
<ReturnModal
order={order}
lineItems={refundableLineItems}
allOrderReturns={returns}
onUpdate={handleReturnUpdate}
as="button"
buttonProps={{colorScheme: "primary", size: "sm"}}
>
Create return
</ReturnModal>
)}
</Stack>
</CardHeader>
<CardBody>
<OrderReturns returns={returns} />
Expand Down
10 changes: 3 additions & 7 deletions src/components/orders/detail/order-returns/OrderReturns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,18 @@ export function OrderReturns({returns}: OrderReturnsProps) {
<Th role="columnheader">ID</Th>
<Th role="columnheader">Status</Th>
<Th role="columnheader">Refund Amount</Th>
<Th role="columnheader"></Th>
</Tr>
</Thead>
<Tbody role="rowgroup">
{returns.map((orderReturn) => (
<Tr key={orderReturn.ID} role="row">
<Td role="cell">{orderReturn.ID}</Td>
<Td role="cell">
<OrderStatus status={orderReturn.Status} />
<Link href={`/returns/${orderReturn.ID}`}>{orderReturn.ID}</Link>
</Td>
<Td role="cell">{priceHelper.formatPrice(orderReturn.RefundAmount)}</Td>
<Td role="cell">
<Link href={`/returns/${orderReturn.ID}`}>
<Button colorScheme="primary">View</Button>
</Link>
<OrderStatus status={orderReturn.Status} />
</Td>
<Td role="cell">{priceHelper.formatPrice(orderReturn.RefundAmount)}</Td>
</Tr>
))}
</Tbody>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {DeleteIcon, EditIcon} from "@chakra-ui/icons"
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Button,
HStack,
IconButton,
Menu,
MenuButton,
MenuDivider,
MenuItem,
MenuList,
Text,
useDisclosure
} from "@chakra-ui/react"
import {TbDotsVertical} from "react-icons/tb"
import {ILineItem} from "types/ordercloud/ILineItem"
import {IOrder} from "types/ordercloud/IOrder"
import {useRef} from "react"
import {IOrderReturn} from "types/ordercloud/IOrderReturn"
import {ReturnModal} from "./ReturnModal"

interface ReturnActionMenuProps {
orderReturn: IOrderReturn
allOrderReturns: IOrderReturn[]
order: IOrder
lineItems: ILineItem[]
onUpdate: () => void
onDelete: () => void
}

export function ReturnActionMenu({
orderReturn,
allOrderReturns,
order,
lineItems,
onUpdate,
onDelete
}: ReturnActionMenuProps) {
const {isOpen: isDeleteDialogOpen, onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog} = useDisclosure()
const cancelRef = useRef()
const handleCancelDeleteReturn = () => {
onDelete()
onCloseDeleteDialog()
}
return (
<>
<Menu>
<MenuButton
as={IconButton}
icon={<TbDotsVertical />}
aria-label={`Return action menu for ${orderReturn.ID}`}
variant="ghost"
></MenuButton>
<MenuList>
<ReturnModal
order={order}
orderReturn={orderReturn}
allOrderReturns={allOrderReturns}
lineItems={lineItems}
onUpdate={onUpdate}
as="menuitem"
>
<HStack width="full" justifyContent="space-between">
<Text>Edit</Text> <EditIcon />
</HStack>
</ReturnModal>
<MenuDivider />
<MenuItem justifyContent="space-between" color="red.500" onClick={onOpenDeleteDialog}>
Delete <DeleteIcon />
</MenuItem>
</MenuList>
</Menu>

<AlertDialog isOpen={isDeleteDialogOpen} leastDestructiveRef={cancelRef} onClose={onCloseDeleteDialog}>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader>Delete Return</AlertDialogHeader>
<AlertDialogBody>Are you sure? You can&apos;t undo this action afterwards.</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={onCloseDeleteDialog}>
Cancel
</Button>
<Button colorScheme="red" onClick={handleCancelDeleteReturn} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import {InputControl, SelectControl} from "@/components/react-hook-form"
import {
Flex,
HStack,
FormControl,
Hide,
Stack,
Table,
TableContainer,
Tbody,
Td,
Thead,
Tr,
VStack,
Text,
useMediaQuery,
theme,
FormLabel,
Divider
} from "@chakra-ui/react"
import {flatten} from "lodash"
import {Control, FieldValues} from "react-hook-form"
import {ILineItem} from "types/ordercloud/ILineItem"
import {IOrderReturn} from "types/ordercloud/IOrderReturn"
import {ProductThumbnail} from "../../order-products/ProductThumbnail"

interface ReturnItemsTableProps {
control: Control<FieldValues, any>
lineItems: ILineItem[]
allOrderReturns: IOrderReturn[]
existingReturn: IOrderReturn
}

export function ReturnItemsTable({control, lineItems, allOrderReturns, existingReturn}: ReturnItemsTableProps) {
const [isMobile] = useMediaQuery(`(max-width: ${theme.breakpoints["md"]})`, {
ssr: true,
fallback: false // return false on the server, and re-evaluate on the client side
})
const maxReturnQuantity = (lineItem: ILineItem) => {
const otherReturnItems = flatten(
allOrderReturns
.filter((orderReturn) => orderReturn.ID !== existingReturn?.ID)
.filter((orderReturn) => orderReturn.ItemsToReturn.some((returnItem) => returnItem.LineItemID === lineItem.ID))
.map((orderReturn) => orderReturn.ItemsToReturn.filter((returnItem) => returnItem.LineItemID === lineItem.ID))
)
const quantityAlreadyReturned = otherReturnItems.reduce((acc, returnItem) => acc + returnItem.Quantity || 0, 0)
return lineItem.Quantity - quantityAlreadyReturned
}

const buildQuantityOptions = (lineItem: ILineItem) => {
const max = maxReturnQuantity(lineItem)
const numberArray = Array(max)
.fill("")
.map((_, i) => {
const value = i + 1
return {
label: value,
value
}
})
.reverse()
return [...numberArray, {label: "Do not return", value: ""}]
}

const tableCellPadding = 2

return (
// without overflow visible, the dropdown for Quantity may not show fully
<TableContainer overflowX="visible" overflowY="visible" w="100%" maxW="container.xl">
<Table variant={{base: "unstyled", sm: "simple"}}>
<Hide below="md">
<Thead>
<Tr>
<Td>Lineitem</Td>
<Td>Quantity</Td>
<Td>Refund Amount</Td>
<Td>Comments</Td>
</Tr>
</Thead>
</Hide>
<Tbody>
{lineItems.map((lineItem, index) => (
<Tr key={lineItem.ID} as={isMobile && VStack} alignItems={{base: "flex-start", sm: "initial"}}>
<Td padding={tableCellPadding}>
<Stack
direction={["row"]}
alignItems="center"
gap={4}
width={{base: "full", sm: "initial"}}
flex="1"
w="max-content"
>
<ProductThumbnail imageProps={{boxSize: {base: 75, sm: 50}}} product={lineItem.Product} />
<VStack justifyContent="space-between">
<Text fontSize={{base: "md", sm: "sm"}}>{lineItem.Product.Name}</Text>
<Text fontSize="xs" color="gray.400">
SKU: {lineItem.Product.ID}
</Text>
</VStack>
</Stack>
</Td>
<Td padding={tableCellPadding} w={{base: "100%", sm: "initial"}}>
<FormControl justifyContent="space-between">
<Hide above="sm">
<FormLabel fontSize="sm">Quantity</FormLabel>
</Hide>
<SelectControl
control={control}
name={`ItemsToReturn.${index}.Quantity`}
selectProps={{
options: buildQuantityOptions(lineItem)
}}
/>
</FormControl>
</Td>
<Td padding={tableCellPadding} w={{base: "100%", sm: "initial"}}>
<FormControl justifyContent="space-between">
<Hide above="sm">
<FormLabel fontSize="sm">Refund Amount</FormLabel>
</Hide>
<InputControl
name={`ItemsToReturn.${index}.RefundAmount`}
control={control}
inputProps={{type: "number"}}
leftAddon="$"
/>
</FormControl>
</Td>
<Td padding={tableCellPadding} w={{base: "100%", sm: "initial"}}>
<FormControl>
<Hide above="sm">
<FormLabel fontSize="sm">Comments</FormLabel>
</Hide>
<InputControl name={`ItemsToReturn.${index}.Comments`} control={control} />
</FormControl>
<Hide above="sm">
<Divider mt={8} />
</Hide>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
)
}
Loading

0 comments on commit 4f84bac

Please sign in to comment.