Files
web-menu-react-version-/src/utils/helpers.ts

397 lines
10 KiB
TypeScript

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<RegisterType>) => {
return (
Number(data.opening_balance || 0) +
Number(data.total_income || 0) -
Number(data.total_expenses || 0)
);
};
export function getArraysDiff(arr1: Array<string>, arr2: Array<string>) {
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<ProColumnType<any>>,
): Partial<ProColumnType<any>> {
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<any>, 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<Record<string, any>>,
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);
};