From ea2ee343f0f2bee347d0e2a3ece2eb3252564c67 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Thu, 26 Oct 2023 09:56:36 +0200 Subject: [PATCH] fix(admin-ui): PriceList bulk editor fixes (#5456) * fix: rounding on decimal numbers, wrap in form * add changeset * fix: decimal numbers in product details * fix: resolve an issue where double clicking an already selected cell would freeze it --- .changeset/afraid-melons-decide.md | 6 + packages/admin-ui/package.json | 2 +- .../edit-prices-modal/index.tsx | 51 +++--- .../ui/src/domain/pricing/edit/edit.tsx | 10 +- .../pricing/edit/prices/edit-prices-modal.tsx | 148 ++++++++++++------ .../forms/price-list-prices-form/helpers.ts | 8 +- .../price-list-product-prices-form.tsx | 27 +++- yarn.lock | 4 +- 8 files changed, 179 insertions(+), 77 deletions(-) create mode 100644 .changeset/afraid-melons-decide.md diff --git a/.changeset/afraid-melons-decide.md b/.changeset/afraid-melons-decide.md new file mode 100644 index 0000000000..5d0a8b9b9c --- /dev/null +++ b/.changeset/afraid-melons-decide.md @@ -0,0 +1,6 @@ +--- +"@medusajs/admin-ui": patch +"@medusajs/admin": patch +--- + +fix(admin-ui): PriceList bulk editor fixes diff --git a/packages/admin-ui/package.json b/packages/admin-ui/package.json index e0ba5bdf01..0cdcdac736 100644 --- a/packages/admin-ui/package.json +++ b/packages/admin-ui/package.json @@ -70,7 +70,7 @@ "i18next-browser-languagedetector": "^7.0.1", "i18next-http-backend": "^2.2.1", "md5": "^2.3.0", - "medusa-react": "^9.0.8", + "medusa-react": "^9.0.9", "mini-css-extract-plugin": "^2.7.6", "moment": "^2.29.4", "path-browserify": "^1.0.1", diff --git a/packages/admin-ui/ui/src/components/organisms/product-variants-section/edit-prices-modal/index.tsx b/packages/admin-ui/ui/src/components/organisms/product-variants-section/edit-prices-modal/index.tsx index 3ceb760e5f..1ce14aab7e 100644 --- a/packages/admin-ui/ui/src/components/organisms/product-variants-section/edit-prices-modal/index.tsx +++ b/packages/admin-ui/ui/src/components/organisms/product-variants-section/edit-prices-modal/index.tsx @@ -1,27 +1,27 @@ -import React, { useEffect, useMemo, useRef, useState } from "react" -import { useAdminRegions, useAdminUpdateVariant } from "medusa-react" import { MoneyAmount, Product } from "@medusajs/client-types" +import mapKeys from "lodash/mapKeys" import pick from "lodash/pick" import pickBy from "lodash/pickBy" -import mapKeys from "lodash/mapKeys" +import { useAdminRegions, useAdminUpdateVariant } from "medusa-react" +import { useEffect, useMemo, useRef, useState } from "react" import { currencies as CURRENCY_MAP } from "../../../../utils/currencies" -import Modal from "../../../molecules/modal" +import useNotification from "../../../../hooks/use-notification" import Fade from "../../../atoms/fade-wrapper" import Button from "../../../fundamentals/button" +import CrossIcon from "../../../fundamentals/icons/cross-icon" +import Modal from "../../../molecules/modal" +import DeletePrompt from "../../delete-prompt" +import EditPricesActions from "./edit-prices-actions" +import EditPricesTable from "./edit-prices-table" +import SavePrompt from "./save-prompt" import { getAllProductPricesCurrencies, getAllProductPricesRegions, getCurrencyPricesOnly, getRegionPricesOnly, } from "./utils" -import CrossIcon from "../../../fundamentals/icons/cross-icon" -import EditPricesTable from "./edit-prices-table" -import EditPricesActions from "./edit-prices-actions" -import useNotification from "../../../../hooks/use-notification" -import DeletePrompt from "../../delete-prompt" -import SavePrompt from "./save-prompt" type EditPricesModalProps = { close: () => void @@ -194,12 +194,15 @@ function EditPricesModal(props: EditPricesModalProps) { if (typeof regionPriceEdits[price.region_id] === "number") { const p = { ...price } - p.amount = + const num = regionPriceEdits[price.region_id]! * Math.pow( 10, CURRENCY_MAP[price.currency_code.toUpperCase()].decimal_digits ) + + p.amount = parseFloat(num.toFixed(0)) + pricesPayload.push(p) } else { // amount is unset -> DELETED case just skip @@ -217,12 +220,15 @@ function EditPricesModal(props: EditPricesModalProps) { if (typeof currencyPriceEdits[price.currency_code] === "number") { const p = { ...price } - p.amount = + const num = currencyPriceEdits[price.currency_code] * Math.pow( 10, CURRENCY_MAP[price.currency_code.toUpperCase()].decimal_digits ) + + p.amount = parseFloat(num.toFixed(0)) + pricesPayload.push(p) } else { // amount is unset -> DELETED case just skip @@ -237,10 +243,12 @@ function EditPricesModal(props: EditPricesModalProps) { Object.entries(currencyPriceEdits).forEach(([currency, amount]) => { if (typeof amount === "number") { - amount *= Math.pow( - 10, - CURRENCY_MAP[currency.toUpperCase()].decimal_digits - ) + const num = + amount * + Math.pow(10, CURRENCY_MAP[currency.toUpperCase()].decimal_digits) + + amount = parseFloat(num.toFixed(0)) + pricesPayload.push({ currency_code: currency, amount }) } }) @@ -248,10 +256,13 @@ function EditPricesModal(props: EditPricesModalProps) { Object.entries(regionPriceEdits).forEach(([region, amount]) => { if (typeof amount === "number") { const currency = regionCurrenciesMap[region] - amount *= Math.pow( - 10, - CURRENCY_MAP[currency.toUpperCase()].decimal_digits - ) + + const num = + amount * + Math.pow(10, CURRENCY_MAP[currency.toUpperCase()].decimal_digits) + + amount = parseFloat(num.toFixed(0)) + pricesPayload.push({ region_id: region, amount }) } }) diff --git a/packages/admin-ui/ui/src/domain/pricing/edit/edit.tsx b/packages/admin-ui/ui/src/domain/pricing/edit/edit.tsx index c7393a6ac8..c3ac16b1b2 100644 --- a/packages/admin-ui/ui/src/domain/pricing/edit/edit.tsx +++ b/packages/admin-ui/ui/src/domain/pricing/edit/edit.tsx @@ -58,8 +58,14 @@ const PriceListEdit = () => { /> ) })} - - + + {getWidgets("price_list.details.after").map((w, i) => { return ( (Tab.PRICES) const [product, setProduct] = React.useState(null) + const originalPrices = React.useRef(null) + const { t } = useTranslation() const promptTitle = t( @@ -106,6 +109,15 @@ const EditPricesModal = ({ const { mutateAsync: deleteAsync, isLoading: isDeleting } = useAdminDeletePriceListPrices(priceList.id) + const onCloseModal = React.useCallback(() => { + onOpenChange(false) + setTab(Tab.PRICES) + + originalPrices.current = null + resetProduct() + resetPrices() + }, [onOpenChange, resetPrices, resetProduct]) + const onModalStateChange = React.useCallback( async (open: boolean) => { if (open) { @@ -124,9 +136,10 @@ const EditPricesModal = ({ } } - onOpenChange(false) + onCloseModal() }, [ + onCloseModal, prompt, onOpenChange, isPricesDirty, @@ -196,6 +209,7 @@ const EditPricesModal = ({ productData.products[product.id!] = productPrices } + originalPrices.current = productData resetPrices(productData) }, [ isLoading, @@ -325,6 +339,25 @@ const EditPricesModal = ({ continue } + const originalPrice = + originalPrices.current?.products[productId]?.variants[variantId] + .currency?.[currencyCode] + + if (originalPrice && originalPrice.id && originalPrice.amount) { + const originalDbSafeAmount = getDbSafeAmount( + currencyCode, + parseFloat(originalPrice.amount) + ) + + /** + * If the price is the same as the original price, + * we don't want to update it. + */ + if (originalDbSafeAmount === dbSafeAmount) { + continue + } + } + const payload: PricePayload = { id: id ? id : undefined, amount: dbSafeAmount, @@ -352,8 +385,12 @@ const EditPricesModal = ({ continue } + const currencyCode = regions.find( + (r) => r.id === regionId + )!.currency_code + const dbSafeAmount = getDbSafeAmount( - regions.find((r) => r.id === regionId)!.currency_code, + currencyCode, parseFloat(amount) ) @@ -361,6 +398,25 @@ const EditPricesModal = ({ continue } + const originalPrice = + originalPrices.current?.products[productId]?.variants[variantId] + .region?.[regionId] + + if (originalPrice && originalPrice.id && originalPrice.amount) { + const originalDbSafeAmount = getDbSafeAmount( + currencyCode, + parseFloat(originalPrice.amount) + ) + + /** + * If the price is the same as the original price, + * we don't want to update it. + */ + if (originalDbSafeAmount === dbSafeAmount) { + continue + } + } + const payload: PricePayload = { id: id ? id : undefined, amount: dbSafeAmount, @@ -454,7 +510,7 @@ const EditPricesModal = ({ ) } - onOpenChange(false) + onCloseModal() }) /** @@ -561,54 +617,56 @@ const EditPricesModal = ({ - {isLoading ? ( -
- -
- ) : isError || isNotFound ? ( -
-
- - - {t( - "price-list-edit-prices-modal-error-loading", - "An error occurred while preparing the form. Reload the page and try again. If the issue persists, try again later." - )} - +
+ {isLoading ? ( +
+
-
- ) : ( - - - - - {product && ( + ) : isError || isNotFound ? ( +
+
+ + + {t( + "price-list-edit-prices-modal-error-loading", + "An error occurred while preparing the form. Reload the page and try again. If the issue persists, try again later." + )} + +
+
+ ) : ( + - - )} - - )} + {product && ( + + + + )} +
+ )} + diff --git a/packages/admin-ui/ui/src/domain/pricing/forms/price-list-prices-form/helpers.ts b/packages/admin-ui/ui/src/domain/pricing/forms/price-list-prices-form/helpers.ts index e198eb3210..0aa6fc8cdb 100644 --- a/packages/admin-ui/ui/src/domain/pricing/forms/price-list-prices-form/helpers.ts +++ b/packages/admin-ui/ui/src/domain/pricing/forms/price-list-prices-form/helpers.ts @@ -20,7 +20,9 @@ export const getDefaultAmount = (code: string, amount?: number) => { return null } - return amount / Math.pow(10, meta.decimal_digits) + const num = amount / Math.pow(10, meta.decimal_digits) + + return parseFloat(num.toFixed(meta.decimal_digits)) } export const getDbSafeAmount = (code: string, amount?: number) => { @@ -38,5 +40,7 @@ export const getDbSafeAmount = (code: string, amount?: number) => { return null } - return amount * Math.pow(10, meta.decimal_digits) + const num = amount * Math.pow(10, meta.decimal_digits) + + return parseFloat(num.toFixed(0)) } diff --git a/packages/admin-ui/ui/src/domain/pricing/forms/price-list-product-prices-form/price-list-product-prices-form.tsx b/packages/admin-ui/ui/src/domain/pricing/forms/price-list-product-prices-form/price-list-product-prices-form.tsx index cce5be535f..5066e98b3e 100644 --- a/packages/admin-ui/ui/src/domain/pricing/forms/price-list-product-prices-form/price-list-product-prices-form.tsx +++ b/packages/admin-ui/ui/src/domain/pricing/forms/price-list-product-prices-form/price-list-product-prices-form.tsx @@ -1852,6 +1852,15 @@ const Cell = React.forwardRef( } }, [isAnchor, onBlur, setIsEditing]) + const onFocusInput = React.useCallback(() => { + inputRef.current?.focus() + + inputRef.current?.setSelectionRange( + inputRef.current.value.length, + inputRef.current.value.length + ) + }, []) + const onMouseDown = React.useCallback( (e: React.MouseEvent) => { e.stopPropagation() @@ -1875,12 +1884,16 @@ const Cell = React.forwardRef( if (e.detail === 2) { setIsActive(true) setIsEditing(true) + + onFocusInput() + + return } } onCellMouseDown(e) }, - [onCellMouseDown, setIsEditing, isAnchor, isActive] + [onCellMouseDown, setIsEditing, onFocusInput, isAnchor, isActive] ) const onEnter = React.useCallback( @@ -1897,9 +1910,9 @@ const Cell = React.forwardRef( setIsActive(true) setIsEditing(true) - inputRef.current?.focus() + onFocusInput() }, - [isAnchor, isActive, onNextRow, setIsEditing] + [isAnchor, isActive, onNextRow, setIsEditing, onFocusInput] ) const onSpace = React.useCallback( @@ -2006,8 +2019,12 @@ const Cell = React.forwardRef( inputRef.current?.focus() setIsActive(false) + + onBlur() + + return }, - [isActive] + [isActive, onBlur] ) const onKeydown = React.useCallback( @@ -2045,7 +2062,7 @@ const Cell = React.forwardRef( const onActiveAwareBlur = React.useCallback(() => { if (isActive) { - return + setIsActive(false) } onBlur() diff --git a/yarn.lock b/yarn.lock index 4c7f9fab3f..d2a62bede9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6311,7 +6311,7 @@ __metadata: i18next-parser: ^8.0.0 jest: 25.5.4 md5: ^2.3.0 - medusa-react: ^9.0.8 + medusa-react: ^9.0.9 mini-css-extract-plugin: ^2.7.6 moment: ^2.29.4 path-browserify: ^1.0.1 @@ -32499,7 +32499,7 @@ __metadata: languageName: unknown linkType: soft -"medusa-react@^9.0.6, medusa-react@^9.0.8, medusa-react@workspace:packages/medusa-react": +"medusa-react@^9.0.6, medusa-react@^9.0.9, medusa-react@workspace:packages/medusa-react": version: 0.0.0-use.local resolution: "medusa-react@workspace:packages/medusa-react" dependencies: