diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 886300e64db..d54ced5a3ef 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -322,6 +322,23 @@ } } }, + { + "id": "createdModifiedDateRange", + "name": "Date", + "enabled": true, + "component": { + "selector": "date-range-advanced", + "settings": { + "dateFormat": "dd-MMM-yy", + "maxDate": "today", + "field": "cm:created, cm:modified", + "displayedLabelsByField": { + "cm:created": "Created Date", + "cm:modified": "Modified Date" + } + } + } + }, { "id": "queryType", "name": "Type", diff --git a/demo-shell/src/app/components/search/search-result.component.scss b/demo-shell/src/app/components/search/search-result.component.scss index e50c780e8e0..a0334839f8e 100644 --- a/demo-shell/src/app/components/search/search-result.component.scss +++ b/demo-shell/src/app/components/search/search-result.component.scss @@ -3,7 +3,6 @@ margin-left: 5px; .app-search-settings { - width: 260px; border: 1px solid #eee; } diff --git a/docs/README.md b/docs/README.md index 3e3cdb73200..7572ca13b1a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -289,12 +289,15 @@ for more information about installing and using the source code. | [Search Chip Input Component](content-services/components/search-chip-input.component.md) | Displays input for providing phrases display as "chips". | [Source](../lib/content-services/src/lib/search/components/search-chip-input/search-chip-input.component.ts) | | [Search Chip Autocomplete Input component](content-services/components/search-chip-autocomplete-input.component.md) | Displays an input with autocomplete options. | [Source](../lib/content-services/src/lib/search/components/search-chip-autocomplete-input/search-chip-autocomplete-input.component.ts) | | [Search Chip List Component](content-services/components/search-chip-list.component.md) | Displays search criteria as a set of "chips". | [Source](../lib/content-services/src/lib/search/components/search-chip-list/search-chip-list.component.ts) | +| [Search Date Range Advanced Component](content-services/components/search-date-range-advanced.component.md) | Displays a UI to configure different kinds of search criteria around date. Options are 'Anyytime', 'In the last' and 'Between' | [Source](../lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts) | | [Search control component](content-services/components/search-control.component.md) | Displays a input text that shows find-as-you-type suggestions. | [Source](../lib/content-services/src/lib/search/components/search-control.component.ts) | | [Search date range component](content-services/components/search-date-range.component.md) | Implements a search widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts) | +| [Search date range advanced tabbed component](content-services/components/search-date-range-advanced-tabbed.component.md) | Implements a tabbed advanced search widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts) | | [Search datetime range component](content-services/components/search-datetime-range.component.md) | Implements a search widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.ts) | | [Search Filter Autocomplete Chips component](content-services/components/search-filter-autocomplete-chips.component.md) | Implements a search widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component.ts) | | [Search Filter Chips component](content-services/components/search-filter-chips.component.md) | Represents a chip based container component for custom search and faceted search settings. | [Source](../lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.ts) | | [Search Filter component](content-services/components/search-filter.component.md) | Represents a main container component for custom search and faceted search settings. | [Source](../lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts) | +| [Search Filter Tabbed component](content-services/components/search-filter-tabbed.component.md) | Represents a container component for creating tabbed layout. | [Source](../lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts) | | [Search Form component](content-services/components/search-form.component.md) | Search Form screenshot | [Source](../lib/content-services/src/lib/search/components/search-form/search-form.component.ts) | | [Search Logical Filter component](content-services/components/search-logical-filter.component.md) | Displays 3 chip inputs each representing different logical condition for search query. | [Source](../lib/content-services/src/lib/search/components/search-logical-filter/search-logical-filter.component.ts) | | [Search Properties component](content-services/components/search-properties.component.md) | Allows to search by file size and type.| [Source](../lib/content-services/src/lib/search/components/search-properties/search-properties.component.ts) | diff --git a/docs/content-services/components/search-date-range-advanced-tabbed.component.md b/docs/content-services/components/search-date-range-advanced-tabbed.component.md new file mode 100644 index 00000000000..de189e4d5d4 --- /dev/null +++ b/docs/content-services/components/search-date-range-advanced-tabbed.component.md @@ -0,0 +1,113 @@ +--- +Title: Search date range advanced tabbed component +Added: v6.2.0 +Status: Active +Last reviewed: 2023-07-10 +--- + +# [Search date range advanced tabbed component](../../../lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts "Defined in search-date-range-advanced-tabbed.component.ts") + +Represents a tabbed advanced date range [search widget](../../../lib/content-services/src/lib/search/models/search-widget.interface.ts) for +the [Search Filter component](search-filter.component.md). + +![Date Range Advanced Widget](../../docassets/images/search-date-range-advanced-tabbed.png) + +## Basic usage + +```json +{ + "search": { + "categories": [ + { + "id": "createdModifiedDateRange", + "name": "Date", + "enabled": true, + "component": { + "selector": "date-range-advanced", + "settings": { + "dateFormat": "dd-MMM-yy", + "maxDate": "today", + "field": "cm:created, cm:modified", + "displayedLabelsByField": { + "cm:created": "Created Date", + "cm:modified": "Modified Date" + } + } + } + } + ] + } +} +``` + +### Settings + +| Name | Type | Description | +|------------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| field | string | Fields to apply the query to. Multiple, comma separated fields can be passed, to create multiple tabs per field. Required value | +| dateFormat | string | Date format. Dates used by the datepicker are Javascript Date objects, using [date-fns](https://date-fns.org/v2.30.0/docs/format) for formatting, so you can use any date format supported by the library. Default is 'dd-MMM-yy (sample date - 07-Jun-23) | +| maxDate | string | A fixed date (in format mentioned above, default format: dd-MMM-yy) or the string `"today"` that will set the maximum searchable date. Default is today. | +| displayedLabelsByField | { [key: string]: string } | A javascript object containing the different display labels to be used for each tab name, identified by the field for a particular tab. | + +## Details + +This component creates a tabbed layout where each tab consists of the [SearchDateRangeAdvanced](./search-date-range-advanced-tabbed.component.md) component, which allows user to create a query containing multiple date related queries in one go. + +See the [Search filter component](search-filter.component.md) for full details of how to use widgets in a search query. + +### Custom date format + +You can set the date range picker to work with any date format your app requires. You can use +any date format supported by the [date-fns](https://date-fns.org/v2.30.0/docs/format) library +in the `dateFormat` and in the `maxDate` setting: + +```json +{ + "search": { + "categories": [ + { + "id": "createdModifiedDateRange", + "name": "Date", + "enabled": true, + "component": { + "selector": "date-range-advanced", + "settings": { + "dateFormat": "dd-MMM-yy", + "maxDate": "02-May-23", + "field": "cm:created, cm:modified", + "displayedLabelsByField": { + "cm:created": "Created Date", + "cm:modified": "Modified Date" + } + } + } + } + ] + } +} +``` + +The [SearchDateRangeAdvanced](./search-date-range-advanced-tabbed.component.md) component allows 3 different kinds of date related operations to be performed. +Based on what information is provided to that component, this component will create different kinds of queries - + +- Anytime - No date filters are applied on the `field`. This option is selected by default +- In the last - Allows to user to apply a filter to only show results from the last 'n' unit of time. +- Between - Allows the user to select a range of dates to filter the search results. + +The queries generated by this filter when using the 'In the last' or 'Between' options is of the form - + +`:[ TO ]` + + +## See also + +- [Search Configuration Guide](../../user-guide/search-configuration-guide.md) +- [Search Query Builder service](../services/search-query-builder.service.md) +- [Search Widget Interface](../interfaces/search-widget.interface.md) +- [Search check list component](search-check-list.component.md) +- [Search date range component](search-date-range.component.md) +- [Search number range component](search-number-range.component.md) +- [Search radio component](search-radio.component.md) +- [Search slider component](search-slider.component.md) +- [Search text component](search-text.component.md) +- [Search filter tabbed component](search-filter-tabbed.component.md) diff --git a/docs/content-services/components/search-date-range-advanced.component.md b/docs/content-services/components/search-date-range-advanced.component.md new file mode 100644 index 00000000000..231365e3f21 --- /dev/null +++ b/docs/content-services/components/search-date-range-advanced.component.md @@ -0,0 +1,47 @@ +--- +Title: Search date range advanced component +Added: v6.2.0 +Status: Active +Last reviewed: 2023-07-10 +--- + +# [Search date range advanced component](../../../lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts "Defined in search-date-range-advanced.component.ts") + +Represents an advanced date range component for +the [SearchAdvancedDateRangeTabbedComponent](search-date-range-advanced-tabbed.component.md). + +![Date Range Advanced Widget](../../docassets/images/search-date-range-advanced.png) + +## Basic usage + +```html + + +``` + +## Class Members + +### Properties + +| Name | Type | Description | +|--------------|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| field | string | Field to apply the query to. Required value | +| maxDate | string | A fixed date (default format: dd-MMM-yy) or the string `"today"` that will set the maximum searchable date. Default is today. | +| dateFormat | string | Date format. Dates used by the datepicker are Javascript Date objects, using [date-fns](https://date-fns.org/v2.30.0/docs/format) for formatting, so you can use any date format supported by the library. Default is 'dd-MMM-yy (sample date - 07-Jun-23) | +| initialValue | SearchDateRangeAdvanced | Initial value for the component | + +### Events + +| Name | Type | Description | +|---------------------|------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| changed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`>` | Emitted whenever a change is made in the component values. Emits the changes being made in the component. | +| valid | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted whenever a change is made in the component values. Emits a flag indicating whether the current state of the component is valid or not. | + +## Details + +This component lets the user choose a variety of options to perform date related operations. + +- Anytime - No date related data will be returned. This option is selected by default +- In the last - Allows user to perform date related operations over a period of time. The user can select the length of the period from current time, +as well as its unit. Currently, 3 units are supported - Days, Weeks, and Months. +- Between - Allows the user to select a range of dates to perform operations on. diff --git a/docs/content-services/components/search-filter-tabbed.component.md b/docs/content-services/components/search-filter-tabbed.component.md new file mode 100644 index 00000000000..404ade13980 --- /dev/null +++ b/docs/content-services/components/search-filter-tabbed.component.md @@ -0,0 +1,41 @@ +--- +Title: Search filter tabbed component +Added: v6.2.0 +Status: Active +Last reviewed: 2023-07-10 +--- + +# [Search filter tabbed component](../../../lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.ts "Defined in search-filter-tabbed.component.ts") + +Represents a container for [Search Filters](search-filter.component.md) to provide a tabbed user interface for the filters. + +![Search Filter Tabbed Widget](../../docassets/images/search-filter-tabbed.png) + +## Basic Usage + +```html + + + + + + +``` + +In order to generate a tabbed widget for multiple search filters, you can pass in the search filter widget component as a content child of the adf-search-filter-tabbed component as shown above. + +Additionally, you also have to make sure that the search filter being passed as a content child of the adf-search-filter-tabbed component, also has the adf-search-filter-tabbed directive applied on it, +with the name input property being assigned the value of whatever name should be displayed for that particular tab + +## See also + +- [Search Configuration Guide](../../user-guide/search-configuration-guide.md) +- [Search Query Builder service](../services/search-query-builder.service.md) +- [Search Widget Interface](../interfaces/search-widget.interface.md) +- [Search check list component](search-check-list.component.md) +- [Search date range component](search-date-range.component.md) +- [Search date range advanced component](search-date-range-advanced.component.md) +- [Search number range component](search-number-range.component.md) +- [Search radio component](search-radio.component.md) +- [Search slider component](search-slider.component.md) +- [Search text component](search-text.component.md) diff --git a/docs/content-services/components/search-filter.component.md b/docs/content-services/components/search-filter.component.md index af6acfd7649..86d457d3100 100644 --- a/docs/content-services/components/search-filter.component.md +++ b/docs/content-services/components/search-filter.component.md @@ -37,7 +37,9 @@ to build and execute the query. - [Search Widget Interface](../interfaces/search-widget.interface.md) - [Search check list component](search-check-list.component.md) - [Search date range component](search-date-range.component.md) +- [Search date range advanced component](search-date-range-advanced.component.md) - [Search number range component](search-number-range.component.md) - [Search radio component](search-radio.component.md) - [Search slider component](search-slider.component.md) - [Search text component](search-text.component.md) +- [Search filter tabbed component](search-filter-tabbed.component.md) diff --git a/docs/docassets/images/search-date-range-advanced-tabbed.png b/docs/docassets/images/search-date-range-advanced-tabbed.png new file mode 100644 index 00000000000..b636120d426 Binary files /dev/null and b/docs/docassets/images/search-date-range-advanced-tabbed.png differ diff --git a/docs/docassets/images/search-date-range-advanced.png b/docs/docassets/images/search-date-range-advanced.png new file mode 100644 index 00000000000..90d17bca04b Binary files /dev/null and b/docs/docassets/images/search-date-range-advanced.png differ diff --git a/docs/docassets/images/search-filter-tabbed.png b/docs/docassets/images/search-filter-tabbed.png new file mode 100644 index 00000000000..85e8a8d2a01 Binary files /dev/null and b/docs/docassets/images/search-filter-tabbed.png differ diff --git a/docs/versionIndex.md b/docs/versionIndex.md index 6b7614fc30e..b7940419cdf 100644 --- a/docs/versionIndex.md +++ b/docs/versionIndex.md @@ -48,6 +48,9 @@ backend services have been tested with each released version of ADF. - [Search Properties component](content-services/components/search-properties.component.md) +- [Search Date Range Advanced Component](content-services/components/search-date-range-advanced.component.md) +- [Search Date Range Advanced Tabbed Component](content-services/components/search-date-range-advanced-tabbed.component.md) +- [Search Filter Tabbed Component](content-services/components/search-filter-tabbed.component.md) diff --git a/e2e/search/components/search-radio.e2e.ts b/e2e/search/components/search-radio.e2e.ts index 771b7ec09d2..42a3e616d90 100644 --- a/e2e/search/components/search-radio.e2e.ts +++ b/e2e/search/components/search-radio.e2e.ts @@ -155,7 +155,7 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13); await navigationBarPage.navigateToContentServices(); @@ -170,7 +170,7 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13); await navigationBarPage.navigateToContentServices(); jsonFile.categories[5].component.settings.pageSize = 9; @@ -184,7 +184,7 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(9); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(12); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); @@ -213,21 +213,21 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(5); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(8); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().clickShowMoreButton(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().clickShowLessButton(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(5); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(8); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); @@ -244,21 +244,21 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(5); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(8); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().clickShowMoreButton(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().clickShowLessButton(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(5); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(8); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); diff --git a/e2e/search/search.config.ts b/e2e/search/search.config.ts index 45ab494d425..89c50d1247b 100644 --- a/e2e/search/search.config.ts +++ b/e2e/search/search.config.ts @@ -176,6 +176,23 @@ export class SearchConfiguration { ] } } + }, + { + id: 'createdModifiedDateRange', + name: 'Date', + enabled: true, + component: { + selector: 'date-range-advanced', + settings: { + dateFormat: 'dd-MMM-yy', + maxDate: 'today', + field: 'cm:created, cm:modified', + displayedLabelsByField: { + "cm:created": 'Created Date', + "cm:modified": 'Modified Date' + } + } + } } ], highlight: { diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json index c6534fb37c1..8162a3d4f11 100644 --- a/lib/content-services/src/lib/i18n/en.json +++ b/lib/content-services/src/lib/i18n/en.json @@ -415,6 +415,38 @@ "EXCLUDE_LABEL": "EXCLUDE these words", "EXCLUDE_HINT": "Results will exclude matches with these words" }, + "DATE_RANGE_ADVANCED": { + "OPTIONS": { + "ANYTIME": "Anytime", + "IN_LAST": "In the last", + "BETWEEN": "Between" + }, + "IN_LAST_LABELS": { + "DAYS": "Days", + "WEEKS": "Weeks", + "MONTHS": "Months" + }, + "IN_LAST_DISPLAY_LABELS": { + "DAYS": "In the last {{value}} days", + "WEEKS": "In the last {{value}} weeks", + "MONTHS": "In the last {{value}} months" + }, + "BETWEEN_PLACEHOLDERS": { + "START_DATE": "Start Date", + "END_DATE": "End Date" + }, + "ERROR": { + "IN_LAST": "Value required", + "START_DATE": { + "REQUIRED": "Start Date required", + "INVALID_FORMAT": "Start Date invalid" + }, + "END_DATE": { + "REQUIRED": "End Date required", + "INVALID_FORMAT": "End Date invalid" + } + } + }, "SEARCH_PROPERTIES": { "FILE_SIZE": "File Size", "FILE_SIZE_PLACEHOLDER": "Enter file size", diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.html b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.html new file mode 100644 index 00000000000..45047255aa2 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.html @@ -0,0 +1,13 @@ + + + + + + diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.spec.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.spec.ts new file mode 100644 index 00000000000..c57141917e8 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.spec.ts @@ -0,0 +1,236 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { ContentTestingModule } from '../../../testing/content.testing.module'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { SearchDateRangeAdvanced } from './search-date-range-advanced/search-date-range-advanced'; +import { SearchFilterTabbedComponent } from '../search-filter-tabbed/search-filter-tabbed.component'; +import { SearchDateRangeAdvancedComponent } from './search-date-range-advanced/search-date-range-advanced.component'; +import { SearchDateRangeAdvancedTabbedComponent } from './search-date-range-advanced-tabbed.component'; +import { DateRangeType } from './search-date-range-advanced/date-range-type'; +import { InLastDateType } from './search-date-range-advanced/in-last-date-type'; +import { + endOfDay, + endOfToday, + formatISO, + parse, + startOfDay, startOfMonth, + startOfWeek, + subDays, + subMonths, + subWeeks +} from 'date-fns'; + +@Component({ + selector: 'adf-search-filter-tabbed', + template: `` +}) +export class MockSearchFilterTabbedComponent {} + +@Component({ + selector: 'adf-search-date-range-advanced', + template: `` +}) +export class MockSearchDateRangeAdvancedComponent { + @Input() + dateFormat: string; + @Input() + maxDate: string; + @Input() + field: string; + @Input() + initialValue: SearchDateRangeAdvanced; + + @Output() + changed = new EventEmitter>(); + @Output() + valid = new EventEmitter(); +} +describe('SearchDateRangeAdvancedTabbedComponent', () => { + let component: SearchDateRangeAdvancedTabbedComponent; + let fixture: ComponentFixture; + let betweenMockData: SearchDateRangeAdvanced; + let inLastMockData: SearchDateRangeAdvanced; + let anyMockDate: SearchDateRangeAdvanced; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [SearchDateRangeAdvancedTabbedComponent, SearchFilterTabbedComponent, SearchDateRangeAdvancedComponent], + imports: [ + TranslateModule.forRoot(), + ContentTestingModule + ], + providers: [ + { provide: SearchFilterTabbedComponent, useClass: MockSearchFilterTabbedComponent }, + { provide: SearchDateRangeAdvancedComponent, useClass: MockSearchDateRangeAdvancedComponent } + ] + }); + fixture = TestBed.createComponent(SearchDateRangeAdvancedTabbedComponent); + + component = fixture.componentInstance; + component.id = 'dateRangeAdvanced'; + component.context = { + queryFragments: { + dateRangeAdvanced: '' + }, + update: jasmine.createSpy('update') + } as any; + component.settings = { + hideDefaultAction: false, + dateFormat: 'dd-MMM-yy', + maxDate: 'today', + field: 'createdDate, modifiedDate', + displayedLabelsByField: { + createdDate: 'Created Date', + modifiedDate: 'Modified Date' + } + }; + component.tabsValidity = { + createdDate: true, + modifiedDate: true + }; + + betweenMockData = { + dateRangeType: DateRangeType.BETWEEN, + inLastValueType: InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: parse('05-Jun-23', 'dd-MMM-yy', new Date()), + betweenEndDate: parse('07-Jun-23', 'dd-MMM-yy', new Date()) + }; + inLastMockData = { + dateRangeType: DateRangeType.IN_LAST, + inLastValueType: InLastDateType.WEEKS, + inLastValue: '5', + betweenStartDate: undefined, + betweenEndDate: undefined + }; + anyMockDate = { + dateRangeType: DateRangeType.ANY, + inLastValueType: InLastDateType.DAYS, + inLastValue: null, + betweenStartDate: null, + betweenEndDate: null + }; + + fixture.detectChanges(); + }); + + it('should be able to generate separate fields on init', () => { + fixture.detectChanges(); + expect(component.fields.length).toBe(2); + expect(component.fields).toEqual(['createdDate', 'modifiedDate']); + }); + + it('should return hasValidValue as false if any of the fields has an invalid value', () => { + component.tabsValidity['createdDate'] = false; + fixture.detectChanges(); + expect(component.hasValidValue()).toBeFalse(); + fixture.detectChanges(); + component.tabsValidity['modifiedDate'] = false; + fixture.detectChanges(); + expect(component.hasValidValue()).toBeFalse(); + component.tabsValidity['createdDate'] = true; + component.tabsValidity['modifiedDate'] = true; + fixture.detectChanges(); + expect(component.hasValidValue()).toBeTrue(); + }); + + it('should update displayValue when values are submitted', () => { + spyOn(component.displayValue$, 'next'); + component.onDateRangedValueChanged(betweenMockData, 'createdDate'); + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + component.submitValues(); + expect(component.displayValue$.next).toHaveBeenCalledWith('CREATED DATE: 05-Jun-23 - 07-Jun-23 MODIFIED DATE: SEARCH.DATE_RANGE_ADVANCED.IN_LAST_DISPLAY_LABELS.WEEKS'); + + component.onDateRangedValueChanged(anyMockDate, 'createdDate'); + component.onDateRangedValueChanged(anyMockDate, 'modifiedDate'); + fixture.detectChanges(); + component.submitValues(); + expect(component.displayValue$.next).toHaveBeenCalledWith(''); + }); + + it('should update query when values are changed', () => { + component.onDateRangedValueChanged(betweenMockData, 'createdDate'); + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + let inLastStartDate = startOfWeek(subWeeks(new Date(), 5)); + let query = `createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` + + ` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`; + expect(component.combinedQuery).toEqual(query); + + inLastMockData = { + dateRangeType: DateRangeType.IN_LAST, + inLastValueType: InLastDateType.DAYS, + inLastValue: '9', + betweenStartDate: null, + betweenEndDate: null + }; + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + inLastStartDate = startOfDay(subDays(new Date(), 9)); + query = `createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` + + ` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`; + expect(component.combinedQuery).toEqual(query); + + inLastMockData = { + dateRangeType: DateRangeType.IN_LAST, + inLastValueType: InLastDateType.MONTHS, + inLastValue: '7', + betweenStartDate: null, + betweenEndDate: null + }; + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + inLastStartDate = startOfMonth(subMonths(new Date(), 7)); + query = `createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` + + ` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`; + expect(component.combinedQuery).toEqual(query); + expect(component.combinedQuery).toEqual(query); + + component.onDateRangedValueChanged(anyMockDate, 'createdDate'); + component.onDateRangedValueChanged(anyMockDate, 'modifiedDate'); + fixture.detectChanges(); + expect(component.combinedQuery).toEqual(''); + }); + + it('should trigger context.update() when values are submitted', () => { + component.onDateRangedValueChanged(betweenMockData, 'createdDate'); + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + component.submitValues(); + fixture.detectChanges(); + const inLastStartDate = startOfWeek(subWeeks(new Date(), 5)); + const query = `createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` + + ` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`; + expect(component.context.queryFragments['dateRangeAdvanced']).toEqual(query); + expect(component.context.update).toHaveBeenCalled(); + }); + + it('should clear values and search filter when widget is reset', () => { + spyOn(component.displayValue$, 'next'); + component.reset(); + fixture.detectChanges(); + expect(component.combinedQuery).toBe(''); + expect(component.combinedDisplayValue).toBe(''); + expect(component.displayValue$.next).toHaveBeenCalledWith(''); + expect(component.context.queryFragments['dateRangeAdvanced']).toEqual(''); + expect(component.context.update).toHaveBeenCalled(); + }); +}); diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts new file mode 100644 index 00000000000..2e20e2b6105 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts @@ -0,0 +1,171 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { Subject } from 'rxjs'; +import { SearchDateRangeAdvanced } from './search-date-range-advanced/search-date-range-advanced'; +import { DateRangeType } from './search-date-range-advanced/date-range-type'; +import { SearchWidget } from '../../models/search-widget.interface'; +import { SearchWidgetSettings } from '../../models/search-widget-settings.interface'; +import { SearchQueryBuilderService } from '../../services/search-query-builder.service'; +import { InLastDateType } from './search-date-range-advanced/in-last-date-type'; +import { TranslationService } from '@alfresco/adf-core'; +import { + endOfDay, + endOfToday, + format, + formatISO, + startOfDay, + startOfMonth, + startOfWeek, + subDays, + subMonths, + subWeeks +} from 'date-fns'; + +@Component({ + selector: 'adf-search-date-range-advanced-tabbed', + templateUrl: './search-date-range-advanced-tabbed.component.html', + encapsulation: ViewEncapsulation.None +}) +export class SearchDateRangeAdvancedTabbedComponent implements SearchWidget, OnInit { + displayValue$ = new Subject(); + id: string; + startValue: SearchDateRangeAdvanced = { + dateRangeType: DateRangeType.ANY, + inLastValueType: InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: undefined, + betweenEndDate: undefined + }; + settings?: SearchWidgetSettings; + context?: SearchQueryBuilderService; + fields: string[]; + tabsValidity: { [key: string]: boolean } = {}; + combinedQuery: string; + combinedDisplayValue: string; + + private value: { [key: string]: Partial } = {}; + private queryMapByField: Map = new Map(); + private displayValueMapByField: Map = new Map(); + + constructor(private translateService: TranslationService) {} + + ngOnInit(): void { + this.fields = this.settings?.field.split(',').map(field => field.trim()); + } + + getCurrentValue(): { [key: string]: Partial } { + return this.value; + } + + hasValidValue(): boolean { + return Object.values(this.tabsValidity).every((valid) => valid); + } + + reset() { + this.combinedQuery = ''; + this.combinedDisplayValue = ''; + this.startValue = { + ...this.startValue + }; + this.submitValues(); + } + + setValue(value: { [key: string]: SearchDateRangeAdvanced }) { + this.value = value; + } + + submitValues() { + this.context.queryFragments[this.id] = this.combinedQuery; + this.displayValue$.next(this.combinedDisplayValue); + if (this.id && this.context) { + this.context.update(); + } + } + onDateRangedValueChanged(value: Partial, field: string) { + this.value[field] = value; + this.updateQuery(value, field); + this.updateDisplayValue(value, field); + } + + private generateQuery(value: Partial, field: string): string { + let query = ''; + let startDate: Date; + let endDate: Date; + if (value.dateRangeType === DateRangeType.IN_LAST) { + if (value.inLastValue) { + switch(value.inLastValueType) { + case InLastDateType.DAYS: + startDate = startOfDay(subDays(new Date(), parseInt(value.inLastValue, 10))); + break; + case InLastDateType.WEEKS: + startDate = startOfWeek(subWeeks(new Date(), parseInt(value.inLastValue, 10))); + break; + case InLastDateType.MONTHS: + startDate = startOfMonth(subMonths(new Date(), parseInt(value.inLastValue, 10))); + break; + default: + break; + } + } + endDate = endOfToday(); + } else if (value.dateRangeType === DateRangeType.BETWEEN) { + if (value.betweenStartDate && value.betweenEndDate) { + startDate = startOfDay(value.betweenStartDate); + endDate = endOfDay(value.betweenEndDate); + } + } + if (startDate && endDate) { + query = `${field}:['${formatISO(startDate)}' TO '${formatISO(endDate)}']`; + } + return query; + } + + private generateDisplayValue(value: Partial): string { + let displayValue = ''; + if (value.dateRangeType === DateRangeType.IN_LAST && value.inLastValue) { + displayValue = this.translateService.instant(`SEARCH.DATE_RANGE_ADVANCED.IN_LAST_DISPLAY_LABELS.${value.inLastValueType}`, { + value: value.inLastValue + }); + } else if (value.dateRangeType === DateRangeType.BETWEEN && value.betweenStartDate && value.betweenEndDate) { + displayValue = `${format(startOfDay(value.betweenStartDate), this.settings.dateFormat)} - ${format(endOfDay(value.betweenEndDate), this.settings.dateFormat)}`; + } + return displayValue; + } + + private updateQuery(value: Partial, field: string) { + this.combinedQuery = ''; + this.queryMapByField.set(field, this.generateQuery(value, field)); + this.queryMapByField.forEach((query: string) => { + if (query) { + this.combinedQuery = this.combinedQuery ? `${this.combinedQuery} AND ${query}` : `${query}`; + } + }); + } + + private updateDisplayValue(value: Partial, field: string) { + this.combinedDisplayValue = ''; + this.displayValueMapByField.set(field, this.generateDisplayValue(value)); + this.displayValueMapByField.forEach((displayValue: string, fieldForDisplayLabel: string) => { + if (displayValue) { + const displayLabelForField = `${this.translateService.instant(this.settings.displayedLabelsByField[fieldForDisplayLabel]).toUpperCase()}: ${displayValue}`; + this.combinedDisplayValue = this.combinedDisplayValue ? `${this.combinedDisplayValue} ${displayLabelForField}` : `${displayLabelForField}`; + } + }); + } +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/date-range-type.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/date-range-type.ts new file mode 100644 index 00000000000..4f01c957e4d --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/date-range-type.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum DateRangeType { + ANY = 'ANY', + IN_LAST = 'IN_LAST', + BETWEEN = 'BETWEEN', +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/in-last-date-type.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/in-last-date-type.ts new file mode 100644 index 00000000000..ab947414e7e --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/in-last-date-type.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum InLastDateType { + DAYS = 'DAYS', + WEEKS = 'WEEKS', + MONTHS = 'MONTHS' +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.html b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.html new file mode 100644 index 00000000000..d30351fbfef --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.html @@ -0,0 +1,45 @@ + + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.OPTIONS.ANYTIME' | translate }} + + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.OPTIONS.IN_LAST' | translate }} + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.IN_LAST' | translate }} + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.IN_LAST_LABELS.DAYS' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.IN_LAST_LABELS.WEEKS' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.IN_LAST_LABELS.MONTHS' | translate }} + + + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.OPTIONS.BETWEEN' | translate }} + + + + + + + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.START_DATE.INVALID_FORMAT' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.START_DATE.REQUIRED' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.END_DATE.INVALID_FORMAT' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.END_DATE.REQUIRED' | translate }} + + + + diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.scss b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.scss new file mode 100644 index 00000000000..a24ad08732e --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.scss @@ -0,0 +1,45 @@ +.adf-search-date-range-advanced { + .mat-radio-group { + display: flex; + flex-direction: column; + padding: 5px; + } + + .mat-radio-button { + margin: 5px; + } + + .adf-search-date-range-horizontal-container { + display: flex; + flex-direction: row; + padding-bottom: 15px; + + .adf-search-date-range-input-field { + width: 75px; + } + + .adf-search-date-range-form-field { + padding-left: 10px; + flex: 1; + + .mat-form-field-wrapper { + padding-bottom: 0; + margin-bottom: 1.25em; + border: 1px solid var(--adf-theme-mat-grey-color-a400); + border-radius: 5px; + + .mat-form-field-infix { + border: 0; + } + + .mat-form-field-underline { + display: none; + } + + .mat-form-field-subscript-wrapper { + margin-top: 2em; + } + } + } + } +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.spec.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.spec.ts new file mode 100644 index 00000000000..fa570f6c6be --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.spec.ts @@ -0,0 +1,281 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { TranslateModule } from '@ngx-translate/core'; +import { ContentTestingModule } from '../../../../testing/content.testing.module'; +import { SearchDateRangeAdvancedComponent } from './search-date-range-advanced.component'; +import { addDays, endOfToday, format, parse, startOfYesterday, subDays } from 'date-fns'; +import { Validators } from '@angular/forms'; + +describe('SearchDateRangeAdvancedComponent', () => { + let component: SearchDateRangeAdvancedComponent; + let fixture: ComponentFixture; + + const startDateSampleValue = parse('05-Jun-23', 'dd-MMM-yy', new Date()); + const endDateSampleValue = parse('07-Jun-23', 'dd-MMM-yy', new Date()); + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [SearchDateRangeAdvancedComponent], + imports: [ + TranslateModule.forRoot(), + ContentTestingModule + ] + }); + + fixture = TestBed.createComponent(SearchDateRangeAdvancedComponent); + component = fixture.componentInstance; + component.field = 'test-field'; + component.dateFormat = 'dd-MMM-yy'; + component.maxDate = 'today'; + component.form.setValue({ + dateRangeType: component.DateRangeType.ANY, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: null, + betweenStartDate: null, + betweenEndDate: null + }); + fixture.detectChanges(); + }); + + const getElementBySelector = (selector: string) => fixture.debugElement.query(By.css(selector)).nativeElement; + + const enterValueInInputFieldAndTriggerEvent = (inputElementId: string, value: string, event = 'input') => { + const inputField = getElementBySelector(`[data-automation-id="${inputElementId}"]`); + inputField.value = value; + inputField.dispatchEvent(new Event(event)); + fixture.detectChanges(); + }; + + const selectDropdownOption = (itemId: string) => { + const matSelect = fixture.debugElement.query(By.css('[data-automation-id="date-range-advanced-in-last-dropdown"]')).nativeElement; + matSelect.click(); + fixture.detectChanges(); + const matOption = fixture.debugElement.query(By.css(`[data-automation-id="${itemId}"]`)).nativeElement; + matOption.click(); + fixture.detectChanges(); + }; + + it('should set values if initial value is provided', () => { + let value: any = { + dateRangeType: component.DateRangeType.ANY + }; + component.initialValue = value; + component.ngOnInit(); + expect(component.form.controls.dateRangeType.value).toEqual(component.DateRangeType.ANY); + + value = { + dateRangeType: component.DateRangeType.IN_LAST, + inLastValueType: component.InLastDateType.WEEKS, + inLastValue: '5' + }; + component.initialValue = value; + component.ngOnInit(); + expect(component.form.controls.dateRangeType.value).toEqual(component.DateRangeType.IN_LAST); + expect(component.form.controls.inLastValueType.value).toEqual(component.InLastDateType.WEEKS); + expect(component.form.controls.inLastValue.value).toEqual('5'); + + value = { + dateRangeType: component.DateRangeType.BETWEEN, + betweenStartDate: startOfYesterday(), + betweenEndDate: endOfToday() + }; + component.initialValue = value; + component.ngOnInit(); + expect(component.form.controls.dateRangeType.value).toEqual(component.DateRangeType.BETWEEN); + expect(component.form.controls.betweenStartDate.value).toEqual(startOfYesterday()); + expect(component.form.controls.betweenEndDate.value).toEqual(endOfToday()); + }); + + it('should not have any validators on any input fields when anytime option is selected', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.ANY); + fixture.detectChanges(); + expect(component.form.controls.inLastValue.validator).toBeNull(); + expect(component.form.controls.betweenStartDate.validator).toBeNull(); + expect(component.form.controls.betweenEndDate.validator).toBeNull(); + }); + + it('should set the required validator on in last input field and remove validators from between input fields when In the last option is selected', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.IN_LAST); + fixture.detectChanges(); + expect(component.form.controls.inLastValue.hasValidator(Validators.required)).toBeTrue(); + expect(component.form.controls.betweenStartDate.validator).toBeNull(); + expect(component.form.controls.betweenEndDate.validator).toBeNull(); + }); + + it('should set the validators on in between input fields and remove validator from in last input fields when Between option is selected', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + expect(component.form.controls.betweenStartDate.hasValidator(Validators.required)).toBeTrue(); + expect(component.form.controls.betweenEndDate.hasValidator(Validators.required)).toBeTrue(); + expect(component.form.controls.betweenEndDate.hasValidator(component.endDateValidator)).toBeTrue(); + expect(component.form.controls.inLastValue.validator).toBeNull(); + }); + + it('should not be able to set zero or negative values in In the last input field', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.IN_LAST); + fixture.detectChanges(); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', '-5'); + let inLastInputFieldValue = getElementBySelector('[data-automation-id="date-range-advanced-in-last-input"]').value; + expect(inLastInputFieldValue).toBe('5'); + + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', '0'); + inLastInputFieldValue = getElementBySelector('[data-automation-id="date-range-advanced-in-last-input"]').value; + expect(inLastInputFieldValue).toBe(''); + }); + + it('should give an invalid date error when manually setting a start date and an end date that are not in the correct format', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-start-input', 'invalid-date-input', 'change'); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-end-input', 'invalid-date-input', 'change'); + expect(component.form.controls.betweenStartDate.errors.invalidDate).toBeTrue(); + expect(component.form.controls.betweenEndDate.errors.invalidDate).toBeTrue(); + }); + + it('should give an invalid date error when manually setting a start Date that is after the end date', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + component.form.controls.betweenEndDate.setValue(new Date()); + const startDate = format(addDays(component.form.controls.betweenEndDate.value, 3), component.dateFormat); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-start-input', startDate, 'change'); + expect(component.form.controls.betweenEndDate.errors.invalidDate).toBeTrue(); + }); + + it('should give an invalid date error when manually setting an end Date that is before the start date', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + component.form.controls.betweenStartDate.setValue(new Date()); + const endDate = format(subDays(component.form.controls.betweenStartDate.value, 3), component.dateFormat); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-end-input', endDate, 'change'); + expect(component.form.controls.betweenEndDate.errors.invalidDate).toBeTrue(); + }); + + it('should give an invalid date error when setting an endDate that is after the max date', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + const endDate = format(addDays(component.convertedMaxDate, 3), component.dateFormat); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-end-input', endDate, 'change'); + expect(component.form.controls.betweenEndDate.errors.invalidDate).toBeTrue(); + }); + + it('should emit valid as false when form is invalid', () => { + spyOn(component.valid, 'emit'); + component.form.controls.dateRangeType.setValue(component.DateRangeType.IN_LAST); + fixture.detectChanges(); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', ''); + selectDropdownOption('date-range-advanced-in-last-option-weeks'); + expect(component.valid.emit).toHaveBeenCalledWith(false); + + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + expect(component.valid.emit).toHaveBeenCalledWith(false); + }); + + it('should emit valid as true when form is valid', () => { + spyOn(component.valid, 'emit'); + component.form.controls.dateRangeType.setValue(component.DateRangeType.IN_LAST); + fixture.detectChanges(); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', '5'); + selectDropdownOption('date-range-advanced-in-last-option-weeks'); + expect(component.valid.emit).toHaveBeenCalledWith(true); + + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + component.betweenStartDateFormControl.setValue(startDateSampleValue); + component.betweenEndDateFormControl.setValue(endDateSampleValue); + fixture.detectChanges(); + expect(component.valid.emit).toHaveBeenCalledWith(true); + }); + + it('should not emit values when form is invalid', () => { + spyOn(component.changed, 'emit'); + let value = { + dateRangeType: component.DateRangeType.IN_LAST, + inLastValueType: component.InLastDateType.WEEKS, + inLastValue: '', + betweenStartDate: undefined, + betweenEndDate: undefined + }; + let dateRangeTypeRadioButton = getElementBySelector('[data-automation-id="date-range-advanced-in-last"] .mat-radio-input'); + dateRangeTypeRadioButton.click(); + selectDropdownOption('date-range-advanced-in-last-option-weeks'); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', ''); + expect(component.changed.emit).not.toHaveBeenCalledWith(value); + + component.form.patchValue({ + dateRangeType: component.DateRangeType.ANY, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: undefined, + betweenEndDate: undefined + }); + + value = { + dateRangeType: component.DateRangeType.BETWEEN, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: '', + betweenEndDate: '' + }; + dateRangeTypeRadioButton = getElementBySelector('[data-automation-id="date-range-advanced-between"] .mat-radio-input'); + dateRangeTypeRadioButton.click(); + fixture.detectChanges(); + expect(component.changed.emit).not.toHaveBeenCalledWith(value); + }); + + it('should emit values when form is valid', () => { + spyOn(component.changed, 'emit'); + let value = { + dateRangeType: component.DateRangeType.IN_LAST, + inLastValueType: component.InLastDateType.WEEKS, + inLastValue: 5, + betweenStartDate: null, + betweenEndDate: null + }; + let dateRangeTypeRadioButton = getElementBySelector('[data-automation-id="date-range-advanced-in-last"] .mat-radio-input'); + dateRangeTypeRadioButton.click(); + selectDropdownOption('date-range-advanced-in-last-option-weeks'); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', '5'); + fixture.detectChanges(); + expect(component.changed.emit).toHaveBeenCalledWith(value); + + component.form.patchValue({ + dateRangeType: component.DateRangeType.ANY, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: undefined, + betweenEndDate: undefined + }); + + value = { + dateRangeType: component.DateRangeType.BETWEEN, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: startDateSampleValue, + betweenEndDate: endDateSampleValue + }; + dateRangeTypeRadioButton = getElementBySelector('[data-automation-id="date-range-advanced-between"] .mat-radio-input'); + dateRangeTypeRadioButton.click(); + component.betweenStartDateFormControl.setValue(startDateSampleValue); + component.betweenEndDateFormControl.setValue(endDateSampleValue); + fixture.detectChanges(); + expect(component.changed.emit).toHaveBeenCalledWith(value); + }); +}); diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts new file mode 100644 index 00000000000..d0792a9c196 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts @@ -0,0 +1,180 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, EventEmitter, Inject, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { Subject } from 'rxjs'; +import { endOfDay, parse, isValid, isBefore, isAfter } from 'date-fns'; +import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatDateFormats } from '@angular/material/core'; +import { DateFnsAdapter, MAT_DATE_FNS_FORMATS } from '@angular/material-date-fns-adapter'; +import { InLastDateType } from './in-last-date-type'; +import { DateRangeType } from './date-range-type'; +import { SearchDateRangeAdvanced } from './search-date-range-advanced'; +import { FormBuilder, UntypedFormControl, Validators } from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import { UserPreferencesService, UserPreferenceValues, DateFnsUtils } from '@alfresco/adf-core'; + +const DEFAULT_DATE_DISPLAY_FORMAT = 'dd-MMM-yy'; + +@Component({ + selector: 'adf-search-date-range-advanced', + templateUrl: './search-date-range-advanced.component.html', + styleUrls: ['./search-date-range-advanced.component.scss'], + providers: [ + { provide: DateAdapter, useClass: DateFnsAdapter, deps: [ MAT_DATE_LOCALE ] }, + { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS } + ], + encapsulation: ViewEncapsulation.None, + host: {class: 'adf-search-date-range-advanced'} +}) +export class SearchDateRangeAdvancedComponent implements OnInit, OnDestroy { + @Input() + dateFormat = DEFAULT_DATE_DISPLAY_FORMAT; + @Input() + maxDate: string; + @Input() + field: string; + @Input() + set initialValue(value: SearchDateRangeAdvanced) { + if (value) { + this.form.patchValue(value); + } + } + + @Output() + changed = new EventEmitter>(); + @Output() + valid = new EventEmitter(); + + form = this.formBuilder.group({ + dateRangeType: DateRangeType.ANY, + inLastValueType: InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: undefined, + betweenEndDate: undefined + }); + betweenStartDateFormControl = this.form.controls.betweenStartDate; + betweenEndDateFormControl = this.form.controls.betweenEndDate; + convertedMaxDate: Date; + private destroy$ = new Subject(); + + readonly DateRangeType = DateRangeType; + readonly InLastDateType = InLastDateType; + + constructor(private formBuilder: FormBuilder, + private userPreferencesService: UserPreferencesService, + private dateAdapter: DateAdapter, + @Inject(MAT_DATE_FORMATS) private dateFormatConfig: MatDateFormats) {} + + readonly endDateValidator = (formControl: UntypedFormControl): ({ [key: string]: boolean } | null) => { + if (isBefore(formControl.value, this.betweenStartDateFormControl.value) || isAfter(formControl.value, this.convertedMaxDate)) { + return { + invalidDate: true + }; + } + return {}; + }; + + ngOnInit(): void { + this.dateFormatConfig.display.dateInput = this.dateFormat; + this.convertedMaxDate = endOfDay(this.maxDate && this.maxDate !== 'today' ? + parse(this.maxDate, this.dateFormat, new Date()) : new Date()); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.destroy$)) + .subscribe(locale => this.dateAdapter.setLocale(DateFnsUtils.getLocaleFromString(locale))); + this.form.controls.dateRangeType.valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe((dateRangeType) => this.updateValidators(dateRangeType)); + this.form.valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe(() => this.onChange()); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + private updateValidators(dateRangeType: DateRangeType) { + switch(dateRangeType) { + case DateRangeType.BETWEEN: + this.betweenStartDateFormControl.setValidators(Validators.required); + this.betweenEndDateFormControl.setValidators([Validators.required, this.endDateValidator]); + this.form.controls.inLastValue.clearValidators(); + break; + case DateRangeType.IN_LAST: + this.form.controls.inLastValue.setValidators(Validators.required); + this.betweenStartDateFormControl.clearValidators(); + this.betweenEndDateFormControl.clearValidators(); + break; + default: + this.form.controls.inLastValue.clearValidators(); + this.betweenStartDateFormControl.clearValidators(); + this.betweenEndDateFormControl.clearValidators(); + break; + } + this.betweenStartDateFormControl.updateValueAndValidity(); + this.betweenEndDateFormControl.updateValueAndValidity(); + this.form.controls.inLastValue.updateValueAndValidity(); + } + + private onChange(): void { + if (this.form.valid) { + this.changed.emit(this.form.value); + } + this.valid.emit(this.form.valid); + } + + dateChanged(event: Event, formControl: UntypedFormControl) { + if (event?.target['value']?.trim()) { + const date = parse(event.target['value'], this.dateFormat, new Date()); + if(!isValid(date)) { + formControl.setErrors({ + ...formControl.errors, + required: false, + invalidDate: true + }); + } else { + formControl.setErrors({ + ...formControl.errors, + invalidDate: false + }); + formControl.setValue(date); + } + } + } + + narrowDownAllowedCharacters(event: Event) { + if (parseInt((event.target as HTMLInputElement).value, 10) === 0) { + (event.target as HTMLInputElement).value = ''; + } else { + (event.target as HTMLInputElement).value = (event.target as HTMLInputElement).value.replace(/\D/g, ''); + } + } + + preventIncorrectNumberCharacters(event: KeyboardEvent): boolean { + switch(event.key) { + case '.': + case '-': + case 'e': + case '+': + return false; + case '0': + return !!(event.target as HTMLInputElement).value; + default: + return true; + } + } +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.ts new file mode 100644 index 00000000000..a51e0d96b2a --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.ts @@ -0,0 +1,27 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DateRangeType } from './date-range-type'; +import { InLastDateType } from './in-last-date-type'; + +export interface SearchDateRangeAdvanced { + dateRangeType: DateRangeType; + inLastValueType?: InLastDateType; + inLastValue?: string; + betweenStartDate?: Date; + betweenEndDate?: Date; +} diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.html b/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.html index 779e40803a2..f645c1dad4d 100644 --- a/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.html +++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.html @@ -19,7 +19,7 @@ {{ chipIcon }} - +
) { } +} diff --git a/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.html b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.html new file mode 100644 index 00000000000..558c13b1eeb --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.html @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.scss b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.scss new file mode 100644 index 00000000000..fa74c8d502d --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.scss @@ -0,0 +1,20 @@ +adf-search-filter-tabbed { + .mat-tab-label { + flex: 1; + + &.mat-tab-label-active { + border-bottom: 2px solid var(--theme-primary-color); + } + } + + // The important tag is used here as a workaround for a bug in angular material, when MatTabs are used in conjunction with MatMenu + // https://github.com/angular/components/issues/27426 + .mat-tab-body.mat-tab-body-active .mat-tab-body-content { + display: block; + visibility: visible !important; + } + + .mat-ink-bar { + display: none; + } +} diff --git a/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.ts b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.ts new file mode 100644 index 00000000000..50a03adff7f --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.ts @@ -0,0 +1,30 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, ContentChildren, QueryList, ViewEncapsulation } from '@angular/core'; +import { SearchFilterTabDirective } from './search-filter-tab.directive'; + +@Component({ + selector: 'adf-search-filter-tabbed', + templateUrl: './search-filter-tabbed.component.html', + styleUrls: ['./search-filter-tabbed.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class SearchFilterTabbedComponent { + @ContentChildren(SearchFilterTabDirective) + tabsContents: QueryList; +} diff --git a/lib/content-services/src/lib/search/public-api.ts b/lib/content-services/src/lib/search/public-api.ts index d47030feac4..52176d8ff17 100644 --- a/lib/content-services/src/lib/search/public-api.ts +++ b/lib/content-services/src/lib/search/public-api.ts @@ -34,6 +34,7 @@ export * from './services/search-facet-filters.service'; export * from './services/search-filter.service'; export * from './services/search.service'; export * from './services/search-configuration.service'; +export * from './services/base-query-builder.service'; export * from './mocks/search.service.mock'; @@ -61,6 +62,8 @@ export * from './components/search-filter-chips/search-filter-chips.component'; export * from './components/search-filter-chips/search-filter-menu-card/search-filter-menu-card.component'; export * from './components/search-facet-field/search-facet-field.component'; export * from './components/search-logical-filter/search-logical-filter.component'; +export * from './components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component'; +export * from './components/search-filter-tabbed/search-filter-tabbed.component'; export * from './components/reset-search.directive'; export * from './components/search-chip-autocomplete-input/search-chip-autocomplete-input.component'; export * from './components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component'; diff --git a/lib/content-services/src/lib/search/search.module.ts b/lib/content-services/src/lib/search/search.module.ts index 7c74d625bd5..4a90815dd72 100644 --- a/lib/content-services/src/lib/search/search.module.ts +++ b/lib/content-services/src/lib/search/search.module.ts @@ -52,6 +52,10 @@ import { SearchFacetChipComponent } from './components/search-filter-chips/searc import { SearchLogicalFilterComponent } from './components/search-logical-filter/search-logical-filter.component'; import { ResetSearchDirective } from './components/reset-search.directive'; import { SearchPropertiesComponent } from './components/search-properties/search-properties.component'; +import { SearchFilterTabbedComponent } from './components/search-filter-tabbed/search-filter-tabbed.component'; +import { SearchDateRangeAdvancedComponent } from './components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component'; +import { SearchDateRangeAdvancedTabbedComponent } from './components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component'; +import { SearchFilterTabDirective } from './components/search-filter-tabbed/search-filter-tab.directive'; @NgModule({ imports: [ @@ -90,7 +94,11 @@ import { SearchPropertiesComponent } from './components/search-properties/search SearchFacetChipComponent, SearchLogicalFilterComponent, ResetSearchDirective, - SearchPropertiesComponent + SearchPropertiesComponent, + SearchFilterTabbedComponent, + SearchDateRangeAdvancedComponent, + SearchDateRangeAdvancedTabbedComponent, + SearchFilterTabDirective ], exports: [ SearchComponent, @@ -116,6 +124,8 @@ import { SearchPropertiesComponent } from './components/search-properties/search SearchFilterMenuCardComponent, SearchFacetFieldComponent, SearchLogicalFilterComponent, + SearchFilterTabbedComponent, + SearchDateRangeAdvancedComponent, ResetSearchDirective ], providers: [ diff --git a/lib/content-services/src/lib/search/services/search-filter.service.ts b/lib/content-services/src/lib/search/services/search-filter.service.ts index a1cc1e9053d..491bf471bfb 100644 --- a/lib/content-services/src/lib/search/services/search-filter.service.ts +++ b/lib/content-services/src/lib/search/services/search-filter.service.ts @@ -26,6 +26,9 @@ import { SearchDatetimeRangeComponent } from '../components/search-datetime-rang import { SearchLogicalFilterComponent } from '../components/search-logical-filter/search-logical-filter.component'; import { SearchFilterAutocompleteChipsComponent } from '../components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component'; import { SearchPropertiesComponent } from '../components/search-properties/search-properties.component'; +import { + SearchDateRangeAdvancedTabbedComponent +} from '../components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component'; @Injectable({ providedIn: 'root' @@ -45,7 +48,8 @@ export class SearchFilterService { 'date-range': SearchDateRangeComponent, 'datetime-range': SearchDatetimeRangeComponent, 'logical-filter': SearchLogicalFilterComponent, - 'autocomplete-chips': SearchFilterAutocompleteChipsComponent + 'autocomplete-chips': SearchFilterAutocompleteChipsComponent, + 'date-range-advanced': SearchDateRangeAdvancedTabbedComponent }; } diff --git a/lib/core/src/lib/common/utils/date-fns-utils.ts b/lib/core/src/lib/common/utils/date-fns-utils.ts new file mode 100644 index 00000000000..56cf705043b --- /dev/null +++ b/lib/core/src/lib/common/utils/date-fns-utils.ts @@ -0,0 +1,81 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {ar, cs, da, de, enUS, es, fi, fr, it, ja, nb, nl, pl, ptBR, ru, sv, zhCN} from 'date-fns/locale'; + +export class DateFnsUtils { + static getLocaleFromString(locale: string): Locale { + let dateFnsLocale: Locale; + switch(locale) { + case 'ar': + dateFnsLocale = ar; + break; + case 'cs': + dateFnsLocale = cs; + break; + case 'da': + dateFnsLocale = da; + break; + case 'de': + dateFnsLocale = de; + break; + case 'en': + dateFnsLocale = enUS; + break; + case 'es': + dateFnsLocale = es; + break; + case 'fi': + dateFnsLocale = fi; + break; + case 'fr': + dateFnsLocale = fr; + break; + case 'it': + dateFnsLocale = it; + break; + case 'ja': + dateFnsLocale = ja; + break; + case 'nb': + dateFnsLocale = nb; + break; + case 'nl': + dateFnsLocale = nl; + break; + case 'pl': + dateFnsLocale = pl; + break; + case 'pt-BR': + dateFnsLocale = ptBR; + break; + case 'ru': + dateFnsLocale = ru; + break; + case 'sv': + dateFnsLocale = sv; + break; + case 'zh-CN': + dateFnsLocale = zhCN; + break; + default: + dateFnsLocale = enUS; + break; + } + return dateFnsLocale; + } +} diff --git a/lib/core/src/lib/common/utils/public-api.ts b/lib/core/src/lib/common/utils/public-api.ts index e2a3d3f80c8..8b3fbaa6164 100644 --- a/lib/core/src/lib/common/utils/public-api.ts +++ b/lib/core/src/lib/common/utils/public-api.ts @@ -20,3 +20,4 @@ export * from './file-utils'; export * from './moment-date-formats.model'; export * from './moment-date-adapter'; export * from './string-utils'; +export * from './date-fns-utils'; diff --git a/lib/testing/src/lib/protractor/content-services/pages/search/search-slider.page.ts b/lib/testing/src/lib/protractor/content-services/pages/search/search-slider.page.ts index 579a4a9b02d..8c7c0d5fbf8 100644 --- a/lib/testing/src/lib/protractor/content-services/pages/search/search-slider.page.ts +++ b/lib/testing/src/lib/protractor/content-services/pages/search/search-slider.page.ts @@ -45,7 +45,7 @@ export class SearchSliderPage { async setValue(value: number): Promise { const elem = this.filter.$(this.slider).$('.mat-slider-wrapper'); await browser.actions().mouseMove(elem, { x: 0, y: 0 }).perform(); - await browser.actions().mouseDown().mouseMove({x: value * 10, y: 0}).mouseUp().perform(); + await browser.actions().mouseDown().mouseMove({x: value * 20, y: 0}).mouseUp().perform(); } async checkSliderIsDisplayed(): Promise { diff --git a/package-lock.json b/package-lock.json index 35a83bdc892..cb53b50f6bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@angular/core": "14.1.3", "@angular/forms": "14.1.3", "@angular/material": "14.1.2", + "@angular/material-date-fns-adapter": "^14.1.2", "@angular/material-moment-adapter": "14.1.2", "@angular/platform-browser": "14.1.3", "@angular/platform-browser-dynamic": "14.1.3", @@ -1216,6 +1217,19 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material-date-fns-adapter": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@angular/material-date-fns-adapter/-/material-date-fns-adapter-14.1.2.tgz", + "integrity": "sha512-98XaKVCybB/6hveqBiVQ88XWklxW27U917Uwq6sIgf6k62ZvrhPMOyz/arTCuR/OYRgJyWV2ykUxdqvGzr28+Q==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^14.0.0 || ^15.0.0", + "@angular/material": "14.1.2", + "date-fns": "^2.23.0" + } + }, "node_modules/@angular/material-moment-adapter": { "version": "14.1.2", "license": "MIT", diff --git a/package.json b/package.json index 0d9e31c08b9..197d491332b 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@angular/core": "14.1.3", "@angular/forms": "14.1.3", "@angular/material": "14.1.2", + "@angular/material-date-fns-adapter": "^14.1.2", "@angular/material-moment-adapter": "14.1.2", "@angular/platform-browser": "14.1.3", "@angular/platform-browser-dynamic": "14.1.3",