Commit 67ab023c authored by Yuri Mikhin's avatar Yuri Mikhin Committed by Yuri Mikhin

Add charts filtering logic to the Stats page.

parent 98de5351
export type StatsSection = { id: StatsSectionIds; title: string; charts: Array<StatsChart> }
export type StatsSectionIds = keyof typeof StatsSectionId;
export enum StatsSectionId {
'all',
'accounts',
......@@ -5,9 +7,9 @@ export enum StatsSectionId {
'transactions',
'gas',
}
export type StatsSectionIds = keyof typeof StatsSectionId;
export type StatsSection = { id: StatsSectionIds; value: string }
export type StatsInterval = { id: StatsIntervalIds; title: string }
export type StatsIntervalIds = keyof typeof StatsIntervalId;
export enum StatsIntervalId {
'all',
'oneMonth',
......@@ -15,5 +17,11 @@ export enum StatsIntervalId {
'sixMonths',
'oneYear',
}
export type StatsIntervalIds = keyof typeof StatsIntervalId;
export type StatsInterval = { id: StatsIntervalIds; value: string }
export type StatsChart = {
visible?: boolean;
id: string;
title: string;
description: string;
apiMethodURL: string;
}
......@@ -5,18 +5,36 @@ import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import StatsFilters from '../stats/StatsFilters';
import useStats from '../stats/useStats';
import WidgetsList from '../stats/WidgetsList';
const Stats = () => {
const {
section,
handleSectionChange,
interval,
handleIntervalChange,
debounceFilterCharts,
displayedCharts,
} = useStats();
return (
<Page>
<PageTitle text="Ethereum Stats"/>
<Box mb={{ base: 6, sm: 8 }}>
<StatsFilters/>
<StatsFilters
section={ section }
onSectionChange={ handleSectionChange }
interval={ interval }
onIntervalChange={ handleIntervalChange }
onFilterInputChange={ debounceFilterCharts }
/>
</Box>
<WidgetsList/>
<WidgetsList
charts={ displayedCharts }
/>
</Page>
);
};
......
......@@ -65,6 +65,7 @@ const ChartWidget = ({ title, description }: Props) => {
items={ demoData }
onZoom={ handleZoom }
isZoomResetInitial={ isZoomResetInitial }
title={ title }
/>
</Box>
);
......
......@@ -14,6 +14,7 @@ import useChartSize from 'ui/shared/chart/useChartSize';
import useTimeChartController from 'ui/shared/chart/useTimeChartController';
interface Props {
title: string;
items: Array<TimeChartItem>;
onZoom: () => void;
isZoomResetInitial: boolean;
......@@ -21,7 +22,7 @@ interface Props {
const CHART_MARGIN = { bottom: 20, left: 65, right: 30, top: 10 };
const ChartWidgetGraph = ({ items, onZoom, isZoomResetInitial }: Props) => {
const ChartWidgetGraph = ({ items, onZoom, isZoomResetInitial, title }: Props) => {
const ref = React.useRef<SVGSVGElement>(null);
const [ range, setRange ] = React.useState<[ number, number ]>([ 0, Infinity ]);
const { width, height, innerWidth, innerHeight } = useChartSize(ref.current, CHART_MARGIN);
......@@ -30,7 +31,7 @@ const ChartWidgetGraph = ({ items, onZoom, isZoomResetInitial }: Props) => {
const displayedData = useMemo(() => items.slice(range[0], range[1]).map((d) =>
({ ...d, date: new Date(d.date) })), [ items, range ]);
const chartData = [ { items: items, name: 'chart', color } ];
const chartData = [ { items: items, name: title, color } ];
const { yTickFormat, xScale, yScale } = useTimeChartController({
data: [ { items: displayedData, name: 'chart', color } ],
......
......@@ -4,7 +4,7 @@ import React, { useCallback } from 'react';
import eastMiniArrowIcon from 'icons/arrows/east-mini.svg';
type Props<T extends string> = {
items: Array<{id: T; value: string}>;
items: Array<{id: T; title: string}>;
selectedId: T;
onSelect: (id: T) => void;
}
......@@ -32,7 +32,7 @@ export function StatsDropdownMenu<T extends string>({ items, selectedId, onSelec
display="flex"
alignItems="center"
>
{ selectedCategory?.value }
{ selectedCategory?.title }
<Icon transform="rotate(-90deg)" ml={{ base: 'auto', sm: 1 }} as={ eastMiniArrowIcon } w={ 5 } h={ 5 }/>
</Box>
</MenuButton>
......@@ -48,7 +48,7 @@ export function StatsDropdownMenu<T extends string>({ items, selectedId, onSelec
key={ item.id }
value={ item.id }
>
{ item.value }
{ item.title }
</MenuItemOption>
)) }
</MenuOptionGroup>
......
import { Grid, GridItem } from '@chakra-ui/react';
import debounce from 'lodash/debounce';
import React, { useCallback, useState } from 'react';
import React from 'react';
import type { StatsInterval, StatsIntervalIds, StatsSection, StatsSectionIds } from 'types/client/stats';
......@@ -11,21 +10,29 @@ import StatsDropdownMenu from './StatsDropdownMenu';
const sectionsList = Object.keys(STATS_SECTIONS).map((id: string) => ({
id: id,
value: STATS_SECTIONS[id as StatsSectionIds],
title: STATS_SECTIONS[id as StatsSectionIds],
})) as Array<StatsSection>;
const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({
id: id,
value: STATS_INTERVALS[id as StatsIntervalIds],
title: STATS_INTERVALS[id as StatsIntervalIds],
})) as Array<StatsInterval>;
const StatsFilters = () => {
const [ selectedSectionId, setSelectedSectionId ] = useState<StatsSectionIds>('all');
const [ selectedIntervalId, setSelectedIntervalId ] = useState<StatsIntervalIds>('all');
const [ , setFilterQuery ] = useState('');
type Props = {
section: StatsSectionIds;
onSectionChange: (newSection: StatsSectionIds) => void;
interval: StatsIntervalIds;
onIntervalChange: (newInterval: StatsIntervalIds) => void;
onFilterInputChange: (q: string) => void;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
const debounceFilterCharts = useCallback(debounce(q => setFilterQuery(q), 500), []);
const StatsFilters = ({
section,
onSectionChange,
interval,
onIntervalChange,
onFilterInputChange,
}: Props) => {
return (
<Grid
......@@ -42,7 +49,7 @@ const StatsFilters = () => {
area="input"
>
<FilterInput
onChange={ debounceFilterCharts }
onChange={ onFilterInputChange }
placeholder="Find chart, metric..."/>
</GridItem>
......@@ -52,8 +59,8 @@ const StatsFilters = () => {
>
<StatsDropdownMenu
items={ sectionsList }
selectedId={ selectedSectionId }
onSelect={ setSelectedSectionId }
selectedId={ section }
onSelect={ onSectionChange }
/>
</GridItem>
......@@ -63,8 +70,8 @@ const StatsFilters = () => {
>
<StatsDropdownMenu
items={ intervalList }
selectedId={ selectedIntervalId }
onSelect={ setSelectedIntervalId }
selectedId={ interval }
onSelect={ onIntervalChange }
/>
</GridItem>
</Grid>
......
import { Grid, GridItem, Heading, List, ListItem } from '@chakra-ui/react';
import React from 'react';
import type { StatsSection } from 'types/client/stats';
import { apos } from 'lib/html-entities';
import EmptySearchResult from '../apps/EmptySearchResult';
import ChartWidget from './ChartWidget';
import { statisticsChartsScheme } from './constants/charts-scheme';
const WidgetsList = () => {
return (
type Props = {
charts: Array<StatsSection>;
}
const WidgetsList = ({ charts }: Props) => {
const isAnyChartDisplayed = charts.some((section) => section.charts.some(chart => chart.visible));
return isAnyChartDisplayed ? (
<List>
{
statisticsChartsScheme.map((section) => (
charts.map((section) => (
<ListItem
display={ section.charts.every((chart) => !chart.visible) ? 'none' : 'block' }
key={ section.id }
mb={ 8 }
_last={{
......@@ -30,7 +41,10 @@ const WidgetsList = () => {
gap={ 4 }
>
{ section.charts.map((chart) => (
<GridItem key={ chart.id }>
<GridItem
key={ chart.id }
display={ chart.visible ? 'block' : 'none' }
>
<ChartWidget
apiMethodURL={ chart.apiMethodURL }
title={ chart.title }
......@@ -43,6 +57,8 @@ const WidgetsList = () => {
))
}
</List>
) : (
<EmptySearchResult text={ `Couldn${ apos }t find a chart that matches your filter query.` }/>
);
};
......
export const statisticsChartsScheme = [
import type { StatsSection } from 'types/client/stats';
export const statsChartsScheme: Array<StatsSection> = [
{
id: 'blocks',
title: 'Blocks',
......
import type { TimeChartItem } from '../../shared/chart/types';
import type { TimeChartItem } from 'ui/shared/chart/types';
export const demoData: Array<TimeChartItem> = [ { date: new Date('2022-10-17T00:00:00.000Z'), value: 432670 }, {
date: new Date('2022-10-18T00:00:00.000Z'),
......
import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useState } from 'react';
import type { StatsChart, StatsIntervalIds, StatsSection, StatsSectionIds } from 'types/client/stats';
import { statsChartsScheme } from './constants/charts-scheme';
function isSectionMatches(section: StatsSection, currentSection: StatsSectionIds): boolean {
return currentSection === 'all' || section.id === currentSection;
}
function isChartNameMatches(q: string, chart: StatsChart) {
return chart.title.toLowerCase().includes(q.toLowerCase());
}
export default function useStats() {
const [ isLoading, setIsLoading ] = useState(true);
const [ defaultCharts, setDefaultCharts ] = useState<Array<StatsSection>>();
const [ displayedCharts, setDisplayedCharts ] = useState<Array<StatsSection>>([]);
const [ section, setSection ] = useState<StatsSectionIds>('all');
const [ interval, setInterval ] = useState<StatsIntervalIds>('all');
const [ filterQuery, setFilterQuery ] = useState('');
// eslint-disable-next-line react-hooks/exhaustive-deps
const debounceFilterCharts = useCallback(debounce(q => setFilterQuery(q), 500), []);
const filterCharts = useCallback((q: string, currentSection: StatsSectionIds) => {
const charts = defaultCharts
?.map((section: StatsSection) => {
const charts = section.charts.map((chart: StatsChart) => ({
...chart,
visible: isSectionMatches(section, currentSection) && isChartNameMatches(q, chart),
}));
return {
...section,
charts,
};
});
setDisplayedCharts(charts || []);
}, [ defaultCharts ]);
const handleSectionChange = useCallback((newSection: StatsSectionIds) => {
setSection(newSection);
}, []);
const handleIntervalChange = useCallback((newInterval: StatsIntervalIds) => {
setInterval(newInterval);
}, []);
useEffect(() => {
filterCharts(filterQuery, section);
}, [ filterQuery, section, filterCharts ]);
useEffect(() => {
setDefaultCharts(statsChartsScheme);
setDisplayedCharts(statsChartsScheme);
setIsLoading(false);
}, []);
return React.useMemo(() => ({
section,
handleSectionChange,
interval,
handleIntervalChange,
debounceFilterCharts,
isLoading,
displayedCharts,
}), [
section,
handleSectionChange,
interval,
handleIntervalChange,
debounceFilterCharts,
displayedCharts,
isLoading,
]);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment