import { t } from "i18next"; import { DEFAULT_LANGUAGE } from "./constants"; import { ProColumnType, RegisterType, TableHeaderType, Translation, } from "./types/appTypes"; export default function getRandomColor() { const letters = "0123456789ABCDEF"; let color = "#"; for (let i = 0; i < 6; i += 1) { color += letters[Math.floor(Math.random() * 16)]; } return color; } export const RGBLinearShade = (p: number, color: string) => { const i = parseInt; const r = Math.round; const [a, b, c, d] = color.split(","); const t = p < 0 ? 0 : 255 * p; 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, )},${r(i(b) * P + t)},${r(i(c) * P + t)}${d ? `,${d}` : ")"}`; }; export const parseTranslationObj = ( prefix: any, obj: object, form: FormData, ) => { Object.entries(obj).forEach(([key, value]) => { form.append(`${prefix}[${key}]`, value as string); }); }; /** * prepare data in the right format * @param formGroup */ export const parseDataToFormData = (data: any) => { const formData = new FormData(); Object.entries(data).map((field: any) => { const key = field[0]; const value = field[1]; 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); }); return formData; }; export const objPropsCalculate = (data: any) => { let total = 0; Object.entries(data).map((field: any) => { const value = field[1]; if (value) total += value; }); return total; }; export const calculateRegisterBalance = (data: Partial) => { return ( Number(data.opening_balance || 0) + Number(data.total_income || 0) - Number(data.total_expenses || 0) ); }; export function getArraysDiff(arr1: Array, arr2: Array) { if (arr1.length > arr2.length) return arr1.filter((el) => arr2.indexOf(el) < 0); return arr2.filter((el) => arr1.indexOf(el) < 0); } export function translatedColumnTitle( title: string | { key: string; varKey: string }, t2: any, ): string { if (title && typeof title === "string") return t2(title); // @ts-ignore if (title?.key) { // @ts-ignore return t2(title.key, { var: t2(title.varKey || "") }); } return ""; } export function getModifiedColProps( col: TableHeaderType | Partial>, ): Partial> { const translatedTitle = translatedColumnTitle(col.title || "", t); let calcColumnWidth = col.width; if (!calcColumnWidth) { const letterCount = translatedTitle.toString().length || 0; const uppercaseLetterCount = ( translatedTitle.toString().match(/[A-Z]/g) || "" ).length; calcColumnWidth = (letterCount - uppercaseLetterCount) * 10 + uppercaseLetterCount * 14 + 25; if (col.sorter) calcColumnWidth += 12; if (calcColumnWidth < 80) calcColumnWidth = 80; } return { title: translatedTitle, width: calcColumnWidth, }; } export function getModifiedColumnProps(col: ProColumnType, t2: any) { const translatedTitle = translatedColumnTitle(col.title || "", t2); let calcColumnWidth = col.width; if (!calcColumnWidth) { const letterCount = translatedTitle.toString().length || 0; const uppercaseLetterCount = ( translatedTitle.toString().match(/[A-Z]/g) || "" ).length; calcColumnWidth = (letterCount - uppercaseLetterCount) * 10 + uppercaseLetterCount * 14 + 25; if (col.sorter) calcColumnWidth += 12; if (calcColumnWidth < 80) calcColumnWidth = 80; } return { title: translatedTitle, width: calcColumnWidth, }; } export function prepareNextKey(dataSource: any[]): number { return ( dataSource.reduce((a, b) => (a.key > b.key ? a : b), { key: 0, } as any).key + 1 ); } export function objectArrayToFormData( arr: Array>, parentKey?: string, formData?: FormData, ): FormData { const form = formData || new FormData(); arr.forEach((obj, index) => { Object.keys(obj).forEach((key) => { const value = obj[key]; const fullKey = parentKey ? `${parentKey}[${index}][${key}]` : `${key}`; if (value instanceof File) { form.append(fullKey, value); } else if (Array.isArray(value)) { value.forEach((v, i) => { form.append(`${fullKey}[${i}]`, v); }); } else if (typeof value === "object" && value !== null) { objectArrayToFormData([value], fullKey, form); } else { form.append(fullKey, value); } }); }); return form; } /** * Add prefix to the invoice number * note: it's static prefix for all the number. * @param invoiceNumber * @returns */ export const addInvoiceNumberPrefix = (invoiceNumber?: string | number) => { return invoiceNumber ? "EP" + invoiceNumber : ""; }; /** * Calculate the ceil * @param value * @returns */ export const ceil = (value: string | number = 0) => { return Math.ceil(Number(value) / 100) * 100; }; /** * Calculate the ceil * @param value * @returns */ export const formatNumbers = (value: string | number = 0) => { return new Intl.NumberFormat("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 2, }).format(Number(value)); }; /** * Translated the current entity name * @param data * @param propName * @returns */ 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")?.name, en: { name: data?.translations?.find((t: Translation) => t.locale === "en") ?.name, }, ar: { name: data?.translations?.find((t: Translation) => t.locale === "ar") ?.name, }, }; }; /** * Translated a foreign info (like categories in the warehouse'product) * @param data * @param propName * @returns */ export const parseTranslationLabel = ( data: any, propName: string, translationPropName?: string, ) => { return { ["arabic_" + propName]: data?.translations?.find( (t: Translation) => t.locale === "ar", )?.[translationPropName || "name"], [propName]: data?.translations?.find( (t: Translation) => t.locale === "en", )?.[translationPropName || "name"], }; }; export const getTranslatedColumnDataIndex = (dataIndex: string) => { return localStorage.getItem(DEFAULT_LANGUAGE) === "ar" ? "arabic_" + dataIndex : dataIndex; }; export const saveTranslationsParse = ( selectedRecord: any, dataIndex: string, lang: "ar" | "en", value: string, ) => { return lang === "ar" ? { ar: { ...selectedRecord.ar, [dataIndex]: value }, } : { en: { ...selectedRecord.en, [dataIndex]: value }, }; }; 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); };