1- import { useMemo } from 'react' ;
1+ import { useMemo , useRef , useState , useLayoutEffect } from 'react' ;
22import { Box , Text , VStack , Skeleton } from '@chakra-ui/react' ;
33import type { RollupBucket , DailyBucket , RawCheck } from '@/api/types' ;
44import type { BucketType } from '@/types/bucket' ;
@@ -24,25 +24,6 @@ export function AvailabilityChart({
2424 height = 300 ,
2525 isLoading,
2626} : AvailabilityChartProps ) {
27- if ( isLoading ) {
28- return (
29- < Box
30- height = '100%'
31- display = 'flex'
32- alignItems = 'center'
33- justifyContent = 'center'
34- borderRadius = 'md'
35- borderWidth = '1px'
36- bg = 'gray.50'
37- _dark = { { bg : 'gray.800' } }
38- >
39- < VStack w = 'full' px = { 6 } gap = { 4 } >
40- < Skeleton height = '100%' width = '100%' />
41- </ VStack >
42- </ Box >
43- ) ;
44- }
45-
4627 const chartData = useMemo ( ( ) => {
4728 if ( ! data ) return null ;
4829 switch ( bucket ) {
@@ -101,56 +82,67 @@ export function AvailabilityChart({
10182 ) ;
10283 }
10384
85+ const containerRef = useRef < HTMLDivElement > ( null ) ;
86+ const [ dimensions , setDimensions ] = useState ( { width : 0 , height } ) ;
87+
88+ useLayoutEffect ( ( ) => {
89+ if ( ! containerRef . current ) return ;
90+
91+ const observer = new ResizeObserver ( entries => {
92+ const entry = entries [ 0 ] ;
93+ setDimensions ( {
94+ width : entry . contentRect . width ,
95+ height : entry . contentRect . height ,
96+ } ) ;
97+ } ) ;
98+
99+ observer . observe ( containerRef . current ) ;
100+ return ( ) => observer . disconnect ( ) ;
101+ } , [ ] ) ;
102+
104103 const margin = { top : 20 , right : 30 , bottom : 40 , left : 70 } ;
104+ const chartWidth = Math . max ( dimensions . width - margin . left - margin . right , 0 ) ;
105+ const chartHeight = Math . max ( dimensions . height - margin . top - margin . bottom , 0 ) ;
105106
106107 return (
107- < Box w = 'full' h = 'full' position = 'relative' >
108- < svg width = '100%' height = '100%' viewBox = { `0 0 1000 ${ height } ` } preserveAspectRatio = 'none' >
108+ // <Skeleton loading={isLoading} w='full' h='full' position='relative'>
109+ < Box ref = { containerRef } flex = { 1 } minH = { 0 } w = 'full' h = 'full' position = 'relative' >
110+ < svg
111+ width = '100%'
112+ height = '100%'
113+ viewBox = { `0 0 ${ dimensions . width } ${ dimensions . height } ` }
114+ preserveAspectRatio = 'none'
115+ >
109116 < g transform = { `translate(${ margin . left } , ${ margin . top } )` } >
110117 { /* Grid + Y-axis labels */ }
111- { [ 0 , 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 , 100 ] . map ( value => (
112- < g key = { value } >
113- < line
114- x1 = { 0 }
115- y1 = {
116- height -
117- margin . top -
118- margin . bottom -
119- ( value / 100 ) * ( height - margin . top - margin . bottom )
120- }
121- x2 = { 1000 - margin . left - margin . right }
122- y2 = {
123- height -
124- margin . top -
125- margin . bottom -
126- ( value / 100 ) * ( height - margin . top - margin . bottom )
127- }
128- stroke = '#e2e8f0'
129- strokeWidth = '1'
130- opacity = '0.5'
131- />
132- < text
133- x = { - 10 }
134- y = {
135- height -
136- margin . top -
137- margin . bottom -
138- ( value / 100 ) * ( height - margin . top - margin . bottom ) +
139- 4
140- }
141- fill = '#718096'
142- textAnchor = 'end'
143- style = { { fontSize : '10px' } }
144- >
145- { value } %
146- </ text >
147- </ g >
148- ) ) }
118+ { [ 0 , 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 , 100 ] . map ( value => {
119+ const y = chartHeight - ( value / 100 ) * chartHeight ;
120+ return (
121+ < g key = { value } >
122+ < line
123+ x1 = { 0 }
124+ y1 = { y }
125+ x2 = { chartWidth }
126+ y2 = { y }
127+ stroke = '#e2e8f0'
128+ strokeWidth = '1'
129+ opacity = '0.5'
130+ />
131+ < text
132+ x = { - 10 }
133+ y = { y + 4 }
134+ fill = '#718096'
135+ textAnchor = 'end'
136+ style = { { fontSize : '10px' } }
137+ >
138+ { value } %
139+ </ text >
140+ </ g >
141+ ) ;
142+ } ) }
149143
150144 { /* Bars */ }
151145 { chartData ?. map ( ( point , index ) => {
152- const chartWidth = 1000 - margin . left - margin . right ;
153- const chartHeight = height - margin . top - margin . bottom ;
154146 const slotWidth = chartWidth / chartData . length ;
155147 const barWidth = slotWidth * 0.6 ;
156148 const x = index * slotWidth + ( slotWidth - barWidth ) / 2 ;
@@ -172,16 +164,17 @@ export function AvailabilityChart({
172164 { /* Y-axis label */ }
173165 < text
174166 x = { - 35 }
175- y = { ( height - margin . top - margin . bottom ) / 2.3 }
176- style = { { fontSize : '12 ' } }
167+ y = { chartHeight / 2.2 }
168+ style = { { fontSize : '12px ' } }
177169 fill = '#718096'
178170 textAnchor = 'middle'
179- transform = { `rotate(-90, -35, ${ ( height - margin . top - margin . bottom ) / 2 } )` }
171+ transform = { `rotate(-90, -35, ${ chartHeight / 2 } )` }
180172 >
181173 Uptime %
182174 </ text >
183175 </ g >
184176 </ svg >
185177 </ Box >
178+ // </Skeleton>
186179 ) ;
187180}
0 commit comments