Skip to content

Commit 091a535

Browse files
committed
show quick filter selections as label
1 parent 8d42f29 commit 091a535

File tree

1 file changed

+132
-76
lines changed

1 file changed

+132
-76
lines changed

packages/web/app/src/components/ui/date-range-picker.tsx

Lines changed: 132 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,56 @@ export const availablePresets: Preset[] = [
9595
{ name: 'last1y', label: 'Last 1 year', range: { from: 'now-364d', to: 'now' } },
9696
];
9797

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+
98148
export function findMatchingPreset(
99149
range: Preset['range'],
100150
availablePresets: Preset[],
@@ -137,29 +187,53 @@ export function DateRangePicker(props: DateRangePickerProps): JSX.Element {
137187
const [showCalendar, setShowCalendar] = useState(false);
138188

139189
function getInitialPreset() {
140-
let preset: Preset | undefined;
190+
const fallbackPreset = staticPresets.at(0) ?? null;
191+
141192
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)
145196
) {
146-
preset = findMatchingPreset(props.selectedRange, staticPresets);
197+
return fallbackPreset;
198+
}
147199

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+
}
151206

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+
}
159222
}
160223
}
161224

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;
163237
}
164238

165239
const [activePreset, setActivePreset] = useResetState<Preset | null>(getInitialPreset, [
@@ -202,71 +276,53 @@ export function DateRangePicker(props: DateRangePickerProps): JSX.Element {
202276
setActivePreset(getInitialPreset());
203277
};
204278

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+
}
214299

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+
);
234321

235322
const dynamicPresets = useMemo(() => {
236323
const number = parseInt(quickRangeFilter.replace(/\D/g, ''), 10);
237324

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);
270326

271327
const uniqueDynamicPresets = dynamicPresets.filter(
272328
preset => !staticPresets.some(p => p.name === preset.name),

0 commit comments

Comments
 (0)