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,64 +108,66 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -92,64 +108,66 @@ 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"
ref={ ref } flexDirection="column"
padding={{ base: 3, lg: 4 }} ref={ ref }
borderRadius="md" padding={{ base: 3, lg: 4 }}
border="1px" borderRadius="md"
borderColor={ borderColor } border="1px"
borderColor={ borderColor }
>
<Grid
gridTemplateColumns="auto auto 36px"
gridColumnGap={ 2 }
> >
<Grid <Text
gridTemplateColumns="auto auto 36px" fontWeight={ 600 }
gridColumnGap={ 2 } fontSize="md"
lineHeight={ 6 }
as="p"
size={{ base: 'xs', lg: 'sm' }}
> >
{ title }
</Text>
{ description && (
<Text <Text
fontWeight={ 600 } mb={ 1 }
fontSize="md" gridColumn={ 1 }
lineHeight={ 6 }
as="p" as="p"
size={{ base: 'xs', lg: 'sm' }} variant="secondary"
fontSize="xs"
> >
{ title } { description }
</Text> </Text>
) }
{ description && ( <Tooltip label="Reset zoom">
<Text <IconButton
mb={ 1 } hidden={ isZoomResetInitial }
gridColumn={ 1 } aria-label="Reset zoom"
as="p" colorScheme="blue"
variant="secondary" w={ 9 }
fontSize="xs" h={ 8 }
> gridColumn={ 2 }
{ description } justifySelf="end"
</Text> alignSelf="top"
) } gridRow="1/3"
size="sm"
<Tooltip label="Reset zoom"> variant="outline"
<IconButton onClick={ handleZoomResetClick }
hidden={ isZoomResetInitial } icon={ <Icon as={ repeatArrowIcon } w={ 4 } h={ 4 }/> }
aria-label="Reset zoom" />
colorScheme="blue" </Tooltip>
w={ 9 }
h={ 8 }
gridColumn={ 2 }
justifySelf="end"
alignSelf="top"
gridRow="1/3"
size="sm"
variant="outline"
onClick={ handleZoomResetClick }
icon={ <Icon as={ repeatArrowIcon } w={ 4 } h={ 4 }/> }
/>
</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>
</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>
{ 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