feat: all project setup

This commit is contained in:
ivannoskov
2025-11-10 12:07:24 +03:00
parent fbfd7719bb
commit b0a9934220
99 changed files with 19597 additions and 0 deletions

164
lib/utils/format.ts Normal file
View 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)}...`;
};