Skip to content

Commit 613d9c5

Browse files
authored
[O2B-1599] && [O2B-1598] Use datetime local input rather than split inputs (date and time) for date time filters and inputs (#2193)
Notable changes for users: - Users will now have only one input that accepts both dates and times for filtering, creating qc flags, etc. - [O2B-1598] fixes a bug where the inputs for date and time would not be reset by clicking "X" button icon Notable changes for developers: - given the fact that it is not using html native input time, it means some logic behind (such as calculating times for days on change of different inputs) is now no longer needed and instead it can easily compare to one value only
1 parent 9bc3ef9 commit 613d9c5

16 files changed

Lines changed: 187 additions & 428 deletions

lib/public/components/common/form/inputs/DateTimeInputComponent.js

Lines changed: 16 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
*/
1313
import { h, StatefulComponent } from '/js/src/index.js';
1414
import { iconX } from '/js/src/icons.js';
15-
import { MILLISECONDS_IN_ONE_DAY } from '../../../../utilities/dateUtils.mjs';
1615
import { formatTimestampForDateTimeInput } from '../../../../utilities/formatting/dateTimeInputFormatters.mjs';
1716

1817
/**
@@ -22,12 +21,11 @@ import { formatTimestampForDateTimeInput } from '../../../../utilities/formattin
2221

2322
/**
2423
* @typedef DateTimeInputComponentAttrs
25-
* @property {DateTimeInputRawData} value the actual inputs values
24+
* @property {string} value the datetime-local input value
2625
* @property {function} onChange function called with the new value when it changes
27-
* @property {Partial<DateTimeInputRawData>} [defaults] the default raw values to use when partially updating inputs
2826
* @property {boolean} [seconds=false] states if the input has granularity up to seconds (if not, granularity is minutes)
2927
* @property {boolean} [milliseconds=false] states if the input has granularity up to milliseconds
30-
* @property {boolean} [required] states if the inputs can be omitted
28+
* @property {boolean} [required] states if the input can be omitted
3129
* @property {number} [min] the timestamp of the minimum date allowed by the input (included)
3230
* @property {number} [max] the timestamp of the maximum date allowed by the input (excluded)
3331
*/
@@ -71,40 +69,29 @@ export class DateTimeInputComponent extends StatefulComponent {
7169
* @return {Component} the component
7270
*/
7371
view() {
74-
const inputsMin = this._getInputsMin(this._minTimestamp, this._value);
75-
const inputsMax = this._getInputsMax(this._maxTimestamp, this._value);
72+
const inputMin = this._minTimestamp !== null
73+
? formatTimestampForDateTimeInput(this._minTimestamp, this._seconds || this._milliseconds, this._milliseconds)
74+
: '';
75+
const inputMax = this._maxTimestamp !== null
76+
? formatTimestampForDateTimeInput(this._maxTimestamp, this._seconds || this._milliseconds, this._milliseconds)
77+
: '';
7678

7779
return h('.flex-row.items-center.g2', [
7880
h(
7981
'input.form-control',
8082
{
81-
type: 'date',
82-
// Firefox shrinks the date inputs, apply a min-width to it
83-
style: { minWidth: '9rem' },
83+
type: 'datetime-local',
8484
required: this._required,
85-
value: this._value.date,
86-
onchange: (e) => this._patchValue({ date: e.target.value }),
87-
// Mithril do not remove min/max if previously set...
88-
min: inputsMin?.date ?? '',
89-
max: inputsMax?.date ?? '',
90-
},
91-
),
92-
h(
93-
'input.form-control',
94-
{
95-
type: 'time',
96-
required: this._required,
97-
value: this._value.time,
98-
step: this._milliseconds ? 0.001 : this._seconds ? 1 : undefined,
99-
onchange: (e) => this._patchValue({ time: e.target.value }),
100-
// Mithril do not remove min/max if previously set...
101-
min: inputsMin?.time ?? '',
102-
max: inputsMax?.time ?? '',
85+
value: this._value,
86+
step: this._milliseconds ? 0.001 : this._seconds ? 1 : 60,
87+
onchange: (e) => this._onChange(e.target.value),
88+
min: inputMin,
89+
max: inputMax,
10390
},
10491
),
10592
h(
10693
'.btn.btn-pill.f7',
107-
{ disabled: this._value.date === '' && this._value.time === '', onclick: () => this._patchValue({ date: '', time: '' }) },
94+
{ disabled: this._value === '', onclick: () => this._onChange('') },
10895
iconX(),
10996
),
11097
]);
@@ -118,8 +105,7 @@ export class DateTimeInputComponent extends StatefulComponent {
118105
* @private
119106
*/
120107
_updateAttrs(attrs) {
121-
const { value, onChange, seconds, milliseconds, required = false, defaults = {}, min = null, max = null } = attrs;
122-
const { date: defaultDate = '', time: defaultTime = '' } = defaults;
108+
const { value, onChange, seconds, milliseconds, required = false, min = null, max = null } = attrs;
123109

124110
this._value = value;
125111
this._onChange = onChange;
@@ -129,105 +115,7 @@ export class DateTimeInputComponent extends StatefulComponent {
129115

130116
this._required = required;
131117

132-
/**
133-
* @type {DateTimeInputRawData}
134-
* @private
135-
*/
136-
this._defaults = {
137-
date: defaultDate,
138-
time: defaultTime,
139-
};
140-
141118
this._minTimestamp = min;
142119
this._maxTimestamp = max;
143120
}
144-
145-
/**
146-
* Update the inputs values
147-
*
148-
* @param {Partial<DateTimeInputRawData>} patch the input values patch
149-
* @return {void}
150-
*/
151-
_patchValue({ time, date }) {
152-
if (date !== undefined) {
153-
this._value.date = date;
154-
if (time === undefined && !this._value.time) {
155-
this._value.time = this._defaults.time;
156-
}
157-
}
158-
159-
if (time !== undefined) {
160-
this._value.time = time;
161-
if (date === undefined && !this._value.date) {
162-
this._value.date = this._defaults.date;
163-
}
164-
}
165-
166-
this._onChange(this._value);
167-
}
168-
169-
/**
170-
* Returns the min values to apply to the inputs
171-
*
172-
* @param {number|null} minTimestamp the minimal timestamp to represent in the inputs
173-
* @param {DateTimeInputRawData} raw the current raw values
174-
* @return {(Partial<{date: string, time: string}>|null)} the min values to apply to date and time inputs
175-
*/
176-
_getInputsMin(minTimestamp, raw) {
177-
if (minTimestamp === null) {
178-
return null;
179-
}
180-
181-
const rawDate = raw.date || null;
182-
const rawTime = raw.time || null;
183-
184-
const minDateAndTime = formatTimestampForDateTimeInput(minTimestamp, this._seconds || this._milliseconds, this._milliseconds);
185-
const inputsMin = {};
186-
187-
if (rawDate !== null && rawDate === minDateAndTime.date) {
188-
inputsMin.time = minDateAndTime.time;
189-
}
190-
191-
if (rawTime !== null && rawTime < minDateAndTime.time) {
192-
inputsMin.date = formatTimestampForDateTimeInput(
193-
minTimestamp + MILLISECONDS_IN_ONE_DAY,
194-
this._seconds || this._milliseconds,
195-
this._milliseconds,
196-
).date;
197-
} else {
198-
inputsMin.date = minDateAndTime.date;
199-
}
200-
201-
return inputsMin;
202-
}
203-
204-
/**
205-
* Returns the max values to apply to the inputs
206-
*
207-
* @param {number|null} maxTimestamp the maximal timestamp to represent in the inputs
208-
* @param {DateTimeInputRawData} raw the current raw values
209-
* @return {(Partial<{date: string, time: string}>|null)} the max values
210-
*/
211-
_getInputsMax(maxTimestamp, raw) {
212-
if (maxTimestamp === null) {
213-
return null;
214-
}
215-
216-
const rawDate = raw.date || null;
217-
const rawTime = raw.time || null;
218-
219-
const maxDateAndTime = formatTimestampForDateTimeInput(maxTimestamp, this._seconds, this._milliseconds);
220-
const inputsMax = {};
221-
222-
if (rawDate !== null && rawDate === maxDateAndTime.date) {
223-
inputsMax.time = maxDateAndTime.time;
224-
}
225-
if (rawTime !== null && rawTime > maxDateAndTime.time) {
226-
inputsMax.date = formatTimestampForDateTimeInput(maxTimestamp - MILLISECONDS_IN_ONE_DAY, this._seconds, this._milliseconds).date;
227-
} else {
228-
inputsMax.date = maxDateAndTime.date;
229-
}
230-
231-
return inputsMax;
232-
}
233121
}

lib/public/components/common/form/inputs/DateTimeInputModel.js

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,6 @@ import {
1717
} from '../../../../utilities/formatting/dateTimeInputFormatters.mjs';
1818
import { Observable } from '/js/src/index.js';
1919

20-
/**
21-
* @typedef DateTimeInputRawData
22-
* @property {string} date the raw date value
23-
* @property {string} time the raw time value
24-
*/
25-
26-
/**
27-
* @typedef DateTimeInputConfiguration
28-
* @property {Partial<DateTimeInputRawData>} [defaults] the default raw values to use when partially updating inputs
29-
* @property {boolean} [required] states if the input can be null
30-
*/
31-
3220
/**
3321
* Store the state of a date time model
3422
*/
@@ -47,10 +35,10 @@ export class DateTimeInputModel extends Observable {
4735
this._milliseconds = milliseconds;
4836

4937
/**
50-
* @type {DateTimeInputRawData}
38+
* @type {string}
5139
* @private
5240
*/
53-
this._raw = { date: '', time: '' };
41+
this._raw = '';
5442

5543
/**
5644
* @type {number|null}
@@ -60,22 +48,21 @@ export class DateTimeInputModel extends Observable {
6048
}
6149

6250
/**
63-
* Update the inputs raw values
51+
* Update the input raw value
6452
*
65-
* @param {DateTimeInputRawData} raw the input raw values
53+
* @param {string} raw the input raw value (datetime-local string)
6654
* @return {void}
6755
*/
6856
update(raw) {
6957
this._raw = raw;
70-
const hasDateAndTime = raw.date && raw.time;
7158

7259
try {
73-
this._value = hasDateAndTime ? extractTimestampFromDateTimeInput(raw) : null;
60+
this._value = raw ? extractTimestampFromDateTimeInput(raw) : null;
7461
} catch {
7562
this._value = null;
7663
}
7764

78-
hasDateAndTime && this.notify();
65+
this.notify();
7966
}
8067

8168
/**
@@ -96,9 +83,9 @@ export class DateTimeInputModel extends Observable {
9683
}
9784

9885
/**
99-
* Returns the raw input values
86+
* Returns the raw input value
10087
*
101-
* @return {DateTimeInputRawData} the raw values
88+
* @return {string} the raw datetime-local value
10289
*/
10390
get raw() {
10491
return this._raw;
@@ -132,7 +119,7 @@ export class DateTimeInputModel extends Observable {
132119
this._value = value;
133120
this._raw = value !== null
134121
? formatTimestampForDateTimeInput(value, this._seconds || this._milliseconds, this._milliseconds)
135-
: { date: '', time: '' };
122+
: '';
136123

137124
!silent && this.notify();
138125
}

lib/public/components/common/form/inputs/TimestampInputComponent.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,15 @@ export class TimestampInputComponent extends StatefulComponent {
4343
const { seconds = false, onChange, value = null } = attrs;
4444

4545
/**
46-
* @type {DateTimeInputRawData}
46+
* @type {string} - format YYYY-MM-DDTHH:MM[:SS[.mmm]]
47+
* @private
48+
*/
49+
this._raw = '';
50+
51+
/**
52+
* @type {number|null} - timestamp in ms
4753
* @private
4854
*/
49-
this._raw = { date: '', time: '' };
5055
this._value = value;
5156

5257
this._onChange = onChange;
@@ -73,17 +78,17 @@ export class TimestampInputComponent extends StatefulComponent {
7378
}
7479

7580
/**
76-
* Handle change of date/time input and trigger the onChange
81+
* Handle change of datetime-local input and trigger the onChange
7782
*
78-
* @param {DateTimeInputRawData} raw the date/time input raw data
83+
* @param {string} raw the datetime-local input value
7984
* @return {void}
8085
* @private
8186
*/
8287
_handleDateTimeChange(raw) {
8388
this._raw = raw;
8489
let value;
8590
try {
86-
value = raw.date && raw.time ? extractTimestampFromDateTimeInput(raw) : null;
91+
value = raw ? extractTimestampFromDateTimeInput(raw) : null;
8792
} catch {
8893
value = null;
8994
}

lib/public/utilities/formatting/dateTimeInputFormatters.mjs

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212
*/
1313

1414
/**
15-
* Returns the given date formatted in two parts, YYYY-MM-DD and HH:MM to fill in HTML date and time inputs (in the user's timezone)
15+
* Returns the given timestamp formatted as a datetime-local input value string (YYYY-MM-DDTHH:MM[:SS[.mmm]],
16+
* in the user's timezone)
1617
*
1718
* @param {number} timestamp the timestamp (ms) to format
18-
* @param {boolean} enableSeconds states if the time input granularity is seconds (else, it is minutes)
19-
* @param {boolean} enableMilliseconds states if the time input granularity is milliseconds
20-
* @return {DateTimeInputRawData} the date expression to use as HTML input values
19+
* @param {boolean} enableSeconds states if the input granularity is seconds (else, it is minutes)
20+
* @param {boolean} enableMilliseconds states if the input granularity is milliseconds
21+
* @return {string} the value to use as a datetime-local HTML input value
2122
*/
2223
export const formatTimestampForDateTimeInput = (timestamp, enableSeconds, enableMilliseconds) => {
2324
const date = new Date(timestamp);
@@ -32,37 +33,26 @@ export const formatTimestampForDateTimeInput = (timestamp, enableSeconds, enable
3233
const year = date.getFullYear();
3334
const month = pad(date.getMonth() + 1);
3435
const day = pad(date.getDate());
35-
const dateExpression = `${year}-${month}-${day}`;
36-
3736
const hours = pad(date.getHours());
3837
const minutes = pad(date.getMinutes());
39-
let timeExpression = `${hours}:${minutes}`;
40-
if (enableSeconds) {
38+
39+
let value = `${year}-${month}-${day}T${hours}:${minutes}`;
40+
if (enableSeconds || enableMilliseconds) {
4141
const seconds = pad(date.getSeconds());
42-
timeExpression = `${timeExpression}:${seconds}`;
42+
value = `${value}:${seconds}`;
4343
}
4444
if (enableMilliseconds) {
4545
const milliseconds = `${date.getMilliseconds()}`.padStart(3, '0');
46-
timeExpression = `${timeExpression}.${milliseconds}`;
46+
value = `${value}.${milliseconds}`;
4747
}
4848

49-
return { date: dateExpression, time: timeExpression };
49+
return value;
5050
};
5151

5252
/**
53-
* Convert the given raw date-time (in the user's timezone) input to UNIX timestamp
53+
* Convert the given datetime-local input value (in the user's timezone) to a UNIX timestamp
5454
*
55-
* @param {DateTimeInputRawData} dateTime the date-time input's value
55+
* @param {string} value the datetime-local input value (YYYY-MM-DDTHH:MM[:SS[.mmm]])
5656
* @return {number} the resulting timestamp
5757
*/
58-
export const extractTimestampFromDateTimeInput = ({ date, time }) => {
59-
if (time.length === 5) {
60-
// HH:MM -> HH:MM:SS.mmm
61-
time = `${time}:00.000`;
62-
} else if (time.length === 8) {
63-
// HH:MM:SS -> HH:MM:SS.mmm
64-
time = `${time}.000`;
65-
}
66-
67-
return Date.parse(`${date}T${time}`);
68-
};
58+
export const extractTimestampFromDateTimeInput = (value) => Date.parse(value);

test/public/Filters/FilteringModel.test.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ module.exports = () => {
7575
const baseRowCount = 109;
7676
const startPopoverSelector = await getPopoverSelector(await page.$('.timeO2Start-filter .popover-trigger'));
7777

78-
const { fromDateSelector, fromTimeSelector } = getPeriodInputsSelectors(startPopoverSelector);
78+
const { fromDateTimeSelector } = getPeriodInputsSelectors(startPopoverSelector);
7979

8080
await checkSelectionFilters(runSelectionFiltersChecks, baseRowCount);
8181

@@ -105,10 +105,9 @@ module.exports = () => {
105105
await waitForTableTotalRowsCountToEqual(page, baseRowCount);
106106

107107
// O2 Start Filter:
108-
await fillInput(page, fromTimeSelector, '11:11', ['change']);
109-
await fillInput(page, fromDateSelector, '2021-02-03', ['change']);
108+
await fillInput(page, fromDateTimeSelector, '2021-02-03T11:11', ['change']);
110109
await waitForTableTotalRowsCountToEqual(page, 1);
111-
await fillInput(page, fromDateSelector, '2020-02-03', ['change']);
110+
await fillInput(page, fromDateTimeSelector, '2020-02-03T11:11', ['change']);
112111
await waitForTableTotalRowsCountToEqual(page, 2);
113112
await page.goBack();
114113
await waitForTableTotalRowsCountToEqual(page, 1);

0 commit comments

Comments
 (0)