diff --git a/packages/main/cypress/specs/DynamicDateRange.cy.tsx b/packages/main/cypress/specs/DynamicDateRange.cy.tsx index db96ee3700a1..b72075cb11f6 100644 --- a/packages/main/cypress/specs/DynamicDateRange.cy.tsx +++ b/packages/main/cypress/specs/DynamicDateRange.cy.tsx @@ -4,13 +4,13 @@ import DateRange from '../../src/dynamic-date-range-options/DateRange.js'; import Today from '../../src/dynamic-date-range-options/Today.js'; import LastOptions from '../../src/dynamic-date-range-options/LastOptions.js'; import NextOptions from '../../src/dynamic-date-range-options/NextOptions.js'; +import DateTimeRange from '../../src/dynamic-date-range-options/DateTimeRange.js'; import FromDateTime from '../../src/dynamic-date-range-options/FromDateTime.js'; import ToDateTime from '../../src/dynamic-date-range-options/ToDateTime.js'; describe('DynamicDateRange Component', () => { beforeEach(() => { - cy.mount( - + cy.mount( ); }); @@ -38,41 +38,16 @@ describe('DynamicDateRange Component', () => { const mockOptions: Array = [ new Today(), new SingleDate(), - new DateRange() + new DateRange(), + new DateTimeRange() ]; cy.get('[ui5-dynamic-date-range]') .as("ddr"); cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .should('exist'); - - cy.get("@input") - .find('[ui5-icon]') - .as("icon"); - - cy.get("@icon") - .realClick(); // Open the picker - - cy.get("@ddr") - .shadow() - .find("[ui5-responsive-popover]") - .as("popover"); - - cy.get("@popover") - .should('exist'); - - cy.get("@popover") - .find("[ui5-list]") - .as("list"); - - cy.get('@list') - .find("[ui5-li]") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeGetOptionsList() .as("listItems"); cy.get("@listItems") @@ -90,34 +65,8 @@ describe('DynamicDateRange Component', () => { .as("ddr"); cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .should('exist'); - - cy.get("@input") - .find('[ui5-icon]') - .as("icon"); - - cy.get("@icon") - .realClick(); // Open the picker - - cy.get("@ddr") - .shadow() - .find("[ui5-responsive-popover]") - .as("popover"); - - cy.get("@popover") - .should('exist'); - - cy.get("@popover") - .find("[ui5-list]") - .as("list"); - - cy.get('@list') - .find("[ui5-li]") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeGetOptionsList() .as("listItems"); cy.get("@listItems") @@ -261,13 +210,8 @@ describe('DynamicDateRange Component', () => { .eq(22) // May 21, 2035 is the 21st day .realClick(); - cy.get("@popover") - .find("[ui5-button][design='Emphasized']") - .as("submitButton"); - - cy.get("@submitButton") - .should('exist') - .realClick(); + cy.get("@ddr") + .ui5DynamicDateRangeSubmit(); cy.get("@input") .shadow() @@ -336,8 +280,7 @@ describe('DynamicDateRange Component', () => { describe('DynamicDateRange Last/Next Options', () => { beforeEach(() => { - cy.mount( - + cy.mount( ); }); @@ -349,36 +292,8 @@ describe('DynamicDateRange Last/Next Options', () => { .as("ddr"); cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .shadow() - .find("input") - .as("innerInput"); - - cy.get("@input") - .find('[ui5-icon]') - .as("icon"); - - cy.get("@icon") - .realClick(); - - cy.get("@ddr") - .shadow() - .find("[ui5-responsive-popover]") - .as("popover"); - - cy.get("@popover") - .should('exist'); - - cy.get("@popover") - .find("[ui5-list]") - .as("list"); - - cy.get("@list") - .find("[ui5-li]") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeGetOptionsList() .as("listItems"); cy.get("@listItems") @@ -411,12 +326,8 @@ describe('DynamicDateRange Last/Next Options', () => { .clear() .realType('7'); - cy.get("@popover") - .find("[ui5-button][design='Emphasized']") - .as("submitButton"); - - cy.get("@submitButton") - .realClick(); + cy.get("@ddr") + .ui5DynamicDateRangeSubmit(); cy.get("@innerInput") .should('have.value', 'Last 7 Days'); @@ -434,33 +345,8 @@ describe('DynamicDateRange Last/Next Options', () => { .as("ddr"); cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .shadow() - .find("input") - .as("innerInput"); - - cy.get("@input") - .find('[ui5-icon]') - .as("icon"); - - cy.get("@icon") - .realClick(); - - cy.get("@ddr") - .shadow() - .find("[ui5-responsive-popover]") - .as("popover"); - - cy.get("@popover") - .find("[ui5-list]") - .as("list"); - - cy.get("@list") - .find("[ui5-li]") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeGetOptionsList() .as("listItems"); cy.get("@listItems") @@ -493,12 +379,8 @@ describe('DynamicDateRange Last/Next Options', () => { .find(".ui5-ddr-current-value") .should('contain.text', 'Selected:'); - cy.get("@popover") - .find("[ui5-button][design='Emphasized']") - .as("submitButton"); - - cy.get("@submitButton") - .realClick(); + cy.get("@ddr") + .ui5DynamicDateRangeSubmit(); cy.get("@innerInput") .should('have.value', 'Next 3 Weeks'); @@ -561,38 +443,16 @@ describe('FromDateTime Option', () => { const mockOptions: Array = [ new FromDateTime(), ]; - cy.get('[ui5-dynamic-date-range]') - .as("ddr"); + cy.get("[ui5-dynamic-date-range]") + .as("ddr") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeSelectOption(); - cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .should('exist'); - - cy.get("@input") - .find('[ui5-icon]') - .click(); - cy.get("@ddr") .shadow() .find("[ui5-responsive-popover]") .as("popover"); - cy.get("@popover") - .should('exist'); - - cy.get("@popover") - .find("[ui5-list]") - .as("list"); - - cy.get("@list") - .find("[ui5-li]") - .contains('From') - .click(); - cy.get("@popover") .find(".ui5-dynamic-date-range-option-datetime-container") .should('exist'); @@ -610,36 +470,23 @@ describe('FromDateTime Option', () => { const mockOptions: Array = [ new FromDateTime(), ]; - cy.get('[ui5-dynamic-date-range]') - .as("ddr"); - - cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .find('[ui5-icon]') - .click(); + cy.get("[ui5-dynamic-date-range]") + .as("ddr") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeSelectOption(); cy.get("@ddr") .shadow() .find("[ui5-responsive-popover]") .as("popover"); - cy.get("@popover") - .find("[ui5-list]") - .find("[ui5-li]") - .contains('From') - .click(); - cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Date']") .should('have.attr', 'selected'); cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Time']") - .click(); + .realClick(); cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Time']") @@ -651,7 +498,7 @@ describe('FromDateTime Option', () => { cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Date']") - .click(); + .realClick(); cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Date']") @@ -662,28 +509,15 @@ describe('FromDateTime Option', () => { const mockOptions: Array = [ new FromDateTime(), ]; - cy.get('[ui5-dynamic-date-range]') - .as("ddr"); - - cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .find('[ui5-icon]') - .click(); + cy.get("[ui5-dynamic-date-range]") + .as("ddr") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeSelectOption(); cy.get("@ddr") .shadow() .find("[ui5-responsive-popover]") .as("popover"); - - cy.get("@popover") - .find("[ui5-list]") - .find("[ui5-li]") - .contains('From') - .click(); cy.get("@popover") .find("[ui5-calendar]") @@ -695,11 +529,10 @@ describe('FromDateTime Option', () => { .shadow() .find(".ui5-dp-daytext") .eq(15) - .click(); // Select day 13 + .realClick(); // Select day 13 - cy.get("@popover") - .find("[ui5-button][design='Emphasized']") - .click(); + cy.get("@ddr") + .ui5DynamicDateRangeSubmit(); cy.get("@input") .shadow() @@ -723,38 +556,16 @@ describe('ToDateTime Option', () => { const mockOptions: Array = [ new ToDateTime(), ]; - cy.get('[ui5-dynamic-date-range]') - .as("ddr"); - - cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .should('exist'); + cy.get("[ui5-dynamic-date-range]") + .as("ddr") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeSelectOption(); - cy.get("@input") - .find('[ui5-icon]') - .click(); - cy.get("@ddr") .shadow() .find("[ui5-responsive-popover]") .as("popover"); - cy.get("@popover") - .should('exist'); - - cy.get("@popover") - .find("[ui5-list]") - .as("list"); - - cy.get("@list") - .find("[ui5-li]") - .contains('To') - .click(); - cy.get("@popover") .find(".ui5-dynamic-date-range-option-datetime-container") .should('exist'); @@ -772,36 +583,23 @@ describe('ToDateTime Option', () => { const mockOptions: Array = [ new ToDateTime(), ]; - cy.get('[ui5-dynamic-date-range]') - .as("ddr"); - - cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .find('[ui5-icon]') - .click(); + cy.get("[ui5-dynamic-date-range]") + .as("ddr") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeSelectOption(); cy.get("@ddr") .shadow() .find("[ui5-responsive-popover]") .as("popover"); - cy.get("@popover") - .find("[ui5-list]") - .find("[ui5-li]") - .contains('To') - .click(); - cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Date']") .should('have.attr', 'selected'); cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Time']") - .click(); + .realClick(); cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Time']") @@ -813,7 +611,7 @@ describe('ToDateTime Option', () => { cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Date']") - .click(); + .realClick(); cy.get("@popover") .find("[ui5-segmented-button-item][data-ui5-key='Date']") @@ -824,28 +622,15 @@ describe('ToDateTime Option', () => { const mockOptions: Array = [ new ToDateTime(), ]; - cy.get('[ui5-dynamic-date-range]') - .as("ddr"); - - cy.get("@ddr") - .shadow() - .find('[ui5-input]') - .as("input"); - - cy.get("@input") - .find('[ui5-icon]') - .click(); + cy.get("[ui5-dynamic-date-range]") + .as("ddr") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeSelectOption(); cy.get("@ddr") .shadow() .find("[ui5-responsive-popover]") .as("popover"); - - cy.get("@popover") - .find("[ui5-list]") - .find("[ui5-li]") - .contains('To') - .click(); cy.get("@popover") .find("[ui5-calendar]") @@ -857,11 +642,10 @@ describe('ToDateTime Option', () => { .shadow() .find(".ui5-dp-daytext") .eq(15) - .click(); // Select day 13 + .realClick(); // Select day 13 - cy.get("@popover") - .find("[ui5-button][design='Emphasized']") - .click(); + cy.get("@ddr") + .ui5DynamicDateRangeSubmit(); cy.get("@input") .shadow() @@ -873,4 +657,101 @@ describe('ToDateTime Option', () => { .find("input") .should('contain.value', 'To Oct 13, 2025'); }); +}); + +describe('DynamicDateRange DateTimeRange Option', () => { + beforeEach(() => { + cy.mount(); + }); + + it("should select DateTimeRange option and pick 2 dates from the DateTimePickers", () => { + cy.get("[ui5-dynamic-date-range]") + .as("ddr") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeSelectOption() + .ui5DynamicDateRangeSetDateTime("from-picker", "Dec 25, 2023, 2:30:00 PM") + .ui5DynamicDateRangeSetDateTime("to-picker", "Dec 26, 2023, 4:45:00 AM") + .ui5DynamicDateRangeSubmit(); + + cy.get("@ddr") + .shadow() + .find("[ui5-input]") + .as("input"); + + cy.get("@input") + .should("have.value", "Dec 25, 2023, 2:30:00 PM - Dec 26, 2023, 4:45:00 AM"); + }); + + it("should auto-correct date order when second date is earlier than first", () => { + cy.get("[ui5-dynamic-date-range]") + .as("ddr") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeSelectOption() + .ui5DynamicDateRangeSetDateTime("to-picker", "Dec 25, 2023, 2:30:00 PM") + .ui5DynamicDateRangeSetDateTime("from-picker", "Dec 26, 2023, 4:45:00 AM") + .ui5DynamicDateRangeSubmit(); + + cy.get("@ddr") + .shadow() + .find("[ui5-input]") + .as("input"); + + cy.get("@input") + .should("have.value", "Dec 25, 2023, 2:30:00 PM - Dec 26, 2023, 4:45:00 AM"); + }); + + it("should parse input value correctly when option is reopened", () => { + cy.get("[ui5-dynamic-date-range]") + .as("ddr"); + + cy.get("@ddr") + .shadow() + .find("[ui5-input]") + .as("input"); + + cy.get("@input") + .shadow() + .find("input") + .as("innerInput"); + + cy.get("@innerInput") + .clear() + .realType("Jan 4, 2025, 12:00:00 AM - Feb 26, 2025, 11:59:00 PM") + .realPress("Enter"); + + cy.get("[ui5-dynamic-date-range]") + .as("ddr") + .ui5DynamicDateRangeOpen() + .ui5DynamicDateRangeSelectOption(); + + // Verify the parsed dates are displayed in the pickers + cy.get("@ddr") + .shadow() + .find("[ui5-responsive-popover]") + .as("popover"); + + cy.get("@popover") + .find("[ui5-datetime-picker]#from-picker") + .as("fromPicker"); + + cy.get("@fromPicker") + .shadow() + .find("[ui5-datetime-input]") + .as("fromInput"); + + cy.get("@fromInput") + .should("have.value", "Jan 4, 2025, 12:00:00 AM"); + + cy.get("@popover") + .find("[ui5-datetime-picker]#to-picker") + .as("toPicker"); + + cy.get("@toPicker") + .shadow() + .find("[ui5-datetime-input]") + .as("toInput"); + + cy.get("@toInput") + .should("have.value", "Feb 26, 2025, 11:59:00 PM"); + }); }); \ No newline at end of file diff --git a/packages/main/cypress/support/commands.ts b/packages/main/cypress/support/commands.ts index 71ef25b616a4..54ff9f71f1a6 100644 --- a/packages/main/cypress/support/commands.ts +++ b/packages/main/cypress/support/commands.ts @@ -47,6 +47,7 @@ import "./commands/ColorPalettePopover.commands.js"; import "./commands/ColorPicker.commands.js"; import "./commands/DateTimePicker.commands.js"; import "./commands/DateRangePicker.commands.js"; +import "./commands/DynamicDateRange.commands.js"; import "./commands/Dialog.commands.ts"; import "./commands/Popover.commands.ts"; import "./commands/ResponsivePopover.commands.js"; diff --git a/packages/main/cypress/support/commands/DynamicDateRange.commands.ts b/packages/main/cypress/support/commands/DynamicDateRange.commands.ts new file mode 100644 index 000000000000..3bbab0dc9b09 --- /dev/null +++ b/packages/main/cypress/support/commands/DynamicDateRange.commands.ts @@ -0,0 +1,135 @@ +Cypress.Commands.add("ui5DynamicDateRangeOpen", { prevSubject: true }, (prevSubject) => { + cy.wrap(prevSubject) + .as("ddr") + .shadow() + .find("[ui5-input]") + .as("input"); + + cy.get("@input") + .shadow() + .find("input") + .as("innerInput"); + + cy.get("@input") + .find('[ui5-icon]') + .as("icon"); + + cy.get("@icon") + .realClick(); + + cy.get("@ddr") + .ui5DynamicDateRangeOpened(); + + return cy.wrap(prevSubject); +}); + +Cypress.Commands.add("ui5DynamicDateRangeOpened", { prevSubject: true }, (subject) => { + cy.wrap(subject) + .as("ddr"); + + cy.get("@ddr") + .should("have.attr", "open"); + + cy.get("@ddr") + .shadow() + .find("[ui5-responsive-popover]") + .as("popover") + .should("have.attr", "open"); + + cy.get("@popover") + .find("[ui5-list]") + .as("list") + .should("be.visible"); +}); + +Cypress.Commands.add("ui5DynamicDateRangeGetOptionsList", { prevSubject: true }, (subject) => { + cy.wrap(subject) + .as("ddr"); + + cy.get("@ddr") + .shadow() + .find("[ui5-responsive-popover]") + .as("popover"); + + cy.get("@popover") + .should('exist'); + + cy.get("@popover") + .find("[ui5-list]") + .as("list"); + + return cy.get('@list') + .find("[ui5-li]"); +}); + +Cypress.Commands.add("ui5DynamicDateRangeSelectOption", { prevSubject: true }, (prevSubject, index?: number) => { + const optionIndex = index ?? 0; + + cy.wrap(prevSubject) + .as("ddr"); + + cy.get("@ddr") + .ui5DynamicDateRangeGetOptionsList() + .eq(optionIndex) + .realClick(); + + return cy.wrap(prevSubject); +}); + +Cypress.Commands.add("ui5DynamicDateRangeSetDateTime", { prevSubject: true }, (prevSubject, pickerId: string, dateTimeValue: string) => { + cy.wrap(prevSubject) + .as("ddr"); + + cy.get("@ddr") + .shadow() + .find("[ui5-responsive-popover]") + .find(`[ui5-datetime-picker]#${pickerId}`) + .as("picker"); + + cy.get("@picker") + .shadow() + .find("[ui5-datetime-input]") + .as("input"); + + cy.get("@input") + .shadow() + .find("input") + .as("innerInput"); + + cy.get("@innerInput") + .clear() + .realType(dateTimeValue) + .realPress("Enter"); + + return cy.wrap(prevSubject); +}); + +Cypress.Commands.add("ui5DynamicDateRangeSubmit", { prevSubject: true }, (prevSubject) => { + cy.wrap(prevSubject) + .as("ddr"); + + cy.get("@ddr") + .shadow() + .find("[ui5-responsive-popover]") + .as("popover"); + + cy.get("@popover") + .find("[ui5-button][design='Emphasized']") + .as("submitButton"); + + cy.get("@submitButton") + .realClick(); +}); + +declare global { + namespace Cypress { + interface Chainable { + ui5DynamicDateRangeOpen(): Chainable> + ui5DynamicDateRangeOpened(): Chainable + ui5DynamicDateRangeGetOptionsList(): Chainable> + ui5DynamicDateRangeSelectOption(index?: number): Chainable> + ui5DynamicDateRangeSetDateTime(pickerId: string, dateTimeValue: string): Chainable> + ui5DynamicDateRangeSubmit(): Chainable + } + } +} \ No newline at end of file diff --git a/packages/main/src/DynamicDateRange.ts b/packages/main/src/DynamicDateRange.ts index d3dab554806b..52bcec26dbaa 100644 --- a/packages/main/src/DynamicDateRange.ts +++ b/packages/main/src/DynamicDateRange.ts @@ -106,6 +106,7 @@ interface IDynamicDateRangeOption { * - "TOMORROW" - Represents the next date. An example value is `{ operator: "TOMORROW"}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/Tomorrow.js";` * - "DATE" - Represents a single date. An example value is `{ operator: "DATE", values: [new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/SingleDate.js";` * - "DATERANGE" - Represents a range of dates. An example value is `{ operator: "DATERANGE", values: [new Date(), new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/DateRange.js";` + * - "DATETIMERANGE" - Represents a range of dates with times. An example value is `{ operator: "DATETIMERANGE", values: [new Date(), new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/DateTimeRange.js";` * - "FROMDATETIME" - Represents a range from date and time. An example value is `{ operator: "FROMDATETIME", values: [new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/FromDateTime.js";` * - "TODATETIME" - Represents a range to date and time. An example value is `{ operator: "TODATETIME", values: [new Date()]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/ToDateTime.js";` * - "LASTDAYS" - Represents Last X Days from today. An example value is `{ operator: "LASTDAYS", values: [2]}`. Import: `import "@ui5/webcomponents/dist/dynamic-date-range-options/LastOptions.js";` diff --git a/packages/main/src/bundle.esm.ts b/packages/main/src/bundle.esm.ts index 376dad86a029..77c873528352 100644 --- a/packages/main/src/bundle.esm.ts +++ b/packages/main/src/bundle.esm.ts @@ -56,6 +56,7 @@ import Yesterday from "./dynamic-date-range-options/Yesterday.js"; import Tomorrow from "./dynamic-date-range-options/Tomorrow.js"; import SingleDate from "./dynamic-date-range-options/SingleDate.js"; import DateRange from "./dynamic-date-range-options/DateRange.js"; +import DateTimeRange from "./dynamic-date-range-options/DateTimeRange.js"; import FromDateTime from "./dynamic-date-range-options/FromDateTime.js"; import ToDateTime from "./dynamic-date-range-options/ToDateTime.js"; import ExpandableText from "./ExpandableText.js"; diff --git a/packages/main/src/dynamic-date-range-options/DateTimeRange.ts b/packages/main/src/dynamic-date-range-options/DateTimeRange.ts new file mode 100644 index 000000000000..d358ea54a6b6 --- /dev/null +++ b/packages/main/src/dynamic-date-range-options/DateTimeRange.ts @@ -0,0 +1,112 @@ +import DateTimeRangeTemplate from "./DateTimeRangeTemplate.js"; +import type { DynamicDateRangeValue, IDynamicDateRangeOption } from "../DynamicDateRange.js"; +import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js"; +import type { JsxTemplate } from "@ui5/webcomponents-base/dist/index.js"; +import { + DYNAMIC_DATE_TIME_RANGE_TEXT, +} from "../generated/i18n/i18n-defaults.js"; +import { dateTimeRangeOptionToDates } from "./toDates.js"; +import DynamicDateRange from "../DynamicDateRange.js"; +import getCachedLocaleDataInstance from "@ui5/webcomponents-localization/dist/getCachedLocaleDataInstance.js"; +import getLocale from "@ui5/webcomponents-base/dist/locale/getLocale.js"; + +const DEFAULT_DELIMITER = "-"; + +/** + * @class + * @constructor + * @public + * @since 2.16.0 + */ + +class DateTimeRange implements IDynamicDateRangeOption { + template: JsxTemplate; + + constructor() { + this.template = DateTimeRangeTemplate; + } + + parse(value: string): DynamicDateRangeValue { + const returnValue = { operator: this.operator, values: [] } as DynamicDateRangeValue; + + if (!value) { + return returnValue; + } + const splitValue = value.split(DEFAULT_DELIMITER); + const startDate = this._parseDate(splitValue[0].trim()) as Date; + const endDate = this._parseDate(splitValue[1].trim()) as Date; + + returnValue.values = [startDate, endDate]; + + if (returnValue.values[0].getTime() > returnValue.values[1].getTime()) { + returnValue.values.reverse(); + } + + return returnValue; + } + + _parseDate(value: string): Date | undefined { + return this.getFormat().parse(value) as Date; + } + + format(value: DynamicDateRangeValue) { + const valuesArray = value?.values as Array; + + if (!valuesArray || valuesArray.length !== 2 || !valuesArray[0] || !valuesArray[1]) { + return ""; + } + + const startDate = this._formatDate(valuesArray[0]); + const endDate = this._formatDate(valuesArray[1]); + + return `${startDate} ${DEFAULT_DELIMITER} ${endDate}`; + } + + _formatDate(date: Date) { + return this.getFormat().format(date); + } + + toDates(value: DynamicDateRangeValue): Array { + return dateTimeRangeOptionToDates(value); + } + + get text(): string { + return DynamicDateRange.i18nBundle.getText(DYNAMIC_DATE_TIME_RANGE_TEXT); + } + + get operator() { + return "DATETIMERANGE"; + } + + get icon() { + return "appointment-2"; + } + + isValidString(value: string): boolean { + const splitValue = value.split(DEFAULT_DELIMITER); + const startDate = this._parseDate(splitValue[0].trim()) as Date; + const endDate = this._parseDate(splitValue[1].trim()) as Date; + + if (!startDate || !endDate || Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) { + return false; + } + + return true; + } + + getFormatPattern() { + const localeData = getCachedLocaleDataInstance(getLocale()); + return localeData.getCombinedDateTimePattern("medium", "medium"); + } + + getFormat(): DateFormat { + return DateFormat.getDateInstance({ + strictParsing: true, + pattern: this.getFormatPattern(), + }); + } +} + +DynamicDateRange.register("DATETIMERANGE", DateTimeRange); + +export default DateTimeRange; diff --git a/packages/main/src/dynamic-date-range-options/DateTimeRangeTemplate.tsx b/packages/main/src/dynamic-date-range-options/DateTimeRangeTemplate.tsx new file mode 100644 index 000000000000..9b2c92b7157e --- /dev/null +++ b/packages/main/src/dynamic-date-range-options/DateTimeRangeTemplate.tsx @@ -0,0 +1,62 @@ +import DynamicDateRange from "../DynamicDateRange.js"; +import DateTimePicker from "../DateTimePicker.js"; +import Label from "../Label.js"; +import { + DYNAMIC_DATE_TIME_RANGE_TEXT_TO_LABEL, + DYNAMIC_DATE_TIME_RANGE_TEXT_FROM_LABEL, +} from "../generated/i18n/i18n-defaults.js"; + +export default function DateTimeRangeTemplate(this: DynamicDateRange) { + const currentOperator = this.currentValue?.operator || "DATETIMERANGE"; + + const getDateFromValue = (index = 0) => { + if (this.value?.operator === currentOperator && this.value.values && this.value.values.length === 2) { + return this.getOption(this.value.operator)?.format(this.value)?.split("-")[index].trim(); + } + return undefined; + }; + + const handleSelectionChange = () => { + const fromPicker = this.shadowRoot?.querySelector("[ui5-datetime-picker]#from-picker") as DateTimePicker; + const toPicker = this.shadowRoot?.querySelector("[ui5-datetime-picker]#to-picker") as DateTimePicker; + + const fromDateValue = fromPicker.dateValue; + const toDateValue = toPicker.dateValue; + + // If there are no dates selected, clear the value + if (!(fromDateValue && toDateValue)) { + this.currentValue = { + operator: currentOperator, + values: [] + }; + return; + } + + const newValue = [fromDateValue, toDateValue]; + + if (newValue[0] && newValue[1] && newValue[0].getTime() > newValue[1].getTime()) { + newValue.reverse(); + } + + this.currentValue = { + operator: currentOperator, + values: newValue, + }; + }; + return ( +
+ + + + + + +
+ ); +} diff --git a/packages/main/src/dynamic-date-range-options/toDates.ts b/packages/main/src/dynamic-date-range-options/toDates.ts index 577cf1626369..cfda66bf9e3c 100644 --- a/packages/main/src/dynamic-date-range-options/toDates.ts +++ b/packages/main/src/dynamic-date-range-options/toDates.ts @@ -2,6 +2,10 @@ import type { DynamicDateRangeValue, IDynamicDateRangeOption } from "../DynamicD import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js"; const dateOptionToDates = (value: DynamicDateRangeValue): Array => { + if (!value || !value.values || value.values.length !== 1) { + return []; + } + const startDate = value.values ? value.values[0] as Date : UI5Date.getInstance(); const endDate = UI5Date.getInstance(startDate.getTime()); @@ -12,6 +16,10 @@ const dateOptionToDates = (value: DynamicDateRangeValue): Array => { }; const dateRangeOptionToDates = (value: DynamicDateRangeValue): Array => { + if (!value || !value.values || value.values.length !== 2) { + return []; + } + const startDate = value.values ? value.values[0] as Date : UI5Date.getInstance(); const endDate = value.values ? value.values[1] as Date : UI5Date.getInstance(); @@ -21,6 +29,17 @@ const dateRangeOptionToDates = (value: DynamicDateRangeValue): Array => { return [startDate, endDate]; }; +const dateTimeRangeOptionToDates = (value: DynamicDateRangeValue): Array => { + if (!value || !value.values || value.values.length !== 2) { + return []; + } + + const startDate = value.values ? value.values[0] as Date : UI5Date.getInstance(); + const endDate = value.values ? value.values[1] as Date : UI5Date.getInstance(); + + return [startDate, endDate]; +}; + const todayToDates = (): Array => { const startDate = UI5Date.getInstance(); const endDate = UI5Date.getInstance(); @@ -194,6 +213,7 @@ const dateTimeOptionToDates = (value: DynamicDateRangeValue): Array => { export { dateOptionToDates, dateRangeOptionToDates, + dateTimeRangeOptionToDates, todayToDates, tomorrowToDates, yesterdayToDates, diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 3523174068e1..c1a7eaf42035 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -817,6 +817,12 @@ DYNAMIC_DATE_RANGE_TO_TEXT=To (Date Time) #XFLD: Text for the selected date in the DynamicDateRange component. DYNAMIC_DATE_RANGE_SELECTED_TEXT=Selected +DYNAMIC_DATE_TIME_RANGE_TEXT=From / To (Date and Time) + +DYNAMIC_DATE_TIME_RANGE_TEXT_TO_LABEL=To + +DYNAMIC_DATE_TIME_RANGE_TEXT_FROM_LABEL=From + #FLD: Text for the selected date section when there's no value in the DynamicDateRange component. DYNAMIC_DATE_RANGE_EMPTY_SELECTED_TEXT=Choose Dates diff --git a/packages/main/test/pages/DynamicDateRange.html b/packages/main/test/pages/DynamicDateRange.html index a027aa1dca29..6a69decd28d3 100644 --- a/packages/main/test/pages/DynamicDateRange.html +++ b/packages/main/test/pages/DynamicDateRange.html @@ -26,7 +26,7 @@

Basic Options

diff --git a/packages/website/docs/_samples/main/DynamicDateRange/Basic/main.js b/packages/website/docs/_samples/main/DynamicDateRange/Basic/main.js index 9dcab9c9bf1d..0d5c26f19089 100644 --- a/packages/website/docs/_samples/main/DynamicDateRange/Basic/main.js +++ b/packages/website/docs/_samples/main/DynamicDateRange/Basic/main.js @@ -4,4 +4,5 @@ import "@ui5/webcomponents/dist/dynamic-date-range-options/Yesterday.js"; import "@ui5/webcomponents/dist/dynamic-date-range-options/Tomorrow.js"; import "@ui5/webcomponents/dist/dynamic-date-range-options/SingleDate.js"; import "@ui5/webcomponents/dist/dynamic-date-range-options/DateRange.js"; +import "@ui5/webcomponents/dist/dynamic-date-range-options/DateTimeRange.js"; import "@ui5/webcomponents/dist/Text.js"; \ No newline at end of file diff --git a/packages/website/docs/_samples/main/DynamicDateRange/Basic/sample.html b/packages/website/docs/_samples/main/DynamicDateRange/Basic/sample.html index aaa733a73c14..26589b4f7fe6 100644 --- a/packages/website/docs/_samples/main/DynamicDateRange/Basic/sample.html +++ b/packages/website/docs/_samples/main/DynamicDateRange/Basic/sample.html @@ -12,7 +12,7 @@ All options - + Only two options