1
1
import { useMemo } from "react" ;
2
2
3
- import { format , subDays , subHours } from "date-fns" ;
3
+ import { format } from "date-fns" ;
4
4
import {
5
5
Bar ,
6
6
BarChart ,
@@ -15,23 +15,59 @@ import { TimeRange } from "./time-range-select";
15
15
16
16
interface DashboardViewsChartProps {
17
17
timeRange : TimeRange ;
18
- data ?: {
19
- date : string ;
20
- views : number ;
21
- } [ ] ;
18
+ data ?: { date : string ; views : number } [ ] ;
19
+ startDate ?: Date ;
20
+ endDate ?: Date ;
22
21
}
23
22
24
23
export default function DashboardViewsChart ( {
25
24
timeRange,
26
25
data = [ ] ,
26
+ startDate,
27
+ endDate,
27
28
} : DashboardViewsChartProps ) {
29
+ const totalDays =
30
+ startDate && endDate
31
+ ? Math . ceil (
32
+ ( endDate . getTime ( ) - startDate . getTime ( ) ) / ( 1000 * 60 * 60 * 24 ) ,
33
+ )
34
+ : 0 ;
28
35
// Format the data for display
29
36
const formattedData = useMemo ( ( ) => {
30
37
// Generate all possible time slots
31
38
const now = new Date ( ) ;
32
39
const slots : { date : Date ; views : number } [ ] = [ ] ;
40
+ if ( timeRange === "custom" && startDate && endDate ) {
41
+ if ( totalDays > 365 ) {
42
+ // More than a year: Group by months
43
+ let current = new Date (
44
+ startDate . getFullYear ( ) ,
45
+ startDate . getMonth ( ) ,
46
+ 1 ,
47
+ ) ;
33
48
34
- if ( timeRange === "24h" ) {
49
+ while ( current <= endDate ) {
50
+ slots . push ( { date : new Date ( current ) , views : 0 } ) ;
51
+ current . setMonth ( current . getMonth ( ) + 1 ) ;
52
+ }
53
+ } else if ( totalDays > 30 ) {
54
+ // More than a month but less than a year: Group by weeks
55
+ for ( let i = 0 ; i <= totalDays ; i += 7 ) {
56
+ const date = new Date ( startDate ) ;
57
+ date . setDate ( date . getDate ( ) + i ) ;
58
+ date . setHours ( 0 , 0 , 0 , 0 ) ;
59
+ slots . push ( { date, views : 0 } ) ;
60
+ }
61
+ } else {
62
+ // Less than a month: Show daily data
63
+ for ( let i = 0 ; i <= totalDays ; i ++ ) {
64
+ const date = new Date ( startDate ) ;
65
+ date . setDate ( date . getDate ( ) + i ) ;
66
+ date . setHours ( 0 , 0 , 0 , 0 ) ;
67
+ slots . push ( { date, views : 0 } ) ;
68
+ }
69
+ }
70
+ } else if ( timeRange === "24h" ) {
35
71
// Generate 24 hourly slots
36
72
for ( let i = 23 ; i >= 0 ; i -- ) {
37
73
const date = new Date ( now ) ;
@@ -54,26 +90,64 @@ export default function DashboardViewsChart({
54
90
if ( data ) {
55
91
data . forEach ( ( point ) => {
56
92
const pointDate = new Date ( point . date ) ;
57
- const slotIndex = slots . findIndex ( ( slot ) => {
58
- if ( timeRange === "24h" ) {
59
- return slot . date . getHours ( ) === pointDate . getHours ( ) ;
93
+
94
+ let slotIndex = - 1 ;
95
+
96
+ if ( timeRange === "24h" ) {
97
+ slotIndex = slots . findIndex (
98
+ ( slot ) => slot . date . getHours ( ) === pointDate . getHours ( ) ,
99
+ ) ;
100
+ } else if ( timeRange === "custom" ) {
101
+ if ( totalDays > 365 ) {
102
+ // If range is more than a year, match by month
103
+ slotIndex = slots . findIndex (
104
+ ( slot ) =>
105
+ slot . date . getFullYear ( ) === pointDate . getFullYear ( ) &&
106
+ slot . date . getMonth ( ) === pointDate . getMonth ( ) ,
107
+ ) ;
108
+ } else if ( totalDays > 30 ) {
109
+ // If range is more than a month but less than a year, match by week
110
+ slotIndex = slots . findIndex (
111
+ ( slot ) =>
112
+ pointDate >= slot . date &&
113
+ pointDate <
114
+ new Date ( slot . date . getTime ( ) + 7 * 24 * 60 * 60 * 1000 ) , // Within the week
115
+ ) ;
60
116
} else {
61
- return slot . date . toDateString ( ) === pointDate . toDateString ( ) ;
117
+ // If range is less than a month, match by exact day
118
+ slotIndex = slots . findIndex (
119
+ ( slot ) => slot . date . toDateString ( ) === pointDate . toDateString ( ) ,
120
+ ) ;
62
121
}
63
- } ) ;
122
+ } else {
123
+ // Default case: match by exact day for '7d' and '30d'
124
+ slotIndex = slots . findIndex (
125
+ ( slot ) => slot . date . toDateString ( ) === pointDate . toDateString ( ) ,
126
+ ) ;
127
+ }
128
+
64
129
if ( slotIndex !== - 1 ) {
65
- slots [ slotIndex ] . views = point . views ;
130
+ slots [ slotIndex ] . views + = point . views ;
66
131
}
67
132
} ) ;
68
133
}
69
134
70
135
// Format for display
71
136
return slots . map ( ( slot ) => ( {
72
137
date : slot . date ,
73
- name : format ( slot . date , timeRange === "24h" ? "h:mm aa" : "EEE, MMM d" ) ,
138
+ name : format (
139
+ slot . date ,
140
+ timeRange === "24h"
141
+ ? "h:mm aa"
142
+ : totalDays > 365
143
+ ? "MMM yyyy"
144
+ : totalDays > 30
145
+ ? "MMM d"
146
+ : "EEE, MMM d" ,
147
+ ) ,
74
148
views : slot . views ,
75
149
} ) ) ;
76
- } , [ data , timeRange ] ) ;
150
+ } , [ data , timeRange , startDate , endDate , totalDays ] ) ;
77
151
78
152
// Calculate tick values based on time range
79
153
const ticks = useMemo ( ( ) => {
@@ -96,17 +170,41 @@ export default function DashboardViewsChart({
96
170
tickIndices . unshift ( i ) ;
97
171
}
98
172
return tickIndices . map ( ( i ) => formattedData [ i ] . name ) ;
173
+ } else if ( timeRange === "custom" ) {
174
+ if ( totalDays > 365 ) {
175
+ // Show every 2rd month
176
+ return formattedData . filter ( ( _ , i ) => i % 2 === 0 ) . map ( ( d ) => d . name ) ;
177
+ }
178
+
179
+ if ( totalDays > 30 ) {
180
+ // Show every 2nd week
181
+ return formattedData . filter ( ( _ , i ) => i % 2 === 0 ) . map ( ( d ) => d . name ) ;
182
+ }
183
+ return formattedData . map ( ( d ) => d . name ) ;
99
184
}
100
185
return formattedData . map ( ( d ) => d . name ) ;
101
- } , [ timeRange , formattedData ] ) ;
186
+ } , [ timeRange , formattedData , totalDays ] ) ;
187
+
188
+ const barSize = useMemo ( ( ) => {
189
+ if ( timeRange === "24h" ) return 8 ;
190
+ if ( timeRange === "7d" ) return 24 ;
191
+ if ( timeRange === "30d" ) return 12 ;
192
+
193
+ if ( startDate && endDate ) {
194
+ if ( totalDays > 365 ) return 24 ;
195
+ if ( totalDays > 30 ) return 16 ;
196
+ }
197
+
198
+ return 12 ;
199
+ } , [ timeRange , startDate , endDate , totalDays ] ) ;
102
200
103
201
return (
104
202
< div className = "h-[300px] w-full" >
105
203
< ResponsiveContainer width = "100%" height = "100%" >
106
204
< BarChart
107
205
data = { formattedData }
108
206
margin = { { top : 10 , right : 30 , left : 0 , bottom : 0 } }
109
- barSize = { timeRange === "24h" ? 8 : timeRange === "7d" ? 24 : 12 }
207
+ barSize = { barSize }
110
208
>
111
209
< XAxis
112
210
dataKey = "name"
@@ -137,9 +235,16 @@ export default function DashboardViewsChart({
137
235
Time
138
236
</ span >
139
237
< span className = "font-bold text-muted-foreground" >
140
- { timeRange === "24h"
141
- ? format ( data . date , "h:mm a" )
142
- : format ( data . date , "MMM d, yyyy" ) }
238
+ { format (
239
+ data . date ,
240
+ timeRange === "24h"
241
+ ? "h:mm aa"
242
+ : totalDays > 365
243
+ ? "MMM yyyy"
244
+ : totalDays > 30
245
+ ? "'Week of' MMM d"
246
+ : "MMM d, yyyy" ,
247
+ ) }
143
248
</ span >
144
249
</ div >
145
250
< div className = "flex flex-col" >
0 commit comments