import { useMemo, useState } from 'react' import { Spinner } from '@shared/ui' import type { Pool } from '@features/pools' import { formatFeeTier } from '../model/meta' import styles from './PoolsTable.module.css' interface Props { pools: Pool[] selected: string | null onSelect: (poolAddress: string) => void loading?: boolean error?: string | null } const DASH = '—' type SortKey = 'pair' | 'feeTier' | 'currentPrice' | 'tvlUSD' | 'volumeUSD24h' | 'aprPercent' type SortDir = 'desc' | 'asc' function formatUsd(value: number | null): string { if (value == null) return DASH if (value >= 1_000_000) return `$${(value / 1_000_000).toFixed(2)}M` if (value >= 1_000) return `$${(value / 1_000).toFixed(2)}K` return `$${value.toFixed(2)}` } function formatPrice(value: number): string { return value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } // APR обрезаем до 3 знаков после запятой и убираем лишние нули. function formatApr(value: number | null): string { if (value == null) return DASH return `${parseFloat(value.toFixed(3))}%` } function pairName(p: Pool): string { return `${p.symbol0}/${p.symbol1}` } // Числовые поля могут быть null — null уходит вниз независимо от направления. function compare(a: Pool, b: Pool, key: SortKey, dir: SortDir): number { if (key === 'pair') { const cmp = pairName(a).localeCompare(pairName(b)) return dir === 'asc' ? cmp : -cmp } const av = a[key] const bv = b[key] if (av == null && bv == null) return 0 if (av == null) return 1 if (bv == null) return -1 const cmp = av - bv return dir === 'asc' ? cmp : -cmp } export function PoolsTable({ pools, selected, onSelect, loading, error }: Props) { const [sortKey, setSortKey] = useState(null) const [sortDir, setSortDir] = useState(null) function handleSort(key: SortKey) { if (sortKey !== key) { // Новая колонка — начинаем с убывания. setSortKey(key) setSortDir('desc') return } // Цикл по той же колонке: убывание → возрастание → по умолчанию. if (sortDir === 'desc') { setSortDir('asc') } else if (sortDir === 'asc') { setSortKey(null) setSortDir(null) } else { setSortDir('desc') } } const sortedPools = useMemo(() => { if (!sortKey || !sortDir) return pools return [...pools].sort((a, b) => compare(a, b, sortKey, sortDir)) }, [pools, sortKey, sortDir]) function sortArrow(key: SortKey): string { if (sortKey !== key || !sortDir) return '' return sortDir === 'desc' ? ' ▼' : ' ▲' } const columns: { key: SortKey; label: string; align: 'left' | 'right' }[] = [ { key: 'pair', label: 'Пул', align: 'left' }, { key: 'feeTier', label: 'Комиссия', align: 'right' }, { key: 'currentPrice', label: 'Цена', align: 'right' }, { key: 'tvlUSD', label: 'TVL', align: 'right' }, { key: 'volumeUSD24h', label: 'Объём 24ч', align: 'right' }, { key: 'aprPercent', label: 'APR', align: 'right' }, ] return (
Курируемые пулы
{loading && } {!loading && error &&
{error}
} {!loading && !error && pools.length === 0 && (
Курируемых пулов пока нет.
)} {!loading && !error && pools.length > 0 && (
{columns.map((col) => ( ))} {sortedPools.map((p) => { const isSelected = p.poolAddress === selected return ( onSelect(p.poolAddress)} > ) })}
handleSort(col.key)} aria-sort={sortKey === col.key ? (sortDir === 'asc' ? 'ascending' : 'descending') : 'none'} > {col.label} {sortArrow(col.key)}
{p.symbol0}/{p.symbol1} {formatFeeTier(p.feeTier)} {formatPrice(p.currentPrice)} {formatUsd(p.tvlUSD)} {formatUsd(p.volumeUSD24h)} {formatApr(p.aprPercent)}
)}
) }