From 44e273042840e70908eff85518dd4e3a0d8d739b Mon Sep 17 00:00:00 2001 From: Mohammed Al-yaseen Date: Wed, 14 Jan 2026 22:33:07 +0300 Subject: [PATCH] fix price format (default oman price) --- src/components/ArabicPrice/ArabicPrice.tsx | 4 +- .../PaymentMethods/PaymentMethods.tsx | 3 +- src/pages/product/components/Variants.tsx | 3 +- src/utils/helpers.ts | 158 +++++++++++++++--- 4 files changed, 146 insertions(+), 22 deletions(-) diff --git a/src/components/ArabicPrice/ArabicPrice.tsx b/src/components/ArabicPrice/ArabicPrice.tsx index 89f878e..d8fedae 100644 --- a/src/components/ArabicPrice/ArabicPrice.tsx +++ b/src/components/ArabicPrice/ArabicPrice.tsx @@ -1,6 +1,7 @@ import React from "react"; import { useAppSelector } from "redux/hooks"; import ProText from "../ProText"; +import { formatPriceUi } from "utils/helpers"; interface ArabicPriceProps { price: number | string; @@ -25,7 +26,8 @@ const ArabicPrice: React.FC = ({ const { restaurant } = useAppSelector((state) => state.order); // Format the price to ensure it has 2 decimal places - const formattedPrice = typeof price === "number" ? price.toFixed(2) : price; + const formattedPrice = + typeof price === "number" ? formatPriceUi(price, 3) : price; const { textDecoration, ...restStyle } = style; const decorationStyle = textDecoration ? ({ textDecoration } as React.CSSProperties) diff --git a/src/components/PaymentMethods/PaymentMethods.tsx b/src/components/PaymentMethods/PaymentMethods.tsx index 1b97846..57cab9a 100644 --- a/src/components/PaymentMethods/PaymentMethods.tsx +++ b/src/components/PaymentMethods/PaymentMethods.tsx @@ -15,6 +15,7 @@ import ProInputCard from "../ProInputCard/ProInputCard"; import styles from "./PaymentMethods.module.css"; import { OrderType } from "pages/checkout/hooks/types.ts"; import RCardIcon from "components/Icons/RCardIcon"; +import { formatPriceUi } from "utils/helpers"; const PaymentMethods = () => { const { t } = useTranslation(); @@ -48,7 +49,7 @@ const PaymentMethods = () => { ), value: "cash", - price: grandTotal.toFixed(2), + price: formatPriceUi(grandTotal, 3), style: { color: colors.primary, }, diff --git a/src/pages/product/components/Variants.tsx b/src/pages/product/components/Variants.tsx index 666bc3b..fa84a62 100644 --- a/src/pages/product/components/Variants.tsx +++ b/src/pages/product/components/Variants.tsx @@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next"; import { useAppSelector } from "redux/hooks"; import { Variant } from "utils/types/appTypes"; import styles from "../product.module.css"; +import { formatPriceUi } from "utils/helpers"; export default function Variants({ selectedVariants, @@ -169,7 +170,7 @@ export default function Variants({ value: value, label: value, price: variant - ? `+${Number(variant.price).toFixed(2)}` + ? `+${formatPriceUi(variant.price, 3)}` : "", }; })} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 2431a88..76f5ef6 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,10 +1,10 @@ import { t } from "i18next"; import { DEFAULT_LANGUAGE } from "./constants"; import { - ProColumnType, - RegisterType, - TableHeaderType, - Translation + ProColumnType, + RegisterType, + TableHeaderType, + Translation, } from "./types/appTypes"; export default function getRandomColor() { @@ -25,14 +25,14 @@ export const RGBLinearShade = (p: number, color: string) => { const P = p < 0 ? 1 + p : 1 - p; return `rgb${d ? "a(" : "("}${r( - i(a[3] === "a" ? a.slice(5) : a.slice(4)) * P + t + i(a[3] === "a" ? a.slice(5) : a.slice(4)) * P + t, )},${r(i(b) * P + t)},${r(i(c) * P + t)}${d ? `,${d}` : ")"}`; }; export const parseTranslationObj = ( prefix: any, obj: object, - form: FormData + form: FormData, ) => { Object.entries(obj).forEach(([key, value]) => { form.append(`${prefix}[${key}]`, value as string); @@ -48,8 +48,7 @@ export const parseDataToFormData = (data: any) => { Object.entries(data).map((field: any) => { const key = field[0]; const value = field[1]; - if (key === "ar" || key === "en") - parseTranslationObj(key, value, formData); + if (key === "ar" || key === "en") parseTranslationObj(key, value, formData); // cleaning (no need to add properties with undefined, null .. etc values) else if (value || value === 0) formData.append(key, value); }); @@ -82,7 +81,7 @@ export function getArraysDiff(arr1: Array, arr2: Array) { export function translatedColumnTitle( title: string | { key: string; varKey: string }, - t2: any + t2: any, ): string { if (title && typeof title === "string") return t2(title); // @ts-ignore @@ -94,7 +93,7 @@ export function translatedColumnTitle( } export function getModifiedColProps( - col: TableHeaderType | Partial> + col: TableHeaderType | Partial>, ): Partial> { const translatedTitle = translatedColumnTitle(col.title || "", t); let calcColumnWidth = col.width; @@ -148,7 +147,7 @@ export function prepareNextKey(dataSource: any[]): number { export function objectArrayToFormData( arr: Array>, parentKey?: string, - formData?: FormData + formData?: FormData, ): FormData { const form = formData || new FormData(); @@ -213,11 +212,9 @@ export const formatNumbers = (value: string | number = 0) => { */ export const parseTranslations = (data: any) => { return { - arabic_name: data?.translations?.find( - (t: Translation) => t.locale === "ar" - )?.name, - name: data?.translations?.find((t: Translation) => t.locale === "en") + arabic_name: data?.translations?.find((t: Translation) => t.locale === "ar") ?.name, + name: data?.translations?.find((t: Translation) => t.locale === "en")?.name, en: { name: data?.translations?.find((t: Translation) => t.locale === "en") ?.name, @@ -238,14 +235,14 @@ export const parseTranslations = (data: any) => { export const parseTranslationLabel = ( data: any, propName: string, - translationPropName?: string + translationPropName?: string, ) => { return { ["arabic_" + propName]: data?.translations?.find( - (t: Translation) => t.locale === "ar" + (t: Translation) => t.locale === "ar", )?.[translationPropName || "name"], [propName]: data?.translations?.find( - (t: Translation) => t.locale === "en" + (t: Translation) => t.locale === "en", )?.[translationPropName || "name"], }; }; @@ -260,7 +257,7 @@ export const saveTranslationsParse = ( selectedRecord: any, dataIndex: string, lang: "ar" | "en", - value: string + value: string, ) => { return lang === "ar" ? { @@ -274,3 +271,126 @@ export const saveTranslationsParse = ( export const fixNumbers = (number: string) => { return number.slice(0, -3); }; + +/** + * Rounds a number if it contains '9' in the decimal part + * If '9' is found, rounds to keep the number up to the first '9' plus one more digit + * Otherwise returns the number as is + * @param number - The number to round + * @returns The rounded number or original number + */ +export const roundNumberIfNeededUi = (number: number): number => { + const numberString = number.toString(); + const decimalIndex = numberString.indexOf("."); + + if (decimalIndex === -1) { + return number; // No decimal point, return as is + } + + // Check if there's a '9' in the decimal part + let foundNine = false; + for (let i = decimalIndex + 1; i < numberString.length; i++) { + if (numberString[i] === "9") { + foundNine = true; + break; + } + } + + if (foundNine) { + // If '9' is found, round to 2 decimal places (keeping one 9 after decimal point) + // The Dart code uses decimalIndex + 2, which means 2 decimal places total + const decimalPlaces = 2; + return parseFloat(number.toFixed(decimalPlaces)); + } + + return number; +}; + +/** + * Formats a price value to have a specific number of decimal places + * Pads with zeros if needed, or truncates if too long + * @param value - The price value to format + * @param currencyDecimals - Number of decimal places (default: 3) + * @returns Formatted price string + */ +export const formatPriceUi = ( + value: number, + currencyDecimals: number = 3, +): string => { + const valueString = value.toString(); + const parts = valueString.split("."); + const integerPart = parts[0]; + let decimalPart = parts.length > 1 ? parts[1] : ""; + + // Pad or truncate decimal part + if (decimalPart.length < currencyDecimals) { + decimalPart = decimalPart.padEnd(currencyDecimals, "0"); + } else if (decimalPart.length > currencyDecimals) { + decimalPart = decimalPart.substring(0, currencyDecimals); + } + + return `${integerPart}.${decimalPart}`; +}; + +/** + * Formats a price for UI display + * Handles scientific notation, NaN, and special cases with '9999999' pattern + * @param value - The value to format (number or string) + * @param currencyDecimals - Number of decimal places (default: 3) + * @returns Formatted price string + */ +export const setPriceUi = ( + value: number | string, + currencyDecimals: number = 3, +): string => { + // Convert to number with 6 decimal places + const numValue = + typeof value === "string" + ? parseFloat(parseFloat(value).toFixed(6)) || 0.0 + : parseFloat(value.toFixed(6)) || 0.0; + + const valueStr = numValue.toString(); + + // Handle scientific notation and NaN + if (valueStr.includes("e") || valueStr === "NaN") { + return "0.000"; + } + + // Handle '9999999' pattern + if (valueStr.includes("9999999")) { + return roundNumberIfNeededUi(numValue).toString(); + } + + return formatPriceUi(numValue, currencyDecimals); +}; + +/** + * Formats a price for payment UI display + * Handles scientific notation and special cases with '9999999' pattern + * @param value - The value to format (number or string) + * @param currencyDecimals - Number of decimal places (default: 3) + * @returns Formatted price string + */ +export const setPricePaymentUi = ( + value: number | string, + currencyDecimals: number = 3, +): string => { + const valueStr = value.toString(); + + // Handle scientific notation + if (valueStr.includes("e")) { + return "0.000"; + } + + // Handle '9999999' pattern + if (valueStr.includes("9999999")) { + const fixed4 = + typeof value === "number" + ? value.toFixed(4) + : parseFloat(value).toFixed(4); + return fixed4.substring(0, fixed4.length - 1); + } + + const numValue = typeof value === "string" ? parseFloat(value) : value; + return formatPriceUi(numValue, currencyDecimals); +};