@@ -95,6 +95,56 @@ export const availablePresets: Preset[] = [
95
95
{ name : 'last1y' , label : 'Last 1 year' , range : { from : 'now-364d' , to : 'now' } } ,
96
96
] ;
97
97
98
+ function createQuickRangePresets ( number : number , validUnits : DurationUnit [ ] ) : Preset [ ] {
99
+ const presets : Preset [ ] = [ ] ;
100
+
101
+ if ( validUnits . includes ( 'm' ) ) {
102
+ presets . push ( {
103
+ name : `last${ number } min` ,
104
+ label : `Last ${ number } minutes` ,
105
+ range : { from : `now-${ number } m` , to : 'now' } ,
106
+ } ) ;
107
+ }
108
+ if ( validUnits . includes ( 'h' ) ) {
109
+ presets . push ( {
110
+ name : `last${ number } h` ,
111
+ label : `Last ${ number } hours` ,
112
+ range : { from : `now-${ number } h` , to : 'now' } ,
113
+ } ) ;
114
+ }
115
+ if ( validUnits . includes ( 'd' ) ) {
116
+ presets . push ( {
117
+ name : `last${ number } d` ,
118
+ label : `Last ${ number } days` ,
119
+ range : { from : `now-${ number } d` , to : 'now' } ,
120
+ } ) ;
121
+ }
122
+ if ( validUnits . includes ( 'w' ) ) {
123
+ presets . push ( {
124
+ name : `last${ number } w` ,
125
+ label : `Last ${ number } weeks` ,
126
+ range : { from : `now-${ number } w` , to : 'now' } ,
127
+ } ) ;
128
+ }
129
+ if ( validUnits . includes ( 'M' ) ) {
130
+ presets . push ( {
131
+ name : `last${ number } M` ,
132
+ label : `Last ${ number } months` ,
133
+ range : { from : `now-${ number } M` , to : 'now' } ,
134
+ } ) ;
135
+ }
136
+
137
+ if ( validUnits . includes ( 'y' ) ) {
138
+ presets . push ( {
139
+ name : `last${ number } y` ,
140
+ label : `Last ${ number } years` ,
141
+ range : { from : `now-${ number } y` , to : 'now' } ,
142
+ } ) ;
143
+ }
144
+
145
+ return presets ;
146
+ }
147
+
98
148
export function findMatchingPreset (
99
149
range : Preset [ 'range' ] ,
100
150
availablePresets : Preset [ ] ,
@@ -137,29 +187,53 @@ export function DateRangePicker(props: DateRangePickerProps): JSX.Element {
137
187
const [ showCalendar , setShowCalendar ] = useState ( false ) ;
138
188
139
189
function getInitialPreset ( ) {
140
- let preset : Preset | undefined ;
190
+ const fallbackPreset = staticPresets . at ( 0 ) ?? null ;
191
+
141
192
if (
142
- props . selectedRange &&
143
- ! hasInvalidUnitRegex ?. test ( props . selectedRange . from ) &&
144
- ! hasInvalidUnitRegex ?. test ( props . selectedRange . to )
193
+ ! props . selectedRange ||
194
+ hasInvalidUnitRegex ?. test ( props . selectedRange . from ) ||
195
+ hasInvalidUnitRegex ?. test ( props . selectedRange . to )
145
196
) {
146
- preset = findMatchingPreset ( props . selectedRange , staticPresets ) ;
197
+ return fallbackPreset ;
198
+ }
147
199
148
- if ( preset ) {
149
- return preset ;
150
- }
200
+ // Attempt to find preset from out pre-defined presets first
201
+ const preset = findMatchingPreset ( props . selectedRange , staticPresets ) ;
202
+
203
+ if ( preset ) {
204
+ return preset ;
205
+ }
151
206
152
- const resolvedRange = resolveRange ( props . selectedRange . from , props . selectedRange . to ) ;
153
- if ( resolvedRange ) {
154
- return {
155
- name : `${ props . selectedRange . from } _${ props . selectedRange . to } ` ,
156
- label : buildDateRangeString ( resolvedRange ) ,
157
- range : props . selectedRange ,
158
- } ;
207
+ // attempt to find the preset based on dynamic presets (so we show something like "last x days" instead of 10. September - 12.September for `now-2d`)
208
+ if ( props . selectedRange . from . startsWith ( 'now-' ) ) {
209
+ const number = parseInt ( props . selectedRange . from . replace ( / \D / g, '' ) , 10 ) ;
210
+ if ( ! Number . isNaN ( number ) ) {
211
+ const quickRangPresets = createQuickRangePresets ( number , validUnits ) ;
212
+
213
+ const preset = quickRangPresets . find (
214
+ preset =>
215
+ preset . range . from === props . selectedRange ?. from &&
216
+ preset . range . to === props . selectedRange . to ,
217
+ ) ;
218
+
219
+ if ( preset ) {
220
+ return preset ;
221
+ }
159
222
}
160
223
}
161
224
162
- return staticPresets . at ( 0 ) ?? null ;
225
+ // if everything else fails we show an absolute range!
226
+
227
+ const resolvedRange = resolveRange ( props . selectedRange . from , props . selectedRange . to ) ;
228
+ if ( resolvedRange ) {
229
+ return {
230
+ name : `${ props . selectedRange . from } _${ props . selectedRange . to } ` ,
231
+ label : buildDateRangeString ( resolvedRange ) ,
232
+ range : props . selectedRange ,
233
+ } ;
234
+ }
235
+
236
+ return fallbackPreset ;
163
237
}
164
238
165
239
const [ activePreset , setActivePreset ] = useResetState < Preset | null > ( getInitialPreset , [
@@ -202,71 +276,53 @@ export function DateRangePicker(props: DateRangePickerProps): JSX.Element {
202
276
setActivePreset ( getInitialPreset ( ) ) ;
203
277
} ;
204
278
205
- const PresetButton = ( { preset } : { preset : Preset } ) : JSX . Element => {
206
- let isDisabled = false ;
207
-
208
- if ( props . startDate ) {
209
- const from = parse ( preset . range . from ) ;
210
- if ( from && from . getTime ( ) < props . startDate . getTime ( ) ) {
211
- isDisabled = true ;
212
- }
213
- }
279
+ const PresetButton = useMemo (
280
+ ( ) =>
281
+ function PresetButton ( { preset } : { preset : Preset } ) : React . ReactNode {
282
+ let isDisabled = false ;
283
+
284
+ if ( props . startDate ) {
285
+ const from = parse ( preset . range . from ) ;
286
+ const time = from ?. getTime ( ) ;
287
+ const startTime = props . startDate ?. getTime ( ) ;
288
+
289
+ if (
290
+ ! time ||
291
+ ! startTime ||
292
+ Number . isNaN ( time ) ||
293
+ Number . isNaN ( startTime ) ||
294
+ time < props . startDate . getTime ( )
295
+ ) {
296
+ isDisabled = true ;
297
+ }
298
+ }
214
299
215
- return (
216
- < Button
217
- variant = "ghost"
218
- onClick = { ( ) => {
219
- setActivePreset ( preset ) ;
220
- setFromValue ( preset . range . from ) ;
221
- setToValue ( preset . range . to ) ;
222
- setRange ( undefined ) ;
223
- setShowCalendar ( false ) ;
224
- setIsOpen ( false ) ;
225
- setQuickRangeFilter ( '' ) ;
226
- } }
227
- disabled = { isDisabled }
228
- className = "w-full justify-start text-left"
229
- >
230
- { preset . label }
231
- </ Button >
232
- ) ;
233
- } ;
300
+ return (
301
+ < Button
302
+ variant = "ghost"
303
+ onClick = { ( ) => {
304
+ setActivePreset ( preset ) ;
305
+ setFromValue ( preset . range . from ) ;
306
+ setToValue ( preset . range . to ) ;
307
+ setRange ( undefined ) ;
308
+ setShowCalendar ( false ) ;
309
+ setIsOpen ( false ) ;
310
+ setQuickRangeFilter ( '' ) ;
311
+ } }
312
+ disabled = { isDisabled }
313
+ className = "w-full justify-start text-left"
314
+ >
315
+ { preset . label }
316
+ </ Button >
317
+ ) ;
318
+ } ,
319
+ [ props . startDate ] ,
320
+ ) ;
234
321
235
322
const dynamicPresets = useMemo ( ( ) => {
236
323
const number = parseInt ( quickRangeFilter . replace ( / \D / g, '' ) , 10 ) ;
237
324
238
- const dynamicPresets : Preset [ ] = [
239
- {
240
- name : `last${ number } min` ,
241
- label : `Last ${ number } minutes` ,
242
- range : { from : `now-${ number } m` , to : 'now' } ,
243
- } ,
244
- {
245
- name : `last${ number } h` ,
246
- label : `Last ${ number } hours` ,
247
- range : { from : `now-${ number } h` , to : 'now' } ,
248
- } ,
249
- {
250
- name : `last${ number } d` ,
251
- label : `Last ${ number } days` ,
252
- range : { from : `now-${ number } d` , to : 'now' } ,
253
- } ,
254
- {
255
- name : `last${ number } w` ,
256
- label : `Last ${ number } weeks` ,
257
- range : { from : `now-${ number } w` , to : 'now' } ,
258
- } ,
259
- {
260
- name : `last${ number } M` ,
261
- label : `Last ${ number } months` ,
262
- range : { from : `now-${ number } M` , to : 'now' } ,
263
- } ,
264
- {
265
- name : `last${ number } y` ,
266
- label : `Last ${ number } years` ,
267
- range : { from : `now-${ number } y` , to : 'now' } ,
268
- } ,
269
- ] ;
325
+ const dynamicPresets = createQuickRangePresets ( number , validUnits ) ;
270
326
271
327
const uniqueDynamicPresets = dynamicPresets . filter (
272
328
preset => ! staticPresets . some ( p => p . name === preset . name ) ,
0 commit comments