mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
Add network error state for charts.
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
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 }
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
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;
|
||||
@@ -1,11 +1,12 @@
|
||||
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 }
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user