diff --git a/.changeset/wise-colts-pretend.md b/.changeset/wise-colts-pretend.md new file mode 100644 index 0000000000..4e2cd4bf80 --- /dev/null +++ b/.changeset/wise-colts-pretend.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": minor +--- + +feat(admin-ui): move customs and shipping into manage inventory modal diff --git a/packages/admin-ui/ui/src/components/forms/product/variant-form/edit-flow-variant-form/index.tsx b/packages/admin-ui/ui/src/components/forms/product/variant-form/edit-flow-variant-form/index.tsx index 8aa506a148..cf609747e9 100644 --- a/packages/admin-ui/ui/src/components/forms/product/variant-form/edit-flow-variant-form/index.tsx +++ b/packages/admin-ui/ui/src/components/forms/product/variant-form/edit-flow-variant-form/index.tsx @@ -2,7 +2,7 @@ import { useFieldArray, UseFormReturn } from "react-hook-form" import CustomsForm, { CustomsFormType } from "../../customs-form" import DimensionsForm, { DimensionsFormType } from "../../dimensions-form" import VariantGeneralForm, { - VariantGeneralFormType + VariantGeneralFormType, } from "../variant-general-form" import VariantStockForm, { VariantStockFormType } from "../variant-stock-form" @@ -115,13 +115,15 @@ const EditFlowVariantForm = ({ form, isEdit }: Props) => {

-
-

Customs

-

- Configure if you are shipping internationally. -

