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';
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
}
...
...
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
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
);
ui/shared/chart/ChartWidgetGraph.tsx
View file @
b01c22e8
...
...
@@ -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
...
...
ui/stats/ChartWidgetContainer.tsx
View file @
b01c22e8
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
}
...
...
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
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
>
))
}
...
...
ui/stats/NumberWidgetSkeleton.tsx
View file @
b01c22e8
...
...
@@ -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
}
>
...
...
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