sort spot markets table

This commit is contained in:
saml33 2023-07-28 12:44:43 +10:00
parent 5daf0173fc
commit 846d1a7bfc
2 changed files with 235 additions and 152 deletions

View File

@ -40,7 +40,7 @@ const Change = ({
? 'text-th-up'
: change < 0
? 'text-th-down'
: 'text-th-fgd-4'
: 'text-th-fgd-2'
}`}
>
{prefix ? prefix : ''}

View File

@ -1,23 +1,49 @@
import { useTranslation } from 'next-i18next'
import { useMemo } from 'react'
import { useCallback } from 'react'
import { useViewport } from '../../hooks/useViewport'
import { COLORS } from '../../styles/colors'
import { breakpoints } from '../../utils/theme'
import ContentBox from '../shared/ContentBox'
import MarketLogos from '@components/trade/MarketLogos'
import useMangoGroup from 'hooks/useMangoGroup'
import { Table, Td, Th, TrBody, TrHead } from '@components/shared/TableElements'
import {
SortableColumnHeader,
Table,
Td,
Th,
TrBody,
TrHead,
} from '@components/shared/TableElements'
import FormatNumericValue from '@components/shared/FormatNumericValue'
import { floorToDecimal, getDecimalCount, numberCompacter } from 'utils/numbers'
import SimpleAreaChart from '@components/shared/SimpleAreaChart'
import { Disclosure, Transition } from '@headlessui/react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import MarketChange from '@components/shared/MarketChange'
import useThemeWrapper from 'hooks/useThemeWrapper'
import useListedMarketsWithMarketData, {
SerumMarketWithMarketData,
} from 'hooks/useListedMarketsWithMarketData'
import { sortSpotMarkets } from 'utils/markets'
import { useSortableData } from 'hooks/useSortableData'
import Change from '@components/shared/Change'
import { Bank } from '@blockworks-foundation/mango-v4'
type TableData = {
baseBank: Bank | undefined
change: number
market: SerumMarketWithMarketData
marketName: string
price: number
priceHistory:
| {
price: number
time: string
}[]
| undefined
quoteBank: Bank | undefined
volume: number
isUp: boolean
}
const SpotMarketsTable = () => {
const { t } = useTranslation('common')
@ -28,6 +54,64 @@ const SpotMarketsTable = () => {
const { serumMarketsWithData, isLoading, isFetching } =
useListedMarketsWithMarketData()
const formatedTableData = useCallback(
(markets: SerumMarketWithMarketData[]) => {
const formatted = []
for (const m of markets) {
const baseBank = group?.getFirstBankByTokenIndex(m.baseTokenIndex)
const quoteBank = group?.getFirstBankByTokenIndex(m.quoteTokenIndex)
const market = group?.getSerum3ExternalMarket(m.serumMarketExternal)
let price = 0
if (baseBank && market && quoteBank) {
price = floorToDecimal(
baseBank.uiPrice / quoteBank.uiPrice,
getDecimalCount(market.tickSize),
).toNumber()
}
const pastPrice = m?.marketData?.price_24h || 0
const priceHistory = m?.marketData?.price_history
const volume = m?.marketData?.quote_volume_24h || 0
const change = volume > 0 ? ((price - pastPrice) / pastPrice) * 100 : 0
const marketName = m.name
const isUp =
price && priceHistory && priceHistory.length
? price >= priceHistory[0].price
: false
const data = {
baseBank,
change,
market: m,
marketName,
price,
priceHistory,
quoteBank,
volume,
isUp,
}
formatted.push(data)
}
return formatted
},
[group],
)
const {
items: tableData,
requestSort,
sortConfig,
} = useSortableData(
formatedTableData(
sortSpotMarkets(serumMarketsWithData, 'quote_volume_24h'),
),
)
const loadingMarketData = isLoading || isFetching
return (
@ -36,142 +120,152 @@ const SpotMarketsTable = () => {
<Table>
<thead>
<TrHead>
<Th className="text-left">{t('market')}</Th>
<Th className="text-right">{t('price')}</Th>
<Th className="text-right">{t('rolling-change')}</Th>
<Th className="text-left">
<SortableColumnHeader
sortKey="marketName"
sort={() => requestSort('marketName')}
sortConfig={sortConfig}
title={t('market')}
/>
</Th>
<Th>
<div className="flex justify-end">
<SortableColumnHeader
sortKey="price"
sort={() => requestSort('price')}
sortConfig={sortConfig}
title={t('price')}
/>
</div>
</Th>
<Th>
<div className="flex justify-end">
<SortableColumnHeader
sortKey="change"
sort={() => requestSort('change')}
sortConfig={sortConfig}
title={t('rolling-change')}
/>
</div>
</Th>
<Th className="hidden text-right md:block"></Th>
<Th className="text-right">{t('trade:24h-volume')}</Th>
<Th>
<div className="flex justify-end">
<SortableColumnHeader
sortKey="volume"
sort={() => requestSort('volume')}
sortConfig={sortConfig}
title={t('trade:24h-volume')}
/>
</div>
</Th>
</TrHead>
</thead>
<tbody>
{sortSpotMarkets(serumMarketsWithData, 'quote_volume_24h').map(
(mkt) => {
const baseBank = group?.getFirstBankByTokenIndex(
mkt.baseTokenIndex,
)
const quoteBank = group?.getFirstBankByTokenIndex(
mkt.quoteTokenIndex,
)
const market = group?.getSerum3ExternalMarket(
mkt.serumMarketExternal,
)
let price
if (baseBank && market && quoteBank) {
price = floorToDecimal(
baseBank.uiPrice / quoteBank.uiPrice,
getDecimalCount(market.tickSize),
).toNumber()
}
{tableData.map((data) => {
const {
baseBank,
change,
market,
marketName,
price,
priceHistory,
quoteBank,
volume,
isUp,
} = data
const priceHistory = mkt?.marketData?.price_history
const volumeData = mkt?.marketData?.quote_volume_24h
const volume = volumeData ? volumeData : 0
const isUp =
price && priceHistory && priceHistory.length
? price >= priceHistory[0].price
: false
return (
<TrBody key={mkt.publicKey.toString()}>
<Td>
<div className="flex items-center">
<MarketLogos market={mkt} size="large" />
<p className="font-body">{mkt.name}</p>
</div>
</Td>
<Td>
<div className="flex flex-col text-right">
<p>
{price ? (
<>
<FormatNumericValue
value={price}
isUsd={quoteBank?.name === 'USDC'}
/>{' '}
{quoteBank?.name !== 'USDC' ? (
<span className="font-body text-th-fgd-4">
{quoteBank?.name}
</span>
) : null}
</>
) : (
''
)}
</p>
</div>
</Td>
<Td>
<div className="flex flex-col items-end">
<MarketChange market={mkt} />
</div>
</Td>
<Td>
{!loadingMarketData ? (
priceHistory && priceHistory.length ? (
<div className="h-10 w-24">
<SimpleAreaChart
color={
isUp ? COLORS.UP[theme] : COLORS.DOWN[theme]
}
data={priceHistory}
name={baseBank!.name + quoteBank!.name}
xKey="time"
yKey="price"
/>
</div>
) : baseBank?.name === 'USDC' ||
baseBank?.name === 'USDT' ? null : (
<p className="mb-0 text-th-fgd-4">
{t('unavailable')}
</p>
)
) : (
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
)}
</Td>
<Td>
<div className="flex flex-col text-right">
<p>
{volume ? (
<span>
{numberCompacter.format(volume)}{' '}
return (
<TrBody key={market.publicKey.toString()}>
<Td>
<div className="flex items-center">
<MarketLogos market={market} size="large" />
<p className="font-body">{marketName}</p>
</div>
</Td>
<Td>
<div className="flex flex-col text-right">
<p>
{price ? (
<>
<FormatNumericValue
value={price}
isUsd={quoteBank?.name === 'USDC'}
/>{' '}
{quoteBank?.name !== 'USDC' ? (
<span className="font-body text-th-fgd-4">
{quoteBank?.name}
</span>
) : null}
</>
) : (
''
)}
</p>
</div>
</Td>
<Td>
<div className="flex flex-col items-end">
<Change change={change} suffix="%" />
</div>
</Td>
<Td>
{!loadingMarketData ? (
priceHistory && priceHistory.length ? (
<div className="h-10 w-24">
<SimpleAreaChart
color={isUp ? COLORS.UP[theme] : COLORS.DOWN[theme]}
data={priceHistory}
name={baseBank!.name + quoteBank!.name}
xKey="time"
yKey="price"
/>
</div>
) : baseBank?.name === 'USDC' ||
baseBank?.name === 'USDT' ? null : (
<p className="mb-0 text-th-fgd-4">{t('unavailable')}</p>
)
) : (
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
)}
</Td>
<Td>
<div className="flex flex-col text-right">
<p>
{volume ? (
<span>
{numberCompacter.format(volume)}{' '}
<span className="font-body text-th-fgd-4">
{quoteBank?.name}
</span>
) : (
<span>
0{' '}
<span className="font-body text-th-fgd-4">
{quoteBank?.name}
</span>
</span>
) : (
<span>
0{' '}
<span className="font-body text-th-fgd-4">
{quoteBank?.name}
</span>
)}
</p>
</div>
</Td>
</TrBody>
)
},
)}
</span>
)}
</p>
</div>
</Td>
</TrBody>
)
})}
</tbody>
</Table>
) : (
<div className="border-b border-th-bkg-3">
{sortSpotMarkets(serumMarketsWithData, 'quote_volume_24h').map(
(market) => {
return (
<MobileSpotMarketItem
key={market.publicKey.toString()}
loadingMarketData={loadingMarketData}
market={market}
/>
)
},
)}
{tableData.map((data) => {
return (
<MobileSpotMarketItem
key={data.market.publicKey.toString()}
loadingMarketData={loadingMarketData}
data={data}
/>
)
})}
</div>
)}
</ContentBox>
@ -181,37 +275,26 @@ const SpotMarketsTable = () => {
export default SpotMarketsTable
const MobileSpotMarketItem = ({
market,
data,
loadingMarketData,
}: {
market: SerumMarketWithMarketData
data: TableData
loadingMarketData: boolean
}) => {
const { t } = useTranslation('common')
const { group } = useMangoGroup()
const { theme } = useThemeWrapper()
const baseBank = group?.getFirstBankByTokenIndex(market.baseTokenIndex)
const quoteBank = group?.getFirstBankByTokenIndex(market.quoteTokenIndex)
const serumMarket = group?.getSerum3ExternalMarket(market.serumMarketExternal)
const price = useMemo(() => {
if (!baseBank || !quoteBank || !serumMarket) return 0
return floorToDecimal(
baseBank.uiPrice / quoteBank.uiPrice,
getDecimalCount(serumMarket.tickSize),
).toNumber()
}, [baseBank, quoteBank, serumMarket])
const priceHistory = market?.marketData?.price_history
const volueData = market?.marketData?.quote_volume_24h
const volume = volueData ? volueData : 0
const isUp =
price && priceHistory && priceHistory.length
? price >= priceHistory[0].price
: false
const {
baseBank,
change,
market,
marketName,
price,
priceHistory,
quoteBank,
volume,
isUp,
} = data
return (
<Disclosure>
@ -225,7 +308,7 @@ const MobileSpotMarketItem = ({
<div className="flex flex-shrink-0 items-center">
<MarketLogos market={market} />
</div>
<p className="leading-none text-th-fgd-1">{market.name}</p>
<p className="leading-none text-th-fgd-1">{marketName}</p>
</div>
<div className="flex items-center space-x-3">
{!loadingMarketData ? (
@ -245,7 +328,7 @@ const MobileSpotMarketItem = ({
) : (
<div className="h-10 w-[104px] animate-pulse rounded bg-th-bkg-3" />
)}
<MarketChange market={market} />
<Change change={change} suffix="%" />
<ChevronDownIcon
className={`${
open ? 'rotate-180' : 'rotate-360'