feat: add BrokerAnalyticsPage component with loading/error/empty states

This commit is contained in:
Sergey Krylov 2026-06-24 09:45:59 +03:00
parent 3f0fb42d99
commit a1e68fb13a
2 changed files with 163 additions and 0 deletions

View File

@ -0,0 +1 @@
export { BrokerAnalyticsPage } from './ui/BrokerAnalyticsPage'

View File

@ -0,0 +1,162 @@
import { useBrokerAnalytics } from '@/entities/broker-analytics'
import { useBrokerAccountContext } from '@/widgets/broker-account-layout'
function formatAmount(value: number, currency: string): string {
return `${value.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ${currency}`
}
export function BrokerAnalyticsPage() {
const { accountId } = useBrokerAccountContext()
const { data, isLoading, isError } = useBrokerAnalytics(accountId)
if (isLoading) {
return (
<section>
<div style={{ padding: '24px', color: 'var(--color-text-secondary)' }}>Загрузка...</div>
</section>
)
}
if (isError) {
return (
<section>
<div style={{ padding: '24px', color: 'var(--color-text-negative)' }}>
Не удалось загрузить аналитику
</div>
</section>
)
}
if (!data || (data.totalDeposits === 0 && data.totalReceived === 0)) {
return (
<section>
<div style={{ padding: '24px', color: 'var(--color-text-secondary)' }}>
Нет данных для аналитики
</div>
</section>
)
}
return (
<section>
<div
style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '16px',
marginBottom: '24px',
}}
>
{/* Вложено */}
<div
style={{
background: 'var(--color-surface-card)',
borderRadius: '12px',
padding: '20px',
}}
>
<h3 style={{ margin: '0 0 16px', fontSize: '16px', fontWeight: 600 }}>Вложено</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<Row label="Пополнения" value={formatAmount(data.totalDeposits, data.currency)} />
<Row
label="Выводы"
value={`${formatAmount(data.totalWithdrawn, data.currency)}`}
negative
/>
<Divider />
<Row label="Нетто" value={formatAmount(data.netInvested, data.currency)} bold />
</div>
</div>
{/* Получено */}
<div
style={{
background: 'var(--color-surface-card)',
borderRadius: '12px',
padding: '20px',
}}
>
<h3 style={{ margin: '0 0 16px', fontSize: '16px', fontWeight: 600 }}>Получено</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<Row label="Дивиденды" value={formatAmount(data.totalDividends, data.currency)} />
<Row label="Купоны" value={formatAmount(data.totalCoupons, data.currency)} />
<Divider />
<Row label="Итого" value={formatAmount(data.totalReceived, data.currency)} bold />
</div>
</div>
</div>
{/* Сводка */}
<div
style={{
background: 'var(--color-surface-card)',
borderRadius: '12px',
padding: '20px',
}}
>
<h3 style={{ margin: '0 0 16px', fontSize: '16px', fontWeight: 600 }}>Сводка</h3>
<div style={{ display: 'flex', gap: '48px' }}>
<SummaryItem
label="Вложено нетто"
value={formatAmount(data.netInvested, data.currency)}
/>
<SummaryItem label="Получено" value={formatAmount(data.totalReceived, data.currency)} />
{data.totalReturnPercent !== null && (
<SummaryItem label="Доходность" value={`${data.totalReturnPercent.toFixed(2)}%`} />
)}
</div>
</div>
</section>
)
}
function Row({
label,
value,
bold,
negative,
}: {
label: string
value: string
bold?: boolean
negative?: boolean
}) {
const color = negative ? 'var(--color-text-negative, #ef4444)' : 'var(--color-text-primary)'
return (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ color: 'var(--color-text-secondary)', fontSize: '14px' }}>{label}</span>
<span style={{ fontWeight: bold ? 600 : 400, color, fontSize: '15px' }}>{value}</span>
</div>
)
}
function Divider() {
return (
<div
style={{
height: '1px',
background: 'var(--color-border)',
margin: '4px 0',
}}
/>
)
}
function SummaryItem({ label, value }: { label: string; value: string }) {
return (
<div>
<div style={{ color: 'var(--color-text-secondary)', fontSize: '13px', marginBottom: '4px' }}>
{label}
</div>
<div
style={{
fontSize: '20px',
fontWeight: 600,
color: 'var(--color-text-primary)',
}}
>
{value}
</div>
</div>
)
}