11'use client' ;
22
3- import { useState , useEffect } from 'react' ;
3+ import { useState , useEffect , useRef , useCallback , useMemo } from 'react' ;
44import { useRouter } from 'next/navigation' ;
55import { useSearchParams } from 'next/navigation' ;
66import { format , subDays , subHours , subMonths , parse , isValid } from 'date-fns' ;
@@ -24,27 +24,38 @@ interface DateRangeSelectorProps {
2424export default function DateRangeSelector ( { onDateRangeChange } : DateRangeSelectorProps ) {
2525 const router = useRouter ( ) ;
2626 const searchParams = useSearchParams ( ) ;
27+
28+ // Use refs for the popover triggers
29+ const rangePopoverTriggerRef = useRef < HTMLButtonElement > ( null ) ;
30+ const calendarPopoverTriggerRef = useRef < HTMLButtonElement > ( null ) ;
31+
32+ // State for popover open/close
2733 const [ isOpen , setIsOpen ] = useState ( false ) ;
34+ const [ calendarOpen , setCalendarOpen ] = useState ( false ) ;
35+
36+ // Other state
2837 const [ selectedRange , setSelectedRange ] = useState < string | null > ( null ) ;
2938 const [ dateRange , setDateRange ] = useState < { start : Date | null ; end : Date | null } > ( {
3039 start : null ,
3140 end : null ,
3241 } ) ;
33- const [ calendarOpen , setCalendarOpen ] = useState ( false ) ;
3442 const [ isPredefinedRange , setIsPredefinedRange ] = useState ( true ) ;
3543
3644 // Time selection states
3745 const [ startHour , setStartHour ] = useState ( "00" ) ;
3846 const [ startMinute , setStartMinute ] = useState ( "00" ) ;
3947 const [ endHour , setEndHour ] = useState ( "23" ) ;
4048 const [ endMinute , setEndMinute ] = useState ( "59" ) ;
49+
50+ // Track if we've already initialized from URL
51+ const initializedRef = useRef ( false ) ;
4152
4253 // Generate hours and minutes for dropdowns
43- const hours = Array . from ( { length : 24 } , ( _ , i ) => i . toString ( ) . padStart ( 2 , '0' ) ) ;
44- const minutes = Array . from ( { length : 60 } , ( _ , i ) => i . toString ( ) . padStart ( 2 , '0' ) ) ;
54+ const hours = useMemo ( ( ) => Array . from ( { length : 24 } , ( _ , i ) => i . toString ( ) . padStart ( 2 , '0' ) ) , [ ] ) ;
55+ const minutes = useMemo ( ( ) => Array . from ( { length : 60 } , ( _ , i ) => i . toString ( ) . padStart ( 2 , '0' ) ) , [ ] ) ;
4556
46- // Predefined date ranges
47- const dateRangeOptions : DateRangeOption [ ] = [
57+ // Predefined date ranges - memoize to prevent recreation
58+ const dateRangeOptions = useMemo < DateRangeOption [ ] > ( ( ) => [
4859 {
4960 label : 'Last Hour' ,
5061 getValue : ( ) => ( {
@@ -87,10 +98,25 @@ export default function DateRangeSelector({ onDateRangeChange }: DateRangeSelect
8798 end : new Date ( ) ,
8899 } ) ,
89100 } ,
90- ] ;
101+ ] , [ ] ) ;
102+
103+ // Helper function to parse date strings - memoize to prevent recreation
104+ const parseDate = useCallback ( ( dateStr : string ) : Date | null => {
105+ const formats = [ 'yyyy-MM-dd HH:mm:ss' , 'yyyy-MM-dd\'T\'HH:mm:ss' , 'yyyy-MM-dd HH:mm' , 'yyyy-MM-dd' , 'MM/dd/yyyy' , 'dd/MM/yyyy' ] ;
106+
107+ for ( const fmt of formats ) {
108+ const date = parse ( dateStr , fmt , new Date ( ) ) ;
109+ if ( isValid ( date ) ) return date ;
110+ }
111+
112+ return null ;
113+ } , [ ] ) ;
91114
92- // Initialize from URL params
115+ // Initialize from URL params - add dependency array and use ref to prevent multiple runs
93116 useEffect ( ( ) => {
117+ if ( initializedRef . current ) return ;
118+ initializedRef . current = true ;
119+
94120 const start_date = searchParams . get ( 'start_date' ) ;
95121 const end_date = searchParams . get ( 'end_date' ) ;
96122
@@ -157,23 +183,10 @@ export default function DateRangeSelector({ onDateRangeChange }: DateRangeSelect
157183 updateUrlParams ( start , end ) ;
158184 }
159185 }
160- } ) ;
161-
162- // Helper function to parse date strings
163- const parseDate = ( dateStr : string ) : Date | null => {
164- // Try different formats, with the primary format first
165- const formats = [ 'yyyy-MM-dd HH:mm:ss' , 'yyyy-MM-dd\'T\'HH:mm:ss' , 'yyyy-MM-dd HH:mm' , 'yyyy-MM-dd' , 'MM/dd/yyyy' , 'dd/MM/yyyy' ] ;
166-
167- for ( const fmt of formats ) {
168- const date = parse ( dateStr , fmt , new Date ( ) ) ;
169- if ( isValid ( date ) ) return date ;
170- }
171-
172- return null ;
173- } ;
186+ } , [ searchParams , dateRangeOptions , parseDate ] ) ;
174187
175- // Update URL params when date range changes
176- const updateUrlParams = ( start : Date | null , end : Date | null ) => {
188+ // Update URL params when date range changes - memoize to prevent recreation
189+ const updateUrlParams = useCallback ( ( start : Date | null , end : Date | null ) => {
177190 if ( ! start || ! end ) return ;
178191
179192 // Format with time
@@ -207,10 +220,10 @@ export default function DateRangeSelector({ onDateRangeChange }: DateRangeSelect
207220 if ( onDateRangeChange ) {
208221 onDateRangeChange ( startDateStr , endDateStr ) ;
209222 }
210- } ;
223+ } , [ router , searchParams , isPredefinedRange , startHour , startMinute , endHour , endMinute , onDateRangeChange ] ) ;
211224
212- // Handle predefined range selection
213- const handleRangeSelect = ( option : DateRangeOption ) => {
225+ // Handle predefined range selection - memoize to prevent recreation
226+ const handleRangeSelect = useCallback ( ( option : DateRangeOption ) => {
214227 const { start, end } = option . getValue ( ) ;
215228 setDateRange ( { start, end } ) ;
216229 setSelectedRange ( option . label ) ; // Use the predefined label (e.g., "Last 30 days")
@@ -224,10 +237,10 @@ export default function DateRangeSelector({ onDateRangeChange }: DateRangeSelect
224237
225238 updateUrlParams ( start , end ) ;
226239 setIsOpen ( false ) ;
227- } ;
240+ } , [ updateUrlParams ] ) ;
228241
229- // Handle calendar date selection
230- const handleCalendarSelect = ( range : { from : Date | undefined ; to ?: Date | undefined } ) => {
242+ // Handle calendar date selection - memoize to prevent recreation
243+ const handleCalendarSelect = useCallback ( ( range : { from : Date | undefined ; to ?: Date | undefined } ) => {
231244 if ( ! range . from ) return ;
232245
233246 const start = range . from ;
@@ -240,24 +253,34 @@ export default function DateRangeSelector({ onDateRangeChange }: DateRangeSelect
240253
241254 // Don't close the calendar or update URL params yet
242255 // Wait for the Apply button to be clicked
243- } ;
256+ } , [ ] ) ;
244257
245- // Handle time change
246- const handleTimeChange = ( ) => {
258+ // Handle time change - memoize to prevent recreation
259+ const handleTimeChange = useCallback ( ( ) => {
247260 if ( ! dateRange . start || ! dateRange . end ) return ;
248261
249262 updateUrlParams ( dateRange . start , dateRange . end ) ;
250263 setCalendarOpen ( false ) ; // Only close the calendar when Apply is clicked
251- } ;
264+ } , [ dateRange , updateUrlParams ] ) ;
265+
266+ // Memoize the open/close handlers to prevent recreation
267+ const handleRangePopoverOpenChange = useCallback ( ( open : boolean ) => {
268+ setIsOpen ( open ) ;
269+ } , [ ] ) ;
270+
271+ const handleCalendarPopoverOpenChange = useCallback ( ( open : boolean ) => {
272+ setCalendarOpen ( open ) ;
273+ } , [ ] ) ;
252274
253275 return (
254276 < div className = "flex items-center" >
255277 { isPredefinedRange ? (
256278 // Predefined range layout - Text with chevron on left, calendar on right
257279 < >
258- < Popover open = { isOpen } onOpenChange = { setIsOpen } >
280+ < Popover open = { isOpen } onOpenChange = { handleRangePopoverOpenChange } >
259281 < PopoverTrigger asChild >
260282 < Button
283+ ref = { rangePopoverTriggerRef }
261284 variant = "outline"
262285 className = { cn (
263286 "flex items-center justify-between gap-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-l-md rounded-r-none px-3 py-2 h-10" ,
@@ -285,9 +308,10 @@ export default function DateRangeSelector({ onDateRangeChange }: DateRangeSelect
285308 </ PopoverContent >
286309 </ Popover >
287310
288- < Popover open = { calendarOpen } onOpenChange = { setCalendarOpen } >
311+ < Popover open = { calendarOpen } onOpenChange = { handleCalendarPopoverOpenChange } >
289312 < PopoverTrigger asChild >
290313 < Button
314+ ref = { calendarPopoverTriggerRef }
291315 variant = "outline"
292316 className = { cn (
293317 "flex items-center bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 border-l-0 rounded-l-none rounded-r-md px-2 h-10" ,
@@ -382,11 +406,12 @@ export default function DateRangeSelector({ onDateRangeChange }: DateRangeSelect
382406 </ Popover >
383407 </ >
384408 ) : (
385- // Custom range layout - Chevron on left, calendar on right
409+ // Custom range layout - similar changes for this section
386410 < >
387- < Popover open = { isOpen } onOpenChange = { setIsOpen } >
411+ < Popover open = { isOpen } onOpenChange = { handleRangePopoverOpenChange } >
388412 < PopoverTrigger asChild >
389413 < Button
414+ ref = { rangePopoverTriggerRef }
390415 variant = "outline"
391416 className = { cn (
392417 "flex items-center bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-l-md rounded-r-none px-2 h-10" ,
@@ -411,17 +436,18 @@ export default function DateRangeSelector({ onDateRangeChange }: DateRangeSelect
411436 </ PopoverContent >
412437 </ Popover >
413438
414- < Popover open = { calendarOpen } onOpenChange = { setCalendarOpen } >
439+ < Popover open = { calendarOpen } onOpenChange = { handleCalendarPopoverOpenChange } >
415440 < PopoverTrigger asChild >
416441 < Button
442+ ref = { calendarPopoverTriggerRef }
417443 variant = "outline"
418444 className = { cn (
419445 "flex items-center gap-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 border-l-0 rounded-l-none rounded-r-md px-3 py-2 h-10" ,
420446 "transition-all duration-200 ease-in-out"
421447 ) }
422448 >
423449 < span className = "text-sm font-medium" >
424- { selectedRange || 'Select date range ' }
450+ { selectedRange || 'Select dates ' }
425451 </ span >
426452 < CalendarIcon className = "h-4 w-4 text-gray-500 dark:text-gray-400" />
427453 </ Button >
0 commit comments