add sorting to market selection
This commit is contained in:
parent
e78e579611
commit
5be792f47f
|
@ -26,6 +26,8 @@ import useListedMarketsWithMarketData, {
|
|||
} from 'hooks/useListedMarketsWithMarketData'
|
||||
import { AllowedKeys, sortPerpMarkets, sortSpotMarkets } from 'utils/markets'
|
||||
import Input from '@components/forms/Input'
|
||||
import { useSortableData } from 'hooks/useSortableData'
|
||||
import { SortableColumnHeader } from '@components/shared/TableElements'
|
||||
|
||||
const MARKET_LINK_CLASSES =
|
||||
'grid grid-cols-3 md:grid-cols-4 flex items-center w-full py-2 px-4 rounded-r-md focus:outline-none focus-visible:text-th-active md:hover:cursor-pointer md:hover:bg-th-bkg-3 md:hover:text-th-fgd-1'
|
||||
|
@ -78,7 +80,7 @@ const MarketSelectDropdown = () => {
|
|||
const [spotOrPerp, setSpotOrPerp] = useState(
|
||||
selectedMarket instanceof PerpMarket ? 'perp' : 'spot',
|
||||
)
|
||||
const [sortByKey] = useState<AllowedKeys>('quote_volume_24h')
|
||||
const defaultSortByKey: AllowedKeys = 'quote_volume_24h'
|
||||
const [search, setSearch] = useState('')
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const { group } = useMangoGroup()
|
||||
|
@ -87,10 +89,10 @@ const MarketSelectDropdown = () => {
|
|||
useListedMarketsWithMarketData()
|
||||
const focusRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const perpMarketsToShow = useMemo(() => {
|
||||
const unsortedPerpMarketsToShow = useMemo(() => {
|
||||
if (!perpMarketsWithData.length) return []
|
||||
return sortPerpMarkets(perpMarketsWithData, sortByKey)
|
||||
}, [perpMarketsWithData, sortByKey])
|
||||
return sortPerpMarkets(perpMarketsWithData, defaultSortByKey)
|
||||
}, [perpMarketsWithData])
|
||||
|
||||
const spotBaseTokens: string[] = useMemo(() => {
|
||||
if (serumMarketsWithData.length) {
|
||||
|
@ -106,7 +108,7 @@ const MarketSelectDropdown = () => {
|
|||
return ['All']
|
||||
}, [serumMarketsWithData])
|
||||
|
||||
const serumMarketsToShow = useMemo(() => {
|
||||
const unsortedSerumMarketsToShow = useMemo(() => {
|
||||
if (!serumMarketsWithData.length) return []
|
||||
if (spotBaseFilter !== 'All') {
|
||||
const filteredMarkets = serumMarketsWithData.filter((m) => {
|
||||
|
@ -115,18 +117,30 @@ const MarketSelectDropdown = () => {
|
|||
})
|
||||
return search
|
||||
? startSearch(filteredMarkets, search)
|
||||
: sortSpotMarkets(filteredMarkets, sortByKey)
|
||||
: sortSpotMarkets(filteredMarkets, defaultSortByKey)
|
||||
} else {
|
||||
return search
|
||||
? startSearch(serumMarketsWithData, search)
|
||||
: sortSpotMarkets(serumMarketsWithData, sortByKey)
|
||||
: sortSpotMarkets(serumMarketsWithData, defaultSortByKey)
|
||||
}
|
||||
}, [search, serumMarketsWithData, sortByKey, spotBaseFilter])
|
||||
}, [search, serumMarketsWithData, spotBaseFilter])
|
||||
|
||||
const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value)
|
||||
}
|
||||
|
||||
const {
|
||||
items: perpMarketsToShow,
|
||||
requestSort: requestPerpSort,
|
||||
sortConfig: perpSortConfig,
|
||||
} = useSortableData(unsortedPerpMarketsToShow)
|
||||
|
||||
const {
|
||||
items: serumMarketsToShow,
|
||||
requestSort: requestSerumSort,
|
||||
sortConfig: serumSortConfig,
|
||||
} = useSortableData(unsortedSerumMarketsToShow)
|
||||
|
||||
useEffect(() => {
|
||||
if (focusRef?.current && spotOrPerp === 'spot') {
|
||||
focusRef.current.focus()
|
||||
|
@ -181,13 +195,39 @@ const MarketSelectDropdown = () => {
|
|||
{spotOrPerp === 'perp' && perpMarketsToShow.length ? (
|
||||
<>
|
||||
<div className="mb-2 grid grid-cols-3 border-b border-th-bkg-3 pb-1 pl-4 pr-14 text-xxs md:grid-cols-4">
|
||||
<p className="col-span-1">{t('market')}</p>
|
||||
<p className="col-span-1 text-right">{t('price')}</p>
|
||||
<p className="col-span-1 text-right">
|
||||
{t('rolling-change')}
|
||||
<div className="col-span-1 flex-1 ">
|
||||
<SortableColumnHeader
|
||||
sortKey="name"
|
||||
sort={() => requestPerpSort('name')}
|
||||
sortConfig={perpSortConfig}
|
||||
title={t('market')}
|
||||
/>
|
||||
</div>
|
||||
<p className="col-span-1 flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="marketData.last_price"
|
||||
sort={() => requestPerpSort('marketData.last_price')}
|
||||
sortConfig={perpSortConfig}
|
||||
title={t('price')}
|
||||
/>
|
||||
</p>
|
||||
<p className="col-span-1 hidden text-right md:block">
|
||||
{t('daily-volume')}
|
||||
<p className="col-span-1 flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="rollingChange"
|
||||
sort={() => requestPerpSort('rollingChange')}
|
||||
sortConfig={perpSortConfig}
|
||||
title={t('rolling-change')}
|
||||
/>
|
||||
</p>
|
||||
<p className="col-span-1 flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="marketData.quote_volume_24h"
|
||||
sort={() =>
|
||||
requestPerpSort('marketData.quote_volume_24h')
|
||||
}
|
||||
sortConfig={perpSortConfig}
|
||||
title={t('daily-volume')}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
{perpMarketsToShow.map((m) => {
|
||||
|
@ -296,33 +336,41 @@ const MarketSelectDropdown = () => {
|
|||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* need to sort out change before enabling more sorting options */}
|
||||
{/* <div>
|
||||
<Select
|
||||
value={sortByKey}
|
||||
onChange={(sortBy) => setSortByKey(sortBy)}
|
||||
className="w-full"
|
||||
>
|
||||
{SORT_KEYS.map((sortBy) => {
|
||||
return (
|
||||
<Select.Option key={sortBy} value={sortBy}>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
{sortBy}
|
||||
</div>
|
||||
</Select.Option>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="mb-2 grid grid-cols-3 border-b border-th-bkg-3 pb-1 pl-4 pr-14 text-xxs md:grid-cols-4">
|
||||
<p className="col-span-1">{t('market')}</p>
|
||||
<p className="col-span-1 text-right">{t('price')}</p>
|
||||
<p className="col-span-1 text-right">
|
||||
{t('rolling-change')}
|
||||
<p className="col-span-1 flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="name"
|
||||
sort={() => requestSerumSort('name')}
|
||||
sortConfig={serumSortConfig}
|
||||
title={t('market')}
|
||||
/>
|
||||
</p>
|
||||
<p className="col-span-1 hidden text-right md:block">
|
||||
{t('daily-volume')}
|
||||
<p className="col-span-1 flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="marketData.last_price"
|
||||
sort={() => requestSerumSort('marketData.last_price')}
|
||||
sortConfig={serumSortConfig}
|
||||
title={t('price')}
|
||||
/>
|
||||
</p>
|
||||
<p className="col-span-1 flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="rollingChange"
|
||||
sort={() => requestSerumSort('rollingChange')}
|
||||
sortConfig={serumSortConfig}
|
||||
title={t('rolling-change')}
|
||||
/>
|
||||
</p>
|
||||
<p className="col-span-1 flex justify-end">
|
||||
<SortableColumnHeader
|
||||
sortKey="marketData.quote_volume_24h"
|
||||
sort={() =>
|
||||
requestSerumSort('marketData.quote_volume_24h')
|
||||
}
|
||||
sortConfig={serumSortConfig}
|
||||
title={t('daily-volume')}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
{serumMarketsToShow.map((m) => {
|
||||
|
|
|
@ -8,9 +8,17 @@ type ApiData = {
|
|||
marketData: MarketsDataItem | undefined
|
||||
}
|
||||
|
||||
export type SerumMarketWithMarketData = Serum3Market & ApiData
|
||||
type MarketRollingChange = {
|
||||
rollingChange: number | undefined
|
||||
}
|
||||
|
||||
export type PerpMarketWithMarketData = PerpMarket & ApiData
|
||||
export type SerumMarketWithMarketData = Serum3Market &
|
||||
ApiData &
|
||||
MarketRollingChange
|
||||
|
||||
export type PerpMarketWithMarketData = PerpMarket &
|
||||
ApiData &
|
||||
MarketRollingChange
|
||||
|
||||
export default function useListedMarketsWithMarketData() {
|
||||
const { data: marketsData, isLoading, isFetching } = useMarketsData()
|
||||
|
@ -27,6 +35,29 @@ export default function useListedMarketsWithMarketData() {
|
|||
return marketsData?.spotData || []
|
||||
}, [marketsData])
|
||||
|
||||
const currentPrices = useMemo(() => {
|
||||
let prices: { [key: string]: number } = {}
|
||||
const group = mangoStore.getState().group
|
||||
serumMarkets.forEach((market) => {
|
||||
if (!group || !market || market instanceof PerpMarket) {
|
||||
prices[market.name] = 0
|
||||
return
|
||||
}
|
||||
const baseBank = group.getFirstBankByTokenIndex(market.baseTokenIndex)
|
||||
const quoteBank = group.getFirstBankByTokenIndex(market.quoteTokenIndex)
|
||||
if (!baseBank || !quoteBank) {
|
||||
prices[market.name] = 0
|
||||
return
|
||||
}
|
||||
prices[market.name] = baseBank.uiPrice / quoteBank.uiPrice
|
||||
})
|
||||
|
||||
perpMarkets.forEach((market) => {
|
||||
prices[market.name] = market.uiPrice
|
||||
})
|
||||
return prices
|
||||
}, [serumMarkets, perpMarkets])
|
||||
|
||||
const serumMarketsWithData = useMemo(() => {
|
||||
if (!serumMarkets || !serumMarkets.length) return []
|
||||
const allSpotMarkets: SerumMarketWithMarketData[] =
|
||||
|
@ -36,6 +67,17 @@ export default function useListedMarketsWithMarketData() {
|
|||
const spotEntries = Object.entries(spotData).find(
|
||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
||||
)
|
||||
|
||||
// calculate price change
|
||||
const pastPrice = spotEntries ? spotEntries[1][0]?.price_24h : 0
|
||||
const dailyVolume = spotEntries
|
||||
? spotEntries[1][0]?.quote_volume_24h
|
||||
: 0
|
||||
const currentPrice = currentPrices[market.name]
|
||||
const change =
|
||||
dailyVolume > 0 ? ((currentPrice - pastPrice) / pastPrice) * 100 : 0
|
||||
|
||||
market.rollingChange = change
|
||||
market.marketData = spotEntries ? spotEntries[1][0] : undefined
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +93,11 @@ export default function useListedMarketsWithMarketData() {
|
|||
const perpEntries = Object.entries(perpData).find(
|
||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
||||
)
|
||||
const pastPrice = perpEntries ? perpEntries[1][0]?.price_24h : 0
|
||||
const currentPrice = currentPrices[market.name]
|
||||
|
||||
market.marketData = perpEntries ? perpEntries[1][0] : undefined
|
||||
market.rollingChange = ((currentPrice - pastPrice) / pastPrice) * 100
|
||||
}
|
||||
}
|
||||
return allPerpMarkets
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { useMemo, useState } from 'react'
|
||||
import get from 'lodash/get'
|
||||
|
||||
type Direction = 'ascending' | 'descending'
|
||||
|
||||
|
@ -22,15 +23,15 @@ export function useSortableData<T extends Record<string, any>>(
|
|||
const sortableItems = items ? [...items] : []
|
||||
if (sortConfig !== null) {
|
||||
sortableItems.sort((a, b) => {
|
||||
if (!isNaN(a[sortConfig.key])) {
|
||||
if (!isNaN(get(a, sortConfig.key))) {
|
||||
return sortConfig.direction === 'ascending'
|
||||
? a[sortConfig.key] - b[sortConfig.key]
|
||||
: b[sortConfig.key] - a[sortConfig.key]
|
||||
? get(a, sortConfig.key) - get(b, sortConfig.key)
|
||||
: get(b, sortConfig.key) - get(a, sortConfig.key)
|
||||
}
|
||||
if (a[sortConfig.key] < b[sortConfig.key]) {
|
||||
if (get(a, sortConfig.key) < get(b, sortConfig.key)) {
|
||||
return sortConfig.direction === 'ascending' ? -1 : 1
|
||||
}
|
||||
if (a[sortConfig.key] > b[sortConfig.key]) {
|
||||
if (get(a, sortConfig.key) > get(b, sortConfig.key)) {
|
||||
return sortConfig.direction === 'ascending' ? 1 : -1
|
||||
}
|
||||
return 0
|
||||
|
|
Loading…
Reference in New Issue