Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(admin-ui,medusa): Reservations management #4081

Merged
merged 43 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8dcd47d
add location filtering to list-location levels
pKorsholm May 10, 2023
53a2040
cleanup
pKorsholm May 10, 2023
3c99ec5
add location filtering to list-location levels
pKorsholm May 10, 2023
9852ac0
cleanup
pKorsholm May 10, 2023
2065495
Initial work on route,table,new reservation form
StephixOne May 10, 2023
19a6d35
generated types
pKorsholm May 10, 2023
ae16a8f
add block
pKorsholm May 10, 2023
bc01a04
udpate clients
pKorsholm May 10, 2023
f895d30
initial create reservation
pKorsholm May 10, 2023
be797c3
update actionables for reservation table
pKorsholm May 11, 2023
9bd46f7
update edit-allocation modal
pKorsholm May 11, 2023
48a1f71
misc naming updates
pKorsholm May 11, 2023
f6ee0eb
update reservations table
pKorsholm May 11, 2023
b2c3092
add expand capabilities for list-reservations
pKorsholm May 11, 2023
e64639d
expand fields and show columns
pKorsholm May 12, 2023
2bd952b
update oas
pKorsholm May 12, 2023
831ec0d
make remove item work in focus modal
pKorsholm May 12, 2023
203ad67
add yarn lock
pKorsholm May 12, 2023
a74c51e
add integration test
pKorsholm May 12, 2023
7d7c921
Fix display when label doesn't match search term
StephixOne May 15, 2023
3382349
remove unused file
pKorsholm May 16, 2023
737d236
Update packages/admin-ui/ui/src/components/templates/reservations-tab…
pKorsholm May 17, 2023
86db18c
Update packages/admin-ui/ui/src/domain/orders/details/allocations/edi…
pKorsholm May 17, 2023
8c5c962
Update packages/admin-ui/ui/src/components/templates/reservations-tab…
pKorsholm May 17, 2023
751eea2
initial changes
pKorsholm May 17, 2023
2a23d5b
add changeset
pKorsholm May 17, 2023
738fbd8
update font size
pKorsholm May 17, 2023
43ca9ae
cleanup reservations table + select
pKorsholm May 17, 2023
15f5bdd
add decorated inventory item type
pKorsholm May 17, 2023
d480350
use type
pKorsholm May 17, 2023
79b56ea
feedback changes
pKorsholm May 19, 2023
9644ffe
Update packages/admin-ui/ui/src/components/molecules/item-search/inde…
pKorsholm May 22, 2023
fc4100b
decorate response for list inventory item to include total quantities
pKorsholm May 22, 2023
46d2f3e
Merge branch 'develop' into feat/reservations-management
olivermrbl May 22, 2023
850b668
update decorated properties
pKorsholm May 22, 2023
ea0bf75
decorate type
pKorsholm May 22, 2023
e9fbea2
Merge branch 'develop' into feat/reservations-management
pKorsholm May 22, 2023
1f86076
adrien feedback
pKorsholm May 22, 2023
24a3dd9
Update packages/generated/client-types/src/lib/models/DecoratedInvent…
pKorsholm May 22, 2023
a628cde
update join-utils
pKorsholm May 22, 2023
c7be80c
fix caching
pKorsholm May 22, 2023
1005370
Merge branch 'develop' into feat/reservations-management
pKorsholm May 22, 2023
c50594c
Merge branch 'develop' into feat/reservations-management
pKorsholm May 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/tall-dolls-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@medusajs/client-types": patch
"medusa-react": patch
"@medusajs/medusa-js": patch
"@medusajs/admin-ui": patch
"@medusajs/medusa": patch
---

feat(medusa,client-types,medusa-js,admin-ui,medusa-react): add reservation table and creation
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,8 @@ describe("Inventory Items endpoints", () => {
available_quantity: 5,
}),
]),
reserved_quantity: 0,
stocked_quantity: 15,
})
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,5 +203,27 @@ describe("Inventory Items endpoints", () => {
"The reservation quantity cannot be greater than the unfulfilled line item quantity",
})
})

