add sorting to market selection
This commit is contained in:
parent
e78e579611
commit
5be792f47f
|
@ -26,6 +26,8 @@ import useListedMarketsWithMarketData, {
|
||||||
} from 'hooks/useListedMarketsWithMarketData'
|
} from 'hooks/useListedMarketsWithMarketData'
|
||||||
import { AllowedKeys, sortPerpMarkets, sortSpotMarkets } from 'utils/markets'
|
import { AllowedKeys, sortPerpMarkets, sortSpotMarkets } from 'utils/markets'
|
||||||
import Input from '@components/forms/Input'
|
import Input from '@components/forms/Input'
|
||||||
|
import { useSortableData } from 'hooks/useSortableData'
|
||||||
|
import { SortableColumnHeader } from '@components/shared/TableElements'
|
||||||
|
|
||||||
const MARKET_LINK_CLASSES =
|
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'
|
'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(
|
const [spotOrPerp, setSpotOrPerp] = useState(
|
||||||
selectedMarket instanceof PerpMarket ? 'perp' : 'spot',
|
selectedMarket instanceof PerpMarket ? 'perp' : 'spot',
|
||||||
)
|
)
|
||||||
const [sortByKey] = useState<AllowedKeys>('quote_volume_24h')
|
const defaultSortByKey: AllowedKeys = 'quote_volume_24h'
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const { group } = useMangoGroup()
|
const { group } = useMangoGroup()
|
||||||
|
@ -87,10 +89,10 @@ const MarketSelectDropdown = () => {
|
||||||
useListedMarketsWithMarketData()
|
useListedMarketsWithMarketData()
|
||||||
const focusRef = useRef<HTMLInputElement>(null)
|
const focusRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
const perpMarketsToShow = useMemo(() => {
|
const unsortedPerpMarketsToShow = useMemo(() => {
|
||||||
if (!perpMarketsWithData.length) return []
|
if (!perpMarketsWithData.length) return []
|
||||||
return sortPerpMarkets(perpMarketsWithData, sortByKey)
|
return sortPerpMarkets(perpMarketsWithData, defaultSortByKey)
|
||||||
}, [perpMarketsWithData, sortByKey])
|
}, [perpMarketsWithData])
|
||||||
|
|
||||||
const spotBaseTokens: string[] = useMemo(() => {
|
const spotBaseTokens: string[] = useMemo(() => {
|
||||||
if (serumMarketsWithData.length) {
|
if (serumMarketsWithData.length) {
|
||||||
|
@ -106,7 +108,7 @@ const MarketSelectDropdown = () => {
|
||||||
return ['All']
|
return ['All']
|
||||||
}, [serumMarketsWithData])
|
}, [serumMarketsWithData])
|
||||||
|
|
||||||
const serumMarketsToShow = useMemo(() => {
|
const unsortedSerumMarketsToShow = useMemo(() => {
|
||||||
if (!serumMarketsWithData.length) return []
|
if (!serumMarketsWithData.length) return []
|
||||||
if (spotBaseFilter !== 'All') {
|
if (spotBaseFilter !== 'All') {
|
||||||
const filteredMarkets = serumMarketsWithData.filter((m) => {
|
const filteredMarkets = serumMarketsWithData.filter((m) => {
|
||||||
|
@ -115,18 +117,30 @@ const MarketSelectDropdown = () => {
|
||||||
})
|
})
|
||||||
return search
|
return search
|
||||||
? startSearch(filteredMarkets, search)
|
? startSearch(filteredMarkets, search)
|
||||||
: sortSpotMarkets(filteredMarkets, sortByKey)
|
: sortSpotMarkets(filteredMarkets, defaultSortByKey)
|
||||||
} else {
|
} else {
|
||||||
return search
|
return search
|
||||||
? startSearch(serumMarketsWithData, search)
|
? startSearch(serumMarketsWithData, search)
|
||||||
: sortSpotMarkets(serumMarketsWithData, sortByKey)
|
: sortSpotMarkets(serumMarketsWithData, defaultSortByKey)
|
||||||
}
|
}
|
||||||
}, [search, serumMarketsWithData, sortByKey, spotBaseFilter])
|
}, [search, serumMarketsWithData, spotBaseFilter])
|
||||||
|
|
||||||
const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleUpdateSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearch(e.target.value)
|
setSearch(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
items: perpMarketsToShow,
|
||||||
|
requestSort: requestPerpSort,
|
||||||
|
sortConfig: perpSortConfig,
|
||||||
|
} = useSortableData(unsortedPerpMarketsToShow)
|
||||||
|
|
||||||
|
const {
|
||||||
|
items: serumMarketsToShow,
|
||||||
|
requestSort: requestSerumSort,
|
||||||
|
sortConfig: serumSortConfig,
|
||||||
|
} = useSortableData(unsortedSerumMarketsToShow)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (focusRef?.current && spotOrPerp === 'spot') {
|
if (focusRef?.current && spotOrPerp === 'spot') {
|
||||||
focusRef.current.focus()
|
focusRef.current.focus()
|
||||||
|
@ -181,13 +195,39 @@ const MarketSelectDropdown = () => {
|
||||||
{spotOrPerp === 'perp' && perpMarketsToShow.length ? (
|
{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">
|
<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>
|
<div className="col-span-1 flex-1 ">
|
||||||
<p className="col-span-1 text-right">{t('price')}</p>
|
<SortableColumnHeader
|
||||||
<p className="col-span-1 text-right">
|
sortKey="name"
|
||||||
{t('rolling-change')}
|
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>
|
||||||
<p className="col-span-1 hidden text-right md:block">
|
<p className="col-span-1 flex justify-end">
|
||||||
{t('daily-volume')}
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{perpMarketsToShow.map((m) => {
|
{perpMarketsToShow.map((m) => {
|
||||||
|
@ -296,33 +336,41 @@ const MarketSelectDropdown = () => {
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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>
|
||||||
<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">
|
<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 flex justify-end">
|
||||||
<p className="col-span-1 text-right">{t('price')}</p>
|
<SortableColumnHeader
|
||||||
<p className="col-span-1 text-right">
|
sortKey="name"
|
||||||
{t('rolling-change')}
|
sort={() => requestSerumSort('name')}
|
||||||
|
sortConfig={serumSortConfig}
|
||||||
|
title={t('market')}
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p className="col-span-1 hidden text-right md:block">
|
<p className="col-span-1 flex justify-end">
|
||||||
{t('daily-volume')}
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{serumMarketsToShow.map((m) => {
|
{serumMarketsToShow.map((m) => {
|
||||||
|
|
|
@ -8,9 +8,17 @@ type ApiData = {
|
||||||
marketData: MarketsDataItem | undefined
|
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() {
|
export default function useListedMarketsWithMarketData() {
|
||||||
const { data: marketsData, isLoading, isFetching } = useMarketsData()
|
const { data: marketsData, isLoading, isFetching } = useMarketsData()
|
||||||
|
@ -27,6 +35,29 @@ export default function useListedMarketsWithMarketData() {
|
||||||
return marketsData?.spotData || []
|
return marketsData?.spotData || []
|
||||||
}, [marketsData])
|
}, [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(() => {
|
const serumMarketsWithData = useMemo(() => {
|
||||||
if (!serumMarkets || !serumMarkets.length) return []
|
if (!serumMarkets || !serumMarkets.length) return []
|
||||||
const allSpotMarkets: SerumMarketWithMarketData[] =
|
const allSpotMarkets: SerumMarketWithMarketData[] =
|
||||||
|
@ -36,6 +67,17 @@ export default function useListedMarketsWithMarketData() {
|
||||||
const spotEntries = Object.entries(spotData).find(
|
const spotEntries = Object.entries(spotData).find(
|
||||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
(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
|
market.marketData = spotEntries ? spotEntries[1][0] : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +93,11 @@ export default function useListedMarketsWithMarketData() {
|
||||||
const perpEntries = Object.entries(perpData).find(
|
const perpEntries = Object.entries(perpData).find(
|
||||||
(e) => e[0].toLowerCase() === market.name.toLowerCase(),
|
(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.marketData = perpEntries ? perpEntries[1][0] : undefined
|
||||||
|
market.rollingChange = ((currentPrice - pastPrice) / pastPrice) * 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allPerpMarkets
|
return allPerpMarkets
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
|
import get from 'lodash/get'
|
||||||
|
|
||||||
type Direction = 'ascending' | 'descending'
|
type Direction = 'ascending' | 'descending'
|
||||||
|
|
||||||
|
@ -22,15 +23,15 @@ export function useSortableData<T extends Record<string, any>>(
|
||||||
const sortableItems = items ? [...items] : []
|
const sortableItems = items ? [...items] : []
|
||||||
if (sortConfig !== null) {
|
if (sortConfig !== null) {
|
||||||
sortableItems.sort((a, b) => {
|
sortableItems.sort((a, b) => {
|
||||||
if (!isNaN(a[sortConfig.key])) {
|
if (!isNaN(get(a, sortConfig.key))) {
|
||||||
return sortConfig.direction === 'ascending'
|
return sortConfig.direction === 'ascending'
|
||||||
? a[sortConfig.key] - b[sortConfig.key]
|
? get(a, sortConfig.key) - get(b, sortConfig.key)
|
||||||
: b[sortConfig.key] - a[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
|
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 sortConfig.direction === 'ascending' ? 1 : -1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|
Loading…
Reference in New Issue