136 lines
4.3 KiB
TypeScript
136 lines
4.3 KiB
TypeScript
import { clsx, type ClassValue } from "clsx";
|
||
import { twMerge } from "tailwind-merge";
|
||
import moment from "moment-jalaali";
|
||
|
||
export function cn(...inputs: ClassValue[]) {
|
||
return twMerge(clsx(inputs));
|
||
}
|
||
|
||
export const formatNumber = (value: string | number) => {
|
||
// if (!value) return "0";
|
||
const numericValue = typeof value === "string" ? parseFloat(value) : value;
|
||
if (isNaN(numericValue)) return "0";
|
||
return new Intl.NumberFormat("fa-IR").format(numericValue);
|
||
};
|
||
|
||
export const formatCurrency = (amount: string | number) => {
|
||
if (!amount) return "0 ریال";
|
||
// Remove commas and convert to number
|
||
const numericAmount =
|
||
typeof amount === "string" ? parseFloat(amount.replace(/,/g, "")) : amount;
|
||
if (isNaN(numericAmount)) return "0 ریال";
|
||
return new Intl.NumberFormat("fa-IR").format(numericAmount) + " ریال";
|
||
};
|
||
|
||
|
||
|
||
/**
|
||
* محاسبه دامنه nice numbers برای محور Y نمودارها
|
||
* @param values آرایه از مقادیر دادهها
|
||
* @param minValue حداقل مقدار (پیشفرض: 0 برای دادههای درصدی)
|
||
* @param marginPercent درصد حاشیه اضافی (پیشفرض: 5%)
|
||
* @returns شیء شامل حداکثر nice، فاصله tick ها، و آرایه tick ها
|
||
*/
|
||
export function calculateNiceRange(
|
||
values: number[],
|
||
minValue: number = 0,
|
||
marginPercent: number = 5
|
||
): {
|
||
niceMax: number;
|
||
tickInterval: number;
|
||
ticks: number[];
|
||
} {
|
||
if (values.length === 0) {
|
||
return { niceMax: 100, tickInterval: 20, ticks: [0, 20, 40, 60, 80, 100] };
|
||
}
|
||
|
||
// پیدا کردن حداکثر مقدار در دادهها
|
||
const dataMax = Math.max(...values);
|
||
|
||
// اگر همه مقادیر صفر یا منفی هستند
|
||
if (dataMax <= 0) {
|
||
return { niceMax: 100, tickInterval: 20, ticks: [0, 20, 40, 60, 80, 100] };
|
||
}
|
||
|
||
// اضافه کردن حاشیه
|
||
const maxWithMargin = dataMax * (1 + marginPercent / 100);
|
||
|
||
// محاسبه nice upper limit
|
||
const niceMax = calculateNiceNumber(maxWithMargin, true);
|
||
|
||
// محاسبه فاصله مناسب tick ها بر اساس niceMax
|
||
const range = niceMax - minValue;
|
||
const targetTicks = 5; // هدف: 5 tick
|
||
const roughTickInterval = range / (targetTicks - 1);
|
||
const niceTickInterval = calculateNiceNumber(roughTickInterval, false);
|
||
|
||
// ایجاد آرایه tick ها
|
||
const ticks: number[] = [];
|
||
for (let i = minValue; i <= niceMax; i += niceTickInterval) {
|
||
ticks.push(Math.round(i));
|
||
}
|
||
|
||
// اطمینان از اینکه niceMax در آرایه tick ها باشد
|
||
if (ticks[ticks.length - 1] !== niceMax) {
|
||
ticks.push(niceMax);
|
||
}
|
||
|
||
return {
|
||
niceMax,
|
||
tickInterval: niceTickInterval,
|
||
ticks,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* محاسبه عدد nice (گرد و خوانا) بر اساس الگوریتم nice numbers
|
||
* @param value مقدار ورودی
|
||
* @param round آیا به سمت بالا گرد شود یا نه
|
||
* @returns عدد nice
|
||
*/
|
||
function calculateNiceNumber(value: number, round: boolean): number {
|
||
if (value <= 0) return 0;
|
||
|
||
// پیدا کردن قدرت 10
|
||
const exponent = Math.floor(Math.log10(value));
|
||
const fraction = value / Math.pow(10, exponent);
|
||
|
||
let niceFraction: number;
|
||
|
||
if (round) {
|
||
// برای حداکثر: به سمت بالا گرد میکنیم با دقت بیشتر
|
||
if (fraction <= 1.0) niceFraction = 1;
|
||
else if (fraction <= 2.0) niceFraction = 2;
|
||
else if (fraction <= 2.5) niceFraction = 2.5;
|
||
else if (fraction <= 5.0) niceFraction = 5;
|
||
else if (fraction <= 7.5) niceFraction = 7.5;
|
||
else niceFraction = 10;
|
||
} else {
|
||
// برای فاصله tick ها: اعداد سادهتر
|
||
if (fraction <= 1.0) niceFraction = 1;
|
||
else if (fraction <= 2.0) niceFraction = 2;
|
||
else if (fraction <= 5.0) niceFraction = 5;
|
||
else niceFraction = 10;
|
||
}
|
||
|
||
return niceFraction * Math.pow(10, exponent);
|
||
}
|
||
|
||
export const handleDataValue = (val: any): any => {
|
||
moment.loadPersian({ usePersianDigits: true });
|
||
if (val == null) return val;
|
||
if (
|
||
typeof val === "string" &&
|
||
/^\d{4}[-/]\d{2}[-/]\d{2}( \d{2}:\d{2}(:\d{2})?)?$/.test(val)
|
||
) {
|
||
return moment(val, "YYYY-MM-DD HH:mm:ss").format("YYYY/MM/DD");
|
||
}
|
||
if (
|
||
typeof val === "number" ||
|
||
(typeof val === "string" && /^-?\d+$/.test(val))
|
||
) {
|
||
return val.toString().replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[+d]);
|
||
}
|
||
return val;
|
||
}
|