[IMP] web: improve datetime picker support for time

This commit improves the support for time in the datetime picker by
doing two things:
 - allow to pick seconds by giving a rounding value of 0
 - check minDate and maxDate by accounting for the time when the picker
is in datetime mode instead of only checking the date

Part-of: odoo/odoo#133349
This commit is contained in:
Samuel Degueldre
2023-09-04 09:19:08 +00:00
parent ebd538a194
commit 6580a435a9
3 changed files with 114 additions and 3 deletions
@@ -37,7 +37,8 @@ import { ensureArray } from "../utils/arrays";
* @property {PrecisionLevel} [minPrecision="days"]
* @property {(value: DateTime) => any} [onSelect]
* @property {boolean} [range]
* @property {number} [rounding=5]
* @property {number} [rounding=5] the rounding in minutes, pass 0 to show seconds, pass 1 to avoid
* rounding minutes without displaying seconds.
* @property {{ buttons?: any }} [slots]
* @property {"date" | "datetime"} [type]
* @property {NullableDateTime | NullableDateRange} value
@@ -143,6 +144,7 @@ const toWeekItem = (weekDayItems) => ({
// Time constants
const HOURS = numberRange(0, 24).map((hour) => [hour, String(hour)]);
const MINUTES = numberRange(0, 60).map((minute) => [minute, String(minute || 0).padStart(2, "0")]);
const SECONDS = [...MINUTES];
const MERIDIEMS = ["AM", "PM"];
/**
@@ -380,14 +382,19 @@ export class DateTimePicker extends Component {
);
this.availableHours = HOURS;
this.availableMinutes = MINUTES.filter((minute) => !(minute[0] % props.rounding));
this.availableSeconds = props.rounding ? [] : SECONDS;
this.allowedPrecisionLevels = this.filterPrecisionLevels(
props.minPrecision,
props.maxPrecision
);
this.additionalMonth = props.range && !this.env.isSmall;
this.maxDate = parseLimitDate(props.maxDate, MAX_VALID_DATE).endOf("day");
this.minDate = parseLimitDate(props.minDate, MIN_VALID_DATE).startOf("day");
this.maxDate = parseLimitDate(props.maxDate, MAX_VALID_DATE);
this.minDate = parseLimitDate(props.minDate, MIN_VALID_DATE);
if (this.props.type === "date") {
this.maxDate = this.maxDate.endOf("day");
this.minDate = this.minDate.startOf("day");
}
if (this.maxDate < this.minDate) {
throw new Error(`DateTimePicker error: given "maxDate" comes before "minDate".`);
@@ -66,6 +66,13 @@
<t t-set="unitIndex" t-value="1" />
<t t-set="unitList" t-value="availableMinutes" />
</t>
<t t-if="availableSeconds.length">
<span>:</span>
<t t-call="web.DateTimePicker.Select">
<t t-set="unitIndex" t-value="2" />
<t t-set="unitList" t-value="availableSeconds" />
</t>
</t>
<t t-if="meridiems">
<t t-call="web.DateTimePicker.Select">
<t t-set="unitIndex" t-value="3" />
@@ -872,6 +872,23 @@ QUnit.module("Components", ({ beforeEach }) => {
);
});
QUnit.test("rounding=0 enables seconds picker", async (assert) => {
await mountPicker({
rounding: 0,
});
const [hourSelect, minuteSelect, secondsSelect] = getTimePickers().at(0);
assert.deepEqual(getTexts(hourSelect, "option"), range(24, String));
assert.deepEqual(
getTexts(minuteSelect, "option"),
range(60, (i) => pad2(i))
);
assert.deepEqual(
getTexts(secondsSelect, "option"),
range(60, (i) => pad2(i))
);
});
QUnit.test("no value, select date without handler", async (assert) => {
await mountPicker();
@@ -903,6 +920,86 @@ QUnit.module("Components", ({ beforeEach }) => {
assert.verifySteps(["2023-04-25T18:00:00", "2023-04-25T18:05:00"]);
});
QUnit.test("minDate with time: selecting out-of-range and in-range times", async (assert) => {
await mountPicker({
onSelect: (value) => assert.step(formatForStep(value)),
minDate: DateTime.fromISO("2023-04-25T16:00:00.000"),
});
const [hourSelect] = getTimePickers().at(0);
await editSelect(hourSelect, null, "15");
assert.verifySteps([]);
await editSelect(hourSelect, null, "16");
assert.verifySteps(["2023-04-25T16:00:00"]);
});
QUnit.test("maxDate with time: selecting out-of-range and in-range times", async (assert) => {
await mountPicker({
onSelect: (value) => assert.step(formatForStep(value)),
maxDate: DateTime.fromISO("2023-04-25T16:00:00.000"),
});
const [hourSelect] = getTimePickers().at(0);
await editSelect(hourSelect, null, "17");
assert.verifySteps([]);
await editSelect(hourSelect, null, "16");
assert.verifySteps(["2023-04-25T16:00:00"]);
});
QUnit.test(
"max and min date with time: selecting out-of-range and in-range times",
async (assert) => {
await mountPicker({
onSelect: (value) => assert.step(formatForStep(value)),
minDate: DateTime.fromISO("2023-04-25T16:00:00.000"),
maxDate: DateTime.fromISO("2023-04-25T16:00:00.000"),
});
const [hourSelect] = getTimePickers().at(0);
await editSelect(hourSelect, null, "15");
await editSelect(hourSelect, null, "17");
assert.verifySteps([]);
await editSelect(hourSelect, null, "16");
assert.verifySteps(["2023-04-25T16:00:00"]);
}
);
QUnit.test(
"max and min date with time: selecting invalid minutes and making it valid by selecting hours",
async (assert) => {
await mountPicker({
onSelect: (value) => assert.step(formatForStep(value)),
minDate: DateTime.fromISO("2023-04-25T16:10:00.000"),
maxDate: DateTime.fromISO("2023-04-25T16:50:00.000"),
});
const [hourSelect, minuteSelect] = getTimePickers().at(0);
await editSelect(hourSelect, null, "13");
await editSelect(minuteSelect, null, "30");
assert.verifySteps([]);
await editSelect(hourSelect, null, "16");
assert.verifySteps(["2023-04-25T16:30:00"]);
}
);
QUnit.test(
"max and min date with time: valid time on invalid day becomes valid when selecting day",
async (assert) => {
await mountPicker({
onSelect: (value) => assert.step(formatForStep(value)),
minDate: DateTime.fromISO("2023-04-24T16:10:00.000"),
maxDate: DateTime.fromISO("2023-04-24T16:50:00.000"),
});
const [hourSelect, minuteSelect] = getTimePickers().at(0);
await editSelect(hourSelect, null, "16");
await editSelect(minuteSelect, null, "30");
assert.verifySteps([]);
await click(getPickerCell("24"));
assert.verifySteps(["2023-04-24T16:30:00"]);
}
);
QUnit.test("single value, select date", async (assert) => {
await mountPicker({
value: DateTime.fromObject({ day: 30, hour: 8, minute: 43 }),