Commit b01c22e8 authored by Yuri Mikhin's avatar Yuri Mikhin Committed by Yuri Mikhin

Add network error state for charts.

parent 20bf686c
...@@ -4,7 +4,6 @@ import React from 'react'; ...@@ -4,7 +4,6 @@ import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import ChartWidget from 'ui/shared/chart/ChartWidget'; import ChartWidget from 'ui/shared/chart/ChartWidget';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
interface Props { interface Props {
addressHash: string; addressHash: string;
...@@ -20,16 +19,9 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { ...@@ -20,16 +19,9 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
value: BigNumber(value).div(10 ** appConfig.network.currency.decimals).toNumber(), value: BigNumber(value).div(10 ** appConfig.network.currency.decimals).toNumber(),
})), [ data ]); })), [ data ]);
if (isError) {
return <DataFetchAlert/>;
}
if (!items?.length) {
return null;
}
return ( return (
<ChartWidget <ChartWidget
isError={ isError }
title="Balances" title="Balances"
items={ items } items={ items }
isLoading={ isLoading } isLoading={ isLoading }
......
import { Box, Grid, Icon, IconButton, Menu, MenuButton, MenuItem, MenuList, Text, Tooltip, useColorModeValue, VisuallyHidden } from '@chakra-ui/react'; import {
Box,
Flex,
Grid,
Icon,
IconButton, Link,
Menu,
MenuButton,
MenuItem,
MenuList,
Text,
Tooltip,
useColorModeValue,
VisuallyHidden,
} from '@chakra-ui/react';
import domToImage from 'dom-to-image'; import domToImage from 'dom-to-image';
import React, { useRef, useCallback, useState } from 'react'; import React, { useRef, useCallback, useState } from 'react';
...@@ -10,6 +24,7 @@ import scopeIcon from 'icons/scope.svg'; ...@@ -10,6 +24,7 @@ import scopeIcon from 'icons/scope.svg';
import svgFileIcon from 'icons/svg_file.svg'; import svgFileIcon from 'icons/svg_file.svg';
import dotsIcon from 'icons/vertical_dots.svg'; import dotsIcon from 'icons/vertical_dots.svg';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import { apos } from 'lib/html-entities';
import saveAsCSV from 'lib/saveAsCSV'; import saveAsCSV from 'lib/saveAsCSV';
import ChartWidgetGraph from './ChartWidgetGraph'; import ChartWidgetGraph from './ChartWidgetGraph';
...@@ -22,11 +37,12 @@ type Props = { ...@@ -22,11 +37,12 @@ type Props = {
description?: string; description?: string;
isLoading: boolean; isLoading: boolean;
chartHeight?: string; chartHeight?: string;
isError: boolean;
} }
const DOWNLOAD_IMAGE_SCALE = 5; const DOWNLOAD_IMAGE_SCALE = 5;
const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Props) => { const ChartWidget = ({ items, title, description, isLoading, chartHeight, isError }: Props) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [ isFullscreen, setIsFullscreen ] = useState(false); const [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true); const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
...@@ -92,14 +108,15 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -92,14 +108,15 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
}, [ items, title ]); }, [ items, title ]);
if (isLoading) { if (isLoading) {
return <ChartWidgetSkeleton hasDescription={ Boolean(description) } chartHeight={ chartHeight }/>; return <ChartWidgetSkeleton hasDescription={ Boolean(description) }/>;
} }
if (items) {
return ( return (
<> <>
<Box <Box
height={ chartHeight } height={ chartHeight }
display="flex"
flexDirection="column"
ref={ ref } ref={ ref }
padding={{ base: 3, lg: 4 }} padding={{ base: 3, lg: 4 }}
borderRadius="md" borderRadius="md"
...@@ -150,6 +167,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -150,6 +167,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
/> />
</Tooltip> </Tooltip>
{ !isError && (
<Menu> <Menu>
<MenuButton <MenuButton
gridColumn={ 3 } gridColumn={ 3 }
...@@ -195,9 +213,11 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -195,9 +213,11 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
</MenuItem> </MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
) }
</Grid> </Grid>
<Box h={ chartHeight || 'auto' }> { items ? (
<Box h={ chartHeight || 'auto' } maxW="100%">
<ChartWidgetGraph <ChartWidgetGraph
margin={{ bottom: 20 }} margin={{ bottom: 20 }}
items={ items } items={ items }
...@@ -206,8 +226,25 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -206,8 +226,25 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
title={ title } title={ title }
/> />
</Box> </Box>
) : (
<Flex
alignItems="center"
justifyContent="center"
flexGrow={ 1 }
py={ 4 }
>
<Text
variant="secondary"
fontSize="sm"
>
{ `Data didn${ apos }t load, please ` }
<Link href={ window.document.location.href }>try to reload page.</Link>
</Text>
</Flex>
) }
</Box> </Box>
{ items && (
<FullscreenChartModal <FullscreenChartModal
isOpen={ isFullscreen } isOpen={ isFullscreen }
items={ items } items={ items }
...@@ -215,11 +252,9 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -215,11 +252,9 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
description={ description } description={ description }
onClose={ clearFullscreenChart } onClose={ clearFullscreenChart }
/> />
) }
</> </>
); );
}
return null;
}; };
export default React.memo(ChartWidget); export default React.memo(ChartWidget);
...@@ -70,7 +70,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -70,7 +70,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
}, [ isZoomResetInitial, items ]); }, [ isZoomResetInitial, items ]);
return ( return (
<svg width={ width || '100%' } height={ height || '100%' } ref={ ref } cursor="pointer" id={ chartId } opacity={ width ? 1 : 0 }> <svg width="100%" height={ height || '100%' } ref={ ref } cursor="pointer" id={ chartId } opacity={ width ? 1 : 0 }>
<g transform={ `translate(${ chartMargin?.left || 0 },${ chartMargin?.top || 0 })` }> <g transform={ `translate(${ chartMargin?.left || 0 },${ chartMargin?.top || 0 })` }>
<ChartGridLine <ChartGridLine
......
import React from 'react'; import React, { useEffect } from 'react';
import type { StatsIntervalIds } from 'types/client/stats'; import type { StatsIntervalIds } from 'types/client/stats';
...@@ -12,19 +12,20 @@ type Props = { ...@@ -12,19 +12,20 @@ type Props = {
title: string; title: string;
description: string; description: string;
interval: StatsIntervalIds; interval: StatsIntervalIds;
onLoadingError: () => void;
} }
function formatDate(date: Date) { function formatDate(date: Date) {
return date.toISOString().substring(0, 10); return date.toISOString().substring(0, 10);
} }
const ChartWidgetContainer = ({ id, title, description, interval }: Props) => { const ChartWidgetContainer = ({ id, title, description, interval, onLoadingError }: Props) => {
const selectedInterval = STATS_INTERVALS[interval]; const selectedInterval = STATS_INTERVALS[interval];
const endDate = selectedInterval.start ? formatDate(new Date()) : undefined; const endDate = selectedInterval.start ? formatDate(new Date()) : undefined;
const startDate = selectedInterval.start ? formatDate(selectedInterval.start) : undefined; const startDate = selectedInterval.start ? formatDate(selectedInterval.start) : undefined;
const { data, isLoading } = useApiQuery('stats_charts', { const { data, isLoading, isError } = useApiQuery('stats_charts', {
queryParams: { queryParams: {
name: id, name: id,
from: startDate, from: startDate,
...@@ -37,8 +38,16 @@ const ChartWidgetContainer = ({ id, title, description, interval }: Props) => { ...@@ -37,8 +38,16 @@ const ChartWidgetContainer = ({ id, title, description, interval }: Props) => {
return { date: new Date(item.date), value: Number(item.value) }; return { date: new Date(item.date), value: Number(item.value) };
}); });
useEffect(() => {
if (isError) {
onLoadingError();
}
}, [ isError, onLoadingError ]);
return ( return (
<ChartWidget <ChartWidget
chartHeight="100%"
isError={ isError }
items={ items } items={ items }
title={ title } title={ title }
description={ description } description={ description }
......
import { Alert, CloseButton, Link, Text, useDisclosure } from '@chakra-ui/react';
import React from 'react';
import { apos } from 'lib/html-entities';
function ChartsLoadingErrorAlert() {
const {
isOpen: isVisible,
onClose,
} = useDisclosure({ defaultIsOpen: true });
return isVisible ? (
<Alert status="warning" mb={ 4 }>
<Text mr={ 2 }>
{ `Some of the charts did not load because the server didn${ apos }t respond. To reload charts ` }
<Link href={ window.document.location.href }>click once again.</Link>
</Text>
<CloseButton
alignSelf={{ base: 'flex-start', lg: 'center' }}
ml="auto"
onClick={ onClose }
/>
</Alert>
) : null;
}
export default ChartsLoadingErrorAlert;
import { Box, Grid, GridItem, Heading, List, ListItem } from '@chakra-ui/react'; import { Box, Grid, GridItem, Heading, List, ListItem } from '@chakra-ui/react';
import React from 'react'; import React, { useCallback, useState } from 'react';
import type { StatsIntervalIds, StatsSection } from 'types/client/stats'; import type { StatsIntervalIds, StatsSection } from 'types/client/stats';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import EmptySearchResult from '../apps/EmptySearchResult'; import EmptySearchResult from '../apps/EmptySearchResult';
import ChartsLoadingErrorAlert from './ChartsLoadingErrorAlert';
import ChartWidgetContainer from './ChartWidgetContainer'; import ChartWidgetContainer from './ChartWidgetContainer';
type Props = { type Props = {
...@@ -14,10 +15,19 @@ type Props = { ...@@ -14,10 +15,19 @@ type Props = {
} }
const ChartsWidgetsList = ({ charts, interval }: Props) => { const ChartsWidgetsList = ({ charts, interval }: Props) => {
const [ isSomeChartLoadingError, setIsSomeChartLoadingError ] = useState(false);
const isAnyChartDisplayed = charts.some((section) => section.charts.length > 0); const isAnyChartDisplayed = charts.some((section) => section.charts.length > 0);
const handleChartLoadingError = useCallback(
() => setIsSomeChartLoadingError(true),
[ setIsSomeChartLoadingError ]);
return ( return (
<Box> <Box>
{ isSomeChartLoadingError && (
<ChartsLoadingErrorAlert/>
) }
{ isAnyChartDisplayed ? ( { isAnyChartDisplayed ? (
<List> <List>
{ {
...@@ -38,7 +48,7 @@ const ChartsWidgetsList = ({ charts, interval }: Props) => { ...@@ -38,7 +48,7 @@ const ChartsWidgetsList = ({ charts, interval }: Props) => {
<Grid <Grid
templateColumns={{ templateColumns={{
lg: 'repeat(2, 1fr)', lg: 'repeat(2, minmax(0, 1fr))',
}} }}
gap={ 4 } gap={ 4 }
> >
...@@ -51,6 +61,7 @@ const ChartsWidgetsList = ({ charts, interval }: Props) => { ...@@ -51,6 +61,7 @@ const ChartsWidgetsList = ({ charts, interval }: Props) => {
title={ chart.title } title={ chart.title }
description={ chart.description } description={ chart.description }
interval={ interval } interval={ interval }
onLoadingError={ handleChartLoadingError }
/> />
</GridItem> </GridItem>
)) } )) }
......
...@@ -2,10 +2,11 @@ import { Box, Skeleton, useColorModeValue } from '@chakra-ui/react'; ...@@ -2,10 +2,11 @@ import { Box, Skeleton, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const NumberWidgetSkeleton = () => { const NumberWidgetSkeleton = () => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return ( return (
<Box <Box
border="1px" backgroundColor={ bgColor }
borderColor={ useColorModeValue('gray.200', 'gray.600') }
p={ 3 } p={ 3 }
borderRadius={ 12 } borderRadius={ 12 }
> >
......
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