it("lists reservations with inventory_items and line items", async () => {
const api = useApi()

const res = await api.get(
`/admin/reservations?expand=line_item,inventory_item`,
adminHeaders
)

expect(res.status).toEqual(200)
expect(res.data.reservations.length).toEqual(1)
expect(res.data.reservations).toEqual(
expect.arrayContaining([
expect.objectContaining({
inventory_item: expect.objectContaining({}),
line_item: expect.objectContaining({
order: expect.objectContaining({}),
}),
}),
])
)
})
})
})
1 change: 1 addition & 0 deletions packages/admin-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-popover": "^1.0.3",
"@radix-ui/react-portal": "^1.0.2",
"@radix-ui/react-radio-group": "^1.1.1",
"@radix-ui/react-select": "^1.2.0",
"@radix-ui/react-switch": "^1.0.1",
Expand Down
110 changes: 110 additions & 0 deletions packages/admin-ui/ui/src/components/molecules/item-search/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
AdminGetInventoryItemsParams,
DecoratedInventoryItemDTO,
} from "@medusajs/medusa"
import { ControlProps, OptionProps, SingleValue } from "react-select"

import Control from "../select/next-select/components/control"
import { NextSelect } from "../select/next-select"
import SearchIcon from "../../fundamentals/icons/search-icon"
import { useAdminInventoryItems } from "medusa-react"
import { useDebounce } from "../../../hooks/use-debounce"
import { useState } from "react"

type Props = {
onItemSelect: (item: itemType) => void
clearOnSelect?: boolean
filters?: AdminGetInventoryItemsParams
}

type ItemOption = {
label: string | undefined
value: string | undefined
inventoryItem: DecoratedInventoryItemDTO
}

const ItemSearch = ({ onItemSelect, clearOnSelect, filters = {} }: Props) => {
const [itemSearchTerm, setItemSearchTerm] = useState<string | undefined>()

const debouncedItemSearchTerm = useDebounce(itemSearchTerm, 500)

const queryEnabled = !!debouncedItemSearchTerm?.length

const { isLoading, inventory_items } = useAdminInventoryItems(
{
q: debouncedItemSearchTerm,
...filters,
},
{ enabled: queryEnabled }
)

const onChange = (item: SingleValue<ItemOption>) => {
if (item) {
onItemSelect(item.inventoryItem)
}
}

const options = inventory_items?.map(
(inventoryItem: DecoratedInventoryItemDTO) => ({
label:
inventoryItem.title ||
inventoryItem.variants?.[0]?.product?.title ||
inventoryItem.sku,
value: inventoryItem.id,
inventoryItem,
})
) as ItemOption[]

const filterOptions = () => true

return (
<div>
<NextSelect
isMulti={false}
components={{ Option: ProductOption, Control: SearchControl }}
onInputChange={setItemSearchTerm}
options={options}
placeholder="Choose an item"
isSearchable={true}
noOptionsMessage={() => "No items found"}
openMenuOnClick={!!inventory_items?.length}
onChange={onChange}
value={null}
isLoading={queryEnabled && isLoading}
filterOption={filterOptions} // TODO: Remove this when we can q for inventory item titles
/>
</div>
)
}

const ProductOption = ({ innerProps, data }: OptionProps<ItemOption>) => {
return (
<div
{...innerProps}
className="text-small grid w-full cursor-pointer grid-cols-2 place-content-between px-4 py-2 transition-all hover:bg-gray-50"
>
<div>
<p>{data.label}</p>
<p className="text-grey-50">{data.inventoryItem.sku}</p>
</div>
<div className="text-right">
<p className="text-grey-50">{`${data.inventoryItem.stocked_quantity} stock`}</p>
<p className="text-grey-50">{`${
data.inventoryItem.stocked_quantity -
data.inventoryItem.reserved_quantity
} available`}</p>
</div>
</div>
)
}

const SearchControl = ({ children, ...props }: ControlProps<ItemOption>) => (
<Control {...props}>
<span className="mr-4">
<SearchIcon size={16} className="text-grey-50" />
</span>
{children}
</Control>
)

