feat(frontend): add pagination and skeleton to BrokerPositionsSection

This commit is contained in:
Sergey Krylov 2026-06-17 14:45:27 +03:00
parent d7b5a376a0
commit e0c9d97ded

View File

@ -1,6 +1,7 @@
import { Link } from 'react-router-dom';
import type { BrokerMoney, BrokerPosition } from '../../api/responses';
import { getBrokerInstrumentPath, getBrokerPositionGroup } from './brokerDisplay';
import { TableSkeleton } from '../../components/TableSkeleton';
type BrokerPositionGroupConfig = {
key: 'shares' | 'bonds' | 'other';
@ -32,9 +33,26 @@ const tdStyle = {
verticalAlign: 'top',
} satisfies React.CSSProperties;
const pagButtonStyle = {
padding: '6px 14px',
borderRadius: 6,
border: '1px solid #e0e0e0',
background: 'var(--color-surface)',
color: 'var(--color-text)',
fontSize: 14,
fontWeight: 600,
cursor: 'pointer',
lineHeight: 1.4,
} satisfies React.CSSProperties;
const pagButtonDisabledStyle = {
...pagButtonStyle,
opacity: 0.35,
cursor: 'not-allowed',
} satisfies React.CSSProperties;
function formatMoney(value: BrokerMoney | null | undefined) {
if (!value) return '-';
return new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: value.currency || 'RUB',
@ -123,29 +141,161 @@ function PositionTable({ title, positions }: { title: string; positions: BrokerP
);
}
export function BrokerPositionsSection({ positions }: { positions: BrokerPosition[] }) {
type BrokerPositionsSectionProps = {
page: { items: BrokerPosition[] } | undefined;
isLoading: boolean;
pageNumber: number;
canGoBack: boolean;
canGoForward: boolean;
onPrevious: () => void;
onNext: () => void;
};
export function BrokerPositionsSection({
page,
isLoading,
pageNumber,
canGoBack,
canGoForward,
onPrevious,
onNext,
}: BrokerPositionsSectionProps) {
const positions = page?.items ?? [];
const grouped = GROUPS.map((group) => ({
...group,
positions: positions.filter((position) => getBrokerPositionGroup(position) === group.key),
})).filter((group) => group.positions.length > 0);
if (grouped.length === 0) {
return (
<section>
<h2 style={{ fontSize: 20, marginBottom: 12 }}>Позиции</h2>
<p style={{ color: 'var(--color-text-secondary)' }}>В портфеле нет позиций</p>
</section>
);
}
return (
<section>
<h2 style={{ fontSize: 20, marginBottom: 12 }}>Позиции</h2>
<div style={{ display: 'grid', gap: 20 }}>
{grouped.map((group) => (
<PositionTable key={group.key} title={group.title} positions={group.positions} />
))}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 12,
justifyContent: 'space-between',
marginBottom: 12,
}}
>
<h2 style={{ fontSize: 20, margin: 0 }}>Позиции</h2>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<button
type="button"
onClick={onPrevious}
disabled={!canGoBack}
style={canGoBack ? pagButtonStyle : pagButtonDisabledStyle}
>
</button>
<span
style={{
minWidth: 20,
textAlign: 'center',
color: 'var(--color-text-secondary)',
fontSize: 14,
fontWeight: 600,
}}
>
{pageNumber}
</span>
<button
type="button"
onClick={onNext}
disabled={!canGoForward}
style={canGoForward ? pagButtonStyle : pagButtonDisabledStyle}
>
</button>
</div>
</div>
{isLoading && grouped.length === 0 ? (
<div style={{ overflowX: 'auto', background: 'var(--color-surface)' }}>
<table style={tableStyle}>
<thead>
<tr>
<th align="left" style={thStyle}>
Тикер
</th>
<th align="left" style={thStyle}>
Название
</th>
<th align="right" style={thStyle}>
Количество
</th>
<th align="right" style={thStyle}>
Цена
</th>
<th align="right" style={thStyle}>
Стоимость
</th>
</tr>
</thead>
<TableSkeleton
rows={4}
columns={[
{ width: '30%' },
{ width: '50%' },
{ width: '20%' },
{ width: '25%' },
{ width: '25%' },
]}
/>
</table>
</div>
) : grouped.length === 0 ? (
<p style={{ color: 'var(--color-text-secondary)' }}>В портфеле нет позиций</p>
) : isLoading ? (
<div>
<div style={{ display: 'grid', gap: 20 }}>
{grouped.map((group) => (
<div key={group.key}>
<h3 style={{ fontSize: 18, marginBottom: 10 }}>{group.title}</h3>
<div style={{ overflowX: 'auto', background: 'var(--color-surface)' }}>
<table style={tableStyle}>
<thead>
<tr>
<th align="left" style={thStyle}>
Тикер
</th>
<th align="left" style={thStyle}>
Название
</th>
<th align="right" style={thStyle}>
Количество
</th>
<th align="right" style={thStyle}>
Цена
</th>
<th align="right" style={thStyle}>
Стоимость
</th>
</tr>
</thead>
<TableSkeleton
rows={2}
columns={[
{ width: '30%' },
{ width: '50%' },
{ width: '20%' },
{ width: '25%' },
{ width: '25%' },
]}
/>
</table>
</div>
</div>
))}
</div>
</div>
) : (
<div style={{ display: 'grid', gap: 20 }}>
{grouped.map((group) => (
<PositionTable key={group.key} title={group.title} positions={group.positions} />
))}
</div>
)}
</section>
);
}