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
2023-03-24 15:11:40 +01:00
committed by GitHub
parent 332a9b686b
commit 284578a67a
5 changed files with 68 additions and 37 deletions

View File

@@ -1,23 +1,25 @@
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa"
import { Controller, useFieldArray } from "react-hook-form"
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/types"
import clsx from "clsx"
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 BuildingsIcon from "../../../../fundamentals/icons/buildings-icon"
import Button from "../../../../fundamentals/button"
import FeatureToggle from "../../../../fundamentals/feature-toggle"
import IconBadge from "../../../../fundamentals/icon-badge"
import BuildingsIcon from "../../../../fundamentals/icons/buildings-icon"
import InputField from "../../../../molecules/input"
import { LayeredModalContext } from "../../../../molecules/modal/layered-modal"
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 = {
manage_inventory: boolean
manage_inventory?: boolean
allow_backorder: boolean
inventory_quantity: number | null
sku: string | null

View File

@@ -37,6 +37,13 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
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 manageInventory = watch(path("manage_inventory"))
@@ -50,6 +57,12 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
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: {
added: string[]
removed: string[]
@@ -142,10 +155,8 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
<div className="">In Stock</div>
</div>
{selectedLocations.map((level, i) => {
console.log(level)
const locationDetails = locations.find(
(l: StockLocationDTO) => l.id === level.location_id
)
const locationDetails = locationsMap.get(level.location_id)
const locationLevel = levelMap.get(level.location_id)
return (
<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="mr-base text-small text-grey-50 flex flex-col">
<span className="whitespace-nowrap text-right">
{`${level.reserved_quantity} reserved`}
{`${locationLevel!.reserved_quantity} reserved`}
</span>
<span className="whitespace-nowrap text-right">{`${
level.stocked_quantity - level.reserved_quantity
locationLevel!.stocked_quantity! -
locationLevel!.reserved_quantity!
} available`}</span>
</div>
<InputField
placeholder={"0"}
type="number"
min={0}
{...register(
path(`location_levels.${i}.stocked_quantity`),
{ valueAsNumber: true }
@@ -191,7 +204,7 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
ManageLocationsScreen(
layeredModalContext.pop,
selectedLocations,
locations,
locations || [],
handleUpdateLocations
)
)

View File

@@ -8,7 +8,7 @@ import useNotification from "../../hooks/use-notification"
type DeletePromptProps = {
heading?: string
text?: string
successText?: string
successText?: string | false
cancelText?: string
confirmText?: string
handleClose: () => void

View File

@@ -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, {
EditFlowVariantFormType,
} from "../../forms/product/variant-form/edit-flow-variant-form"
import Button from "../../fundamentals/button"
import Modal from "../../molecules/modal"
import LayeredModal, {
LayeredModalContext,
} 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 { 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 = {
onClose: () => void
@@ -46,14 +48,15 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => {
const { onUpdateVariant, updatingVariant } = useEditProductActions(product.id)
const onSubmit = async (data: EditFlowVariantFormType) => {
const locationLevels = data.stock.location_levels || []
const locationLevels = data.stock.stock_location || []
const manageInventory = data.stock.manage_inventory
delete data.stock.manage_inventory
delete data.stock.location_levels
delete data.stock.stock_location
let inventoryItemId: string | undefined = itemId
const upsertPayload = removeNullish(data.stock)
let shouldInvalidateCache = false
if (variantInventoryItem) {
// 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) {
// has an inventory item but no longer wants to manage inventory
await client.admin.inventoryItems.delete(itemId!)
inventoryItemId = undefined
shouldInvalidateCache = true
} else {
// has an inventory item and wants to update inventory
await client.admin.inventoryItems.update(itemId!, upsertPayload)
@@ -122,11 +129,17 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => {
}
})
)
if (locationLevels.length) {
shouldInvalidateCache = true
}
}
// @ts-ignore
onUpdateVariant(variant.id, createUpdatePayload(data), () => {
refetch()
if (shouldInvalidateCache) {
queryClient.invalidateQueries(adminInventoryItemsKeys.lists())
}
handleClose()
})
}

View File

@@ -47,7 +47,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
})
const {
handleSubmit,
formState: { isDirty, isValid },
formState: { isDirty },
} = form
const {
@@ -63,7 +63,10 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
const { mutateAsync: associateSalesChannel } =
useAdminAddLocationToSalesChannel()
const createSalesChannelAssociationPromise = (salesChannelId, locationId) =>
const createSalesChannelAssociationPromise = (
salesChannelId: string,
locationId: string
) =>
associateSalesChannel({
sales_channel_id: salesChannelId,
location_id: locationId,
@@ -126,7 +129,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
heading="Are you sure you want to cancel with unsaved changes"
confirmText="Yes, cancel"
cancelText="No, continue creating"
successText={undefined}
successText={false}
handleClose={closeClosePrompt}
onDelete={async () => onClose()}
/>
@@ -136,7 +139,7 @@ const NewLocation = ({ onClose }: { onClose: () => void }) => {
size="small"
variant="primary"
type="button"
disabled={!isDirty || !isValid}
disabled={!isDirty}
onClick={onSubmit()}
>
Add location