Files
frontend/src/shared/lib/display.ts
T
ksv741 c3c1528fd4
Frontend verify / commits (pull_request) Successful in 29s
Frontend verify / security (pull_request) Successful in 8s
Frontend verify / verify (pull_request) Failing after 4m34s
feat: show return, cash and structure on account cards (#114)
2026-10-27 05:03:12 +03:00

150 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const DECIMAL_PART_COUNT = 2;
const CENTS_PRECISION = 2;
type FormatMoneyOptions = {
withRoundedCents?: boolean;
withSign?: boolean;
withZeroCents?: boolean;
};
const portfolioCategoryPresentation = {
BOND: { color: "grape.6", label: "portfolio.bonds" },
CASH: { color: "green.6", label: "portfolio.structure.cash" },
OTHER: { color: "orange.6", label: "portfolio.structure.other" },
STOCK: { color: "blue.6", label: "portfolio.stocks" },
} as const;
type Money = { currency: string; value: string };
const dateFormatter = new Intl.DateTimeFormat("ru-RU", {
day: "2-digit",
month: "2-digit",
timeZone: "UTC",
year: "numeric",
});
function formatDate(value: string): string {
return dateFormatter.format(new Date(value));
}
function formatDecimal(value: string): string {
const [integer = "", fraction] = value.split(".", DECIMAL_PART_COUNT);
const sign = integer.startsWith("-") ? "-" : "";
const digits = sign ? integer.slice(1) : integer;
const groupSize = 3;
const groups: string[] = [];
for (let end = digits.length; end > 0; end -= groupSize) {
groups.push(digits.slice(Math.max(0, end - groupSize), end));
}
const grouped = groups.toReversed().join(" ");
const suffix = fraction ? `,${fraction}` : "";
return `${sign}${grouped}${suffix}`;
}
function formatMoney(
money: Money
| null
| undefined,
{
withRoundedCents = false,
withSign = false,
withZeroCents = false,
}: FormatMoneyOptions = {},
): string {
if (!money) return "—";
const value = withRoundedCents
? roundDecimalToCents(money.value)
: money.value;
const [integerPart = "", fractionPart = ""] = value.split(
".",
DECIMAL_PART_COUNT,
);
const normalizedFraction = withRoundedCents || withZeroCents
? fractionPart.padEnd(CENTS_PRECISION, "0")
: fractionPart;
const decimal = normalizedFraction
? `${integerPart}.${normalizedFraction}`
: integerPart;
const currency = money.currency === "RUB" ? "₽" : money.currency;
const formatted = `${formatDecimal(decimal)} ${currency}`;
if (!withSign) return formatted;
if (isZeroDecimal(value))
return value.startsWith("-") ? formatted.slice(1) : formatted;
return value.startsWith("-") ? `−${formatted.slice(1)}` : `+${formatted}`;
}
function isZeroDecimal(value: string) {
const unsigned = value.startsWith("-") ? value.slice(1) : value;
const [integer = "", fraction = ""] = unsigned.split(
".",
DECIMAL_PART_COUNT,
);
return integer.replaceAll("0", "") === ""
&& fraction.replaceAll("0", "") === "";
}
function roundDecimalToCents(value: string): string {
const [integer = "", fraction = ""] = value.split(".", DECIMAL_PART_COUNT);
const sign = integer.startsWith("-") ? "-" : "";
const digits = sign ? integer.slice(1) : integer;
const roundedFraction = fraction.padEnd(CENTS_PRECISION + 1, "0");
const cents = `${digits}${roundedFraction.slice(0, CENTS_PRECISION)}`;
const roundingDigit = roundedFraction.at(CENTS_PRECISION) ?? "0";
const rounded = roundingDigit >= "5"
? (BigInt(cents) + 1n).toString().padStart(cents.length, "0")
: cents;
const roundedInteger = rounded.slice(0, -CENTS_PRECISION) || "0";
return `${sign}${roundedInteger}.${rounded.slice(-CENTS_PRECISION)}`;
}
// Без timeZone Intl берёт часовой пояс браузера пользователя.
const snapshotFormatter = new Intl.DateTimeFormat("ru-RU", {
dateStyle: "short",
timeStyle: "short",
});
function formatSnapshotTime(value: string): string {
return snapshotFormatter.format(new Date(value));
}
const logoSize = 160;
const chartFormatter = new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 4 });
function formatChartValue(value: number) {
return chartFormatter.format(value);
}
function instrumentLogoUrl(logoName?: null | string): null | string {
const stem = logoName?.trim().replace(/\.png$/i, "");
return stem
? `https://invest-brands.cdn-tinkoff.ru/${stem}x${logoSize}.png`
: null;
}
export {
formatChartValue,
formatDate,
formatDecimal,
formatMoney,
formatSnapshotTime,
instrumentLogoUrl,
portfolioCategoryPresentation,
};