165 lines
4.3 KiB
TypeScript
165 lines
4.3 KiB
TypeScript
// ============================================================================
|
||
// Formatting Utilities
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Форматирование числа с разделением тысяч
|
||
*/
|
||
export const formatNumber = (
|
||
value: number | null | undefined,
|
||
showSign: boolean = false
|
||
): string => {
|
||
if (value === null || value === undefined) return "—";
|
||
const sign = showSign && value > 0 ? "+" : "";
|
||
return `${sign}${new Intl.NumberFormat("ru-RU").format(value)}`;
|
||
};
|
||
|
||
/**
|
||
* Форматирование валюты (рубли)
|
||
*/
|
||
export const formatCurrency = (value: number | null | undefined): string => {
|
||
if (value === null || value === undefined) return "—";
|
||
return new Intl.NumberFormat("ru-RU", {
|
||
style: "currency",
|
||
currency: "RUB",
|
||
minimumFractionDigits: 0,
|
||
maximumFractionDigits: 0,
|
||
}).format(value);
|
||
};
|
||
|
||
/**
|
||
* Форматирование CPF/CPM с двумя знаками после запятой
|
||
*/
|
||
export const formatMetric = (value: number | null | undefined): string => {
|
||
if (value === null || value === undefined) return "—";
|
||
return new Intl.NumberFormat("ru-RU", {
|
||
minimumFractionDigits: 2,
|
||
maximumFractionDigits: 2,
|
||
}).format(value);
|
||
};
|
||
|
||
/**
|
||
* Форматирование процента
|
||
*/
|
||
export const formatPercent = (
|
||
value: number | null | undefined,
|
||
showSign: boolean = false
|
||
): string => {
|
||
if (value === null || value === undefined) return "—";
|
||
const sign = showSign && value > 0 ? "+" : "";
|
||
return `${sign}${formatMetric(value)}%`;
|
||
};
|
||
|
||
/**
|
||
* Форматирование даты
|
||
*/
|
||
export const formatDate = (
|
||
date: string | Date | null | undefined,
|
||
includeTime: boolean = false
|
||
): string => {
|
||
if (!date) return "—";
|
||
|
||
const d = typeof date === "string" ? new Date(date) : date;
|
||
|
||
if (includeTime) {
|
||
return new Intl.DateTimeFormat("ru-RU", {
|
||
day: "2-digit",
|
||
month: "2-digit",
|
||
year: "numeric",
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
}).format(d);
|
||
}
|
||
|
||
return new Intl.DateTimeFormat("ru-RU", {
|
||
day: "2-digit",
|
||
month: "2-digit",
|
||
year: "numeric",
|
||
}).format(d);
|
||
};
|
||
|
||
/**
|
||
* Форматирование относительной даты (сколько прошло)
|
||
*/
|
||
export const formatRelativeDate = (date: string | Date): string => {
|
||
const d = typeof date === "string" ? new Date(date) : date;
|
||
const now = new Date();
|
||
const diff = now.getTime() - d.getTime();
|
||
|
||
const seconds = Math.floor(diff / 1000);
|
||
const minutes = Math.floor(seconds / 60);
|
||
const hours = Math.floor(minutes / 60);
|
||
const days = Math.floor(hours / 24);
|
||
|
||
if (days > 7) {
|
||
return formatDate(d);
|
||
} else if (days > 0) {
|
||
return `${days} ${pluralize(days, "день", "дня", "дней")} назад`;
|
||
} else if (hours > 0) {
|
||
return `${hours} ${pluralize(hours, "час", "часа", "часов")} назад`;
|
||
} else if (minutes > 0) {
|
||
return `${minutes} ${pluralize(
|
||
minutes,
|
||
"минута",
|
||
"минуты",
|
||
"минут"
|
||
)} назад`;
|
||
} else {
|
||
return "только что";
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Плюрализация русских слов
|
||
*/
|
||
export const pluralize = (
|
||
count: number,
|
||
one: string,
|
||
few: string,
|
||
many: string
|
||
): string => {
|
||
const mod10 = count % 10;
|
||
const mod100 = count % 100;
|
||
|
||
if (mod10 === 1 && mod100 !== 11) {
|
||
return one;
|
||
} else if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) {
|
||
return few;
|
||
} else {
|
||
return many;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Сокращение больших чисел (1000 -> 1K)
|
||
*/
|
||
export const formatCompactNumber = (
|
||
value: number | null | undefined
|
||
): string => {
|
||
if (value === null || value === undefined) return "—";
|
||
|
||
if (value >= 1000000) {
|
||
return `${(value / 1000000).toFixed(1)}M`;
|
||
} else if (value >= 1000) {
|
||
return `${(value / 1000).toFixed(1)}K`;
|
||
}
|
||
|
||
return formatNumber(value);
|
||
};
|
||
|
||
/**
|
||
* Форматирование username (с @)
|
||
*/
|
||
export const formatUsername = (username: string | null | undefined): string => {
|
||
if (!username) return "—";
|
||
return username.startsWith("@") ? username : `@${username}`;
|
||
};
|
||
|
||
/**
|
||
* Обрезка длинного текста
|
||
*/
|
||
export const truncate = (text: string, maxLength: number = 50): string => {
|
||
if (text.length <= maxLength) return text;
|
||
return `${text.substring(0, maxLength)}...`;
|
||
};
|