Fix(admin-ui): multi warehouse minor fixes (#3540)

**What**
1. Enable the "create location" button in "create stock location" when a field has changed
2. Remove the "successful delete" toast when cancelling stock location creation
3. Properly update available and reserved when editing stock levels for variant
4. invalidate inventoryItemList queryKeys when changing location levels

**Why**
- we had the same bug with form validation when creating location levels as we had when editing them (1)
- when updating location levels, listing inventory items wouldn't show the newly added location levels (4)
- fixing ui bugs (2, 4)
This commit is contained in:
Philip Korsholm authored and GitHub committed 2023-03-24 14:11:40 +00:00
1 parent 332a9b686b
commit 284578a67a
5 files changed
+68 -37

No files matched your search

@@ -1,23 +1,25 @@
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa"
import { Controller, useFieldArray } from "react-hook-form" import { Controller, useFieldArray } from "react-hook-form"
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/types"
import clsx from "clsx" import BuildingsIcon from "../../../../fundamentals/icons/buildings-icon"
import { sum } from "lodash"
import { useAdminStockLocations } from "medusa-react"
import React from "react"
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
import { NestedForm } from "../../../../../utils/nested-form"
import Switch from "../../../../atoms/switch"
import Button from "../../../../fundamentals/button" import Button from "../../../../fundamentals/button"
import FeatureToggle from "../../../../fundamentals/feature-toggle" import FeatureToggle from "../../../../fundamentals/feature-toggle"
import IconBadge from "../../../../fundamentals/icon-badge" import IconBadge from "../../../../fundamentals/icon-badge"
import BuildingsIcon from "../../../../fundamentals/icons/buildings-icon"
import InputField from "../../../../molecules/input" import InputField from "../../../../molecules/input"
import { LayeredModalContext } from "../../../../molecules/modal/layered-modal" import { LayeredModalContext } from "../../../../molecules/modal/layered-modal"
import { ManageLocationsScreen } from "../../variant-inventory-form/variant-stock-form" import { ManageLocationsScreen } from "../../variant-inventory-form/variant-stock-form"
import { NestedForm } from "../../../../../utils/nested-form"
import React from "react"
import Switch from "../../../../atoms/switch"
import clsx from "clsx"
import { sum } from "lodash"
import { useAdminStockLocations } from "medusa-react"
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
// import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa"
export type VariantStockFormType = { export type VariantStockFormType = {
manage_inventory: boolean manage_inventory?: boolean
allow_backorder: boolean allow_backorder: boolean
inventory_quantity: number | null inventory_quantity: number | null
sku: string | null sku: string | null
@@ -37,6 +37,13 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
const { stock_locations: locations, isLoading } = useAdminStockLocations() const { stock_locations: locations, isLoading } = useAdminStockLocations()
const locationsMap = useMemo(() => {
if (isLoading) {
return new Map()
}
return new Map(locations.map((l) => [l.id, l]))
}, [locations, isLoading])
const { path, control, register, watch } = form const { path, control, register, watch } = form
const manageInventory = watch(path("manage_inventory")) const manageInventory = watch(path("manage_inventory"))
@@ -50,6 +57,12 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
name: path("location_levels"), name: path("location_levels"),
}) })
const selectedLocationLevels = watch(path("location_levels"))
const levelMap = new Map(
selectedLocationLevels?.map((l) => [l.location_id, l])
)
const handleUpdateLocations = async (data: { const handleUpdateLocations = async (data: {
added: string[] added: string[]
removed: string[] removed: string[]
@@ -142,10 +155,8 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
<div className="">In Stock</div> <div className="">In Stock</div>
</div> </div>
{selectedLocations.map((level, i) => { {selectedLocations.map((level, i) => {
console.log(level) const locationDetails = locationsMap.get(level.location_id)
const locationDetails = locations.find( const locationLevel = levelMap.get(level.location_id)
(l: StockLocationDTO) => l.id === level.location_id
)
return ( return (
<div key={level.id} className="flex items-center py-3"> <div key={level.id} className="flex items-center py-3">
@@ -158,15 +169,17 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
<div className="ml-auto flex"> <div className="ml-auto flex">
<div className="mr-base text-small text-grey-50 flex flex-col"> <div className="mr-base text-small text-grey-50 flex flex-col">
<span className="whitespace-nowrap text-right"> <span className="whitespace-nowrap text-right">
{`${level.reserved_quantity} reserved`} {`${locationLevel!.reserved_quantity} reserved`}
</span> </span>
<span className="whitespace-nowrap text-right">{`${ <span className="whitespace-nowrap text-right">{`${
level.stocked_quantity - level.reserved_quantity locationLevel!.stocked_quantity! -
locationLevel!.reserved_quantity!
} available`}</span> } available`}</span>
</div> </div>
<InputField <InputField
placeholder={"0"} placeholder={"0"}
type="number" type="number"
min={0}
{...register( {...register(
path(`location_levels.${i}.stocked_quantity`), path(`location_levels.${i}.stocked_quantity`),
{ valueAsNumber: true } { valueAsNumber: true }
@@ -191,7 +204,7 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
ManageLocationsScreen( ManageLocationsScreen(
layeredModalContext.pop, layeredModalContext.pop,
selectedLocations, selectedLocations,
locations, locations || [],
handleUpdateLocations handleUpdateLocations
) )
) )
@@ -8,7 +8,7 @@ import useNotification from "../../hooks/use-notification"
type DeletePromptProps = { type DeletePromptProps = {
heading?: string heading?: string
text?: string text?: string
successText?: string successText?: string | false
cancelText?: string cancelText?: string
confirmText?: string confirmText?: string
handleClose: () => void handleClose: () => void
@@ -1,23 +1,25 @@
import {
InventoryLevelDTO,
Product,
ProductVariant,
VariantInventory,
} from "@medusajs/medusa"
import { useAdminVariantsInventory, useMedusa } from "medusa-react"
import { useContext } from "react"
import { useForm } from "react-hook-form"
import useEditProductActions from "../../../hooks/use-edit-product-actions"
import { removeNullish } from "../../../utils/remove-nullish"
import EditFlowVariantForm, { import EditFlowVariantForm, {
EditFlowVariantFormType, EditFlowVariantFormType,
} from "../../forms/product/variant-form/edit-flow-variant-form" } from "../../forms/product/variant-form/edit-flow-variant-form"
import Button from "../../fundamentals/button"
import Modal from "../../molecules/modal"
import LayeredModal, { import LayeredModal, {
LayeredModalContext, LayeredModalContext,
} from "../../molecules/modal/layered-modal" } from "../../molecules/modal/layered-modal"
import { Product, ProductVariant, VariantInventory } from "@medusajs/medusa"
import {
adminInventoryItemsKeys,
useAdminVariantsInventory,
useMedusa,
} from "medusa-react"
import Button from "../../fundamentals/button"
import { InventoryLevelDTO } from "@medusajs/types"
import Modal from "../../molecules/modal"
import { createUpdatePayload } from "./edit-variants-modal/edit-variant-screen" import { createUpdatePayload } from "./edit-variants-modal/edit-variant-screen"
import { queryClient } from "../../../constants/query-client"
import { removeNullish } from "../../../utils/remove-nullish"
import { useContext } from "react"
import useEditProductActions from "../../../hooks/use-edit-product-actions"
import { useForm } from "react-hook-form"
type Props = { type Props = {
onClose: () => void onClose: () => void
@@ -46,14 +48,15 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => {
const { onUpdateVariant, updatingVariant } = useEditProductActions(product.id) const { onUpdateVariant, updatingVariant } = useEditProductActions(product.id)
const onSubmit = async (data: EditFlowVariantFormType) => { const onSubmit = async (data: EditFlowVariantFormType) => {
const locationLevels = data.stock.location_levels || [] const locationLevels = data.stock.stock_location || []
const manageInventory = data.stock.manage_inventory const manageInventory = data.stock.manage_inventory
delete data.stock.manage_inventory delete data.stock.manage_inventory
delete data.stock.location_levels delete data.stock.stock_location
let inventoryItemId: string | undefined = itemId let inventoryItemId: string | undefined = itemId
const upsertPayload = removeNullish(data.stock) const upsertPayload = removeNullish(data.stock)
let shouldInvalidateCache = false
if (variantInventoryItem) { if (variantInventoryItem) {
// variant inventory exists and we can remove location levels // variant inventory exists and we can remove location levels
@@ -77,12 +80,16 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => {
) )
}) })
) )
if (deleteLocations.length) {
shouldInvalidateCache = true
}
} }
if (!manageInventory) { if (!manageInventory) {
// has an inventory item but no longer wants to manage inventory // has an inventory item but no longer wants to manage inventory
await client.admin.inventoryItems.delete(itemId!) await client.admin.inventoryItems.delete(itemId!)
inventoryItemId = undefined inventoryItemId = undefined
shouldInvalidateCache = true
} else { } else {
// has an inventory item and wants to update inventory // has an inventory item and wants to update inventory
await client.admin.inventoryItems.update(itemId!, upsertPayload) await client.admin.inventoryItems.update(itemId!, upsertPayload)
@@ -122,11 +129,17 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => {
} }
}) })
) )
if (locationLevels.length) {
shouldInvalidateCache = true
}
} }
// @ts-ignore // @ts-ignore
onUpdateVariant(variant.id, createUpdatePayload(data), () => { onUpdateVariant(variant.id, createUpdatePayload(data), () => {
refetch() refetch()
if (shouldInvalidateCache) {
queryClient.invalidateQueries(adminInventoryItemsKeys.lists())
}
handleClose() handleClose()
}) })
} }
@@ -47,7 +47,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
}) })
const { const {
handleSubmit, handleSubmit,
formState: { isDirty, isValid }, formState: { isDirty },
} = form } = form
const { const {
@@ -63,7 +63,10 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
const { mutateAsync: associateSalesChannel } = const { mutateAsync: associateSalesChannel } =
useAdminAddLocationToSalesChannel() useAdminAddLocationToSalesChannel()
const createSalesChannelAssociationPromise = (salesChannelId, locationId) => const createSalesChannelAssociationPromise = (
salesChannelId: string,
locationId: string
) =>
associateSalesChannel({ associateSalesChannel({
sales_channel_id: salesChannelId, sales_channel_id: salesChannelId,
location_id: locationId, location_id: locationId,
@@ -126,7 +129,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
heading="Are you sure you want to cancel with unsaved changes" heading="Are you sure you want to cancel with unsaved changes"
confirmText="Yes, cancel" confirmText="Yes, cancel"
cancelText="No, continue creating" cancelText="No, continue creating"
successText={undefined} successText={false}
handleClose={closeClosePrompt} handleClose={closeClosePrompt}
onDelete={async () => onClose()} onDelete={async () => onClose()}
/> />
@@ -136,7 +139,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
size="small" size="small"
variant="primary" variant="primary"
type="button" type="button"
disabled={!isDirty || !isValid} disabled={!isDirty}
onClick={onSubmit()} onClick={onSubmit()}
> >
Add location Add location