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';
import appConfig from 'configs/app/config';
import useApiQuery from 'lib/api/useApiQuery';
import ChartWidget from 'ui/shared/chart/ChartWidget';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
interface Props {
addressHash: string;
......@@ -20,16 +19,9 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
value: BigNumber(value).div(10 ** appConfig.network.currency.decimals).toNumber(),
})), [ data ]);
if (isError) {
return <DataFetchAlert/>;
}
if (!items?.length) {
return null;
}
return (
<ChartWidget
isError={ isError }
title="Balances"
items={ items }
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 React, { useRef, useCallback, useState } from 'react';
......@@ -10,6 +24,7 @@ import scopeIcon from 'icons/scope.svg';
import svgFileIcon from 'icons/svg_file.svg';
import dotsIcon from 'icons/vertical_dots.svg';
import dayjs from 'lib/date/dayjs';
import { apos } from 'lib/html-entities';
import saveAsCSV from 'lib/saveAsCSV';
import ChartWidgetGraph from './ChartWidgetGraph';
......@@ -22,11 +37,12 @@ type Props = {
description?: string;
isLoading: boolean;
chartHeight?: string;
isError: boolean;
}
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 [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
......@@ -92,64 +108,66 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
}, [ items, title ]);
if (isLoading) {
return <ChartWidgetSkeleton hasDescription={ Boolean(description) } chartHeight={ chartHeight }/>;
return <ChartWidgetSkeleton hasDescription={ Boolean(description) }/>;
}
if (items) {
return (
<>
<Box
height={ chartHeight }
ref={ ref }
padding={{ base: 3, lg: 4 }}
borderRadius="md"
border="1px"
borderColor={ borderColor }
return (
<>
<Box
height={ chartHeight }
display="flex"
flexDirection="column"
ref={ ref }
padding={{ base: 3, lg: 4 }}
borderRadius="md"
border="1px"
borderColor={ borderColor }
>
<Grid
gridTemplateColumns="auto auto 36px"
gridColumnGap={ 2 }
>
<Grid
gridTemplateColumns="auto auto 36px"
gridColumnGap={ 2 }
<Text
fontWeight={ 600 }
fontSize="md"
lineHeight={ 6 }
as="p"
size={{ base: 'xs', lg: 'sm' }}
>
{ title }
</Text>
{ description && (
<Text
fontWeight={ 600 }
fontSize="md"
lineHeight={ 6 }
mb={ 1 }
gridColumn={ 1 }
as="p"
size={{ base: 'xs', lg: 'sm' }}
variant="secondary"
fontSize="xs"
>
{ title }
{ description }
</Text>
) }
{ description && (
<Text
mb={ 1 }
gridColumn={ 1 }
as="p"
variant="secondary"
fontSize="xs"
>
{ description }
</Text>
) }
<Tooltip label="Reset zoom">
<IconButton
hidden={ isZoomResetInitial }
aria-label="Reset zoom"
colorScheme="blue"
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>
<Tooltip label="Reset zoom">
<IconButton
hidden={ isZoomResetInitial }
aria-label="Reset zoom"
colorScheme="blue"
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>
<MenuButton
gridColumn={ 3 }
......@@ -195,9 +213,11 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
</MenuItem>
</MenuList>
</Menu>
</Grid>
) }
</Grid>
<Box h={ chartHeight || 'auto' }>
{ items ? (
<Box h={ chartHeight || 'auto' } maxW="100%">
<ChartWidgetGraph
margin={{ bottom: 20 }}
items={ items }
......@@ -206,8 +226,25 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
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>
{ items && (
<FullscreenChartModal
isOpen={ isFullscreen }
items={ items }
......@@ -215,11 +252,9 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
description={ description }
onClose={ clearFullscreenChart }
/>
</>
);
}
return null;
) }
</>
);
};
export default React.memo(ChartWidget);
......@@ -70,7 +70,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
}, [ isZoomResetInitial, items ]);
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 })` }>
<ChartGridLine
......
import React from 'react';
import React, { useEffect } from 'react';
import type { StatsIntervalIds } from 'types/client/stats';
......@@ -12,19 +12,20 @@ type Props = {
title: string;
description: string;
interval: StatsIntervalIds;
onLoadingError: () => void;
}
function formatDate(date: Date) {
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 endDate = selectedInterval.start ? formatDate(new Date()) : undefined;
const startDate = selectedInterval.start ? formatDate(selectedInterval.start) : undefined;
const { data, isLoading } = useApiQuery('stats_charts', {
const { data, isLoading, isError } = useApiQuery('stats_charts', {
queryParams: {
name: id,
from: startDate,
......@@ -37,8 +38,16 @@ const ChartWidgetContainer = ({ id, title, description, interval }: Props) => {
return { date: new Date(item.date), value: Number(item.value) };
});
useEffect(() => {
if (isError) {
onLoadingError();
}
}, [ isError, onLoadingError ]);
return (
<ChartWidget
chartHeight="100%"
isError={ isError }
items={ items }
title={ title }
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 React from 'react';
import React, { useCallback, useState } from 'react';
import type { StatsIntervalIds, StatsSection } from 'types/client/stats';
import { apos } from 'lib/html-entities';
import EmptySearchResult from '../apps/EmptySearchResult';
import ChartsLoadingErrorAlert from './ChartsLoadingErrorAlert';
import ChartWidgetContainer from './ChartWidgetContainer';
type Props = {
......@@ -14,10 +15,19 @@ type Props = {
}
const ChartsWidgetsList = ({ charts, interval }: Props) => {
const [ isSomeChartLoadingError, setIsSomeChartLoadingError ] = useState(false);
const isAnyChartDisplayed = charts.some((section) => section.charts.length > 0);
const handleChartLoadingError = useCallback(
() => setIsSomeChartLoadingError(true),
[ setIsSomeChartLoadingError ]);
return (
<Box>
{ isSomeChartLoadingError && (
<ChartsLoadingErrorAlert/>
) }
{ isAnyChartDisplayed ? (
<List>
{
......@@ -38,7 +48,7 @@ const ChartsWidgetsList = ({ charts, interval }: Props) => {
<Grid
templateColumns={{
lg: 'repeat(2, 1fr)',
lg: 'repeat(2, minmax(0, 1fr))',
}}
gap={ 4 }
>
......@@ -51,6 +61,7 @@ const ChartsWidgetsList = ({ charts, interval }: Props) => {
title={ chart.title }
description={ chart.description }
interval={ interval }
onLoadingError={ handleChartLoadingError }
/>
</GridItem>
)) }
......
......@@ -2,10 +2,11 @@ import { Box, Skeleton, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
const NumberWidgetSkeleton = () => {
const bgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return (
<Box
border="1px"
borderColor={ useColorModeValue('gray.200', 'gray.600') }
backgroundColor={ bgColor }
p={ 3 }
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