moex-vibe/apps/frontend/src/pages/broker-operations/ui/BrokerOperationsPage.tsx
Sergey Krylov 3dfcf5aaa8
Some checks failed
CI / ci (pull_request) Failing after 3m39s
CI / ci (push) Failing after 3m10s
feat: complete broker account analytics with sync button, tests, and state filter
- Add state filter (EXECUTED/null) to analytics query (spec compliance)
- Add service unit tests (11 tests) and controller test
- Add sync button to operations page with mutation hook
- Regenerate frontend types via codegen
- Update tasks.md marking all items complete

Backend: 114 tests, Frontend: 116 tests — all pass
2026-06-24 11:29:09 +03:00

114 lines
4.0 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 { Button, Heading, Text } from '@moex-vibe/design-system'
import { Box } from '@mui/material'
import { useEffect } from 'react'
import {
BROKER_OPERATION_TYPE_OPTIONS,
isBrokerOperationType,
useBrokerOperations,
useSyncBrokerOperations,
} from '@/entities/broker-operation'
import { useSearchParamsCompat } from '@/shared/lib/router/useSearchParams'
import { useCursorPagination } from '@/shared/lib/useCursorPagination'
import { useBrokerAccountContext } from '@/widgets/broker-account-layout'
import { BrokerOperationsTable } from '@/widgets/broker-operations-table'
function getDefaultSyncRange() {
const now = new Date()
const start = new Date(Date.UTC(now.getUTCFullYear(), 0, 1))
return {
from: start.toISOString(),
to: now.toISOString(),
}
}
export function BrokerOperationsPage() {
const { accountId } = useBrokerAccountContext()
const sync = useSyncBrokerOperations(accountId)
const [searchParams, setSearchParams] = useSearchParamsCompat()
const urlType = searchParams.get('type')
const selectedType = isBrokerOperationType(urlType) ? urlType : ''
const pagination = useCursorPagination()
const operations = useBrokerOperations(accountId, {
limit: 10,
cursor: pagination.cursor,
operationTypes: selectedType || undefined,
})
useEffect(() => {
pagination.reset()
}, [pagination.reset])
function handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>) {
const nextType = event.target.value
setSearchParams(nextType ? { type: nextType } : {}, { replace: true })
}
const history = operations.error ? (
<Text component="p" tone="negative" role="alert">
Не удалось загрузить историю операций
</Text>
) : (
<BrokerOperationsTable
title="История операций"
emptyMessage={
selectedType ? 'Операций выбранного типа нет' : 'Операций с начала текущего года нет'
}
isLoading={operations.isLoading}
isFetching={operations.isFetching}
page={operations.data}
pagination={{
pageNumber: pagination.pageNumber,
canGoBack: pagination.pageNumber > 1,
canGoForward: Boolean(operations.data?.hasNext && operations.data.nextCursor),
onPrevious: pagination.handlePrevious,
onNext: () => pagination.handleNext(operations.data?.nextCursor),
}}
/>
)
function handleSync() {
const range = getDefaultSyncRange()
sync.mutate(range)
}
return (
<Box component="section" aria-labelledby="broker-operations-heading">
<Box
sx={{ display: 'flex', alignItems: 'end', justifyContent: 'space-between', gap: 2, mb: 2 }}
>
<Box sx={{ display: 'flex', alignItems: 'end', gap: 2 }}>
<Heading level={2} id="broker-operations-heading">
Операции
</Heading>
<Button variant="secondary" size="small" loading={sync.isPending} onClick={handleSync}>
Синхронизировать
</Button>
</Box>
<Box component="label" sx={{ display: 'grid', gap: 0.5, color: 'text.secondary' }}>
<Text variant="label">Тип операции</Text>
<select value={selectedType} onChange={handleTypeChange}>
<option value="">Все операции</option>
{BROKER_OPERATION_TYPE_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</Box>
</Box>
{sync.isSuccess && (
<Text component="p" tone="positive" style={{ marginBottom: '12px' }}>
Синхронизировано: {sync.data?.upserted} операций
</Text>
)}
{sync.isError && (
<Text component="p" tone="negative" style={{ marginBottom: '12px' }}>
Ошибка синхронизации
</Text>
)}
{history}
</Box>
)
}