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:
committed by
GitHub
parent
ebba93e03d
commit
ea2ee343f0
@@ -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",
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user