From b23a8d02f4ba993365165198328bd6c6fd593ff0 Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 25 Nov 2024 17:49:46 +0300 Subject: [PATCH 01/13] Implement Rewards Chart on validator page --- .../src/app/validator/[hash]/RewardsChart.js | 106 ++++++++++++++++++ .../src/app/validator/[hash]/Validator.js | 12 +- .../frontend/src/components/charts/index.js | 11 +- packages/frontend/src/util/Api.js | 5 + 4 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 packages/frontend/src/app/validator/[hash]/RewardsChart.js diff --git a/packages/frontend/src/app/validator/[hash]/RewardsChart.js b/packages/frontend/src/app/validator/[hash]/RewardsChart.js new file mode 100644 index 000000000..b53aa3848 --- /dev/null +++ b/packages/frontend/src/app/validator/[hash]/RewardsChart.js @@ -0,0 +1,106 @@ +import { useState, useEffect, useRef } from 'react' +import useResizeObserver from '@react-hook/resize-observer' +import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates, getDynamicRange } from '../../../util' +import { LineChart, TimeframeSelector } from './../../../components/charts' +import * as Api from '../../../util/Api' +import { ErrorMessageBlock } from '../../../components/Errors' + +const chartConfig = { + timespan: { + defaultIndex: 3, + values: [ + { + label: '24 hours', + range: getDynamicRange(24 * 60 * 60 * 1000) + }, + { + label: '3 days', + range: getDynamicRange(3 * 24 * 60 * 60 * 1000) + }, + { + label: '1 week', + range: getDynamicRange(7 * 24 * 60 * 60 * 1000) + }, + { + label: '1 Month', + range: getDynamicRange(30 * 24 * 60 * 60 * 1000) + } + ] + } +} + +export default function RewardsChart ({ hash, isActive }) { + const [rewardsHistory, setRewardsHistory] = useState({ data: {}, loading: true, error: false }) + const [timespan, setTimespan] = useState(chartConfig.timespan.values[chartConfig.timespan.defaultIndex]) + const [customRange, setCustomRange] = useState({ start: null, end: null }) + const [menuIsOpen, setMenuIsOpen] = useState(false) + const TimeframeMenuRef = useRef(null) + const [selectorHeight, setSelectorHeight] = useState(0) + + useEffect(() => { + const { start = null, end = null } = timespan.range + if (!start || !end) return + + setRewardsHistory(state => ({ ...state, loading: true })) + + Api.getRewardsStatsByValidator(hash, start, end) + .then(res => fetchHandlerSuccess(setRewardsHistory, { resultSet: res })) + .catch(err => fetchHandlerError(setRewardsHistory, err)) + }, [timespan, customRange]) + + console.log('rewards', rewardsHistory) + + const updateMenuHeight = () => { + if (menuIsOpen && TimeframeMenuRef?.current) { + const element = TimeframeMenuRef.current + const height = element.getBoundingClientRect().height + setSelectorHeight(height) + } else { + setSelectorHeight(0) + } + } + + useEffect(updateMenuHeight, [menuIsOpen, TimeframeMenuRef]) + + useResizeObserver(TimeframeMenuRef, updateMenuHeight) + + if (rewardsHistory.error || (!rewardsHistory.loading && !rewardsHistory.data?.resultSet)) { + return () + } + + return ( +
+ setCustomRange({ start, end })} + /> +
+ ({ + x: new Date(item.timestamp), + y: item.data.reward + })) || []} + dataLoading={rewardsHistory.loading} + timespan={timespan.range} + xAxis={{ + type: (() => { + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 3) return { axis: 'date', tooltip: 'datetime' } + return { axis: 'time' } + })() + }} + yAxis={{ + type: 'number', + abbreviation: 'Credits' + }} + height={'350px'} + /> +
+
+ ) +} diff --git a/packages/frontend/src/app/validator/[hash]/Validator.js b/packages/frontend/src/app/validator/[hash]/Validator.js index 3af72ed4b..d7dc549fb 100644 --- a/packages/frontend/src/app/validator/[hash]/Validator.js +++ b/packages/frontend/src/app/validator/[hash]/Validator.js @@ -9,6 +9,7 @@ import { ErrorMessageBlock } from '../../../components/Errors' import BlocksList from '../../../components/blocks/BlocksList' import TransactionsList from '../../../components/transactions/TransactionsList' import BlocksChart from './BlocksChart' +import RewardsChart from './RewardsChart' import Link from 'next/link' import { Identifier, DateBlock, Endpoint, IpAddress, InfoLine, Credits } from '../../../components/data' import { ValueContainer, PageDataContainer, InfoContainer } from '../../../components/ui/containers' @@ -31,7 +32,7 @@ function Validator ({ hash }) { const [currentPage, setCurrentPage] = useState(1) const [transactions, setTransactions] = useState({ data: {}, props: { currentPage: 0 }, loading: true, error: false }) const [withdrawals, setWithdrawals] = useState({ data: {}, props: { currentPage: 0 }, loading: true, error: false }) - const [activeChartTab, setActiveChartTab] = useState(0) + const [activeChartTab, setActiveChartTab] = useState(1) const baseUrl = process.env.NEXT_PUBLIC_BASE_URL const activeNetwork = networks.find(network => network.explorerBaseUrl === baseUrl) const l1explorerBaseUrl = activeNetwork?.l1explorerBaseUrl || null @@ -401,19 +402,22 @@ function Validator ({ hash }) { setActiveChartTab(index)} index={activeChartTab}> Proposed Blocks - Reward Earned + Reward Earned - Reward Earned + diff --git a/packages/frontend/src/components/charts/index.js b/packages/frontend/src/components/charts/index.js index b809eb198..a5fc7d4c8 100644 --- a/packages/frontend/src/components/charts/index.js +++ b/packages/frontend/src/components/charts/index.js @@ -109,6 +109,7 @@ const LineGraph = ({ const xAxisFormatCode = typeof xAxis.type === 'string' ? xAxis.type : xAxis.type.axis const [chartWidth, setChartWidth] = useState(0) const svgRef = useRef(null) + const uniqueComponentId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` const getFormatByCode = (code) => { if (code === 'number') return d3.format(',.0f') @@ -379,7 +380,7 @@ const LineGraph = ({ overflow={'visible'} viewBox={`0 0 ${width} ${height}`} > - + @@ -413,7 +414,7 @@ const LineGraph = ({ - + @@ -427,9 +428,9 @@ const LineGraph = ({ - + - + @@ -441,7 +442,7 @@ const LineGraph = ({ - + diff --git a/packages/frontend/src/util/Api.js b/packages/frontend/src/util/Api.js index e76ce3577..9adb02855 100644 --- a/packages/frontend/src/util/Api.js +++ b/packages/frontend/src/util/Api.js @@ -119,6 +119,10 @@ const getBlocksStatsByValidator = (proTxHash, start, end) => { return call(`validator/${proTxHash}/stats?start=${start}&end=${end}`, 'GET') } +const getRewardsStatsByValidator = (proTxHash, start, end) => { + return call(`validator/${proTxHash}/rewards/stats?start=${start}&end=${end}`, 'GET') +} + const getStatus = () => { return call('status', 'GET') } @@ -159,6 +163,7 @@ export { getValidatorByProTxHash, getBlocksByValidator, getBlocksStatsByValidator, + getRewardsStatsByValidator, getEpoch, getRate } From 3d5bdbe044482bcda911068cb301cb1cab920bfc Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 25 Nov 2024 20:48:19 +0300 Subject: [PATCH 02/13] Chart code refactoring --- packages/frontend/src/components/charts/index.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/components/charts/index.js b/packages/frontend/src/components/charts/index.js index a5fc7d4c8..9dab2a70c 100644 --- a/packages/frontend/src/components/charts/index.js +++ b/packages/frontend/src/components/charts/index.js @@ -111,16 +111,14 @@ const LineGraph = ({ const svgRef = useRef(null) const uniqueComponentId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` - const getFormatByCode = (code) => { - if (code === 'number') return d3.format(',.0f') - if (code === 'date') return d3.timeFormat('%B %d') - if (code === 'datetime') return d3.timeFormat('%B %d, %H:%M') - if (code === 'time') return d3.timeFormat('%H:%M') + const tickFormats = { + number: d3.format(',.0f'), + date: d3.timeFormat('%B %d'), + datetime: d3.timeFormat('%B %d, %H:%M'), + time: d3.timeFormat('%H:%M') } - const xTickFormat = (() => { - return getFormatByCode(xAxisFormatCode) - })() + const xTickFormat = tickFormats[xAxisFormatCode] const y = d3.scaleLinear(d3.extent(data, d => d.y), [height - marginBottom, marginTop]) @@ -319,7 +317,7 @@ const LineGraph = ({ const infoLines = [] const xFormatCode = typeof xAxis.type.tooltip === 'string' ? xAxis.type.tooltip : xAxis.type.axis - const xFormat = getFormatByCode(xFormatCode) + const xFormat = tickFormats[xFormatCode] infoLines.push({ styles: ['inline', 'tiny'], From 862c56c87f87a2c752d2eab4c7e6306c1e26edd4 Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 17 Dec 2024 18:45:12 +0300 Subject: [PATCH 03/13] Update display big numbers in chart --- packages/frontend/src/components/charts/index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/components/charts/index.js b/packages/frontend/src/components/charts/index.js index 9dab2a70c..838848101 100644 --- a/packages/frontend/src/components/charts/index.js +++ b/packages/frontend/src/components/charts/index.js @@ -167,6 +167,15 @@ const LineGraph = ({ .y0(y(0)) .y1((d) => y(d.y))) + const valuesFormat = (value) => { + if (typeof value !== 'number' || isNaN(value)) return value + + if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B` + if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M` + if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k` + return value + } + useEffect(() => { d3.select(gx.current) .call((axis) => { @@ -203,6 +212,7 @@ const LineGraph = ({ .call(d3.axisLeft(y) .tickSize(0) .ticks(5) + .tickFormat(valuesFormat) .tickPadding(10) ) @@ -324,7 +334,7 @@ const LineGraph = ({ value: `${xFormat(data[i].x)}: ` }, { styles: ['inline', 'bold'], - value: ` ${data[i].y} ` + value: ` ${new Intl.NumberFormat('fr-FR', { useGrouping: true, grouping: [3], minimumFractionDigits: 0 }).format(data[i].y)} ` }, { styles: ['inline', 'tiny'], value: ` ${yAxis.abbreviation}` From 0e5c9ef08b9d44b8bbca77cad9d49afe023d1b6c Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 17 Dec 2024 19:18:08 +0300 Subject: [PATCH 04/13] Add data filter to chart --- packages/frontend/src/components/charts/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/charts/index.js b/packages/frontend/src/components/charts/index.js index 838848101..0529e14bd 100644 --- a/packages/frontend/src/components/charts/index.js +++ b/packages/frontend/src/components/charts/index.js @@ -119,8 +119,8 @@ const LineGraph = ({ } const xTickFormat = tickFormats[xAxisFormatCode] - - const y = d3.scaleLinear(d3.extent(data, d => d.y), [height - marginBottom, marginTop]) + const filteredData = data.filter(d => typeof d.y === 'number' && !isNaN(d.y)) + const y = d3.scaleLinear(d3.extent(filteredData, d => d.y), [height - marginBottom, marginTop]) const [x, setX] = useState(() => { if (xAxisFormatCode === 'number') return d3.scaleLinear(d3.extent(data, d => d.x), [marginLeft, width - marginRight]) From 7518b81ddcf269cdb536ec8710cc99a046c5bb6b Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 19 Dec 2024 13:47:31 +0300 Subject: [PATCH 05/13] Remove debug log --- packages/frontend/src/app/validator/[hash]/RewardsChart.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/frontend/src/app/validator/[hash]/RewardsChart.js b/packages/frontend/src/app/validator/[hash]/RewardsChart.js index b53aa3848..c497d6b9d 100644 --- a/packages/frontend/src/app/validator/[hash]/RewardsChart.js +++ b/packages/frontend/src/app/validator/[hash]/RewardsChart.js @@ -48,8 +48,6 @@ export default function RewardsChart ({ hash, isActive }) { .catch(err => fetchHandlerError(setRewardsHistory, err)) }, [timespan, customRange]) - console.log('rewards', rewardsHistory) - const updateMenuHeight = () => { if (menuIsOpen && TimeframeMenuRef?.current) { const element = TimeframeMenuRef.current From 218a24918d5eb497562c7049c13992f71d7f4d9e Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 19 Dec 2024 14:17:41 +0300 Subject: [PATCH 06/13] Fix clip path id in LineChart --- packages/frontend/src/components/charts/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/components/charts/index.js b/packages/frontend/src/components/charts/index.js index 0529e14bd..5ac19c416 100644 --- a/packages/frontend/src/components/charts/index.js +++ b/packages/frontend/src/components/charts/index.js @@ -422,11 +422,11 @@ const LineGraph = ({ - - - + + + - + - + From ff1903145e7ec37db27ff8a0040570eb69b35ce1 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 19 Dec 2024 14:19:04 +0300 Subject: [PATCH 07/13] Remove unused properties --- packages/frontend/src/app/validator/[hash]/BlocksChart.js | 1 - packages/frontend/src/app/validator/[hash]/RewardsChart.js | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/frontend/src/app/validator/[hash]/BlocksChart.js b/packages/frontend/src/app/validator/[hash]/BlocksChart.js index 92dec1585..ab84fa8a5 100644 --- a/packages/frontend/src/app/validator/[hash]/BlocksChart.js +++ b/packages/frontend/src/app/validator/[hash]/BlocksChart.js @@ -75,7 +75,6 @@ export default function BlocksChart ({ hash, isActive }) { changeCallback={setTimespan} isActive={isActive} openStateCallback={setMenuIsOpen} - customRangeCallback={(start, end) => setCustomRange({ start, end })} />
setCustomRange({ start, end })} />
Date: Thu, 19 Dec 2024 14:50:19 +0300 Subject: [PATCH 08/13] Create TabsChartBlock component and optimization Charts code on the specific Validator page --- .../src/app/validator/[hash]/BlocksChart.js | 115 +++++------------- .../src/app/validator/[hash]/RewardsChart.js | 115 +++++------------- .../src/components/charts/TabsChartBlock.js | 69 +++++++++++ .../charts/TabsChartBlock.scss} | 7 +- .../frontend/src/components/charts/config.js | 25 ++++ packages/frontend/src/styles/theme.scss | 1 - 6 files changed, 156 insertions(+), 176 deletions(-) create mode 100644 packages/frontend/src/components/charts/TabsChartBlock.js rename packages/frontend/src/{styles/components/TabsChart.scss => components/charts/TabsChartBlock.scss} (88%) create mode 100644 packages/frontend/src/components/charts/config.js diff --git a/packages/frontend/src/app/validator/[hash]/BlocksChart.js b/packages/frontend/src/app/validator/[hash]/BlocksChart.js index ab84fa8a5..ac9f8bdc4 100644 --- a/packages/frontend/src/app/validator/[hash]/BlocksChart.js +++ b/packages/frontend/src/app/validator/[hash]/BlocksChart.js @@ -1,44 +1,15 @@ -import { useState, useEffect, useRef } from 'react' -import useResizeObserver from '@react-hook/resize-observer' -import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates, getDynamicRange } from '../../../util' -import { LineChart, TimeframeSelector } from './../../../components/charts' import * as Api from '../../../util/Api' -import { ErrorMessageBlock } from '../../../components/Errors' - -const chartConfig = { - timespan: { - defaultIndex: 3, - values: [ - { - label: '24 hours', - range: getDynamicRange(24 * 60 * 60 * 1000) - }, - { - label: '3 days', - range: getDynamicRange(3 * 24 * 60 * 60 * 1000) - }, - { - label: '1 week', - range: getDynamicRange(7 * 24 * 60 * 60 * 1000) - }, - { - label: '1 Month', - range: getDynamicRange(30 * 24 * 60 * 60 * 1000) - } - ] - } -} +import { useState, useEffect } from 'react' +import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates } from '../../../util' +import TabsChartBlock from '../../../components/charts/TabsChartBlock' +import { defaultChartConfig } from '../../../components/charts/config' export default function BlocksChart ({ hash, isActive }) { const [blocksHistory, setBlocksHistory] = useState({ data: {}, loading: true, error: false }) - const [timespan, setTimespan] = useState(chartConfig.timespan.values[chartConfig.timespan.defaultIndex]) - const [customRange, setCustomRange] = useState({ start: null, end: null }) - const [menuIsOpen, setMenuIsOpen] = useState(false) - const TimeframeMenuRef = useRef(null) - const [selectorHeight, setSelectorHeight] = useState(0) + const [timespan, setTimespan] = useState(defaultChartConfig.timespan.values[defaultChartConfig.timespan.defaultIndex]) useEffect(() => { - const { start = null, end = null } = timespan.range + const { start = null, end = null } = timespan?.range if (!start || !end) return setBlocksHistory(state => ({ ...state, loading: true })) @@ -46,58 +17,30 @@ export default function BlocksChart ({ hash, isActive }) { Api.getBlocksStatsByValidator(hash, start, end) .then(res => fetchHandlerSuccess(setBlocksHistory, { resultSet: res })) .catch(err => fetchHandlerError(setBlocksHistory, err)) - }, [timespan, customRange]) - - const updateMenuHeight = () => { - if (menuIsOpen && TimeframeMenuRef?.current) { - const element = TimeframeMenuRef.current - const height = element.getBoundingClientRect().height - setSelectorHeight(height) - } else { - setSelectorHeight(0) - } - } - - useEffect(updateMenuHeight, [menuIsOpen, TimeframeMenuRef]) - - useResizeObserver(TimeframeMenuRef, updateMenuHeight) - - if (blocksHistory.error || (!blocksHistory.loading && !blocksHistory.data?.resultSet)) { - return () - } + }, [timespan, hash]) return ( -
- -
- ({ - x: new Date(item.timestamp), - y: item.data.blocksCount - })) || []} - dataLoading={blocksHistory.loading} - timespan={timespan.range} - xAxis={{ - type: (() => { - if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } - if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 3) return { axis: 'date', tooltip: 'datetime' } - return { axis: 'time' } - })() - }} - yAxis={{ - type: 'number', - abbreviation: 'blocks' - }} - height={'350px'} - /> -
-
+ ({ + x: new Date(item.timestamp), + y: item.data.blocksCount + })) || []} + loading={blocksHistory.loading} + error={blocksHistory.error} + xAxis={{ + type: (() => { + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 3) return { axis: 'date', tooltip: 'datetime' } + return { axis: 'time' } + })() + }} + yAxis={{ + type: 'number', + abbreviation: 'blocks' + }} + /> ) } diff --git a/packages/frontend/src/app/validator/[hash]/RewardsChart.js b/packages/frontend/src/app/validator/[hash]/RewardsChart.js index d1438f2bb..cda35d02b 100644 --- a/packages/frontend/src/app/validator/[hash]/RewardsChart.js +++ b/packages/frontend/src/app/validator/[hash]/RewardsChart.js @@ -1,44 +1,15 @@ -import { useState, useEffect, useRef } from 'react' -import useResizeObserver from '@react-hook/resize-observer' -import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates, getDynamicRange } from '../../../util' -import { LineChart, TimeframeSelector } from './../../../components/charts' import * as Api from '../../../util/Api' -import { ErrorMessageBlock } from '../../../components/Errors' - -const chartConfig = { - timespan: { - defaultIndex: 3, - values: [ - { - label: '24 hours', - range: getDynamicRange(24 * 60 * 60 * 1000) - }, - { - label: '3 days', - range: getDynamicRange(3 * 24 * 60 * 60 * 1000) - }, - { - label: '1 week', - range: getDynamicRange(7 * 24 * 60 * 60 * 1000) - }, - { - label: '1 Month', - range: getDynamicRange(30 * 24 * 60 * 60 * 1000) - } - ] - } -} +import { useState, useEffect } from 'react' +import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates } from '../../../util' +import TabsChartBlock from '../../../components/charts/TabsChartBlock' +import { defaultChartConfig } from '../../../components/charts/config' export default function RewardsChart ({ hash, isActive }) { const [rewardsHistory, setRewardsHistory] = useState({ data: {}, loading: true, error: false }) - const [timespan, setTimespan] = useState(chartConfig.timespan.values[chartConfig.timespan.defaultIndex]) - const [customRange, setCustomRange] = useState({ start: null, end: null }) - const [menuIsOpen, setMenuIsOpen] = useState(false) - const TimeframeMenuRef = useRef(null) - const [selectorHeight, setSelectorHeight] = useState(0) + const [timespan, setTimespan] = useState(defaultChartConfig.timespan.values[defaultChartConfig.timespan.defaultIndex]) useEffect(() => { - const { start = null, end = null } = timespan.range + const { start = null, end = null } = timespan?.range if (!start || !end) return setRewardsHistory(state => ({ ...state, loading: true })) @@ -46,58 +17,30 @@ export default function RewardsChart ({ hash, isActive }) { Api.getRewardsStatsByValidator(hash, start, end) .then(res => fetchHandlerSuccess(setRewardsHistory, { resultSet: res })) .catch(err => fetchHandlerError(setRewardsHistory, err)) - }, [timespan, customRange]) - - const updateMenuHeight = () => { - if (menuIsOpen && TimeframeMenuRef?.current) { - const element = TimeframeMenuRef.current - const height = element.getBoundingClientRect().height - setSelectorHeight(height) - } else { - setSelectorHeight(0) - } - } - - useEffect(updateMenuHeight, [menuIsOpen, TimeframeMenuRef]) - - useResizeObserver(TimeframeMenuRef, updateMenuHeight) - - if (rewardsHistory.error || (!rewardsHistory.loading && !rewardsHistory.data?.resultSet)) { - return () - } + }, [timespan, hash]) return ( -
- -
- ({ - x: new Date(item.timestamp), - y: item.data.reward - })) || []} - dataLoading={rewardsHistory.loading} - timespan={timespan.range} - xAxis={{ - type: (() => { - if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } - if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 3) return { axis: 'date', tooltip: 'datetime' } - return { axis: 'time' } - })() - }} - yAxis={{ - type: 'number', - abbreviation: 'Credits' - }} - height={'350px'} - /> -
-
+ ({ + x: new Date(item.timestamp), + y: item.data.reward + })) || []} + loading={rewardsHistory.loading} + error={rewardsHistory.error} + xAxis={{ + type: (() => { + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } + if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 3) return { axis: 'date', tooltip: 'datetime' } + return { axis: 'time' } + })() + }} + yAxis={{ + type: 'number', + abbreviation: 'Credits' + }} + /> ) } diff --git a/packages/frontend/src/components/charts/TabsChartBlock.js b/packages/frontend/src/components/charts/TabsChartBlock.js new file mode 100644 index 000000000..7f778426d --- /dev/null +++ b/packages/frontend/src/components/charts/TabsChartBlock.js @@ -0,0 +1,69 @@ +'use client' + +import { useState, useEffect, useRef } from 'react' +import useResizeObserver from '@react-hook/resize-observer' +import { LineChart, TimeframeSelector } from './' +import { ErrorMessageBlock } from '../Errors' +import { defaultChartConfig } from './config' +import './TabsChartBlock.scss' + +export default function TabsChartBlock ({ + isActive, + timespanChangeCallback, + loading, + error, + data, + xAxis, + yAxis, + timespan, + chartConfig = defaultChartConfig, + heightPx = 350 +}) { + const [menuIsOpen, setMenuIsOpen] = useState(false) + const TimeframeMenuRef = useRef(null) + const [selectorHeight, setSelectorHeight] = useState(0) + + const updateMenuHeight = () => { + if (menuIsOpen && TimeframeMenuRef?.current) { + const element = TimeframeMenuRef.current + const height = element.getBoundingClientRect().height + setSelectorHeight(height) + } else { + setSelectorHeight(0) + } + } + + useEffect(updateMenuHeight, [menuIsOpen, TimeframeMenuRef]) + + useResizeObserver(TimeframeMenuRef, updateMenuHeight) + + if (error || (!loading && !data)) { + return () + } + + return ( +
+ +
+ +
+
+ ) +} diff --git a/packages/frontend/src/styles/components/TabsChart.scss b/packages/frontend/src/components/charts/TabsChartBlock.scss similarity index 88% rename from packages/frontend/src/styles/components/TabsChart.scss rename to packages/frontend/src/components/charts/TabsChartBlock.scss index 08e9c001e..478f92a23 100644 --- a/packages/frontend/src/styles/components/TabsChart.scss +++ b/packages/frontend/src/components/charts/TabsChartBlock.scss @@ -1,4 +1,5 @@ -.TabsChart { +.TabsChartBlock { + container-type: inline-size; height: 100%; transition: all .2s; @@ -20,8 +21,8 @@ top: -36px; right: 10px; } - - @media screen and (max-width: 555px) { + + @container (max-width: 500px) { &__Button { top: 10px; right: 12px; diff --git a/packages/frontend/src/components/charts/config.js b/packages/frontend/src/components/charts/config.js new file mode 100644 index 000000000..18670c7d8 --- /dev/null +++ b/packages/frontend/src/components/charts/config.js @@ -0,0 +1,25 @@ +import { getDynamicRange } from '../../util' + +export const defaultChartConfig = { + timespan: { + defaultIndex: 3, + values: [ + { + label: '24 hours', + range: getDynamicRange(24 * 60 * 60 * 1000) + }, + { + label: '3 days', + range: getDynamicRange(3 * 24 * 60 * 60 * 1000) + }, + { + label: '1 week', + range: getDynamicRange(7 * 24 * 60 * 60 * 1000) + }, + { + label: '1 Month', + range: getDynamicRange(30 * 24 * 60 * 60 * 1000) + } + ] + } +} diff --git a/packages/frontend/src/styles/theme.scss b/packages/frontend/src/styles/theme.scss index effe112ea..85a986913 100644 --- a/packages/frontend/src/styles/theme.scss +++ b/packages/frontend/src/styles/theme.scss @@ -1,6 +1,5 @@ @import 'components/Table.scss'; @import 'components/InfoBlock.scss'; -@import 'components/TabsChart.scss'; @import './variables.scss'; * { From 309f65015d2249e56edb4607c617b68d3e617fab Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 19 Dec 2024 15:06:52 +0300 Subject: [PATCH 09/13] Update menuIsActive property name --- .../frontend/src/app/validator/[hash]/BlocksChart.js | 2 +- .../frontend/src/app/validator/[hash]/RewardsChart.js | 2 +- .../frontend/src/components/charts/TabsChartBlock.js | 4 ++-- .../src/components/charts/TimeframeSelector.js | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/app/validator/[hash]/BlocksChart.js b/packages/frontend/src/app/validator/[hash]/BlocksChart.js index ac9f8bdc4..2e064e290 100644 --- a/packages/frontend/src/app/validator/[hash]/BlocksChart.js +++ b/packages/frontend/src/app/validator/[hash]/BlocksChart.js @@ -21,7 +21,7 @@ export default function BlocksChart ({ hash, isActive }) { return ( ({ diff --git a/packages/frontend/src/app/validator/[hash]/RewardsChart.js b/packages/frontend/src/app/validator/[hash]/RewardsChart.js index cda35d02b..0508c3915 100644 --- a/packages/frontend/src/app/validator/[hash]/RewardsChart.js +++ b/packages/frontend/src/app/validator/[hash]/RewardsChart.js @@ -21,7 +21,7 @@ export default function RewardsChart ({ hash, isActive }) { return ( ({ diff --git a/packages/frontend/src/components/charts/TabsChartBlock.js b/packages/frontend/src/components/charts/TabsChartBlock.js index 7f778426d..bf8f9f49b 100644 --- a/packages/frontend/src/components/charts/TabsChartBlock.js +++ b/packages/frontend/src/components/charts/TabsChartBlock.js @@ -8,7 +8,7 @@ import { defaultChartConfig } from './config' import './TabsChartBlock.scss' export default function TabsChartBlock ({ - isActive, + menuIsActive, timespanChangeCallback, loading, error, @@ -51,7 +51,7 @@ export default function TabsChartBlock ({ className={'TabsChartBlock__TimeframeSelector'} config={chartConfig} changeCallback={timespanChangeCallback} - isActive={isActive} + menuIsActive={menuIsActive} openStateCallback={setMenuIsOpen} />
diff --git a/packages/frontend/src/components/charts/TimeframeSelector.js b/packages/frontend/src/components/charts/TimeframeSelector.js index e423a85ab..3bfa4e4a3 100644 --- a/packages/frontend/src/components/charts/TimeframeSelector.js +++ b/packages/frontend/src/components/charts/TimeframeSelector.js @@ -1,12 +1,12 @@ import { useState, useEffect } from 'react' import TimeframeMenu from './TimeframeMenu' import { Button } from '@chakra-ui/react' -import { CalendarIcon2, CloseIcon } from '../../components/ui/icons' +import { CalendarIcon2, CloseIcon } from '../ui/icons' import './TimeframeSelector.scss' export default function TimeframeSelector ({ config, - isActive, + menuIsActive, changeCallback, openStateCallback, menuRef, @@ -22,12 +22,12 @@ export default function TimeframeSelector ({ } useEffect(() => { - if (!isActive) setMenuIsOpen(false) - }, [isActive]) + if (!menuIsActive) setMenuIsOpen(false) + }, [menuIsActive]) useEffect(() => { if (typeof openStateCallback === 'function') openStateCallback(menuIsOpen) - }, [menuIsOpen]) + }, [menuIsOpen, openStateCallback]) return (
From 04a866e94de6c0365827984fa8087b8f3dc2a45c Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 19 Dec 2024 15:09:48 +0300 Subject: [PATCH 10/13] Update loading and error conditions of Charts on the specific validator page --- packages/frontend/src/app/validator/[hash]/BlocksChart.js | 6 +++--- packages/frontend/src/app/validator/[hash]/RewardsChart.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/app/validator/[hash]/BlocksChart.js b/packages/frontend/src/app/validator/[hash]/BlocksChart.js index 2e064e290..5124c20ef 100644 --- a/packages/frontend/src/app/validator/[hash]/BlocksChart.js +++ b/packages/frontend/src/app/validator/[hash]/BlocksChart.js @@ -4,7 +4,7 @@ import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates } from '../ import TabsChartBlock from '../../../components/charts/TabsChartBlock' import { defaultChartConfig } from '../../../components/charts/config' -export default function BlocksChart ({ hash, isActive }) { +export default function BlocksChart ({ hash, isActive, loading }) { const [blocksHistory, setBlocksHistory] = useState({ data: {}, loading: true, error: false }) const [timespan, setTimespan] = useState(defaultChartConfig.timespan.values[defaultChartConfig.timespan.defaultIndex]) @@ -28,8 +28,8 @@ export default function BlocksChart ({ hash, isActive }) { x: new Date(item.timestamp), y: item.data.blocksCount })) || []} - loading={blocksHistory.loading} - error={blocksHistory.error} + loading={loading || blocksHistory.loading} + error={!hash || blocksHistory.error} xAxis={{ type: (() => { if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } diff --git a/packages/frontend/src/app/validator/[hash]/RewardsChart.js b/packages/frontend/src/app/validator/[hash]/RewardsChart.js index 0508c3915..13b57b146 100644 --- a/packages/frontend/src/app/validator/[hash]/RewardsChart.js +++ b/packages/frontend/src/app/validator/[hash]/RewardsChart.js @@ -4,7 +4,7 @@ import { fetchHandlerSuccess, fetchHandlerError, getDaysBetweenDates } from '../ import TabsChartBlock from '../../../components/charts/TabsChartBlock' import { defaultChartConfig } from '../../../components/charts/config' -export default function RewardsChart ({ hash, isActive }) { +export default function RewardsChart ({ hash, isActive, loading }) { const [rewardsHistory, setRewardsHistory] = useState({ data: {}, loading: true, error: false }) const [timespan, setTimespan] = useState(defaultChartConfig.timespan.values[defaultChartConfig.timespan.defaultIndex]) @@ -28,8 +28,8 @@ export default function RewardsChart ({ hash, isActive }) { x: new Date(item.timestamp), y: item.data.reward })) || []} - loading={rewardsHistory.loading} - error={rewardsHistory.error} + loading={loading || rewardsHistory.loading} + error={!hash || rewardsHistory.error} xAxis={{ type: (() => { if (getDaysBetweenDates(timespan.range.start, timespan.range.end) > 7) return { axis: 'date' } From c74fc68170b57714c23d71eda4d075b6739b0ec0 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 19 Dec 2024 15:12:08 +0300 Subject: [PATCH 11/13] Update size of ErrorMessageBlock in TabsChartBlock --- packages/frontend/src/components/charts/TabsChartBlock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/charts/TabsChartBlock.js b/packages/frontend/src/components/charts/TabsChartBlock.js index bf8f9f49b..9032a1214 100644 --- a/packages/frontend/src/components/charts/TabsChartBlock.js +++ b/packages/frontend/src/components/charts/TabsChartBlock.js @@ -38,7 +38,7 @@ export default function TabsChartBlock ({ useResizeObserver(TimeframeMenuRef, updateMenuHeight) if (error || (!loading && !data)) { - return () + return () } return ( From 27474918d3e48180d79c379b91723624f564fd3f Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 19 Dec 2024 15:12:43 +0300 Subject: [PATCH 12/13] Update default tab on Validator page --- packages/frontend/src/app/validator/[hash]/Validator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/app/validator/[hash]/Validator.js b/packages/frontend/src/app/validator/[hash]/Validator.js index 1ea240015..dd1015235 100644 --- a/packages/frontend/src/app/validator/[hash]/Validator.js +++ b/packages/frontend/src/app/validator/[hash]/Validator.js @@ -35,7 +35,7 @@ function Validator ({ hash }) { const [currentPage, setCurrentPage] = useState(1) const [transactions, setTransactions] = useState({ data: {}, props: { currentPage: 0 }, loading: true, error: false }) const [withdrawals, setWithdrawals] = useState({ data: {}, props: { currentPage: 0 }, loading: true, error: false }) - const [activeChartTab, setActiveChartTab] = useState(1) + const [activeChartTab, setActiveChartTab] = useState(0) const baseUrl = process.env.NEXT_PUBLIC_BASE_URL const activeNetwork = networks.find(network => network.explorerBaseUrl === baseUrl) const l1explorerBaseUrl = activeNetwork?.l1explorerBaseUrl || null From 21cb228347cf21c53541f2825cb668d417d22cd5 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 19 Dec 2024 15:27:53 +0300 Subject: [PATCH 13/13] Set default value for menuIsActive property --- packages/frontend/src/components/charts/TabsChartBlock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/charts/TabsChartBlock.js b/packages/frontend/src/components/charts/TabsChartBlock.js index 9032a1214..c0c5b95c6 100644 --- a/packages/frontend/src/components/charts/TabsChartBlock.js +++ b/packages/frontend/src/components/charts/TabsChartBlock.js @@ -8,7 +8,7 @@ import { defaultChartConfig } from './config' import './TabsChartBlock.scss' export default function TabsChartBlock ({ - menuIsActive, + menuIsActive = true, timespanChangeCallback, loading, error,