feat: add BrokerAnalyticsPage component with loading/error/empty states
This commit is contained in:
parent
3f0fb42d99
commit
a1e68fb13a
1
apps/frontend/src/pages/broker-analytics/index.ts
Normal file
1
apps/frontend/src/pages/broker-analytics/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { BrokerAnalyticsPage } from './ui/BrokerAnalyticsPage'
|
||||||
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user