import { useState } from 'react'; import { Link } from 'react-router-dom'; import type { BrokerMoney, BrokerPosition, BrokerPositionsPage as BrokerPositionsPageData, } from '@/shared/api/responses'; import { TableSkeleton } from '@/shared/ui/TableSkeleton'; import { getBrokerInstrumentPath, useBrokerPositions } from '@/entities/broker-position'; import { useBrokerAccountContext } from '@/widgets/broker-account-layout'; const tableStyle = { width: '100%', borderCollapse: 'collapse', fontSize: 14, } satisfies React.CSSProperties; const thStyle = { borderBottom: '1px solid #e0e0e0', color: 'var(--color-text-secondary)', fontWeight: 600, padding: '10px 8px', } satisfies React.CSSProperties; const tdStyle = { borderBottom: '1px solid #eeeeee', padding: '10px 8px', 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', maximumFractionDigits: 2, }).format(value.value); } function formatQuantity(value: number | null | undefined) { return value == null ? '-' : value.toLocaleString('ru-RU'); } function PositionTicker({ position }: { position: BrokerPosition }) { const label = position.ticker || position.figi || '-'; const path = getBrokerInstrumentPath({ ticker: position.ticker, instrumentType: position.instrumentType, classCode: position.classCode, }); if (!path || label === '-') { return {label}; } return ( {label} ); } function BrokerPositionTable({ title, page, isLoading, isFetching, emptyMessage, pageNumber, onNext, onPrevious, }: { title: string; page: BrokerPositionsPageData | undefined; isLoading: boolean; isFetching: boolean; emptyMessage: string; pageNumber: number; onNext: () => void; onPrevious: () => void; }) { const positions = page?.items ?? []; const canGoBack = pageNumber > 1; const canGoForward = Boolean(page?.hasNext && page.nextCursor); return (

{title}

{pageNumber}
{isLoading ? (
Тикер Название Количество Цена Стоимость
) : positions.length === 0 && !isFetching ? (

{emptyMessage}

) : (
{positions.map((position) => ( ))}
Тикер Название Количество Цена Стоимость
{position.name || '-'} {formatQuantity(position.quantity)} {formatMoney(position.currentPrice)} {formatMoney(position.currentValue)}
{isFetching && (
Загрузка страницы {pageNumber}…
)}
)}
); } type BrokerPositionsPageProps = { type: 'share' | 'bond'; title: 'Акции' | 'Облигации'; }; export function BrokerPositionsPage({ type, title }: BrokerPositionsPageProps) { const { accountId } = useBrokerAccountContext(); const [cursor, setCursor] = useState(undefined); const [cursorStack, setCursorStack] = useState>([]); const positions = useBrokerPositions(accountId, { type, limit: 10, cursor }); function handleNext() { const nextCursor = positions.data?.nextCursor; if (!nextCursor || !positions.data?.hasNext) return; setCursorStack((previous) => [...previous, cursor]); setCursor(nextCursor); } function handlePrevious() { if (cursorStack.length === 0) return; setCursor(cursorStack[cursorStack.length - 1]); setCursorStack((previous) => previous.slice(0, -1)); } if (positions.error) { return (

{title}

{type === 'share' ? 'Не удалось загрузить акции' : 'Не удалось загрузить облигации'}

); } return ( ); }