Skip to content

Commit 69a2a6a

Browse files
authored
fix: 'Around a time' duration update in TimePicker (#1196)
The ‘Around a time’ mode should use the midpoint instead of the date range, so the range gets reset every time. Ref: HDX-2459
1 parent 2ed16e9 commit 69a2a6a

File tree

4 files changed

+79
-16
lines changed

4 files changed

+79
-16
lines changed

.changeset/honest-shoes-sip.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperdx/app": patch
3+
---
4+
5+
fix: 'Around a time' duration update in TimePicker

packages/app/src/components/TimePicker/TimePicker.tsx

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { useUserPreferences } from '@/useUserPreferences';
2525

2626
import { Icon } from '../Icon';
2727

28+
import { TimePickerMode } from './types';
2829
import { useTimePickerForm } from './useTimePickerForm';
2930
import {
3031
dateParser,
@@ -34,7 +35,10 @@ import {
3435
RELATIVE_TIME_OPTIONS,
3536
} from './utils';
3637

37-
const modeAtom = atomWithStorage('hdx-time-picker-mode', 'Time range');
38+
const modeAtom = atomWithStorage<TimePickerMode>(
39+
'hdx-time-picker-mode',
40+
TimePickerMode.Range,
41+
);
3842

3943
const DATE_INPUT_PLACEHOLDER = 'YYY-MM-DD HH:mm:ss';
4044
const DATE_INPUT_FORMAT = 'YYYY-MM-DD HH:mm:ss';
@@ -101,15 +105,25 @@ export const TimePicker = ({
101105
}, [value]);
102106

103107
React.useEffect(() => {
104-
if (dateRange[0] && dateRange[1]) {
105-
form.setValues({
106-
startDate: dateRange[0],
107-
endDate: dateRange[1],
108-
});
108+
// Only update form values from external dateRange when popover is closed
109+
// This prevents overwriting user inputs while they're editing
110+
if (!opened && dateRange[0] && dateRange[1]) {
111+
if (mode === TimePickerMode.Range) {
112+
form.setValues({
113+
startDate: dateRange[0],
114+
endDate: dateRange[1],
115+
});
116+
} else if (mode === TimePickerMode.Around) {
117+
// For "Around a time" mode, set the startDate to the midpoint of the range
118+
const midpoint = new Date(
119+
(dateRange[0].getTime() + dateRange[1].getTime()) / 2,
120+
);
121+
form.setFieldValue('startDate', midpoint);
122+
}
109123
}
110-
// only run when dateRange changes
124+
// only run when dateRange changes or opened state changes
111125
// eslint-disable-next-line react-hooks/exhaustive-deps
112-
}, [dateRange]);
126+
}, [dateRange, opened, mode]);
113127

114128
const handleSearch = React.useCallback(
115129
(value: string | [Date | null, Date | null]) => {
@@ -141,11 +155,11 @@ export const TimePicker = ({
141155
return;
142156
}
143157
const { startDate, endDate } = form.values;
144-
if (mode === 'Time range') {
158+
if (mode === TimePickerMode.Range) {
145159
handleSearch([startDate, endDate]);
146160
close();
147161
}
148-
if (mode === 'Around a time') {
162+
if (mode === TimePickerMode.Around) {
149163
const duration = DURATIONS[form.values.duration];
150164
const from = startDate && sub(startDate, duration);
151165
const to = startDate && add(startDate, duration);
@@ -281,11 +295,49 @@ export const TimePicker = ({
281295
<SegmentedControl
282296
size="xs"
283297
mb="xs"
284-
data={['Time range', 'Around a time']}
298+
data={[TimePickerMode.Range, TimePickerMode.Around]}
285299
value={mode}
286-
onChange={setMode}
300+
onChange={newMode => {
301+
const value = newMode as TimePickerMode;
302+
setMode(value);
303+
// When switching to "Around a time", calculate the center point and appropriate duration
304+
if (
305+
value === TimePickerMode.Around &&
306+
form.values.startDate &&
307+
form.values.endDate
308+
) {
309+
const midpoint = new Date(
310+
(form.values.startDate.getTime() +
311+
form.values.endDate.getTime()) /
312+
2,
313+
);
314+
const halfRangeMs =
315+
(form.values.endDate.getTime() -
316+
form.values.startDate.getTime()) /
317+
2;
318+
319+
// Find the closest duration option
320+
const halfRangeMinutes = halfRangeMs / (1000 * 60);
321+
let closestDuration = '15m'; // default
322+
323+
if (halfRangeMinutes <= 0.5) closestDuration = '30s';
324+
else if (halfRangeMinutes <= 1) closestDuration = '1m';
325+
else if (halfRangeMinutes <= 5) closestDuration = '5m';
326+
else if (halfRangeMinutes <= 15) closestDuration = '15m';
327+
else if (halfRangeMinutes <= 30) closestDuration = '30m';
328+
else if (halfRangeMinutes <= 60) closestDuration = '1h';
329+
else if (halfRangeMinutes <= 180) closestDuration = '3h';
330+
else if (halfRangeMinutes <= 360) closestDuration = '6h';
331+
else closestDuration = '12h';
332+
333+
form.setValues({
334+
startDate: midpoint,
335+
duration: closestDuration,
336+
});
337+
}
338+
}}
287339
/>
288-
{mode === 'Time range' ? (
340+
{mode === TimePickerMode.Range ? (
289341
<>
290342
<H>Start time</H>
291343
<DateInputCmp
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum TimePickerMode {
2+
Range = 'Time range',
3+
Around = 'Around a time',
4+
}

packages/app/src/components/TimePicker/useTimePickerForm.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { useForm } from '@mantine/form';
22

3+
import { TimePickerMode } from './types';
4+
35
type TimePickerForm = {
46
startDate: Date | null;
57
endDate: Date | null;
68
duration: string;
79
};
810

9-
export const useTimePickerForm = ({ mode }: { mode: string }) => {
11+
export const useTimePickerForm = ({ mode }: { mode: TimePickerMode }) => {
1012
const form = useForm<TimePickerForm>({
1113
mode: 'controlled',
1214
initialValues: {
@@ -16,12 +18,12 @@ export const useTimePickerForm = ({ mode }: { mode: string }) => {
1618
},
1719

1820
validate: values => {
19-
if (mode === 'Time range') {
21+
if (mode === TimePickerMode.Range) {
2022
if (!values.startDate || !values.endDate) {
2123
return { startDate: 'Required', endDate: 'Required' };
2224
}
2325
}
24-
if (mode === 'Around a time') {
26+
if (mode === TimePickerMode.Around) {
2527
if (!values.startDate) {
2628
return { time: 'Required' };
2729
}

0 commit comments

Comments
 (0)