feat: show return, cash and structure on account cards (#114)
This commit is contained in:
@@ -5,7 +5,20 @@ const ru = {
|
||||
all: "Все счета",
|
||||
allDescription: "Портфель по всем доступным инвестиционным счетам",
|
||||
bonds: "Облигации",
|
||||
brokerDescription: "По данным брокера",
|
||||
cash: "Деньги на счёте",
|
||||
count_few: "{{count}} счёта",
|
||||
count_many: "{{count}} счетов",
|
||||
count_one: "{{count}} счёт",
|
||||
count_other: "{{count}} счёта",
|
||||
currentValue: "Текущая стоимость",
|
||||
description: {
|
||||
currentValue: "Текущая стоимость: {{value}}. ",
|
||||
return: "Доходность {{currency}}: {{percent}}, {{absolute}}",
|
||||
start: "Счёт {{name}}, {{type}}. ",
|
||||
structure: "Структура {{currency}}: {{value}}",
|
||||
structureCategory: "{{category}} {{value}}",
|
||||
},
|
||||
emptyText:
|
||||
"Откройте инвестиционный счет в T-Bank, чтобы он появился здесь.",
|
||||
emptyTitle: "Нет инвестиционных счетов",
|
||||
@@ -18,6 +31,11 @@ const ru = {
|
||||
openAll: "Портфель по всем счетам",
|
||||
openedOn: "Открыт {{date}}",
|
||||
openSettings: "Открыть настройки T-Bank",
|
||||
partial: "частично",
|
||||
positions_few: "{{count}} позиции",
|
||||
positions_many: "{{count}} позиций",
|
||||
positions_one: "{{count}} позиция",
|
||||
positions_other: "{{count}} позиции",
|
||||
refreshError:
|
||||
"Не удалось обновить данные. Показаны последние полученные.",
|
||||
refreshing: "Обновляем данные",
|
||||
@@ -28,8 +46,22 @@ const ru = {
|
||||
retry: "Повторить",
|
||||
scope: "Счёт",
|
||||
stocks: "Акции",
|
||||
structure: {
|
||||
category: "{{category}} {{percent}} %",
|
||||
incomplete: "{{category}}: доля неизвестна",
|
||||
label: "Структура в {{currency}}: {{value}}",
|
||||
},
|
||||
subtitle: "Ваши инвестиции по счетам",
|
||||
summaryEmpty: "Нет текущих сумм",
|
||||
summaryExcluded_few:
|
||||
"Ещё {{count}} счёта не включены: нет актуального портфеля",
|
||||
summaryExcluded_many:
|
||||
"Ещё {{count}} счетов не включены: нет актуального портфеля",
|
||||
summaryExcluded_one:
|
||||
"Ещё {{count}} счёт не включён: нет актуального портфеля",
|
||||
summaryExcluded_other:
|
||||
"Ещё {{count}} счёта не включены: нет актуального портфеля",
|
||||
summaryNoTotals: "Нет данных для сводки",
|
||||
summaryUnavailable: "Сводка временно недоступна",
|
||||
title: "Счета",
|
||||
types: {
|
||||
|
||||
@@ -42,6 +42,18 @@ describe("shared display helpers", () => {
|
||||
formatMoney({ currency: "RUB", value: "1.5" }, { withZeroCents: true }),
|
||||
).toBe("1,50 ₽");
|
||||
expect(formatMoney(null)).toBe("—");
|
||||
expect(formatMoney(
|
||||
{ currency: "RUB", value: "136900" },
|
||||
{ withSign: true },
|
||||
)).toBe("+136 900 ₽");
|
||||
expect(formatMoney(
|
||||
{ currency: "RUB", value: "-4200" },
|
||||
{ withSign: true },
|
||||
)).toBe("−4 200 ₽");
|
||||
expect(formatMoney(
|
||||
{ currency: "RUB", value: "-0.00" },
|
||||
{ withSign: true },
|
||||
)).toBe("0,00 ₽");
|
||||
});
|
||||
|
||||
it("builds the brand logo url from an upstream file name", () => {
|
||||
|
||||
@@ -3,9 +3,17 @@ 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", {
|
||||
@@ -40,7 +48,11 @@ function formatMoney(
|
||||
money: Money
|
||||
| null
|
||||
| undefined,
|
||||
{ withRoundedCents = false, withZeroCents = false }: FormatMoneyOptions = {},
|
||||
{
|
||||
withRoundedCents = false,
|
||||
withSign = false,
|
||||
withZeroCents = false,
|
||||
}: FormatMoneyOptions = {},
|
||||
): string {
|
||||
if (!money) return "—";
|
||||
|
||||
@@ -62,8 +74,25 @@ function formatMoney(
|
||||
: integerPart;
|
||||
|
||||
const currency = money.currency === "RUB" ? "₽" : money.currency;
|
||||
const formatted = `${formatDecimal(decimal)} ${currency}`;
|
||||
|
||||
return `${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 {
|
||||
@@ -116,4 +145,5 @@ export {
|
||||
formatMoney,
|
||||
formatSnapshotTime,
|
||||
instrumentLogoUrl,
|
||||
portfolioCategoryPresentation,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ export {
|
||||
formatMoney,
|
||||
formatSnapshotTime,
|
||||
instrumentLogoUrl,
|
||||
portfolioCategoryPresentation,
|
||||
} from "./display";
|
||||
export { useInitialAvailability } from "./initial-availability";
|
||||
export { useUrlPeriod } from "./useUrlPeriod";
|
||||
|
||||
Reference in New Issue
Block a user