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";
|
} from "@glideapps/glide-data-grid";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { Locale } from "../Locale";
|
|
||||||
import { usePriceField } from "../PriceField/usePriceField";
|
import { usePriceField } from "../PriceField/usePriceField";
|
||||||
|
|
||||||
interface MoneyCellProps {
|
interface MoneyCellProps {
|
||||||
|
@ -46,33 +45,13 @@ const MoneyCellEdit: ReturnType<ProvideEditorCallback<MoneyCell>> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFractionDigits = (locale: Locale, currency: string) => {
|
export const moneyCellRenderer = (): CustomCellRenderer<MoneyCell> => ({
|
||||||
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> => ({
|
|
||||||
isMatch: (c): c is MoneyCell => (c.data as any).kind === "money-cell",
|
isMatch: (c): c is MoneyCell => (c.data as any).kind === "money-cell",
|
||||||
draw: (args, cell) => {
|
draw: (args, cell) => {
|
||||||
const { ctx, theme, rect } = args;
|
const { ctx, theme, rect } = args;
|
||||||
const { currency, value } = cell.data;
|
const { currency, value } = cell.data;
|
||||||
const hasValue = value === 0 ? true : !!value;
|
const hasValue = value === 0 ? true : !!value;
|
||||||
const currencyFractionDigits = getFractionDigits(locale, currency);
|
const formatted = value?.toString() ?? "-";
|
||||||
const formatted =
|
|
||||||
value?.toLocaleString(locale, {
|
|
||||||
maximumFractionDigits: currencyFractionDigits,
|
|
||||||
minimumFractionDigits: currencyFractionDigits,
|
|
||||||
}) ?? "-";
|
|
||||||
|
|
||||||
ctx.fillStyle = theme.textDark;
|
ctx.fillStyle = theme.textDark;
|
||||||
ctx.textAlign = "right";
|
ctx.textAlign = "right";
|
||||||
|
|
|
@ -10,7 +10,7 @@ function useCells() {
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => [
|
() => [
|
||||||
moneyCellRenderer(locale),
|
moneyCellRenderer(),
|
||||||
numberCellRenderer(locale),
|
numberCellRenderer(locale),
|
||||||
dropdownCellRenderer,
|
dropdownCellRenderer,
|
||||||
],
|
],
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { ProductFragment } from "@saleor/graphql";
|
||||||
import useForm from "@saleor/hooks/useForm";
|
import useForm from "@saleor/hooks/useForm";
|
||||||
import useFormset from "@saleor/hooks/useFormset";
|
import useFormset from "@saleor/hooks/useFormset";
|
||||||
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
|
import useLocale from "@saleor/hooks/useLocale";
|
||||||
import {
|
import {
|
||||||
getAttributeInputFromProduct,
|
getAttributeInputFromProduct,
|
||||||
getProductUpdatePageFormData,
|
getProductUpdatePageFormData,
|
||||||
|
@ -46,6 +47,7 @@ import {
|
||||||
UseProductUpdateFormOpts,
|
UseProductUpdateFormOpts,
|
||||||
UseProductUpdateFormOutput,
|
UseProductUpdateFormOutput,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { prepareVariantChangeData } from "./utils";
|
||||||
|
|
||||||
function useProductUpdateForm(
|
function useProductUpdateForm(
|
||||||
product: ProductFragment,
|
product: ProductFragment,
|
||||||
|
@ -71,6 +73,7 @@ function useProductUpdateForm(
|
||||||
data: formData,
|
data: formData,
|
||||||
setIsSubmitDisabled,
|
setIsSubmitDisabled,
|
||||||
} = form;
|
} = form;
|
||||||
|
const { locale } = useLocale();
|
||||||
|
|
||||||
const datagrid = useDatagridChangeState();
|
const datagrid = useDatagridChangeState();
|
||||||
const variants = useRef<DatagridChangeOpts>({
|
const variants = useRef<DatagridChangeOpts>({
|
||||||
|
@ -78,10 +81,14 @@ function useProductUpdateForm(
|
||||||
removed: [],
|
removed: [],
|
||||||
updates: [],
|
updates: [],
|
||||||
});
|
});
|
||||||
const handleVariantChange = React.useCallback((data: DatagridChangeOpts) => {
|
|
||||||
variants.current = data;
|
const handleVariantChange = React.useCallback(
|
||||||
|
(data: DatagridChangeOpts) => {
|
||||||
|
variants.current = prepareVariantChangeData(data, locale, product);
|
||||||
triggerChange();
|
triggerChange();
|
||||||
}, []);
|
},
|
||||||
|
[locale, product, triggerChange],
|
||||||
|
);
|
||||||
|
|
||||||
const attributes = useFormset(getAttributeInputFromProduct(product));
|
const attributes = useFormset(getAttributeInputFromProduct(product));
|
||||||
const {
|
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