diff --git a/apps/trading/client-pages/market/header-stats.tsx b/apps/trading/client-pages/market/header-stats.tsx index d5853b80e..c142ea6d7 100644 --- a/apps/trading/client-pages/market/header-stats.tsx +++ b/apps/trading/client-pages/market/header-stats.tsx @@ -56,13 +56,7 @@ export const HeaderStats = ({ market }: HeaderStatsProps) => { decimalPlaces={market?.decimalPlaces} /> - + diff --git a/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx b/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx index 05fb4cc22..633b78a8a 100644 --- a/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx +++ b/libs/markets/src/lib/components/last-24h-price-change/last-24h-price-change.tsx @@ -1,12 +1,15 @@ import type { RefObject } from 'react'; import { useInView } from 'react-intersection-observer'; import { isNumeric } from '@vegaprotocol/utils'; -import { useYesterday } from '@vegaprotocol/react-helpers'; +import { useFiveDaysAgo, useYesterday } from '@vegaprotocol/react-helpers'; import { useThrottledDataProvider } from '@vegaprotocol/data-provider'; import { PriceChangeCell } from '@vegaprotocol/datagrid'; import * as Schema from '@vegaprotocol/types'; import type { CandleClose } from '@vegaprotocol/types'; import { marketCandlesProvider } from '../../market-candles-provider'; +import type { MarketCandlesFieldsFragment } from '../../__generated__/market-candles'; +import { Tooltip } from '@vegaprotocol/ui-toolkit'; +import { t } from '@vegaprotocol/i18n'; interface Props { marketId?: string; @@ -24,30 +27,71 @@ export const Last24hPriceChange = ({ inViewRoot, }: Props) => { const [ref, inView] = useInView({ root: inViewRoot?.current }); + const fiveDaysAgo = useFiveDaysAgo(); const yesterday = useYesterday(); const { data, error } = useThrottledDataProvider({ dataProvider: marketCandlesProvider, variables: { marketId: marketId || '', interval: Schema.Interval.INTERVAL_I1H, - since: new Date(yesterday).toISOString(), + since: new Date(fiveDaysAgo).toISOString(), }, skip: !marketId || !inView, }); - const candles = - data + const fiveDaysCandles = data?.filter((candle) => Boolean(candle)); + + const candles = fiveDaysCandles?.filter((candle) => + isCandleLessThan24hOld(candle, yesterday) + ); + const oneDayCandles = + candles ?.map((candle) => candle?.close) .filter((c): c is CandleClose => c !== null) || initialValue; + if ( + fiveDaysCandles && + fiveDaysCandles.length > 0 && + (!oneDayCandles || oneDayCandles?.length === 0) + ) { + return ( + + {t( + '24 hour change is unavailable at this time. The price change in the last 120 hours is:' + )}{' '} + c.close) || []} + decimalPlaces={decimalPlaces} + /> + + } + > + {t('Unknown')} + + ); + } + if (error || !isNumeric(decimalPlaces)) { return -; } return ( ); }; + +export const isCandleLessThan24hOld = ( + candle: MarketCandlesFieldsFragment | undefined, + yesterday: number +) => { + if (!candle?.open) { + return false; + } + const candleDate = new Date(candle.close); + return candleDate > new Date(yesterday); +}; diff --git a/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx b/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx index 3ddd71a7b..516d066b5 100644 --- a/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx +++ b/libs/markets/src/lib/components/last-24h-volume/last-24h-volume.tsx @@ -3,9 +3,12 @@ import { useInView } from 'react-intersection-observer'; import { marketCandlesProvider } from '../../market-candles-provider'; import { calcCandleVolume } from '../../market-utils'; import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils'; -import { useYesterday } from '@vegaprotocol/react-helpers'; +import { useFiveDaysAgo, useYesterday } from '@vegaprotocol/react-helpers'; import { useThrottledDataProvider } from '@vegaprotocol/data-provider'; import * as Schema from '@vegaprotocol/types'; +import { isCandleLessThan24hOld } from '../last-24h-price-change'; +import { t } from '@vegaprotocol/i18n'; +import { Tooltip } from '@vegaprotocol/ui-toolkit'; interface Props { marketId?: string; @@ -23,6 +26,7 @@ export const Last24hVolume = ({ initialValue, }: Props) => { const yesterday = useYesterday(); + const fiveDaysAgo = useFiveDaysAgo(); const [ref, inView] = useInView({ root: inViewRoot?.current }); const { data } = useThrottledDataProvider({ @@ -30,20 +34,66 @@ export const Last24hVolume = ({ variables: { marketId: marketId || '', interval: Schema.Interval.INTERVAL_I1H, - since: new Date(yesterday).toISOString(), + since: new Date(fiveDaysAgo).toISOString(), }, skip: !(inView && marketId), }); - const candleVolume = data ? calcCandleVolume(data) : initialValue; - return ( - - {candleVolume && isNumeric(positionDecimalPlaces) + + const fiveDaysCandles = data?.filter((candle) => Boolean(candle)); + + const oneDayCandles = fiveDaysCandles?.filter((candle) => + isCandleLessThan24hOld(candle, yesterday) + ); + + if ( + fiveDaysCandles && + fiveDaysCandles.length > 0 && + (!oneDayCandles || oneDayCandles?.length === 0) + ) { + const candleVolume = calcCandleVolume(fiveDaysCandles); + const candleVolumeValue = + candleVolume && isNumeric(positionDecimalPlaces) ? addDecimalsFormatNumber( candleVolume, positionDecimalPlaces, formatDecimals ) - : '-'} - + : '-'; + return ( + + + {t( + '24 hour change is unavailable at this time. The volume change in the last 120 hours is %s', + [candleVolumeValue] + )} + + + } + > + {t('Unknown')} + + ); + } + const candleVolume = oneDayCandles + ? calcCandleVolume(oneDayCandles) + : initialValue; + return ( + + + {candleVolume && isNumeric(positionDecimalPlaces) + ? addDecimalsFormatNumber( + candleVolume, + positionDecimalPlaces, + formatDecimals + ) + : '-'} + + ); }; diff --git a/libs/react-helpers/src/hooks/use-yesterday.spec.ts b/libs/react-helpers/src/hooks/use-yesterday.spec.ts index 98348fa66..7ef0a2425 100644 --- a/libs/react-helpers/src/hooks/use-yesterday.spec.ts +++ b/libs/react-helpers/src/hooks/use-yesterday.spec.ts @@ -1,5 +1,5 @@ import { act } from 'react-dom/test-utils'; -import { now, useYesterday } from './use-yesterday'; +import { createAgo, now } from './use-yesterday'; import { renderHook } from '@testing-library/react'; describe('now', () => { @@ -25,33 +25,41 @@ describe('now', () => { ); }); -describe('useYesterday', () => { - beforeAll(() => { - jest.useFakeTimers().setSystemTime(new Date('1970-01-05T14:36:20.100Z')); +describe('createAgo', () => { + beforeEach(() => { + jest.useFakeTimers().setSystemTime(new Date('1970-01-30T14:36:20.100Z')); }); afterAll(() => { jest.useRealTimers(); }); - it('returns yesterday timestamp rounded by 5 minutes', () => { - const { result, rerender } = renderHook(() => useYesterday()); - expect(result.current).toEqual( - new Date('1970-01-04T14:35:00.000Z').getTime() + it.each([ + ['yesterday', 24 * 60 * 60 * 1000, '1970-01-29T14:35:00.000Z'], + ['2 days ago', 2 * 24 * 60 * 60 * 1000, '1970-01-28T14:35:00.000Z'], + ['5 days ago', 5 * 24 * 60 * 60 * 1000, '1970-01-25T14:35:00.000Z'], + ['20 days ago', 20 * 24 * 60 * 60 * 1000, '1970-01-10T14:35:00.000Z'], + ])('returns %s timestamp rounded by 5 minutes', (_, ago, expectedTime) => { + const { result, rerender } = renderHook(() => + createAgo(ago)(5 * 60 * 1000) ); + expect(result.current).toEqual(new Date(expectedTime).getTime()); rerender(); rerender(); rerender(); - expect(result.current).toEqual( - new Date('1970-01-04T14:35:00.000Z').getTime() - ); + expect(result.current).toEqual(new Date(expectedTime).getTime()); }); - it('updates yesterday timestamp after 5 minutes', () => { - const { result, rerender } = renderHook(() => useYesterday()); + it.each([ + ['yesterday', 24 * 60 * 60 * 1000, '1970-01-29T14:40:00.000Z'], + ['2 days ago', 2 * 24 * 60 * 60 * 1000, '1970-01-28T14:40:00.000Z'], + ['5 days ago', 5 * 24 * 60 * 60 * 1000, '1970-01-25T14:40:00.000Z'], + ['20 days ago', 20 * 24 * 60 * 60 * 1000, '1970-01-10T14:40:00.000Z'], + ])('updates %s timestamp after 5 minutes', (_, ago, expectedTime) => { + const { result, rerender } = renderHook(() => + createAgo(ago)(5 * 60 * 1000) + ); act(() => { jest.advanceTimersByTime(5 * 60 * 1000); rerender(); }); - expect(result.current).toEqual( - new Date('1970-01-04T14:40:00.000Z').getTime() - ); + expect(result.current).toEqual(new Date(expectedTime).getTime()); }); }); diff --git a/libs/react-helpers/src/hooks/use-yesterday.ts b/libs/react-helpers/src/hooks/use-yesterday.ts index 3b7b0cbc8..72862b41f 100644 --- a/libs/react-helpers/src/hooks/use-yesterday.ts +++ b/libs/react-helpers/src/hooks/use-yesterday.ts @@ -1,22 +1,30 @@ import { useEffect, useRef } from 'react'; -const DEFAULT_ROUND_BY_MS = 5 * 60 * 1000; -const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000; +const MINUTE = 60 * 1000; +const DAY = 24 * 60 * 60 * 1000; export const now = (roundBy = 1) => { return Math.floor((Math.round(Date.now() / 1000) * 1000) / roundBy) * roundBy; }; +export const createAgo = + (ago: number) => + (roundBy = 5 * MINUTE) => { + const timestamp = useRef(now(roundBy) - ago); + useEffect(() => { + const i = setInterval(() => { + timestamp.current = now(roundBy) - ago; + }, roundBy); + return () => clearInterval(i); + }, [roundBy]); + return timestamp.current; + }; + /** * Returns the yesterday's timestamp rounded by given number (in milliseconds; 5 minutes by default) */ -export const useYesterday = (roundBy = DEFAULT_ROUND_BY_MS) => { - const yesterday = useRef(now(roundBy) - TWENTY_FOUR_HOURS_MS); - useEffect(() => { - const i = setInterval(() => { - yesterday.current = now(roundBy) - TWENTY_FOUR_HOURS_MS; - }, roundBy); - return () => clearInterval(i); - }, [roundBy]); - return yesterday.current; -}; +export const useYesterday = createAgo(DAY); +/** + * Returns the five days ago timestamp rounded by given number (in milliseconds; 5 minutes by default) + */ +export const useFiveDaysAgo = createAgo(5 * DAY);