add sorting to market selection

This commit is contained in:
Lou-Kamades 2023-09-08 17:01:08 -05:00
parent e78e579611
commit 5be792f47f
No known key found for this signature in database
GPG Key ID: 87A166E4D7C01F30
3 changed files with 140 additions and 45 deletions

View File

@ -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) => {

View File

@ -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

View File

@ -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