From 5be792f47fbaa9942f5fdea023496cbc35f7c50b Mon Sep 17 00:00:00 2001 From: Lou-Kamades Date: Fri, 8 Sep 2023 17:01:08 -0500 Subject: [PATCH] add sorting to market selection --- components/trade/MarketSelectDropdown.tsx | 124 +++++++++++++++------- hooks/useListedMarketsWithMarketData.ts | 50 ++++++++- hooks/useSortableData.ts | 11 +- 3 files changed, 140 insertions(+), 45 deletions(-) diff --git a/components/trade/MarketSelectDropdown.tsx b/components/trade/MarketSelectDropdown.tsx index 06d0dbb1..d072b03b 100644 --- a/components/trade/MarketSelectDropdown.tsx +++ b/components/trade/MarketSelectDropdown.tsx @@ -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('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(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) => { 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 ? ( <>
-

{t('market')}

-

{t('price')}

-

- {t('rolling-change')} +

+ requestPerpSort('name')} + sortConfig={perpSortConfig} + title={t('market')} + /> +
+

+ requestPerpSort('marketData.last_price')} + sortConfig={perpSortConfig} + title={t('price')} + />

-

- {t('daily-volume')} +

+ requestPerpSort('rollingChange')} + sortConfig={perpSortConfig} + title={t('rolling-change')} + /> +

+

+ + requestPerpSort('marketData.quote_volume_24h') + } + sortConfig={perpSortConfig} + title={t('daily-volume')} + />

{perpMarketsToShow.map((m) => { @@ -296,33 +336,41 @@ const MarketSelectDropdown = () => { ))} - {/* need to sort out change before enabling more sorting options */} - {/*
- -
*/}
-

{t('market')}

-

{t('price')}

-

- {t('rolling-change')} +

+ requestSerumSort('name')} + sortConfig={serumSortConfig} + title={t('market')} + />

-

- {t('daily-volume')} +

+ requestSerumSort('marketData.last_price')} + sortConfig={serumSortConfig} + title={t('price')} + /> +

+

+ requestSerumSort('rollingChange')} + sortConfig={serumSortConfig} + title={t('rolling-change')} + /> +

+

+ + requestSerumSort('marketData.quote_volume_24h') + } + sortConfig={serumSortConfig} + title={t('daily-volume')} + />

{serumMarketsToShow.map((m) => { diff --git a/hooks/useListedMarketsWithMarketData.ts b/hooks/useListedMarketsWithMarketData.ts index 712d5f1f..b568f804 100644 --- a/hooks/useListedMarketsWithMarketData.ts +++ b/hooks/useListedMarketsWithMarketData.ts @@ -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 diff --git a/hooks/useSortableData.ts b/hooks/useSortableData.ts index 06ffde6c..ebff2aa5 100644 --- a/hooks/useSortableData.ts +++ b/hooks/useSortableData.ts @@ -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>( 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