Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
b01c22e8
Commit
b01c22e8
authored
Jan 12, 2023
by
Yuri Mikhin
Committed by
Yuri Mikhin
Jan 13, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add network error state for charts.
parent
20bf686c
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
151 additions
and
75 deletions
+151
-75
AddressCoinBalanceChart.tsx
ui/address/coinBalance/AddressCoinBalanceChart.tsx
+1
-9
ChartWidget.tsx
ui/shared/chart/ChartWidget.tsx
+93
-58
ChartWidgetGraph.tsx
ui/shared/chart/ChartWidgetGraph.tsx
+1
-1
ChartWidgetContainer.tsx
ui/stats/ChartWidgetContainer.tsx
+12
-3
ChartsLoadingErrorAlert.tsx
ui/stats/ChartsLoadingErrorAlert.tsx
+28
-0
ChartsWidgetsList.tsx
ui/stats/ChartsWidgetsList.tsx
+13
-2
NumberWidgetSkeleton.tsx
ui/stats/NumberWidgetSkeleton.tsx
+3
-2
No files found.
ui/address/coinBalance/AddressCoinBalanceChart.tsx
View file @
b01c22e8
...
@@ -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
}
...
...
ui/shared/chart/ChartWidget.tsx
View file @
b01c22e8
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
);
ui/shared/chart/ChartWidgetGraph.tsx
View file @
b01c22e8
...
@@ -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
...
...
ui/stats/ChartWidgetContainer.tsx
View file @
b01c22e8
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
}
...
...
ui/stats/ChartsLoadingErrorAlert.tsx
0 → 100644
View file @
b01c22e8
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
;
ui/stats/ChartsWidgetsList.tsx
View file @
b01c22e8
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
>
))
}
))
}
...
...
ui/stats/NumberWidgetSkeleton.tsx
View file @
b01c22e8
...
@@ -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
}
>
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment