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
This commit is contained in:
Kasper Fabricius Kristensen
2023-10-26 09:56:36 +02:00
committed by GitHub
parent ebba93e03d
commit ea2ee343f0
8 changed files with 179 additions and 77 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/admin-ui": patch
"@medusajs/admin": patch
---
fix(admin-ui): PriceList bulk editor fixes

View File

@@ -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",

View File

@@ -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 })
}
})

View File

@@ -58,8 +58,14 @@ const PriceListEdit = () => {
/>
)
})}
<PriceListGeneralSection priceList={price_list} />
<PriceListPricesSection priceList={price_list} />
<PriceListGeneralSection
key={`${price_list.updated_at}`}
priceList={price_list}
/>
<PriceListPricesSection
key={`${price_list.updated_at}`}
priceList={price_list}
/>
{getWidgets("price_list.details.after").map((w, i) => {
return (
<WidgetContainer

View File

@@ -10,6 +10,7 @@ import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { useTranslation } from "react-i18next"
import { Form } from "../../../../components/helpers/form"
import useNotification from "../../../../hooks/use-notification"
import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
import { getErrorMessage } from "../../../../utils/error-messages"
@@ -50,6 +51,8 @@ const EditPricesModal = ({
const [tab, setTab] = React.useState<Tab>(Tab.PRICES)
const [product, setProduct] = React.useState<Product | null>(null)
const originalPrices = React.useRef<PriceListPricesSchema | null>(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 = ({
</div>
</FocusModal.Header>
<FocusModal.Body className="h-full w-full overflow-y-auto">
{isLoading ? (
<div className="flex h-full w-full items-center justify-center">
<Spinner className="text-ui-fg-subtle animate-spin" />
</div>
) : isError || isNotFound ? (
<div className="flex h-full w-full items-center justify-center">
<div className="text-ui-fg-subtle flex items-center gap-x-2">
<ExclamationCircle />
<Text>
{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."
)}
</Text>
<Form {...pricesForm}>
{isLoading ? (
<div className="flex h-full w-full items-center justify-center">
<Spinner className="text-ui-fg-subtle animate-spin" />
</div>
</div>
) : (
<React.Fragment>
<ProgressTabs.Content
value={Tab.PRICES}
className="h-full w-full"
>
<PriceListPricesForm
setProduct={onSetProduct}
form={nestedForm(pricesForm)}
priceListId={priceList.id}
productIds={productIds}
/>
</ProgressTabs.Content>
{product && (
) : isError || isNotFound ? (
<div className="flex h-full w-full items-center justify-center">
<div className="text-ui-fg-subtle flex items-center gap-x-2">
<ExclamationCircle />
<Text>
{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."
)}
</Text>
</div>
</div>
) : (
<React.Fragment>
<ProgressTabs.Content
value={Tab.EDIT}
value={Tab.PRICES}
className="h-full w-full"
>
<PriceListProductPricesForm
product={product}
currencies={currencies}
regions={regions}
control={productControl}
taxInclEnabled={isTaxInclPricesEnabled}
priceListTaxInclusive={priceList.includes_tax}
getValues={getProductValues}
setValue={setProductValue}
<PriceListPricesForm
setProduct={onSetProduct}
form={nestedForm(pricesForm)}
priceListId={priceList.id}
productIds={productIds}
/>
</ProgressTabs.Content>
)}
</React.Fragment>
)}
{product && (
<ProgressTabs.Content
value={Tab.EDIT}
className="h-full w-full"
>
<PriceListProductPricesForm
product={product}
currencies={currencies}
regions={regions}
control={productControl}
taxInclEnabled={isTaxInclPricesEnabled}
priceListTaxInclusive={priceList.includes_tax}
getValues={getProductValues}
setValue={setProductValue}
/>
</ProgressTabs.Content>
)}
</React.Fragment>
)}
</Form>
</FocusModal.Body>
</FocusModal.Content>
</ProgressTabs>

View File

@@ -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))
}

View File

@@ -1852,6 +1852,15 @@ const Cell = React.forwardRef<HTMLInputElement, CellProps>(
}
}, [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<HTMLInputElement, CellProps>(
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<HTMLInputElement, CellProps>(
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<HTMLInputElement, CellProps>(
inputRef.current?.focus()
setIsActive(false)
onBlur()
return
},
[isActive]
[isActive, onBlur]
)
const onKeydown = React.useCallback(
@@ -2045,7 +2062,7 @@ const Cell = React.forwardRef<HTMLInputElement, CellProps>(
const onActiveAwareBlur = React.useCallback(() => {
if (isActive) {
return
setIsActive(false)
}
onBlur()

View File

@@ -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: