@@ -16,7 +16,7 @@ import {
1616 isSunday ,
1717} from "date-fns" ;
1818import parse from "parse-duration" ;
19- import { startTransition , useCallback , useEffect , useState , type ReactNode } from "react" ;
19+ import { startTransition , useCallback , useEffect , useRef , useState , type ReactNode } from "react" ;
2020import { AppliedFilter } from "~/components/primitives/AppliedFilter" ;
2121import { DateTimePicker } from "~/components/primitives/DateTimePicker" ;
2222import { DateTime } from "~/components/primitives/DateTime" ;
@@ -30,6 +30,7 @@ import { filterIcon } from "./RunFilters";
3030import { Paragraph } from "~/components/primitives/Paragraph" ;
3131import { ShortcutDefinition } from "~/hooks/useShortcutKeys" ;
3232import { SpanPresenter } from "~/presenters/v3/SpanPresenter.server" ;
33+ import { useLocation } from "@remix-run/react" ;
3334
3435export type DisplayableEnvironment = Pick < RuntimeEnvironment , "type" | "id" > & {
3536 userName ?: string ;
@@ -270,23 +271,65 @@ export function timeFilterRenderValues({
270271 return { label, valueLabel, rangeType } ;
271272}
272273
274+ /** Values passed to onApply callback when a time filter is applied */
275+ export interface TimeFilterApplyValues {
276+ period ?: string ;
277+ from ?: string ;
278+ to ?: string ;
279+ }
280+
273281export interface TimeFilterProps {
274282 defaultPeriod ?: string ;
275283 /** Label name used in the filter display, defaults to "Created" */
276284 labelName ?: string ;
277285 applyShortcut ?: ShortcutDefinition | undefined ;
286+ /** Callback when the user applies a time filter selection, receives the applied values */
287+ onApply ?: ( values : TimeFilterApplyValues ) => void ;
278288}
279289
280- export function TimeFilter ( { defaultPeriod, labelName = "Created" , applyShortcut } : TimeFilterProps = { } ) {
281- const { value, del } = useSearchParams ( ) ;
282-
290+ export function TimeFilter ( {
291+ defaultPeriod,
292+ labelName = "Created" ,
293+ applyShortcut,
294+ onApply,
295+ } : TimeFilterProps = { } ) {
296+ const { value } = useSearchParams ( ) ;
297+ const periodValue = value ( "period" ) ;
298+ const fromValue = value ( "from" ) ;
299+ const toValue = value ( "to" ) ;
300+
301+ //non-optimistic location
302+ const location = useLocation ( ) ;
303+
283304 const { period, from, to, label, valueLabel } = timeFilters ( {
284- period : value ( "period" ) ,
285- from : value ( "from" ) ,
286- to : value ( "to" ) ,
305+ period : periodValue ,
306+ from : fromValue ,
307+ to : toValue ,
287308 defaultPeriod,
288309 labelName,
289310 } ) ;
311+
312+ // Track the search string, not individual values
313+ const previousSearch = useRef < string | null > ( null ) ;
314+
315+ useEffect ( ( ) => {
316+ // Skip first render - set initial value and return
317+ if ( previousSearch . current === null ) {
318+ previousSearch . current = location . search ;
319+ return ;
320+ }
321+
322+ // Only call onApply if the URL actually changed
323+ if ( previousSearch . current !== location . search ) {
324+ const currentSearch = new URLSearchParams ( location . search ) ;
325+ onApply ?.( {
326+ period : currentSearch . get ( "period" ) ?? undefined ,
327+ from : currentSearch . get ( "from" ) ?? undefined ,
328+ to : currentSearch . get ( "to" ) ?? undefined
329+ } ) ;
330+ previousSearch . current = location . search ;
331+ }
332+ } , [ location . search , onApply ] ) ;
290333
291334 return (
292335 < FilterMenuProvider >
@@ -309,6 +352,7 @@ export function TimeFilter({ defaultPeriod, labelName = "Created", applyShortcut
309352 defaultPeriod = { defaultPeriod }
310353 labelName = { labelName }
311354 applyShortcut = { applyShortcut }
355+ onApply = { onApply }
312356 />
313357 ) }
314358 </ FilterMenuProvider >
@@ -334,6 +378,7 @@ export function TimeDropdown({
334378 defaultPeriod = DEFAULT_PERIOD ,
335379 labelName = "Created" ,
336380 applyShortcut,
381+ onApply,
337382} : {
338383 trigger : ReactNode ;
339384 period ?: string ;
@@ -342,6 +387,7 @@ export function TimeDropdown({
342387 defaultPeriod ?: string ;
343388 labelName ?: string ;
344389 applyShortcut ?: ShortcutDefinition | undefined ;
390+ onApply ?: ( values : TimeFilterApplyValues ) => void ;
345391} ) {
346392 const [ open , setOpen ] = useState < boolean | undefined > ( ) ;
347393 const { replace } = useSearchParams ( ) ;
@@ -409,6 +455,7 @@ export function TimeDropdown({
409455 setFromValue ( undefined ) ;
410456 setToValue ( undefined ) ;
411457 setOpen ( false ) ;
458+ onApply ?.( { period : periodToApply , from : undefined , to : undefined } ) ;
412459 } else {
413460 // Validate date range
414461 if ( ! fromValue && ! toValue ) {
@@ -421,15 +468,19 @@ export function TimeDropdown({
421468 return ;
422469 }
423470
471+ const fromStr = fromValue ?. getTime ( ) . toString ( ) ;
472+ const toStr = toValue ?. getTime ( ) . toString ( ) ;
473+
424474 replace ( {
425475 period : undefined ,
426476 cursor : undefined ,
427477 direction : undefined ,
428- from : fromValue ?. getTime ( ) . toString ( ) ,
429- to : toValue ?. getTime ( ) . toString ( ) ,
478+ from : fromStr ,
479+ to : toStr ,
430480 } ) ;
431481
432482 setOpen ( false ) ;
483+ onApply ?.( { period : undefined , from : fromStr , to : toStr } ) ;
433484 }
434485 } , [
435486 activeSection ,
@@ -440,6 +491,7 @@ export function TimeDropdown({
440491 fromValue ,
441492 toValue ,
442493 replace ,
494+ onApply ,
443495 ] ) ;
444496
445497 return (
0 commit comments