fix: datagrid does not save prices without decimal prices (#2955)
This commit is contained in:
parent
5bc8e39eb7
commit
e619866338
5 changed files with 168 additions and 28 deletions
|
@ -6,7 +6,6 @@ import {
|
|||
} from "@glideapps/glide-data-grid";
|
||||
import React from "react";
|
||||
|
||||
import { Locale } from "../Locale";
|
||||
import { usePriceField } from "../PriceField/usePriceField";
|
||||
|
||||
interface MoneyCellProps {
|
||||
|
@ -46,33 +45,13 @@ const MoneyCellEdit: ReturnType<ProvideEditorCallback<MoneyCell>> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const getFractionDigits = (locale: Locale, currency: string) => {
|
||||
try {
|
||||
const numberFormat = new Intl.NumberFormat(locale, {
|
||||
style: "currency",
|
||||
currency,
|
||||
});
|
||||
|
||||
return numberFormat.resolvedOptions().maximumFractionDigits;
|
||||
} catch (e) {
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
|
||||
export const moneyCellRenderer = (
|
||||
locale: Locale,
|
||||
): CustomCellRenderer<MoneyCell> => ({
|
||||
export const moneyCellRenderer = (): CustomCellRenderer<MoneyCell> => ({
|
||||
isMatch: (c): c is MoneyCell => (c.data as any).kind === "money-cell",
|
||||
draw: (args, cell) => {
|
||||
const { ctx, theme, rect } = args;
|
||||
const { currency, value } = cell.data;
|
||||
const hasValue = value === 0 ? true : !!value;
|
||||
const currencyFractionDigits = getFractionDigits(locale, currency);
|
||||
const formatted =
|
||||
value?.toLocaleString(locale, {
|
||||
maximumFractionDigits: currencyFractionDigits,
|
||||
minimumFractionDigits: currencyFractionDigits,
|
||||
}) ?? "-";
|
||||
const formatted = value?.toString() ?? "-";
|
||||
|
||||
ctx.fillStyle = theme.textDark;
|
||||
ctx.textAlign = "right";
|
||||
|
|
|
@ -10,7 +10,7 @@ function useCells() {
|
|||
const { locale } = useLocale();
|
||||
const value = useMemo(
|
||||
() => [
|
||||
moneyCellRenderer(locale),
|
||||
moneyCellRenderer(),
|
||||
numberCellRenderer(locale),
|
||||
dropdownCellRenderer,
|
||||
],
|
||||
|
|
|
@ -23,6 +23,7 @@ import { ProductFragment } from "@saleor/graphql";
|
|||
import useForm from "@saleor/hooks/useForm";
|
||||
import useFormset from "@saleor/hooks/useFormset";
|
||||
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||
import useLocale from "@saleor/hooks/useLocale";
|
||||
import {
|
||||
getAttributeInputFromProduct,
|
||||
getProductUpdatePageFormData,
|
||||
|
@ -46,6 +47,7 @@ import {
|
|||
UseProductUpdateFormOpts,
|
||||
UseProductUpdateFormOutput,
|
||||
} from "./types";
|
||||
import { prepareVariantChangeData } from "./utils";
|
||||
|
||||
function useProductUpdateForm(
|
||||
product: ProductFragment,
|
||||
|
@ -71,6 +73,7 @@ function useProductUpdateForm(
|
|||
data: formData,
|
||||
setIsSubmitDisabled,
|
||||
} = form;
|
||||
const { locale } = useLocale();
|
||||
|
||||
const datagrid = useDatagridChangeState();
|
||||
const variants = useRef<DatagridChangeOpts>({
|
||||
|
@ -78,10 +81,14 @@ function useProductUpdateForm(
|
|||
removed: [],
|
||||
updates: [],
|
||||
});
|
||||
const handleVariantChange = React.useCallback((data: DatagridChangeOpts) => {
|
||||
variants.current = data;
|
||||
triggerChange();
|
||||
}, []);
|
||||
|
||||
const handleVariantChange = React.useCallback(
|
||||
(data: DatagridChangeOpts) => {
|
||||
variants.current = prepareVariantChangeData(data, locale, product);
|
||||
triggerChange();
|
||||
},
|
||||
[locale, product, triggerChange],
|
||||
);
|
||||
|
||||
const attributes = useFormset(getAttributeInputFromProduct(product));
|
||||
const {
|
||||
|
|
67
src/products/components/ProductUpdatePage/utils.test.ts
Normal file
67
src/products/components/ProductUpdatePage/utils.test.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { Locale } from "@saleor/components/Locale";
|
||||
|
||||
import { parseCurrency } from "./utils";
|
||||
|
||||
describe("parseCurrency", () => {
|
||||
it("rounds down to 3 decimals in 3 digit currency - EN locale (dot)", () => {
|
||||
// Arrange
|
||||
const value = "5.6777";
|
||||
const currency = "BHD";
|
||||
const locale = Locale.EN;
|
||||
|
||||
// Act
|
||||
const parsed = parseCurrency(value, locale, currency);
|
||||
|
||||
// Assert
|
||||
expect(parsed).toBe(5.677);
|
||||
});
|
||||
|
||||
it("rounds down to 0 decimals in 0 digit currency - PL locale (comma)", () => {
|
||||
// Arrange
|
||||
const value = "12,90679";
|
||||
const currency = "JPY";
|
||||
const locale = Locale.PL;
|
||||
|
||||
// Act
|
||||
const parsed = parseCurrency(value, locale, currency);
|
||||
|
||||
// Assert
|
||||
expect(parsed).toBe(12);
|
||||
});
|
||||
it("rounds down to 2 decimals in 2 digit currency - EN locale (dot)", () => {
|
||||
// Arrange
|
||||
const value = "244.98721";
|
||||
const currency = "PLN";
|
||||
const locale = Locale.EN;
|
||||
|
||||
// Act
|
||||
const parsed = parseCurrency(value, locale, currency);
|
||||
|
||||
// Assert
|
||||
expect(parsed).toBe(244.98);
|
||||
});
|
||||
it("rounds down to 3 decimals in 3 digit currency - PL locale (comma)", () => {
|
||||
// Arrange
|
||||
const value = "0,27386";
|
||||
const currency = "TND";
|
||||
const locale = Locale.PL;
|
||||
|
||||
// Act
|
||||
const parsed = parseCurrency(value, locale, currency);
|
||||
|
||||
// Assert
|
||||
expect(parsed).toBe(0.273);
|
||||
});
|
||||
it("rounds down correctly (floating point difficult case)", () => {
|
||||
// Arrange
|
||||
const value = "2.07";
|
||||
const currency = "EUR";
|
||||
const locale = Locale.PL;
|
||||
|
||||
// Act
|
||||
const parsed = parseCurrency(value, locale, currency);
|
||||
|
||||
// Assert
|
||||
expect(parsed).toBe(2.07);
|
||||
});
|
||||
});
|
87
src/products/components/ProductUpdatePage/utils.ts
Normal file
87
src/products/components/ProductUpdatePage/utils.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
import {
|
||||
DatagridChange,
|
||||
DatagridChangeOpts,
|
||||
} from "@saleor/components/Datagrid/useDatagridChange";
|
||||
import { Locale } from "@saleor/components/Locale";
|
||||
import { ProductFragment } from "@saleor/graphql";
|
||||
|
||||
const getFractionDigits = (locale: Locale, currency: string) => {
|
||||
try {
|
||||
const numberFormat = new Intl.NumberFormat(locale, {
|
||||
style: "currency",
|
||||
currency,
|
||||
});
|
||||
|
||||
return numberFormat.resolvedOptions().maximumFractionDigits;
|
||||
} catch (e) {
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
|
||||
export const parseCurrency = (
|
||||
value: string,
|
||||
locale: Locale,
|
||||
currency: string,
|
||||
): number => {
|
||||
// Thousand seperators are not allowedd
|
||||
const number = value.replace(/,/, ".");
|
||||
const fractionDigits = getFractionDigits(locale, currency);
|
||||
const trimmedNumber = number.slice(
|
||||
0,
|
||||
number.lastIndexOf(".") + 1 + fractionDigits,
|
||||
);
|
||||
|
||||
return parseFloat(trimmedNumber);
|
||||
};
|
||||
|
||||
export const prepareVariantChangeData = (
|
||||
data: DatagridChangeOpts,
|
||||
locale: Locale,
|
||||
product: ProductFragment,
|
||||
): DatagridChangeOpts => {
|
||||
data.updates = data.updates.map(update => {
|
||||
if (isColumnChannelPrice(update.column)) {
|
||||
return updateVaraintWithPriceFormat(update, locale, product);
|
||||
}
|
||||
return update;
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
function updateVaraintWithPriceFormat(
|
||||
dataChange: DatagridChange,
|
||||
locale: Locale,
|
||||
product: ProductFragment,
|
||||
) {
|
||||
const channelId = dataChange.column.split(":")[1];
|
||||
const currencyCode = getChannelCurrencyCodeById(
|
||||
channelId,
|
||||
product.channelListings,
|
||||
);
|
||||
|
||||
dataChange.data.value = parseCurrency(
|
||||
`${dataChange.data.value}`,
|
||||
locale,
|
||||
currencyCode,
|
||||
);
|
||||
|
||||
return dataChange;
|
||||
}
|
||||
|
||||
function isColumnChannelPrice(name: string) {
|
||||
return name.includes("channel:");
|
||||
}
|
||||
|
||||
function getChannelCurrencyCodeById(
|
||||
channelId: string,
|
||||
channelList: ProductFragment["channelListings"],
|
||||
): string {
|
||||
const channel = channelList.find(({ channel }) => channel.id === channelId);
|
||||
|
||||
if (channel) {
|
||||
return channel.channel.currencyCode;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
Loading…
Reference in a new issue