From 9278d9296fa9943a74cf5d77ccee277528fa389b Mon Sep 17 00:00:00 2001 From: Tomasz Gnyp <49343696+tomgny@users.noreply.github.com> Date: Fri, 3 Nov 2023 10:31:13 +0100 Subject: [PATCH] [AAE-16965] Improve data table date column (#9038) * [AAE-16965] Improve Date data table column type * [AAE-16965] Date unit tests * update docs * cleanup cells after implement inject * bring back removed constructors * remove empty constructors * replace constructor token injection by inject function * [AAE-16965] Use other variable for template * implement suggestions * update demo shell module * fix unit test * fix timeAgo problem * add some more unit tests * fake change in extensions * [AAE-16965] fix for backward compatibility --- .../task-list-demo/task-list.module.ts | 5 +- docs/core/components/data-column.component.md | 24 ++ .../amount-cell/amount-cell.component.ts | 17 +- .../boolean-cell/boolean-cell.component.ts | 10 +- .../datatable-cell.component.spec.ts | 104 +++++--- .../datatable-cell.component.ts | 8 +- .../datatable/datatable.component.html | 3 +- .../datatable/datatable.component.spec.ts | 11 + .../date-cell/date-cell.component.html | 15 ++ .../date-cell/date-cell.component.spec.ts | 235 ++++++++++++++++++ .../date-cell/date-cell.component.ts | 81 +++--- .../filesize-cell/filesize-cell.component.ts | 15 +- .../json-cell/json-cell.component.ts | 11 +- .../location-cell/location-cell.component.ts | 7 +- .../number-cell/number-cell.component.ts | 10 +- .../lib/datatable/data/data-column.model.ts | 15 +- .../datatable/data/object-datacolumn.model.ts | 4 +- .../src/lib/datatable/datatable.module.ts | 7 +- lib/core/src/lib/pipes/localized-date.pipe.ts | 1 + lib/core/src/lib/pipes/pipe.module.ts | 11 +- lib/core/src/lib/pipes/time-ago.pipe.ts | 1 + .../lib/config/document-list.extensions.ts | 28 +-- 22 files changed, 452 insertions(+), 171 deletions(-) create mode 100644 lib/core/src/lib/datatable/components/date-cell/date-cell.component.html create mode 100644 lib/core/src/lib/datatable/components/date-cell/date-cell.component.spec.ts diff --git a/demo-shell/src/app/components/task-list-demo/task-list.module.ts b/demo-shell/src/app/components/task-list-demo/task-list.module.ts index 55b5b199fd..0748e9d6fb 100644 --- a/demo-shell/src/app/components/task-list-demo/task-list.module.ts +++ b/demo-shell/src/app/components/task-list-demo/task-list.module.ts @@ -19,7 +19,7 @@ import { NgModule } from '@angular/core'; import { TaskListDemoComponent } from './task-list-demo.component'; import { Routes, RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; -import { CoreModule } from '@alfresco/adf-core'; +import { CoreModule, LocalizedDatePipe } from '@alfresco/adf-core'; import { ProcessModule } from '@alfresco/adf-process-services'; const routes: Routes = [ @@ -38,7 +38,8 @@ const routes: Routes = [ CommonModule, RouterModule.forChild(routes), CoreModule, - ProcessModule.forChild() + ProcessModule.forChild(), + LocalizedDatePipe ], declarations: [TaskListDemoComponent] }) diff --git a/docs/core/components/data-column.component.md b/docs/core/components/data-column.component.md index b31a296e46..8fc0748c4a 100644 --- a/docs/core/components/data-column.component.md +++ b/docs/core/components/data-column.component.md @@ -63,6 +63,7 @@ Defines column properties for DataTable, Tasklist, Document List and other compo | order | `number` | | Sets position of column. | | currencyConfig | `CurrencyConfig` | [Default currency config](#default-currency-config) | Currency configuration to customize the formatting and display of currency values within the component. | | decimalConfig | `DecimalConfig` | [Default decimal config](#default-decimal-config) | Decimal configuration to customize the formatting and display of decimal values within the component. | +| dateConfig | `DateConfig` | [Default date config](#default-date-config) | Date configuration to customize the formatting and localization of date values within the component. | ## Properties configuration @@ -72,6 +73,7 @@ The `type` input allows us to specify the type of hosted values for a given colu - `text` - The given values are represented as a strings (default option). - `boolean` - The column expects true / false (boolean values) and in addition accepts two strings - 'false' and 'true'. Other values are not recognized by the column, and the cell remains empty. +- `date` - This column is responsible for displaying dates. It expects date represented by a string, number or Date object. This type comes with [`dateConfig`](#default-date-config), - `amount` - This column is responsible for displaying currencies. It expects numerals represented by a string or a number. This type comes with [`currencyConfig`](#default-currency-config), - `number` - This column is responsible for displaying numbers (integers and decimals). It expects numerals represented by a string or a number. This type comes with [`decimalConfig`](#default-decimal-config) - `location` - This column displays a clickable location link pointing to the parent path of the node. **Note:** This type is strongly related to the document list component ([document-list.component.md](../../content-services/components/document-list.component.md)). @@ -120,6 +122,28 @@ These properties offer flexibility in customizing how decimal values are present For more details on the possible use cases of the above properties, see the [official Angular documents](https://angular.io/api/common/DecimalPipe). +### `dateConfig` Input + +The `dateConfig` input allows you to configure date formatting and localization for a component. It accepts an object of type `DateConfig` with optional properties for specifying the format of displayed date, tooltip and locale. + +#### Properties + +- `format` (optional): A string specifying the date format ([pre-defined formats](https://angular.io/api/common/DatePipe#pre-defined-format-options)). +- `tooltipFormat` (optional): A string specifying the date format for tooltips. +- `locale` (optional): A string indicating the locale or region-specific formatting to use for the currency. + +#### Default date config + +By default, the `dateConfig` object is not required. If not provided, the component will use the following default values: + +- `format`: "medium" +- `tooltipFormat`: "medium" +- `locale`: undefined + +These properties offer flexibility in customizing how date values are presented within the component. + +For more details on the possible use cases of the above properties, see the [official Angular documents](https://angular.io/api/common/DatePipe). + ## Details ### Conditional visibility diff --git a/lib/core/src/lib/datatable/components/amount-cell/amount-cell.component.ts b/lib/core/src/lib/datatable/components/amount-cell/amount-cell.component.ts index 4fd3bd8db1..34fed1a890 100644 --- a/lib/core/src/lib/datatable/components/amount-cell/amount-cell.component.ts +++ b/lib/core/src/lib/datatable/components/amount-cell/amount-cell.component.ts @@ -20,13 +20,11 @@ import { Component, ViewEncapsulation, Input, - Optional, OnInit, DEFAULT_CURRENCY_CODE, - Inject + inject } from '@angular/core'; import { DataTableCellComponent } from '../datatable-cell/datatable-cell.component'; -import { DataTableService } from '../../services/datatable.service'; import { CurrencyConfig } from '../../data/data-column.model'; import { CommonModule } from '@angular/common'; @@ -44,6 +42,8 @@ export class AmountCellComponent extends DataTableCellComponent implements OnIni @Input() currencyConfig: CurrencyConfig; + private readonly defaultCurrencyCode: string = inject(DEFAULT_CURRENCY_CODE); + readonly defaultCurrencyConfig: CurrencyConfig = { code: this.defaultCurrencyCode, display: 'symbol', @@ -51,16 +51,7 @@ export class AmountCellComponent extends DataTableCellComponent implements OnIni locale: undefined }; - constructor( - @Optional() dataTableService: DataTableService, - @Inject(DEFAULT_CURRENCY_CODE) private readonly defaultCurrencyCode: string - ) { - super(dataTableService); - } - ngOnInit() { - if (this.column?.key && this.row && this.data) { - this.value$.next(this.data.getValue(this.row, this.column, this.resolverFn)); - } + super.ngOnInit(); } } diff --git a/lib/core/src/lib/datatable/components/boolean-cell/boolean-cell.component.ts b/lib/core/src/lib/datatable/components/boolean-cell/boolean-cell.component.ts index f9ba601fd5..19cf3bd289 100644 --- a/lib/core/src/lib/datatable/components/boolean-cell/boolean-cell.component.ts +++ b/lib/core/src/lib/datatable/components/boolean-cell/boolean-cell.component.ts @@ -15,10 +15,9 @@ * limitations under the License. */ -import { ChangeDetectionStrategy, Component, OnInit, Optional, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation } from '@angular/core'; import { DataTableCellComponent } from '../datatable-cell/datatable-cell.component'; import { CommonModule } from '@angular/common'; -import { DataTableService } from '../../services/datatable.service'; import { BooleanPipe } from '../../../pipes/boolean.pipe'; @Component({ @@ -37,13 +36,8 @@ import { BooleanPipe } from '../../../pipes/boolean.pipe'; host: { class: 'adf-datatable-content-cell' } }) export class BooleanCellComponent extends DataTableCellComponent implements OnInit { - constructor(@Optional() dataTableService: DataTableService) { - super(dataTableService); - } ngOnInit() { - if (this.column?.key && this.row && this.data) { - this.value$.next(this.data.getValue(this.row, this.column, this.resolverFn)); - } + super.ngOnInit(); } } diff --git a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.spec.ts b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.spec.ts index 53aeeefb4d..245996a2e8 100644 --- a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.spec.ts +++ b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.spec.ts @@ -15,58 +15,86 @@ * limitations under the License. */ -import { UserPreferencesService } from '../../../common/services/user-preferences.service'; -import { AppConfigService } from '../../../app-config'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CoreTestingModule } from '../../../testing'; -import { DateCellComponent } from '../date-cell/date-cell.component'; +import { DataTableCellComponent } from './datatable-cell.component'; +import { DataRow } from '../../data/data-row.model'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { DataTableService } from '../../services/datatable.service'; +import { ObjectDataTableAdapter } from '../../data/object-datatable-adapter'; +import { mockCarsData, mockCarsSchemaDefinition } from '../mocks/datatable.mock'; -describe('DateCellComponent', () => { - let appConfigService: AppConfigService; - let userPreferencesService: UserPreferencesService; - let fixture: ComponentFixture; - let component: DateCellComponent; - let getLocaleSpy: jasmine.Spy; +describe('DataTableCellComponent', () => { + let component: DataTableCellComponent; + let fixture: ComponentFixture; + let dataTableService: DataTableService; + + const renderTextCell = (value: string, tooltip: string) => { + component.value$ = new BehaviorSubject(value); + component.tooltip = tooltip; + + fixture.detectChanges(); + }; + + const checkDisplayedText = (expectedText: string) => { + const displayedText = fixture.nativeElement.querySelector('span').textContent.trim(); + + expect(displayedText).toBeTruthy(); + expect(displayedText).toBe(expectedText); + }; + + const checkDisplayedTooltip = (expectedTooltip: string) => { + const displayedTooltip = fixture.nativeElement.querySelector('span').title; + + expect(displayedTooltip).toBeTruthy(); + expect(displayedTooltip).toBe(expectedTooltip); + }; beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - CoreTestingModule - ], - declarations: [DateCellComponent] + declarations: [DataTableCellComponent], + providers: [DataTableService] }); - appConfigService = TestBed.inject(AppConfigService); - userPreferencesService = TestBed.inject(UserPreferencesService); - getLocaleSpy = spyOn(userPreferencesService, 'select').and.callThrough(); - appConfigService.config = { - dateValues: { - defaultDateFormat: 'mediumDate', - defaultTooltipDateFormat: 'medium' - } - }; - fixture = TestBed.createComponent(DateCellComponent); + fixture = TestBed.createComponent(DataTableCellComponent); component = fixture.componentInstance; + dataTableService = TestBed.inject(DataTableService); }); - it('should read locale from user preferences service', () => { - expect(getLocaleSpy).toHaveBeenCalledWith('locale'); - expect(component.currentLocale).toEqual('en'); + it('should display text and tooltip', () => { + const row: DataRow = { + id: '1', + isSelected: false, + hasValue: () => true, + getValue: () => 'hello world', + obj: 'Initial Value', + cache: [] + }; + + component.row = row; + + renderTextCell('hello world', 'hello world tooltip'); + + checkDisplayedText('hello world'); + checkDisplayedTooltip('hello world tooltip'); }); - it('should read date format values from app config service', () => { - expect(component.format).toEqual('mediumDate'); - expect(component.tooltipDateFormat).toEqual('medium'); - }); + it('should update row obj value on data changes', () => { + const row: DataRow = { + id: '1', + isSelected: false, + hasValue: () => true, + getValue: () => 'hello world', + obj: 'Initial Value', + cache: [] + }; + + component.data = new ObjectDataTableAdapter(mockCarsData, mockCarsSchemaDefinition); + component.column = { key: 'car_name', type: 'text' }; + component.row = row; - it('should date values be formatted based on the formats defined in the app config', () => { - component.value$.next('2022-07-14T11:50:45.973+0000'); - component.tooltip = '2022-07-14T11:50:45.973+0000'; fixture.detectChanges(); - const dateCellValue = fixture.nativeElement.querySelector('.adf-datatable-cell-value'); - const tooltipValue = dateCellValue.attributes['title'].value; + dataTableService.rowUpdate.next({ id: '1', obj: 'New Value' }); - expect(dateCellValue.textContent.trim()).toEqual('Jul 14, 2022'); - expect(tooltipValue).toEqual('Jul 14, 2022, 11:50:45 AM'); + expect(component.row.obj).toBe('New Value'); }); }); diff --git a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts index cce35d794c..bffa987bad 100644 --- a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts +++ b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation, OnDestroy, Optional } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation, OnDestroy, inject } from '@angular/core'; import { DataColumn } from '../../data/data-column.model'; import { DataRow } from '../../data/data-row.model'; import { DataTableAdapter } from '../../data/datatable-adapter'; @@ -58,8 +58,6 @@ export class DataTableCellComponent implements OnInit, OnDestroy { @Input() row: DataRow; - value$ = new BehaviorSubject(''); - /** Enables/disables a Clipboard directive to allow copying of the cell's content. */ @Input() copyContent: boolean; @@ -73,8 +71,9 @@ export class DataTableCellComponent implements OnInit, OnDestroy { resolverFn: (row: DataRow, col: DataColumn) => any = null; protected onDestroy$ = new Subject(); + protected dataTableService = inject(DataTableService, { optional: true }); - constructor(@Optional() protected dataTableService: DataTableService) {} + value$ = new BehaviorSubject(''); ngOnInit() { this.updateValue(); @@ -97,6 +96,7 @@ export class DataTableCellComponent implements OnInit, OnDestroy { if (!this.dataTableService || !this.row.obj) { return; } + this.dataTableService.rowUpdate.pipe(takeUntil(this.onDestroy$)).subscribe((data) => { if (data?.id === this.row?.id && data.obj) { this.row.obj = data.obj; diff --git a/lib/core/src/lib/datatable/components/datatable/datatable.component.html b/lib/core/src/lib/datatable/components/datatable/datatable.component.html index d8faac9c53..3f5b576773 100644 --- a/lib/core/src/lib/datatable/components/datatable/datatable.component.html +++ b/lib/core/src/lib/datatable/components/datatable/datatable.component.html @@ -253,7 +253,8 @@ [column]="col" [row]="row" [resolverFn]="resolverFn" - [tooltip]="getCellTooltip(row, col)"> + [tooltip]="getCellTooltip(row, col)" + [dateConfig]="col.dateConfig">
{ expect(rows[1].getValue('fuel_consumption')).toBe(6); expect(rows[2].getValue('fuel_consumption')).toBe(4.9); }); + + it('should be able to display column of type date', () => { + dataTable.data = new ObjectDataTableAdapter(mockCarsData, mockCarsSchemaDefinition); + + fixture.detectChanges(); + const rows = dataTable.data.getRows(); + + expect(rows[0].getValue('production_start')).toBe('1972-04-23'); + expect(rows[1].getValue('production_start')).toBe('1998-06-25T12:25:20'); + expect(rows[2].getValue('production_start')).toBe('2004-02-10T12:25:43.511Z'); + }); }); describe('Accesibility', () => { diff --git a/lib/core/src/lib/datatable/components/date-cell/date-cell.component.html b/lib/core/src/lib/datatable/components/date-cell/date-cell.component.html new file mode 100644 index 0000000000..6dac741a32 --- /dev/null +++ b/lib/core/src/lib/datatable/components/date-cell/date-cell.component.html @@ -0,0 +1,15 @@ + + + {{ date | adfTimeAgo: config.locale }} + + + + {{ date | adfLocalizedDate: format: config.locale }} + + + diff --git a/lib/core/src/lib/datatable/components/date-cell/date-cell.component.spec.ts b/lib/core/src/lib/datatable/components/date-cell/date-cell.component.spec.ts new file mode 100644 index 0000000000..66fa6919ac --- /dev/null +++ b/lib/core/src/lib/datatable/components/date-cell/date-cell.component.spec.ts @@ -0,0 +1,235 @@ +/*! + * @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 { DateCellComponent } from './date-cell.component'; +import { DataColumn, DateConfig } from '../../data/data-column.model'; +import { BehaviorSubject } from 'rxjs'; +import { AppConfigService } from '../../../app-config'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { LOCALE_ID } from '@angular/core'; +import { registerLocaleData } from '@angular/common'; +import localePL from '@angular/common/locales/pl'; + +let component: DateCellComponent; +let appConfigService: AppConfigService; +let fixture: ComponentFixture; + +const mockDate = new Date('2023-10-25'); +const mockTooltip = mockDate.toISOString(); +const mockColumn: DataColumn = { + key: 'mock-date', + type: 'date', + format: 'full' +}; + +const renderDateCell = (dateConfig: DateConfig, value: number | string | Date, tooltip: string) => { + component.value$ = new BehaviorSubject(value); + component.dateConfig = dateConfig; + component.tooltip = tooltip; + + fixture.detectChanges(); +}; + +const checkDisplayedDate = (expectedDate: string) => { + const displayedDate = fixture.nativeElement.querySelector('span').textContent.trim(); + + expect(displayedDate).toBeTruthy(); + expect(displayedDate).toBe(expectedDate); +}; + +const checkDisplayedTooltip = (expectedTooltip: string) => { + const displayedTooltip = fixture.nativeElement.querySelector('span').title; + + expect(displayedTooltip).toBeTruthy(); + expect(displayedTooltip).toBe(expectedTooltip); +}; + +const configureTestingModule = (providers: any[]) => { + TestBed.configureTestingModule({ + imports: [ + DateCellComponent, + HttpClientTestingModule, + TranslateModule.forRoot() + ], + providers + }); + fixture = TestBed.createComponent(DateCellComponent); + component = fixture.componentInstance; + + appConfigService = TestBed.inject(AppConfigService); + + appConfigService.config = { + dateValues: { + defaultDateFormat: 'mediumDate', + defaultTooltipDateFormat: 'long', + defaultLocale: 'en-US' + } + }; +}; + +describe('DateCellComponent', () => { + beforeEach(() => { + configureTestingModule([]); + }); + + it('should set default date config', () => { + expect(component.defaultDateConfig.format).toBe('medium'); + expect(component.defaultDateConfig.tooltipFormat).toBe('medium'); + expect(component.defaultDateConfig.locale).toBeUndefined(); + }); + + it('should display date and tooltip with provided config', () => { + const mockDateConfig: DateConfig = { + format: 'short', + tooltipFormat: 'shortDate' + }; + + const expectedDate = '10/25/23, 12:00 AM'; + const expectedTooltip = '10/25/23'; + + renderDateCell(mockDateConfig, mockDate, mockTooltip); + checkDisplayedDate(expectedDate); + checkDisplayedTooltip(expectedTooltip); + }); + + it('should display date and tooltip with based on appConfig values if dateConfig is NOT provided', () => { + const mockDateConfig: DateConfig = {}; + + const expectedDate = 'Oct 25, 2023'; + const expectedTooltip = 'October 25, 2023 at 12:00:00 AM GMT+0'; + + renderDateCell(mockDateConfig, mockDate, mockTooltip); + checkDisplayedDate(expectedDate); + checkDisplayedTooltip(expectedTooltip); + + expect(component.config.format).toEqual('mediumDate'); + expect(component.config.tooltipFormat).toEqual('long'); + expect(component.config.locale).toEqual('en-US'); + }); + + it('should display date and tooltip with defaules values if NO dateConfig or appConfig is provided', () => { + appConfigService.config = { + dateValues: {} + }; + const mockDateConfig: DateConfig = {}; + + const expectedDate = 'Oct 25, 2023, 12:00:00 AM'; + const expectedTooltip = expectedDate; + + renderDateCell(mockDateConfig, mockDate, mockTooltip); + checkDisplayedDate(expectedDate); + checkDisplayedTooltip(expectedTooltip); + }); + + it('should display date with timeAgo format', () => { + const mockDateConfig: DateConfig = { + format: 'timeAgo' + }; + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(today.getDate() - 1); + + const expectedDate = '1 day ago'; + + renderDateCell(mockDateConfig, yesterday, mockTooltip); + checkDisplayedDate(expectedDate); + }); + + it('should display date with timeAgo format if NO dateConfig and column format provided', () => { + component.column = { ...mockColumn, format: 'timeAgo' }; + const mockDateConfig = undefined as any; + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(today.getDate() - 1); + + const expectedDate = '1 day ago'; + renderDateCell(mockDateConfig, yesterday, mockTooltip); + checkDisplayedDate(expectedDate); + }); + + it('should display date with column format if dateConfig format is not provided', () => { + component.column = mockColumn; + const mockDateConfig: DateConfig = { + tooltipFormat: 'short' + }; + + const expectedDate = 'Wednesday, October 25, 2023 at 12:00:00 AM GMT+00:00'; + + renderDateCell(mockDateConfig, mockDate, mockTooltip); + checkDisplayedDate(expectedDate); + }); + + it('should display date and override dateConfig by column format if is provided', () => { + component.column = mockColumn; + const mockDateConfig: DateConfig = { + format: 'short' + }; + + const expectedDate = 'Wednesday, October 25, 2023 at 12:00:00 AM GMT+00:00'; + + renderDateCell(mockDateConfig, mockDate, mockTooltip); + checkDisplayedDate(expectedDate); + }); + + it('should display date based on string', () => { + const mockDateConfig: DateConfig = { + format: 'short', + tooltipFormat: 'short' + }; + const mockStringDate = 'Oct 25, 2023'; + + const expectedDate = '10/25/23, 12:00 AM'; + + renderDateCell(mockDateConfig, mockStringDate, mockTooltip); + checkDisplayedDate(expectedDate); + }); + + it('should display date based on timestamp', () => { + const mockDateConfig: DateConfig = { + format: 'short', + tooltipFormat: 'short' + }; + const mockTimestamp = Date.parse('Oct 25, 2023'); + + const expectedDate = '10/25/23, 12:00 AM'; + + renderDateCell(mockDateConfig, mockTimestamp, mockTooltip); + checkDisplayedDate(expectedDate); + }); +}); + +describe('DateCellComponent locale', () => { + it('should display date and tooltip with custom locale', () => { + configureTestingModule([{ provide: LOCALE_ID, useValue: 'pl-PL' }]); + registerLocaleData(localePL); + + const mockDateConfig: DateConfig = { + format: 'short', + tooltipFormat: 'medium', + locale: 'pl-PL' + }; + + const expectedDate = '25.10.2023, 00:00'; + const expectedTooltip = '25 paź 2023, 00:00:00'; + + renderDateCell(mockDateConfig, mockDate, mockTooltip); + checkDisplayedDate(expectedDate); + checkDisplayedTooltip(expectedTooltip); + }); +}); diff --git a/lib/core/src/lib/datatable/components/date-cell/date-cell.component.ts b/lib/core/src/lib/datatable/components/date-cell/date-cell.component.ts index a0477d23e2..e91c07bd43 100644 --- a/lib/core/src/lib/datatable/components/date-cell/date-cell.component.ts +++ b/lib/core/src/lib/datatable/components/date-cell/date-cell.component.ts @@ -15,67 +15,52 @@ * limitations under the License. */ -import { Component, Optional, ViewEncapsulation } from '@angular/core'; +import { Component, Input, OnInit, ViewEncapsulation, inject } from '@angular/core'; import { DataTableCellComponent } from '../datatable-cell/datatable-cell.component'; -import { - UserPreferencesService, - UserPreferenceValues -} from '../../../common/services/user-preferences.service'; import { AppConfigService } from '../../../app-config/app-config.service'; -import { takeUntil } from 'rxjs/operators'; -import { DataTableService } from '../../services/datatable.service'; +import { DateConfig } from '../../data/data-column.model'; +import { CommonModule } from '@angular/common'; +import { LocalizedDatePipe, TimeAgoPipe } from '../../../pipes'; @Component({ + standalone: true, + imports: [CommonModule, LocalizedDatePipe, TimeAgoPipe], selector: 'adf-date-cell', - template: ` - - - {{ value$ | async | adfTimeAgo: currentLocale }} - - - - - {{ value$ | async | adfLocalizedDate: format }} - - - `, + templateUrl: './date-cell.component.html', encapsulation: ViewEncapsulation.None, - host: { class: 'adf-date-cell adf-datatable-content-cell' } + host: { class: 'adf-datatable-content-cell' } }) -export class DateCellComponent extends DataTableCellComponent { +export class DateCellComponent extends DataTableCellComponent implements OnInit { - static DATE_FORMAT = 'medium'; + @Input() + dateConfig: DateConfig; - currentLocale: string; - dateFormat: string; - tooltipDateFormat: string; + config: DateConfig = {}; + + private readonly appConfig: AppConfigService = inject(AppConfigService); + + readonly defaultDateConfig: DateConfig = { + format: 'medium', + tooltipFormat: 'medium', + locale: undefined + }; + + ngOnInit(): void { + super.ngOnInit(); + this.setConfig(); + } get format(): string { - if (this.column) { - return this.column.format || this.dateFormat; - } - return this.dateFormat; + return this.column?.format ?? this.config.format; } - constructor( - userPreferenceService: UserPreferencesService, - @Optional() dataTableService: DataTableService, - appConfig: AppConfigService - ) { - super(dataTableService); + private setConfig(): void { + this.config.format = this.dateConfig?.format || this.getAppConfigPropertyValue('dateValues.defaultDateFormat', this.defaultDateConfig.format); + this.config.tooltipFormat = this.dateConfig?.tooltipFormat || this.getAppConfigPropertyValue('dateValues.defaultTooltipDateFormat', this.defaultDateConfig.tooltipFormat); + this.config.locale = this.dateConfig?.locale || this.getAppConfigPropertyValue('dateValues.defaultLocale', this.defaultDateConfig.locale); + } - this.dateFormat = appConfig.get('dateValues.defaultDateFormat', DateCellComponent.DATE_FORMAT); - this.tooltipDateFormat = appConfig.get('dateValues.defaultTooltipDateFormat', DateCellComponent.DATE_FORMAT); - if (userPreferenceService) { - userPreferenceService - .select(UserPreferenceValues.Locale) - .pipe(takeUntil(this.onDestroy$)) - .subscribe(locale => this.currentLocale = locale); - } + private getAppConfigPropertyValue(key: string, defaultValue: string): string { + return this.appConfig.get(key, defaultValue); } } diff --git a/lib/core/src/lib/datatable/components/filesize-cell/filesize-cell.component.ts b/lib/core/src/lib/datatable/components/filesize-cell/filesize-cell.component.ts index 4ca035459e..01f82009cf 100644 --- a/lib/core/src/lib/datatable/components/filesize-cell/filesize-cell.component.ts +++ b/lib/core/src/lib/datatable/components/filesize-cell/filesize-cell.component.ts @@ -15,25 +15,22 @@ * limitations under the License. */ -import { Component, Optional, ViewEncapsulation } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { DataTableCellComponent } from '../datatable-cell/datatable-cell.component'; -import { DataTableService } from '../../services/datatable.service'; @Component({ selector: 'adf-filesize-cell', template: ` - {{ fileSize }} + {{ fileSize }} `, encapsulation: ViewEncapsulation.None, host: { class: 'adf-filesize-cell' } }) -export class FileSizeCellComponent extends DataTableCellComponent { - constructor(@Optional() dataTableService: DataTableService) { - super(dataTableService); +export class FileSizeCellComponent extends DataTableCellComponent implements OnInit { + + ngOnInit(): void { + super.ngOnInit(); } } diff --git a/lib/core/src/lib/datatable/components/json-cell/json-cell.component.ts b/lib/core/src/lib/datatable/components/json-cell/json-cell.component.ts index 39b24a7448..4261aab84b 100644 --- a/lib/core/src/lib/datatable/components/json-cell/json-cell.component.ts +++ b/lib/core/src/lib/datatable/components/json-cell/json-cell.component.ts @@ -15,11 +15,10 @@ * limitations under the License. */ -import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation, Input, Optional } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation, Input } from '@angular/core'; import { DataTableCellComponent } from '../datatable-cell/datatable-cell.component'; import { MatDialog } from '@angular/material/dialog'; import { EditJsonDialogComponent, EditJsonDialogSettings } from '../../../dialogs/edit-json/edit-json.dialog'; -import { DataTableService } from '../../services/datatable.service'; @Component({ selector: 'adf-json-cell', @@ -42,14 +41,12 @@ export class JsonCellComponent extends DataTableCellComponent implements OnInit @Input() editable: boolean = false; - constructor(private dialog: MatDialog, @Optional() dataTableService: DataTableService) { - super(dataTableService); + constructor(private dialog: MatDialog) { + super(); } ngOnInit() { - if (this.column?.key && this.row && this.data) { - this.value$.next(this.data.getValue(this.row, this.column, this.resolverFn)); - } + super.ngOnInit(); } view() { diff --git a/lib/core/src/lib/datatable/components/location-cell/location-cell.component.ts b/lib/core/src/lib/datatable/components/location-cell/location-cell.component.ts index 7fdcfb7ec7..e82f1d3aad 100644 --- a/lib/core/src/lib/datatable/components/location-cell/location-cell.component.ts +++ b/lib/core/src/lib/datatable/components/location-cell/location-cell.component.ts @@ -15,9 +15,8 @@ * limitations under the License. */ -import { ChangeDetectionStrategy, Component, Input, OnInit, Optional, ViewEncapsulation } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; import { DataTableCellComponent } from '../datatable-cell/datatable-cell.component'; -import { DataTableService } from '../../services/datatable.service'; import { AsyncPipe } from '@angular/common'; import { RouterModule } from '@angular/router'; import { PathInfo } from '@alfresco/js-api'; @@ -41,10 +40,6 @@ export class LocationCellComponent extends DataTableCellComponent implements OnI @Input() link: any[]; - constructor(@Optional() dataTableService: DataTableService) { - super(dataTableService); - } - ngOnInit() { super.ngOnInit(); } diff --git a/lib/core/src/lib/datatable/components/number-cell/number-cell.component.ts b/lib/core/src/lib/datatable/components/number-cell/number-cell.component.ts index ad1457f982..66ace781c3 100644 --- a/lib/core/src/lib/datatable/components/number-cell/number-cell.component.ts +++ b/lib/core/src/lib/datatable/components/number-cell/number-cell.component.ts @@ -20,11 +20,9 @@ import { Component, ViewEncapsulation, Input, - Optional, OnInit } from '@angular/core'; import { DataTableCellComponent } from '../datatable-cell/datatable-cell.component'; -import { DataTableService } from '../../services/datatable.service'; import { DecimalConfig } from '../../data/data-column.model'; import { CommonModule } from '@angular/common'; @@ -47,13 +45,7 @@ export class NumberCellComponent extends DataTableCellComponent implements OnIni locale: undefined }; - constructor(@Optional() dataTableService: DataTableService) { - super(dataTableService); - } - ngOnInit() { - if (this.column?.key && this.row && this.data) { - this.value$.next(this.data.getValue(this.row, this.column, this.resolverFn)); - } + super.ngOnInit(); } } diff --git a/lib/core/src/lib/datatable/data/data-column.model.ts b/lib/core/src/lib/datatable/data/data-column.model.ts index 4ff80f0bff..974ab94cd5 100644 --- a/lib/core/src/lib/datatable/data/data-column.model.ts +++ b/lib/core/src/lib/datatable/data/data-column.model.ts @@ -43,12 +43,23 @@ export interface DataColumn { order?: number; currencyConfig?: CurrencyConfig; decimalConfig?: DecimalConfig; + dateConfig?: DateConfig; } -export interface DecimalConfig { - digitsInfo?: string; + +export interface LocaleConfig { locale?: string; } + +export interface DecimalConfig extends LocaleConfig { + digitsInfo?: string; +} + export interface CurrencyConfig extends DecimalConfig { code?: string; display?: string; } + +export interface DateConfig extends LocaleConfig { + format?: string; + tooltipFormat?: string; +} diff --git a/lib/core/src/lib/datatable/data/object-datacolumn.model.ts b/lib/core/src/lib/datatable/data/object-datacolumn.model.ts index deeacc5363..c9bf0d6296 100644 --- a/lib/core/src/lib/datatable/data/object-datacolumn.model.ts +++ b/lib/core/src/lib/datatable/data/object-datacolumn.model.ts @@ -17,7 +17,7 @@ import { TemplateRef } from '@angular/core'; import { DataColumnType } from '@alfresco/adf-extensions'; -import { CurrencyConfig, DataColumn, DecimalConfig } from './data-column.model'; +import { CurrencyConfig, DataColumn, DecimalConfig, DateConfig } from './data-column.model'; // Simple implementation of the DataColumn interface. export class ObjectDataColumn implements DataColumn { @@ -41,6 +41,7 @@ export class ObjectDataColumn implements DataColumn { order?: number; currencyConfig?: CurrencyConfig; decimalConfig?: DecimalConfig; + dateConfig?: DateConfig; constructor(input: any) { this.id = input.id ?? ''; @@ -63,5 +64,6 @@ export class ObjectDataColumn implements DataColumn { this.order = input.order; this.currencyConfig = input.currencyConfig; this.decimalConfig = input.decimalConfig; + this.dateConfig = input.dateConfig; } } diff --git a/lib/core/src/lib/datatable/datatable.module.ts b/lib/core/src/lib/datatable/datatable.module.ts index f3c21c5201..1808142d9d 100644 --- a/lib/core/src/lib/datatable/datatable.module.ts +++ b/lib/core/src/lib/datatable/datatable.module.ts @@ -56,6 +56,7 @@ import { DataColumnModule } from './data-column/data-column.module'; import { BooleanCellComponent } from './components/boolean-cell/boolean-cell.component'; import { AmountCellComponent } from './components/amount-cell/amount-cell.component'; import { NumberCellComponent } from './components/number-cell/number-cell.component'; +import { LocalizedDatePipe } from '../pipes'; @NgModule({ imports: [ @@ -76,7 +77,9 @@ import { NumberCellComponent } from './components/number-cell/number-cell.compon BooleanCellComponent, AmountCellComponent, NumberCellComponent, - LocationCellComponent + LocationCellComponent, + DateCellComponent, + LocalizedDatePipe ], declarations: [ DataTableComponent, @@ -86,7 +89,6 @@ import { NumberCellComponent } from './components/number-cell/number-cell.compon EmptyListFooterDirective, DataTableCellComponent, DataTableRowComponent, - DateCellComponent, FileSizeCellComponent, JsonCellComponent, ColumnsSelectorComponent, @@ -108,7 +110,6 @@ import { NumberCellComponent } from './components/number-cell/number-cell.compon EmptyListFooterDirective, DataTableCellComponent, DataTableRowComponent, - DateCellComponent, ColumnsSelectorComponent, FileSizeCellComponent, JsonCellComponent, diff --git a/lib/core/src/lib/pipes/localized-date.pipe.ts b/lib/core/src/lib/pipes/localized-date.pipe.ts index d9e22dba44..49b21cfd7b 100644 --- a/lib/core/src/lib/pipes/localized-date.pipe.ts +++ b/lib/core/src/lib/pipes/localized-date.pipe.ts @@ -23,6 +23,7 @@ import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @Pipe({ + standalone: true, name: 'adfLocalizedDate', pure: false }) diff --git a/lib/core/src/lib/pipes/pipe.module.ts b/lib/core/src/lib/pipes/pipe.module.ts index 29bbf9a156..5719ce2e6f 100644 --- a/lib/core/src/lib/pipes/pipe.module.ts +++ b/lib/core/src/lib/pipes/pipe.module.ts @@ -40,19 +40,19 @@ import { DateTimePipe } from './date-time.pipe'; @NgModule({ imports: [ CommonModule, - TranslateModule + TranslateModule, + LocalizedDatePipe, + TimeAgoPipe ], declarations: [ FileSizePipe, HighlightPipe, - TimeAgoPipe, MimeTypeIconPipe, InitialUsernamePipe, FullNamePipe, FormatSpacePipe, FileTypePipe, MultiValuePipe, - LocalizedDatePipe, DecimalNumberPipe, LocalizedRolePipe, MomentDatePipe, @@ -70,14 +70,14 @@ import { DateTimePipe } from './date-time.pipe'; FormatSpacePipe, FileTypePipe, MultiValuePipe, - LocalizedDatePipe, DecimalNumberPipe, LocalizedRolePipe, MomentDatePipe, MomentDateTimePipe, DateTimePipe, FilterStringPipe, - FilterOutArrayObjectsByPropPipe + FilterOutArrayObjectsByPropPipe, + LocalizedDatePipe ], exports: [ FileSizePipe, @@ -89,7 +89,6 @@ import { DateTimePipe } from './date-time.pipe'; FormatSpacePipe, FileTypePipe, MultiValuePipe, - LocalizedDatePipe, DecimalNumberPipe, LocalizedRolePipe, MomentDatePipe, diff --git a/lib/core/src/lib/pipes/time-ago.pipe.ts b/lib/core/src/lib/pipes/time-ago.pipe.ts index 1a4e310822..9119a18d6f 100644 --- a/lib/core/src/lib/pipes/time-ago.pipe.ts +++ b/lib/core/src/lib/pipes/time-ago.pipe.ts @@ -25,6 +25,7 @@ import { differenceInDays, formatDistance } from 'date-fns'; import * as Locales from 'date-fns/locale'; @Pipe({ + standalone: true, name: 'adfTimeAgo' }) export class TimeAgoPipe implements PipeTransform, OnDestroy { diff --git a/lib/extensions/src/lib/config/document-list.extensions.ts b/lib/extensions/src/lib/config/document-list.extensions.ts index 5df4bcd020..635da5b1ce 100644 --- a/lib/extensions/src/lib/config/document-list.extensions.ts +++ b/lib/extensions/src/lib/config/document-list.extensions.ts @@ -35,18 +35,18 @@ export interface DataColumnTypes { export type DataColumnType = keyof DataColumnTypes; export interface DocumentListPresetRef extends ExtensionElement { - key: string; - type: DataColumnType; - title?: string; - format?: string; - class?: string; - sortable: boolean; - template: string; - desktopOnly: boolean; - sortingKey: string; - isHidden?: boolean; - rules?: { - [key: string]: string; - visible?: string; - }; + key: string; + type: DataColumnType; + title?: string; + format?: string; + class?: string; + sortable: boolean; + template: string; + desktopOnly: boolean; + sortingKey: string; + isHidden?: boolean; + rules?: { + [key: string]: string; + visible?: string; + }; }