@@ -17,7 +17,6 @@ import { Skeleton } from '@/components/ui/skeleton';
1717import type { UsageResponse } from '@databuddy/shared' ;
1818import { calculateOverageCost , type OverageInfo } from '../utils/billing-utils' ;
1919
20-
2120type ViewMode = 'daily' | 'cumulative' ;
2221
2322import { METRIC_COLORS } from '@/components/charts/metrics-constants' ;
@@ -37,7 +36,12 @@ interface ConsumptionChartProps {
3736 overageInfo : OverageInfo | null ;
3837}
3938
40- export function ConsumptionChart ( { usageData, isLoading, onDateRangeChange, overageInfo } : ConsumptionChartProps ) {
39+ export function ConsumptionChart ( {
40+ usageData,
41+ isLoading,
42+ onDateRangeChange,
43+ overageInfo,
44+ } : ConsumptionChartProps ) {
4145 const [ viewMode , setViewMode ] = useState < ViewMode > ( 'daily' ) ;
4246 const [ hiddenTypes , setHiddenTypes ] = useState < Record < string , boolean > > ( { } ) ;
4347
@@ -46,9 +50,11 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
4650
4751 // Group the real daily usage by type data by date
4852 const dailyDataMap = new Map < string , Record < string , number > > ( ) ;
49-
53+
5054 // Initialize all dates with zero values for all event types
51- const allDates = [ ...new Set ( usageData . dailyUsageByType . map ( row => row . date ) ) ] . sort ( ) ;
55+ const allDates = [
56+ ...new Set ( usageData . dailyUsageByType . map ( ( row ) => row . date ) ) ,
57+ ] . sort ( ) ;
5258 for ( const date of allDates ) {
5359 dailyDataMap . set ( date , {
5460 event : 0 ,
@@ -68,10 +74,13 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
6874 }
6975
7076 // Convert to chart format with cumulative calculation if needed
71- let runningTotals = Object . keys ( EVENT_TYPE_COLORS ) . reduce ( ( acc , key ) => {
72- acc [ key ] = 0 ;
73- return acc ;
74- } , { } as Record < string , number > ) ;
77+ let runningTotals = Object . keys ( EVENT_TYPE_COLORS ) . reduce (
78+ ( acc , key ) => {
79+ acc [ key ] = 0 ;
80+ return acc ;
81+ } ,
82+ { } as Record < string , number >
83+ ) ;
7584
7685 return Array . from ( dailyDataMap . entries ( ) ) . map ( ( [ date , eventCounts ] ) => {
7786 const dayData : any = {
@@ -83,13 +92,13 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
8392 } ;
8493
8594 // Use real data from ClickHouse, not approximations
86- Object . keys ( EVENT_TYPE_COLORS ) . forEach ( eventType => {
95+ Object . keys ( EVENT_TYPE_COLORS ) . forEach ( ( eventType ) => {
8796 if ( hiddenTypes [ eventType ] ) {
8897 dayData [ eventType ] = 0 ;
8998 return ;
9099 }
91100 const actualAmount = eventCounts [ eventType ] || 0 ;
92-
101+
93102 if ( viewMode === 'cumulative' ) {
94103 runningTotals [ eventType ] += actualAmount ;
95104 dayData [ eventType ] = runningTotals [ eventType ] ;
@@ -129,23 +138,33 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
129138 </ div >
130139 < div className = "flex-1 px-6 py-6 flex items-center justify-center" >
131140 < div className = "text-center" >
132- < CalendarIcon className = "mx-auto h-12 w-12 text-muted-foreground mb-4" weight = "duotone" />
141+ < CalendarIcon
142+ className = "mx-auto h-12 w-12 text-muted-foreground mb-4"
143+ weight = "duotone"
144+ />
133145 < h3 className = "text-lg font-semibold" > No Data Available</ h3 >
134- < p className = "text-muted-foreground" > No usage data found for the selected period</ p >
146+ < p className = "text-muted-foreground" >
147+ No usage data found for the selected period
148+ </ p >
135149 </ div >
136150 </ div >
137151 </ div >
138152 ) ;
139153 }
140154
141- const maxValue = Math . max ( ...chartData . map ( d =>
142- Object . keys ( EVENT_TYPE_COLORS ) . reduce ( ( sum , key ) => sum + ( d [ key ] || 0 ) , 0 )
143- ) ) ;
155+ const maxValue = Math . max (
156+ ...chartData . map ( ( d ) =>
157+ Object . keys ( EVENT_TYPE_COLORS ) . reduce (
158+ ( sum , key ) => sum + ( d [ key ] || 0 ) ,
159+ 0
160+ )
161+ )
162+ ) ;
144163 const yAxisMax = Math . ceil ( maxValue * 1.1 ) ;
145164
146165 return (
147166 < div className = "h-full flex flex-col border-b" >
148- < div className = "px-6 py-4 border-b bg-muted/20" >
167+ < div className = "px-6 py-4 border-b bg-muted/20" >
149168 < div className = "flex items-center justify-between" >
150169 < div className = "flex items-center gap-2" >
151170 < ChartBarIcon className = "h-5 w-5" weight = "duotone" />
@@ -191,10 +210,13 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
191210 variant = "outline"
192211 size = "sm"
193212 onClick = { ( ) => {
194- const allHidden = Object . keys ( EVENT_TYPE_COLORS ) . reduce ( ( acc , key ) => {
195- acc [ key ] = true ;
196- return acc ;
197- } , { } as Record < string , boolean > ) ;
213+ const allHidden = Object . keys ( EVENT_TYPE_COLORS ) . reduce (
214+ ( acc , key ) => {
215+ acc [ key ] = true ;
216+ return acc ;
217+ } ,
218+ { } as Record < string , boolean >
219+ ) ;
198220 setHiddenTypes ( allHidden ) ;
199221 } }
200222 className = "text-xs"
@@ -211,7 +233,7 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
211233 data = { chartData }
212234 margin = { { top : 20 , right : 30 , left : 20 , bottom : 5 } }
213235 style = { {
214- cursor : 'default'
236+ cursor : 'default' ,
215237 } }
216238 >
217239 < defs >
@@ -224,21 +246,13 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
224246 y1 = "0"
225247 y2 = "1"
226248 >
227- < stop
228- offset = "0%"
229- stopColor = { color }
230- stopOpacity = { 0.8 }
231- />
232- < stop
233- offset = "100%"
234- stopColor = { color }
235- stopOpacity = { 0.6 }
236- />
249+ < stop offset = "0%" stopColor = { color } stopOpacity = { 0.8 } />
250+ < stop offset = "100%" stopColor = { color } stopOpacity = { 0.6 } />
237251 </ linearGradient >
238252 ) ) }
239253 </ defs >
240- < XAxis
241- dataKey = "date"
254+ < XAxis
255+ dataKey = "date"
242256 axisLine = { { stroke : 'var(--border)' , strokeOpacity : 0.5 } }
243257 tickLine = { false }
244258 tick = { {
@@ -247,14 +261,19 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
247261 fontWeight : 500 ,
248262 } }
249263 />
250- < YAxis
264+ < YAxis
251265 axisLine = { false }
252266 tickLine = { false }
253- tick = { { fontSize : 11 , fill : 'var(--muted-foreground)' , fontWeight : 500 } }
267+ tick = { {
268+ fontSize : 11 ,
269+ fill : 'var(--muted-foreground)' ,
270+ fontWeight : 500 ,
271+ } }
254272 width = { 45 }
255273 domain = { [ 0 , yAxisMax ] }
256274 tickFormatter = { ( value ) => {
257- if ( value >= 1_000_000 ) return `${ ( value / 1_000_000 ) . toFixed ( 1 ) } M` ;
275+ if ( value >= 1_000_000 )
276+ return `${ ( value / 1_000_000 ) . toFixed ( 1 ) } M` ;
258277 if ( value >= 1000 ) return `${ ( value / 1000 ) . toFixed ( 1 ) } k` ;
259278 return value . toString ( ) ;
260279 } }
@@ -266,40 +285,57 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
266285 < div className = "min-w-[200px] rounded border border-border/50 bg-card p-4" >
267286 < div className = "mb-3 flex items-center gap-2 border-border/30 border-b pb-2" >
268287 < div className = "h-2 w-2 animate-pulse rounded-full bg-primary" />
269- < p className = "font-semibold text-foreground text-sm" > { label } </ p >
288+ < p className = "font-semibold text-foreground text-sm" >
289+ { label }
290+ </ p >
270291 </ div >
271- < div className = "space-y-2.5" >
272- { payload
273- . filter ( entry => entry . value && ( entry . value as number ) > 0 )
274- . map ( ( entry , index ) => {
275- const eventType = entry . dataKey as keyof typeof EVENT_TYPE_COLORS ;
276- const color = EVENT_TYPE_COLORS [ eventType ] ;
277- const eventCount = entry . value as number ;
278- const overageCost = usageData ? calculateOverageCost ( eventCount , usageData . totalEvents , overageInfo ) : 0 ;
279-
280- return (
281- < div key = { index } className = "group flex items-center justify-between gap-3" >
282- < div className = "flex items-center gap-2.5" >
283- < div
284- className = "h-3 w-3 rounded-full shadow-sm ring-2 ring-background"
285- style = { { backgroundColor : color } }
286- />
287- < span className = "font-medium text-muted-foreground text-xs capitalize" >
288- { entry . dataKey ?. toString ( ) . replace ( '_' , ' ' ) }
289- </ span >
290- </ div >
291- < div className = "text-right" >
292- < div className = "font-bold text-foreground text-sm group-hover:text-primary" >
293- { eventCount . toLocaleString ( ) }
292+ < div className = "space-y-2.5" >
293+ { payload
294+ . filter (
295+ ( entry ) =>
296+ entry . value && ( entry . value as number ) > 0
297+ )
298+ . map ( ( entry , index ) => {
299+ const eventType =
300+ entry . dataKey as keyof typeof EVENT_TYPE_COLORS ;
301+ const color = EVENT_TYPE_COLORS [ eventType ] ;
302+ const eventCount = entry . value as number ;
303+ const overageCost = usageData
304+ ? calculateOverageCost (
305+ eventCount ,
306+ usageData . totalEvents ,
307+ overageInfo
308+ )
309+ : 0 ;
310+
311+ return (
312+ < div
313+ key = { index }
314+ className = "group flex items-center justify-between gap-3"
315+ >
316+ < div className = "flex items-center gap-2.5" >
317+ < div
318+ className = "h-3 w-3 rounded-full shadow-sm ring-2 ring-background"
319+ style = { { backgroundColor : color } }
320+ />
321+ < span className = "font-medium text-muted-foreground text-xs capitalize" >
322+ { entry . dataKey
323+ ?. toString ( )
324+ . replace ( '_' , ' ' ) }
325+ </ span >
294326 </ div >
295- < div className = "text-xs text-muted-foreground" >
296- ${ overageCost . toFixed ( 6 ) }
327+ < div className = "text-right" >
328+ < div className = "font-bold text-foreground text-sm group-hover:text-primary" >
329+ { eventCount . toLocaleString ( ) }
330+ </ div >
331+ < div className = "text-xs text-muted-foreground" >
332+ ${ overageCost . toFixed ( 6 ) }
333+ </ div >
297334 </ div >
298335 </ div >
299- </ div >
300- ) ;
301- } ) }
302- </ div >
336+ ) ;
337+ } ) }
338+ </ div >
303339 </ div >
304340 ) ;
305341 }
@@ -327,7 +363,10 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
327363 iconSize = { 10 }
328364 iconType = "circle"
329365 onClick = { ( payload ) => {
330- const anyPayload = payload as unknown as { dataKey ?: string | number ; value ?: string | number } ;
366+ const anyPayload = payload as unknown as {
367+ dataKey ?: string | number ;
368+ value ?: string | number ;
369+ } ;
331370 const raw = anyPayload ?. dataKey ?? anyPayload ?. value ;
332371 if ( raw == null ) return ;
333372 const key = String ( raw ) ;
@@ -352,12 +391,16 @@ export function ConsumptionChart({ usageData, isLoading, onDateRangeChange, over
352391 dataKey = { eventType }
353392 stackId = "events"
354393 fill = { `url(#gradient-${ eventType } )` }
355- stroke = { EVENT_TYPE_COLORS [ eventType as keyof typeof EVENT_TYPE_COLORS ] }
394+ stroke = {
395+ EVENT_TYPE_COLORS [
396+ eventType as keyof typeof EVENT_TYPE_COLORS
397+ ]
398+ }
356399 strokeWidth = { 0.5 }
357400 hide = { ! ! hiddenTypes [ eventType ] }
358401 style = { {
359402 filter : 'none' ,
360- transition : 'none'
403+ transition : 'none' ,
361404 } }
362405 />
363406 ) ) }
0 commit comments