sort spot markets table
This commit is contained in:
parent
5daf0173fc
commit
846d1a7bfc
|
@ -40,7 +40,7 @@ const Change = ({
|
|||
? 'text-th-up'
|
||||
: change < 0
|
||||
? 'text-th-down'
|
||||
: 'text-th-fgd-4'
|
||||
: 'text-th-fgd-2'
|
||||
}`}
|
||||
>
|
||||
{prefix ? prefix : ''}
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue