206 lines
6.7 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.

import { SkeletonBlock } from '@/shared/ui/SkeletonBlock';
import type { BrokerAccountsAggregate } from '../../../entities/broker-account/model/brokerAccountsOverview';
import { buildBrokerAllocation } from '../../../entities/broker-position';
import { BrokerAllocationBar } from '../../../widgets/broker-allocation-chart';
function formatBrokerCurrencyValue(currency: string, value: number): string {
return new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: currency || 'RUB',
maximumFractionDigits: 2,
}).format(value);
}
function formatBrokerSignedCurrencyValue(currency: string, value: number | null): string {
if (value === null) {
return '—';
}
const formatted = formatBrokerCurrencyValue(currency, Math.abs(value));
if (value > 0) {
return `+${formatted}`;
}
if (value < 0) {
return `${formatted}`;
}
return formatted;
}
function formatBrokerSignedPercent(value: number | null): string {
if (value === null) {
return '—';
}
const formatted = `${new Intl.NumberFormat('ru-RU', {
maximumFractionDigits: 2,
}).format(Math.abs(value))}%`;
if (value > 0) {
return `+${formatted}`;
}
if (value < 0) {
return `${formatted}`;
}
return formatted;
}
export function BrokerAccountsSummary({
aggregate,
availableCount,
totalCount,
isLoading,
}: {
aggregate: BrokerAccountsAggregate;
availableCount: number;
totalCount: number;
isLoading: boolean;
}) {
if (isLoading) {
return (
<section className="broker-accounts-summary broker-accounts-summary--loading">
<div className="broker-accounts-summary__hero">
<SkeletonBlock height={18} width="34%" />
<SkeletonBlock height={52} width="48%" />
<SkeletonBlock height={18} width="28%" />
</div>
<div className="broker-accounts-summary__metrics">
{[1, 2, 3].map((item) => (
<div className="broker-accounts-summary__metric" key={item}>
<SkeletonBlock height={14} width="45%" />
<SkeletonBlock height={26} width="70%" />
</div>
))}
</div>
</section>
);
}
return (
<section className="broker-accounts-summary" aria-label="Общая сводка по счетам">
<div className="broker-accounts-summary__hero">
<div>
<p className="broker-accounts-summary__eyebrow">Финансовый обзор</p>
<h2 className="broker-accounts-summary__title">Счета в одном кадре</h2>
</div>
<div className="broker-accounts-summary__status">
<span>{totalCount} счетов</span>
{availableCount !== totalCount ? (
<span>
Доступно по {availableCount} из {totalCount} счетов
</span>
) : null}
</div>
</div>
<div className="broker-accounts-summary__currency-grid">
{aggregate.portfolios.map((portfolioSummary) => {
const allocation = buildBrokerAllocation({
account: {
id: 'aggregate',
type: 'brokerage',
name: 'aggregate',
status: 'ACCOUNT_STATUS_OPEN',
openedAt: null,
accessLevel: null,
},
positionCounts: { shares: 0, bonds: 0, etf: 0, other: 0 },
totals: {
shares: {
currency: portfolioSummary.currency,
units: '0',
nano: 0,
value: portfolioSummary.allocation.shares,
},
bonds: {
currency: portfolioSummary.currency,
units: '0',
nano: 0,
value: portfolioSummary.allocation.bonds,
},
etf:
portfolioSummary.allocation.etf > 0
? {
currency: portfolioSummary.currency,
units: '0',
nano: 0,
value: portfolioSummary.allocation.etf,
}
: null,
currencies: {
currency: portfolioSummary.currency,
units: '0',
nano: 0,
value: portfolioSummary.allocation.cash,
},
futures: null,
options: null,
structuredProducts: null,
dfa: null,
portfolio: {
currency: portfolioSummary.currency,
units: '0',
nano: 0,
value: portfolioSummary.total,
},
},
yields: { expectedPercent: null, daily: null, dailyPercent: null },
cash: [],
blockedCash: [],
asOf: '',
});
return (
<article
className="broker-accounts-summary__currency-card"
key={portfolioSummary.currency}
>
<div className="broker-accounts-summary__currency-header">
<span className="broker-accounts-summary__currency">
{portfolioSummary.currency}
</span>
<strong className="broker-accounts-summary__total">
{formatBrokerCurrencyValue(portfolioSummary.currency, portfolioSummary.total)}
</strong>
</div>
<dl className="broker-accounts-summary__metrics">
<div className="broker-accounts-summary__metric">
<dt>За день</dt>
<dd>
{formatBrokerSignedCurrencyValue(
portfolioSummary.currency,
portfolioSummary.daily,
)}
</dd>
</div>
<div className="broker-accounts-summary__metric">
<dt>Динамика</dt>
<dd>{formatBrokerSignedPercent(portfolioSummary.dailyPercent)}</dd>
</div>
<div className="broker-accounts-summary__metric">
<dt>Свободные деньги</dt>
<dd>
{formatBrokerCurrencyValue(
portfolioSummary.currency,
aggregate.cash.find((cash) => cash.currency === portfolioSummary.currency)
?.value ?? 0,
)}
</dd>
</div>
</dl>
<BrokerAllocationBar
title={`Распределение активов ${portfolioSummary.currency}`}
items={allocation.sectors}
/>
</article>
);
})}
</div>
</section>
);
}