- -
+ {showStockAndInventory && ( +
+

Customs

+

+ Configure if you are shipping internationally. +

+ +
+ )} ) diff --git a/packages/admin-ui/ui/src/components/forms/product/variant-inventory-form/edit-flow-variant-form/index.tsx b/packages/admin-ui/ui/src/components/forms/product/variant-inventory-form/edit-flow-variant-form/index.tsx index 2d7b4fe185..6b7ab49c75 100644 --- a/packages/admin-ui/ui/src/components/forms/product/variant-inventory-form/edit-flow-variant-form/index.tsx +++ b/packages/admin-ui/ui/src/components/forms/product/variant-inventory-form/edit-flow-variant-form/index.tsx @@ -1,11 +1,8 @@ -import { InventoryLevelDTO } from "@medusajs/medusa" +import { InventoryLevelDTO } from "@medusajs/types" import { UseFormReturn } from "react-hook-form" -import { nestedForm } from "../../../../../utils/nested-form" import VariantStockForm, { VariantStockFormType } from "../variant-stock-form" -export type EditFlowVariantFormType = { - stock: VariantStockFormType -} +export type EditFlowVariantFormType = VariantStockFormType type Props = { form: UseFormReturn @@ -39,10 +36,7 @@ const EditFlowVariantForm = ({ form, isLoading, locationLevels }: Props) => { return ( <> - + ) } diff --git a/packages/admin-ui/ui/src/components/forms/product/variant-inventory-form/variant-stock-form/index.tsx b/packages/admin-ui/ui/src/components/forms/product/variant-inventory-form/variant-stock-form/index.tsx index 66fd5da831..3ef8a712ef 100644 --- a/packages/admin-ui/ui/src/components/forms/product/variant-inventory-form/variant-stock-form/index.tsx +++ b/packages/admin-ui/ui/src/components/forms/product/variant-inventory-form/variant-stock-form/index.tsx @@ -1,15 +1,19 @@ -import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa" -import { useAdminStockLocations } from "medusa-react" +import { Controller, useFieldArray, UseFormReturn } from "react-hook-form" +import { nestedForm } from "../../../../../utils/nested-form" import React, { useMemo, useState } from "react" -import { Controller, useFieldArray } from "react-hook-form" -import { NestedForm } from "../../../../../utils/nested-form" -import Switch from "../../../../atoms/switch" -import Button from "../../../../fundamentals/button" -import IconBadge from "../../../../fundamentals/icon-badge" + +import Accordion from "../../../../organisms/accordion" import BuildingsIcon from "../../../../fundamentals/icons/buildings-icon" +import Button from "../../../../fundamentals/button" +import CustomsForm, { CustomsFormType } from "../../customs-form" +import DimensionsForm, { DimensionsFormType } from "../../dimensions-form" +import IconBadge from "../../../../fundamentals/icon-badge" import InputField from "../../../../molecules/input" import Modal from "../../../../molecules/modal" +import Switch from "../../../../atoms/switch" +import { useAdminStockLocations } from "medusa-react" import { useLayeredModal } from "../../../../molecules/modal/layered-modal" +import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/types" export type VariantStockFormType = { manage_inventory?: boolean @@ -20,11 +24,13 @@ export type VariantStockFormType = { upc: string | null barcode: string | null location_levels?: Partial[] | null + dimensions: DimensionsFormType + customs: CustomsFormType } type Props = { locationLevels: InventoryLevelDTO[] - form: NestedForm + form: UseFormReturn } const VariantStockForm = ({ form, locationLevels }: Props) => { @@ -44,9 +50,9 @@ const VariantStockForm = ({ form, locationLevels }: Props) => { return new Map(locations.map((l) => [l.id, l])) }, [locations, isLoading]) - const { path, control, register, watch } = form + const { control, register, watch } = form - const manageInventory = watch(path("manage_inventory")) + const manageInventory = watch("manage_inventory") const { fields: selectedLocations, @@ -54,10 +60,10 @@ const VariantStockForm = ({ form, locationLevels }: Props) => { remove, } = useFieldArray({ control, - name: path("location_levels"), + name: "location_levels", }) - const selectedLocationLevels = watch(path("location_levels")) + const selectedLocationLevels = watch("location_levels") const levelMap = new Map( selectedLocationLevels?.map((l) => [l.location_id, l]) @@ -83,140 +89,171 @@ const VariantStockForm = ({ form, locationLevels }: Props) => { } return ( -
-
-
-

General

-
- - - - + + +
+
+
+ + + + +
-
-
-

Manage inventory

- { - return - }} - /> -
-

- When checked Medusa will regulate the inventory when orders and - returns are made. -

-
- {manageInventory && ( - <> -
-
-

- Allow backorders -

- { - return - }} - /> -
-

- When checked the product will be available for purchase despite - the product being sold out -

-
-
-

Quantity

- {!isLoading && locations && ( -
-
-
Location
-
In Stock
-
- {selectedLocations.map((level, i) => { - const locationDetails = locationsMap.get(level.location_id) - const locationLevel = levelMap.get(level.location_id) - - return ( -
-
- - - - {locationDetails?.name} -
-
-
- - {`${locationLevel!.reserved_quantity} reserved`} - - {`${ - locationLevel!.stocked_quantity! - - locationLevel!.reserved_quantity! - } available`} -
- -
-
- ) - })} -
- )} -
-
- + />
- - )} -
-
+

+ When checked Medusa will regulate the inventory when orders and + returns are made. +

+
+ {manageInventory && ( + <> +
+
+

+ Allow backorders +

+ { + return ( + + ) + }} + /> +
+

+ When checked the product will be available for purchase + despite the product being sold out +

+
+
+

Quantity

+ {!isLoading && locations && ( +
+
+
Location
+
In Stock
+
+ {selectedLocations.map((level, i) => { + const locationDetails = locationsMap.get( + level.location_id + ) + const locationLevel = levelMap.get(level.location_id) + + return ( +
+
+ + + + {locationDetails?.name} +
+
+
+ + {`${locationLevel!.reserved_quantity} reserved`} + + {`${ + locationLevel!.stocked_quantity! - + locationLevel!.reserved_quantity! + } available`} +
+ +
+
+ ) + })} +
+ )} +
+
+ +
+ + )} +
+ + +

+ Shipping information can be required depending on your shipping + provider, and whether or not you are shipping internationally. +

+
+

Dimensions

+

+ Configure to calculate the most accurate shipping rates. +

+ +
+
+

Customs

+

+ Configure if you are shipping internationally. +

+ +
+
+ ) } diff --git a/packages/admin-ui/ui/src/components/organisms/product-variants-section/edit-variant-inventory-modal.tsx b/packages/admin-ui/ui/src/components/organisms/product-variants-section/edit-variant-inventory-modal.tsx index 7bdb0919ef..254bbfe42d 100644 --- a/packages/admin-ui/ui/src/components/organisms/product-variants-section/edit-variant-inventory-modal.tsx +++ b/packages/admin-ui/ui/src/components/organisms/product-variants-section/edit-variant-inventory-modal.tsx @@ -14,12 +14,13 @@ import { 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" +import { countries } from "../../../utils/countries" +import { Option } from "../../../types/shared" type Props = { onClose: () => void @@ -44,19 +45,41 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => { const { onUpdateVariant, updatingVariant } = useEditProductActions(product.id) + const createUpdateInventoryItemPayload = ( + data: Partial + ) => { + const updateDimensions = data.dimensions || {} + const updateCustoms = data.customs || {} + const originCountry = data.customs?.origin_country?.value + + delete data.dimensions + delete data.customs + delete data.ean + delete data.barcode + delete data.upc + + return removeNullish({ + ...updateDimensions, + ...updateCustoms, + ...data, + ...(originCountry && { origin_country: originCountry }), + }) + } + const onSubmit = async (data: EditFlowVariantFormType) => { - const locationLevels = data.stock.location_levels || [] - const manageInventory = data.stock.manage_inventory + const locationLevels = data.location_levels || [] + const manageInventory = data.manage_inventory const variantInventoryItem = variantInventory?.inventory?.[0] const itemId = variantInventoryItem?.id - delete data.stock.manage_inventory - delete data.stock.location_levels + delete data.manage_inventory + delete data.location_levels let inventoryItemId: string | undefined = itemId - const upsertPayload = removeNullish(data.stock) + const { ean, barcode, upc } = data + const upsertPayload = createUpdateInventoryItemPayload(data) let shouldInvalidateCache = false if (variantInventoryItem) { @@ -135,14 +158,27 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => { } } + const { dimensions, customs, ...stock } = data + // @ts-ignore - onUpdateVariant(variant.id, createUpdatePayload(data), () => { - refetch() - if (shouldInvalidateCache) { - queryClient.invalidateQueries(adminInventoryItemsKeys.lists()) + onUpdateVariant( + variant.id, + removeNullish({ + ...dimensions, + ...customs, + ...stock, + ean, + barcode, + upc, + }), + () => { + refetch() + if (shouldInvalidateCache) { + queryClient.invalidateQueries(adminInventoryItemsKeys.lists()) + } + handleClose() } - handleClose() - }) + ) } return ( @@ -154,6 +190,7 @@ const EditVariantInventoryModal = ({ onClose, product, variant }: Props) => { void isLoadingInventory: boolean handleClose: () => void @@ -178,7 +217,7 @@ const StockForm = ({ }) => { const form = useForm({ // @ts-ignore - defaultValues: getEditVariantDefaultValues(variantInventory), + defaultValues: getEditVariantDefaultValues(variantInventory, variant), }) const { @@ -231,34 +270,68 @@ const StockForm = ({ } export const getEditVariantDefaultValues = ( - variantInventory?: any + variantInventory?: any, + variant?: ProductVariant ): EditFlowVariantFormType => { const inventoryItem = variantInventory?.inventory[0] if (!inventoryItem) { return { - stock: { - sku: null, - ean: null, - inventory_quantity: null, - manage_inventory: false, - allow_backorder: false, - barcode: null, - upc: null, - location_levels: null, + sku: null, + ean: variant?.ean || null, + barcode: variant?.barcode || null, + upc: variant?.upc || null, + inventory_quantity: null, + manage_inventory: false, + allow_backorder: false, + location_levels: null, + dimensions: { + height: null, + length: null, + width: null, + weight: null, + }, + customs: { + origin_country: null, + mid_code: null, + hs_code: null, }, } } + let originCountry: Option | null = null + if (inventoryItem.origin_country) { + const country = countries.find( + (c) => + c.alpha2 === inventoryItem.origin_country || + c.alpha3 === inventoryItem.origin_country + ) + if (country) { + originCountry = { + label: country?.name, + value: country?.alpha2, + } + } + } + return { - stock: { - sku: inventoryItem.sku, - ean: inventoryItem.ean, - inventory_quantity: inventoryItem.inventory_quantity, - manage_inventory: !!inventoryItem, - allow_backorder: inventoryItem.allow_backorder, - barcode: inventoryItem.barcode, - upc: inventoryItem.upc, - location_levels: inventoryItem.location_levels, + sku: inventoryItem.sku, + ean: variant?.ean || null, + barcode: variant?.barcode || null, + upc: variant?.upc || null, + inventory_quantity: inventoryItem.inventory_quantity, + manage_inventory: !!inventoryItem, + allow_backorder: inventoryItem.allow_backorder, + location_levels: inventoryItem.location_levels, + dimensions: { + height: inventoryItem.height, + length: inventoryItem.length, + width: inventoryItem.width, + weight: inventoryItem.weight, + }, + customs: { + origin_country: originCountry, + mid_code: inventoryItem.mid_code, + hs_code: inventoryItem.hs_code, }, } }