feat: all project setup
This commit is contained in:
164
lib/utils/format.ts
Normal file
164
lib/utils/format.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
// ============================================================================
|
||||
// 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)}...`;
|
||||
};
|
||||
Reference in New Issue
Block a user