65 lines
1.7 KiB
TypeScript
65 lines
1.7 KiB
TypeScript
import type { Portfolio } from "../api/usePortfolio";
|
|
|
|
type Money = Portfolio["totalsByCurrency"][number];
|
|
|
|
function formatDecimal(value: string): string {
|
|
const [integer, fraction] = value.split(".");
|
|
const sign = integer.startsWith("-") ? "-" : "";
|
|
const grouped = (sign ? integer.slice(1) : integer).replace(
|
|
/\B(?=(\d{3})+(?!\d))/g,
|
|
" ",
|
|
);
|
|
return `${sign}${grouped}${fraction ? `,${fraction}` : ""}`;
|
|
}
|
|
|
|
function formatMoney(money: Money | null): string {
|
|
if (!money) return "—";
|
|
return `${formatDecimal(money.value)} ${money.currency === "RUB" ? "₽" : money.currency}`;
|
|
}
|
|
|
|
function formatShare(
|
|
numerator: Money | null,
|
|
denominator: Money | null,
|
|
): string | null {
|
|
if (!numerator || !denominator) return null;
|
|
const total = Number(denominator.value);
|
|
if (!Number.isFinite(total) || total <= 0) return null;
|
|
const percent = (Number(numerator.value) / total) * 100;
|
|
if (!Number.isFinite(percent)) return null;
|
|
const rounded = new Intl.NumberFormat("ru-RU", {
|
|
maximumFractionDigits: 1,
|
|
}).format(percent);
|
|
return `${rounded} %`;
|
|
}
|
|
|
|
function formatSnapshotTime(value: string): string {
|
|
return new Intl.DateTimeFormat("ru-RU", {
|
|
dateStyle: "medium",
|
|
timeStyle: "short",
|
|
timeZone: "UTC",
|
|
}).format(new Date(value));
|
|
}
|
|
|
|
function portfolioErrorCode(error: unknown): string | undefined {
|
|
return typeof error === "object" && error !== null && "code" in error
|
|
? String(error.code)
|
|
: undefined;
|
|
}
|
|
|
|
const logoSize = 160;
|
|
|
|
function instrumentLogoUrl(logoName: string | null | undefined): string | null {
|
|
const stem = logoName?.trim().replace(/\.png$/i, "");
|
|
return stem
|
|
? `https://invest-brands.cdn-tinkoff.ru/${stem}x${logoSize}.png`
|
|
: null;
|
|
}
|
|
|
|
export {
|
|
formatMoney,
|
|
formatShare,
|
|
formatSnapshotTime,
|
|
instrumentLogoUrl,
|
|
portfolioErrorCode,
|
|
};
|