export default ItemSearch
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useEffect, useMemo } from "react"
import { useAdminStockLocations } from "medusa-react"
import { NextSelect } from "../select/next-select"

const LocationDropdown = ({
selectedLocation,
onChange,
}: {
selectedLocation?: string
onChange: (id: string) => void
}) => {
const { stock_locations: locations, isLoading } = useAdminStockLocations()

useEffect(() => {
if (!selectedLocation && !isLoading && locations?.length) {
onChange(locations[0].id)
}
}, [isLoading, locations, onChange, selectedLocation])

const selectedLocObj = useMemo(() => {
if (!isLoading && locations) {
return locations.find((l) => l.id === selectedLocation) ?? locations[0]
}
}, [selectedLocation, locations, isLoading])

if (isLoading || !locations || !selectedLocObj) {
return null
}

return (
<NextSelect
isMulti={false}
onChange={(loc) => {
onChange(loc!.value)
}}
options={locations.map((l) => ({
label: l.name,
value: l.id,
}))}
value={{ value: selectedLocObj.id, label: selectedLocObj.name }}
/>
)
}

export default LocationDropdown
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PropsWithChildren } from "react"
import { AnimatePresence, motion } from "framer-motion"
import * as Portal from "@radix-ui/react-portal"

const MODAL_WIDTH = 560

Expand All @@ -14,46 +15,48 @@ type SideModalProps = PropsWithChildren<{
function SideModal(props: SideModalProps) {
const { isVisible, children, close } = props
return (
<AnimatePresence>
{isVisible && (
<>
<motion.div
onClick={close}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ ease: "easeInOut" }}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
zIndex: 99,
background: "rgba(0,0,0,.3)",
}}
></motion.div>
<motion.div
transition={{ ease: "easeInOut" }}
initial={{ right: -MODAL_WIDTH }}
style={{
position: "fixed",
height: "100%",
width: MODAL_WIDTH,
background: "white",
right: 0,
top: 0,
zIndex: 200,
}}
className="overflow-hidden rounded border"
animate={{ right: 0 }}
exit={{ right: -MODAL_WIDTH }}
>
{children}
</motion.div>
</>
)}
</AnimatePresence>
<Portal.Root>
<AnimatePresence>
{isVisible && (
<>
<motion.div
onClick={close}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ ease: "easeInOut" }}
style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
zIndex: 99,
background: "rgba(0,0,0,.3)",
}}
></motion.div>
<motion.div
transition={{ ease: "easeInOut" }}
initial={{ right: -MODAL_WIDTH }}
style={{
position: "fixed",
height: "100%",
width: MODAL_WIDTH,
background: "white",
right: 0,
top: 0,
zIndex: 200,
}}
className="overflow-hidden rounded border"
animate={{ right: 0 }}
exit={{ right: -MODAL_WIDTH }}
>
{children}
</motion.div>
</>
)}
</AnimatePresence>
</Portal.Root>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import type { GroupBase, Props, SelectInstance } from "react-select"
import {
forwardRef,
MutableRefObject,
ReactElement,
RefAttributes,
forwardRef,
useContext,
useRef,
} from "react"
import type { GroupBase, Props, SelectInstance } from "react-select"
import ReactSelect from "react-select"
import { ModalContext } from "../../../modal"

import { AdjacentContainer } from "../components"
import { ModalContext } from "../../../modal"
import ReactSelect from "react-select"
import { useSelectProps } from "../use-select-props"

export type SelectComponent = <
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import isEqual from "lodash/isEqual"
import { useEffect, useState } from "react"
import { ActionMeta, GroupBase, OnChangeValue, Props } from "react-select"
import Components from "./components"
import BaseComponents from "./components"
import { formatOptionLabel, hasLabel } from "./utils"

export const useSelectProps = <
Expand Down Expand Up @@ -64,7 +64,7 @@ export const useSelectProps = <

return {
label,
components: Components,
components: { ...BaseComponents, ...components },
styles: {
menuPortal: (base) => ({ ...base, zIndex: 60 }),
...styles,
Expand Down
Loading