geonetwork-ui 2.10.3-dev.4154db07d → 2.11.0-dev.4976ded4f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1473 -811
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +230 -82
  4. package/index.d.ts.map +1 -1
  5. package/package.json +8 -8
  6. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +5 -5
  7. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +5 -5
  8. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  9. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  10. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +2 -2
  11. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +9 -5
  12. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/association-type.mapper.ts +12 -0
  13. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +2 -2
  14. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +70 -10
  15. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  16. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +40 -2
  17. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +13 -1
  18. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  19. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  20. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  21. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  22. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  23. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.css +0 -0
  24. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  25. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  26. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  27. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +6 -0
  28. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +6 -0
  29. package/src/libs/feature/editor/src/lib/fields.config.ts +5 -0
  30. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  31. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  32. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  33. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  34. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  35. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  36. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  37. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  38. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  39. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +9 -4
  40. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +46 -19
  41. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +17 -8
  42. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  43. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
  44. package/src/libs/feature/search/src/lib/utils/service/fields.ts +27 -2
  45. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  46. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  47. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  48. package/src/libs/ui/inputs/src/index.ts +2 -0
  49. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  50. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  51. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  52. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  53. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  54. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  55. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  56. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  57. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  58. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  59. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  60. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  61. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
  62. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  63. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  64. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  65. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  66. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  67. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  68. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  69. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  70. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  71. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  72. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  73. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  74. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  75. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  76. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  77. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  78. package/src/libs/util/shared/src/index.ts +1 -0
  79. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  80. package/src/libs/util/shared/src/lib/gn-ui-version.ts +1 -1
  81. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  82. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  83. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  84. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  85. package/tailwind.base.css +5 -0
  86. package/translations/de.json +26 -3
  87. package/translations/en.json +25 -2
  88. package/translations/es.json +23 -0
  89. package/translations/fr.json +26 -3
  90. package/translations/it.json +24 -1
  91. package/translations/nl.json +23 -0
  92. package/translations/pt.json +23 -0
  93. package/translations/sk.json +24 -1
  94. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -1,5 +1,5 @@
1
1
  import { Component, Input, OnInit, inject } from '@angular/core'
2
- import { CommonModule, DatePipe } from '@angular/common'
2
+ import { CommonModule } from '@angular/common'
3
3
  import {
4
4
  catchError,
5
5
  firstValueFrom,
@@ -15,12 +15,14 @@ import { FieldType, FieldValue } from '../utils/service/fields'
15
15
  import { SearchFacade } from '../state/search.facade'
16
16
  import { SearchService } from '../utils/service/search.service'
17
17
  import { FieldsService } from '../utils/service/fields.service'
18
- import { formatUserInfo } from '../../../../../../libs/util/shared/src'
18
+ import { DateService, formatUserInfo } from '../../../../../../libs/util/shared/src'
19
19
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
20
20
 
21
21
  marker('search.filters.summaryLabel.user')
22
22
  marker('search.filters.summaryLabel.changeDate')
23
23
 
24
+ const OPEN_BOUND = '…'
25
+
24
26
  interface DisplayedValue {
25
27
  label: string
26
28
  value: FieldValue | DateRange
@@ -32,13 +34,12 @@ interface DisplayedValue {
32
34
  imports: [CommonModule, BadgeComponent],
33
35
  templateUrl: './search-filters-summary-item.component.html',
34
36
  styleUrls: ['./search-filters-summary-item.component.css'],
35
- providers: [DatePipe],
36
37
  })
37
38
  export class SearchFiltersSummaryItemComponent implements OnInit {
38
39
  private searchFacade = inject(SearchFacade)
39
40
  private searchService = inject(SearchService)
40
41
  private fieldsService = inject(FieldsService)
41
- private datePipe = inject(DatePipe)
42
+ private dateService = inject(DateService)
42
43
  private translate = inject(TranslateService)
43
44
 
44
45
  @Input() fieldName: string
@@ -82,12 +83,10 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
82
83
  getReadableValues(fieldValues: FieldValue[] | DateRange[]): DisplayedValue[] {
83
84
  return fieldValues.map((value) => {
84
85
  if (this.fieldType === 'dateRange') {
86
+ const { start, end } = value as DateRange
85
87
  return {
86
88
  value,
87
- label: `${this.datePipe.transform(
88
- value.start,
89
- 'dd.MM.yyyy'
90
- )} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
89
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
91
90
  }
92
91
  } else if (this.fieldName === 'user') {
93
92
  return { value, label: formatUserInfo(value) }
@@ -97,6 +96,16 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
97
96
  })
98
97
  }
99
98
 
99
+ private formatBound(date: Date | undefined): string {
100
+ return date
101
+ ? this.dateService.formatDate(date, {
102
+ day: '2-digit',
103
+ month: '2-digit',
104
+ year: 'numeric',
105
+ })
106
+ : OPEN_BOUND
107
+ }
108
+
100
109
  async removeFilterValue(fieldValue: FieldValue | DateRange) {
101
110
  const currentFieldValues: DisplayedValue[] = await firstValueFrom(
102
111
  this.fieldValues$
@@ -34,7 +34,7 @@ export class SortByComponent implements OnInit {
34
34
  },
35
35
  {
36
36
  label: marker('results.sortBy.dateStamp'),
37
- value: SortByEnum.RESOURCE_DATES,
37
+ value: SortByEnum.RESOURCE_DATE,
38
38
  },
39
39
  {
40
40
  label: marker('results.sortBy.popularity'),
@@ -2,6 +2,7 @@ import { Injectable, Injector, inject } from '@angular/core'
2
2
  import {
3
3
  AbstractSearchField,
4
4
  AvailableServicesField,
5
+ BoundingBoxSearchField,
5
6
  DateRangeSearchField,
6
7
  FieldValue,
7
8
  FullTextSearchField,
@@ -42,7 +43,7 @@ marker('search.filters.producerOrg')
42
43
  marker('search.filters.publisherOrg')
43
44
  marker('search.filters.user')
44
45
  marker('search.filters.changeDate')
45
-
46
+ marker('search.filters.spatialExtent')
46
47
  @Injectable({
47
48
  providedIn: 'root',
48
49
  })
@@ -95,6 +96,7 @@ export class FieldsService {
95
96
  user: new UserSearchField(this.injector),
96
97
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
97
98
  availableServices: new AvailableServicesField(this.injector),
99
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
98
100
  } as Record<string, AbstractSearchField>
99
101
 
100
102
  get supportedFields() {
@@ -20,10 +20,14 @@ import {
20
20
  isDateRange,
21
21
  METADATA_LANGUAGE,
22
22
  } from '../../../../../../../libs/api/repository/src'
23
- import { formatUserInfo } from '../../../../../../../libs/util/shared/src'
23
+ import {
24
+ BoundingBox,
25
+ formatUserInfo,
26
+ isBoundingBox,
27
+ } from '../../../../../../../libs/util/shared/src'
24
28
  import { PossibleResourceTypes } from '../../../../../../../libs/api/metadata-converter/src'
25
29
 
26
- export type FieldType = 'values' | 'dateRange'
30
+ export type FieldType = 'values' | 'dateRange' | 'spatialExtent'
27
31
 
28
32
  export type FieldValue = string | number
29
33
  export interface FieldAvailableValue {
@@ -431,6 +435,27 @@ export class DateRangeSearchField extends SimpleSearchField {
431
435
  }
432
436
  }
433
437
 
438
+ export class BoundingBoxSearchField extends SimpleSearchField {
439
+ getAvailableValues(): Observable<FieldAvailableValue[]> {
440
+ return of([])
441
+ }
442
+
443
+ getFiltersForValues(values: FieldValue[]): Observable<FieldFilters> {
444
+ return of({
445
+ [this.esFieldName]: values.map(Number) as BoundingBox,
446
+ })
447
+ }
448
+
449
+ getValuesForFilter(filters: FieldFilters): Observable<FieldValue[]> {
450
+ const filter = filters[this.esFieldName]
451
+ return of(isBoundingBox(filter) ? filter : [])
452
+ }
453
+
454
+ getType(): FieldType {
455
+ return 'spatialExtent'
456
+ }
457
+ }
458
+
434
459
  marker('search.filters.availableServices.view')
435
460
  marker('search.filters.availableServices.download')
436
461
 
@@ -6,33 +6,32 @@
6
6
  matSort
7
7
  (matSortChange)="setSort($event)"
8
8
  [matSortDisableClear]="true"
9
- *ngrxLet="properties$ as properties"
10
9
  >
11
- @for (attr of _featureAttributes; track attr) {
12
- <ng-container [matColumnDef]="attr.value">
10
+ @for (column of columns; track column) {
11
+ <ng-container [matColumnDef]="column.name">
13
12
  <th
14
13
  mat-header-cell
15
14
  *matHeaderCellDef
16
15
  mat-sort-header
17
16
  class="text-sm text-black bg-white"
18
17
  >
19
- {{ attr.label }}
18
+ {{ column.label }}
20
19
  </th>
21
20
  <td
22
21
  mat-cell
23
22
  *matCellDef="let element"
24
23
  class="whitespace-nowrap pr-1 truncate"
25
24
  >
26
- {{ element[attr.value] }}
25
+ {{ element[column.name] }}
27
26
  </td>
28
27
  </ng-container>
29
28
  }
30
29
 
31
- <tr mat-header-row *matHeaderRowDef="properties; sticky: true"></tr>
30
+ <tr mat-header-row *matHeaderRowDef="columnNames; sticky: true"></tr>
32
31
  <tr
33
32
  [id]="getRowEltId(row.id)"
34
33
  mat-row
35
- *matRowDef="let row; columns: properties"
34
+ *matRowDef="let row; columns: columnNames"
36
35
  (click)="selected.emit(row)"
37
36
  [class.active]="
38
37
  activeId !== undefined && activeId !== null && row.id === activeId
@@ -6,14 +6,14 @@ import {
6
6
  Component,
7
7
  ElementRef,
8
8
  EventEmitter,
9
+ inject,
9
10
  Input,
10
11
  OnChanges,
11
12
  OnInit,
12
13
  Output,
13
14
  ViewChild,
14
- inject,
15
15
  } from '@angular/core'
16
- import { MatSort, MatSortModule } from '@angular/material/sort'
16
+ import { MatSort, MatSortModule, Sort } from '@angular/material/sort'
17
17
  import { MatTableModule } from '@angular/material/table'
18
18
  import {
19
19
  TranslateDirective,
@@ -29,12 +29,11 @@ import {
29
29
  } from '@angular/material/paginator'
30
30
  import { CustomMatPaginatorIntl } from './custom.mat.paginator.intl'
31
31
  import { CommonModule } from '@angular/common'
32
- import { BehaviorSubject, filter, firstValueFrom } from 'rxjs'
32
+ import { BehaviorSubject } from 'rxjs'
33
33
  import {
34
34
  LoadingMaskComponent,
35
35
  PopupAlertComponent,
36
36
  } from '../../../../../../libs/ui/widgets/src'
37
- import { LetDirective } from '@ngrx/component'
38
37
 
39
38
  const rowIdPrefix = 'table-item-'
40
39
 
@@ -46,6 +45,11 @@ export interface TableItemModel {
46
45
  [key: string]: TableItemType
47
46
  }
48
47
 
48
+ interface TableColumn {
49
+ name: string
50
+ label: string
51
+ }
52
+
49
53
  @Component({
50
54
  standalone: true,
51
55
  imports: [
@@ -56,7 +60,6 @@ export interface TableItemModel {
56
60
  CommonModule,
57
61
  LoadingMaskComponent,
58
62
  PopupAlertComponent,
59
- LetDirective,
60
63
  TranslatePipe,
61
64
  TranslateDirective,
62
65
  ],
@@ -71,15 +74,21 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
71
74
  private cdr = inject(ChangeDetectorRef)
72
75
  private translateService = inject(TranslateService)
73
76
 
74
- _featureAttributes = []
77
+ columnsFromFeatureCatalog: TableColumn[] = null
75
78
  @Input() set featureAttributes(value: { value: string; label: string }[]) {
76
- this._featureAttributes = value
77
- this.properties$.next(value.map((attr) => attr.value))
79
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
80
+ name: attrs.value,
81
+ label: attrs.label,
82
+ }))
78
83
  }
84
+ columnsFromDataset: TableColumn[] = []
79
85
  @Input() set dataset(value: BaseReader) {
80
86
  this.dataset_ = value
81
87
  this.dataset_.load()
82
- this.dataset_.info.then((info) => (this.count = info.itemsCount))
88
+ this.dataset_.info.then((info) => {
89
+ this.count = info.itemsCount
90
+ this.cdr.detectChanges()
91
+ })
83
92
  }
84
93
  @Input() activeId: TableItemId
85
94
  @Output() selected = new EventEmitter<any>()
@@ -88,13 +97,19 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
88
97
  @ViewChild(MatPaginator) paginator: MatPaginator
89
98
 
90
99
  dataset_: BaseReader
91
- properties$ = new BehaviorSubject<string[]>(null)
92
100
  dataSource: DataTableDataSource
93
101
  headerHeight: number
94
102
  count: number
95
103
  loading$ = new BehaviorSubject<boolean>(false)
96
104
  error = null
97
105
 
106
+ get columns() {
107
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset
108
+ }
109
+ get columnNames() {
110
+ return this.columns.map((c) => c.name)
111
+ }
112
+
98
113
  ngOnInit() {
99
114
  this.dataSource = new DataTableDataSource()
100
115
  }
@@ -110,7 +125,7 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
110
125
  this.setPagination()
111
126
  }
112
127
 
113
- setSort(sort: MatSort) {
128
+ setSort(sort: Sort) {
114
129
  if (!this.dataset_) return
115
130
  if (!sort.active) {
116
131
  this.dataset_.orderBy()
@@ -132,15 +147,12 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
132
147
 
133
148
  async readData() {
134
149
  this.loading$.next(true)
135
- // wait for properties to be read
136
- const properties = await firstValueFrom(
137
- this.properties$.pipe(filter((p) => !!p))
138
- )
139
- const propsWithoutGeom = properties.filter(
140
- (p) => !p.toLowerCase().startsWith('geom')
141
- )
142
- this.dataset_.select(...propsWithoutGeom)
143
150
  try {
151
+ // wait for properties to be read
152
+ if (!this.columnsFromFeatureCatalog) {
153
+ this.columnsFromDataset = await this.dataset_.properties
154
+ }
155
+ this.dataset_.select(...this.columnNames)
144
156
  await this.dataSource.showData(this.dataset_.read())
145
157
  this.error = null
146
158
  } catch (error) {
@@ -25,8 +25,8 @@ export const tableItemsFixture = {
25
25
  geometry: null,
26
26
  properties: {
27
27
  id: '0003',
28
- firstName: 'Claude',
29
- lastName: 'François',
28
+ firstName: 'Lady',
29
+ lastName: 'Gaga',
30
30
  },
31
31
  },
32
32
  ] as DataItem[],
@@ -4,6 +4,7 @@ export * from './lib/button/button.component'
4
4
  export * from './lib/check-toggle/check-toggle.component'
5
5
  export * from './lib/checkbox/checkbox.component'
6
6
  export * from './lib/copy-text-button/copy-text-button.component'
7
+ export * from './lib/date-adapter.providers'
7
8
  export * from './lib/date-picker/date-picker.component'
8
9
  export * from './lib/date-range-dropdown/date-range-dropdown.component'
9
10
  export * from './lib/date-range-picker/date-range-picker.component'
@@ -25,3 +26,4 @@ export * from './lib/url-input/url-input.component'
25
26
  export * from './lib/viewport-intersector/viewport-intersector.component'
26
27
  export * from './lib/search-feature-catalog/search-feature-catalog.component'
27
28
  export * from './lib/date-range-inputs/date-range-inputs.component'
29
+ export * from './lib/spatial-extent-dropdown/spatial-extent-dropdown.component'
@@ -0,0 +1,30 @@
1
+ import { inject, Provider } from '@angular/core'
2
+ import {
3
+ DateAdapter,
4
+ MAT_DATE_FORMATS,
5
+ MAT_DATE_LOCALE,
6
+ } from '@angular/material/core'
7
+ import {
8
+ DateFnsAdapter,
9
+ MAT_DATE_FNS_FORMATS,
10
+ } from '@angular/material-date-fns-adapter'
11
+ import { enUS } from 'date-fns/locale/en-US'
12
+ import { DateService } from '../../../../../libs/util/shared/src'
13
+
14
+ export function provideLocalizedDateAdapter(): Provider[] {
15
+ return [
16
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
17
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
18
+ {
19
+ provide: DateAdapter,
20
+ useFactory: () => {
21
+ const dateService = inject(DateService)
22
+ const adapter = new DateFnsAdapter()
23
+ dateService
24
+ .getDateFnsLocale()
25
+ .then((locale) => adapter.setLocale(locale))
26
+ return adapter
27
+ },
28
+ },
29
+ ]
30
+ }
@@ -2,15 +2,9 @@ import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
4
  EventEmitter,
5
- inject,
6
5
  Input,
7
6
  Output,
8
7
  } from '@angular/core'
9
- import {
10
- DateAdapter,
11
- MAT_DATE_LOCALE,
12
- MatNativeDateModule,
13
- } from '@angular/material/core'
14
8
  import { MatDatepickerModule } from '@angular/material/datepicker'
15
9
  import { ButtonComponent } from '../button/button.component'
16
10
  import {
@@ -19,7 +13,7 @@ import {
19
13
  provideNgIconsConfig,
20
14
  } from '@ng-icons/core'
21
15
  import { iconoirCalendar } from '@ng-icons/iconoir'
22
- import { TranslateService } from '@ngx-translate/core'
16
+ import { provideLocalizedDateAdapter } from '../date-adapter.providers'
23
17
 
24
18
  @Component({
25
19
  selector: 'gn-ui-date-picker',
@@ -27,31 +21,16 @@ import { TranslateService } from '@ngx-translate/core'
27
21
  styleUrls: ['./date-picker.component.css'],
28
22
  changeDetection: ChangeDetectionStrategy.OnPush,
29
23
  standalone: true,
30
- imports: [
31
- MatNativeDateModule,
32
- MatDatepickerModule,
33
- ButtonComponent,
34
- NgIconComponent,
35
- ],
24
+ imports: [MatDatepickerModule, ButtonComponent, NgIconComponent],
36
25
  providers: [
37
26
  provideIcons({ iconoirCalendar }),
38
27
  provideNgIconsConfig({
39
28
  size: '1.5rem',
40
29
  }),
41
- {
42
- provide: MAT_DATE_LOCALE,
43
- useFactory: (locale: string) => locale,
44
- },
30
+ provideLocalizedDateAdapter(),
45
31
  ],
46
32
  })
47
33
  export class DatePickerComponent {
48
- private dateAdapter = inject<DateAdapter<Date>>(DateAdapter)
49
- private translate = inject(TranslateService)
50
-
51
34
  @Input() date: Date
52
35
  @Output() dateChange = new EventEmitter<Date>()
53
-
54
- constructor() {
55
- this.dateAdapter.setLocale(this.translate.getCurrentLang())
56
- }
57
36
  }
@@ -1,5 +1,39 @@
1
- :host {
2
- .mat-date-range-input-container {
3
- display: none;
1
+ mat-calendar {
2
+ width: 100%;
3
+ }
4
+
5
+ .calendar {
6
+ display: grid;
7
+ grid-template-rows: 1fr;
8
+ }
9
+
10
+ .calendar > mat-calendar {
11
+ min-height: 0;
12
+ overflow: hidden;
13
+ }
14
+
15
+ .calendar.expand {
16
+ animation: open 200ms ease-out;
17
+ }
18
+
19
+ .calendar.collapse {
20
+ animation: open 200ms ease-in reverse;
21
+ }
22
+
23
+ @keyframes open {
24
+ from {
25
+ grid-template-rows: 0fr;
26
+ opacity: 0;
27
+ }
28
+ to {
29
+ grid-template-rows: 1fr;
30
+ opacity: 1;
31
+ }
32
+ }
33
+
34
+ @media (prefers-reduced-motion: reduce) {
35
+ .calendar.expand,
36
+ .calendar.collapse {
37
+ animation-duration: 1ms;
4
38
  }
5
39
  }
@@ -1,30 +1,149 @@
1
1
  <gn-ui-button
2
2
  type="outline"
3
3
  extraClass="bg-background w-full !p-[8px] !pl-[16px]"
4
- (buttonClick)="picker.open()"
4
+ [title]="title"
5
+ (buttonClick)="openOverlay()"
6
+ cdkOverlayOrigin
7
+ #overlayOrigin="cdkOverlayOrigin"
5
8
  >
6
9
  <div class="grow flex items-center mr-2 gap-2 overflow-hidden">
7
10
  <div class="text-left font-medium truncate py-1">
8
11
  {{ title }}
9
12
  </div>
13
+ @if (selectedDatesCount) {
14
+ <div
15
+ class="gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count"
16
+ >
17
+ {{ selectedDatesCount }}
18
+ </div>
19
+ }
10
20
  </div>
21
+ <button
22
+ class="h-6 w-6 flex items-center justify-center"
23
+ data-test="dropdown-clear"
24
+ >
25
+ @if (selectedDatesCount && !overlayOpen) {
26
+ <ng-icon
27
+ class="shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn"
28
+ (click)="clearDates($event)"
29
+ name="matClose"
30
+ ></ng-icon>
31
+ }
32
+ </button>
11
33
  <ng-icon
12
- [name]="isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'"
34
+ data-test="dropdown-toggle"
35
+ [name]="overlayOpen ? 'matExpandLess' : 'matExpandMore'"
13
36
  class="shrink-0 opacity-40"
14
37
  >
15
38
  </ng-icon>
16
39
  </gn-ui-button>
17
- <!--date range input is just present for output events and hidden from the DOM-->
18
- <mat-date-range-input [rangePicker]="picker">
19
- <input
20
- matStartDate
21
- [value]="startDate"
22
- (dateInput)="startDateChange.emit($event.value)"
23
- />
24
- <input
25
- matEndDate
26
- [value]="endDate"
27
- (dateInput)="endDateChange.emit($event.value)"
28
- />
29
- </mat-date-range-input>
30
- <mat-date-range-picker #picker></mat-date-range-picker>
40
+
41
+ <ng-template
42
+ cdkConnectedOverlay
43
+ cdkConnectedOverlayHasBackdrop
44
+ cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop"
45
+ [cdkConnectedOverlayOrigin]="overlayOrigin"
46
+ [cdkConnectedOverlayOpen]="overlayOpen"
47
+ [cdkConnectedOverlayPositions]="overlayPositions"
48
+ [cdkConnectedOverlayScrollStrategy]="scrollStrategy"
49
+ (overlayOutsideClick)="closeOverlay()"
50
+ (detach)="closeOverlay()"
51
+ >
52
+ <div
53
+ class="w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]"
54
+ data-test="date-range-panel"
55
+ >
56
+ <div class="flex flex-col justify-center border-b border-gray-300 pb-1">
57
+ <ng-container
58
+ [ngTemplateOutlet]="boundRow"
59
+ [ngTemplateOutletContext]="{
60
+ bound: 'start',
61
+ label: 'daterange.filter.from',
62
+ date: startDate,
63
+ }"
64
+ ></ng-container>
65
+ @if (expandedBound === 'start') {
66
+ <div class="calendar" animate.enter="expand" animate.leave="collapse">
67
+ <mat-calendar
68
+ data-test="start-date-calendar"
69
+ [selected]="startDate"
70
+ [startAt]="startDate ?? endDate"
71
+ [maxDate]="endDate"
72
+ (selectedChange)="setStartDate($event)"
73
+ ></mat-calendar>
74
+ </div>
75
+ }
76
+ </div>
77
+ <div class="flex flex-col justify-center">
78
+ <ng-container
79
+ [ngTemplateOutlet]="boundRow"
80
+ [ngTemplateOutletContext]="{
81
+ bound: 'end',
82
+ label: 'daterange.filter.to',
83
+ date: endDate,
84
+ }"
85
+ ></ng-container>
86
+ @if (expandedBound === 'end') {
87
+ <div class="calendar" animate.enter="expand" animate.leave="collapse">
88
+ <mat-calendar
89
+ data-test="end-date-calendar"
90
+ [selected]="endDate"
91
+ [startAt]="endDate ?? startDate"
92
+ [minDate]="startDate"
93
+ (selectedChange)="setEndDate($event)"
94
+ ></mat-calendar>
95
+ </div>
96
+ }
97
+ </div>
98
+ </div>
99
+ </ng-template>
100
+
101
+ <ng-template #boundRow let-bound="bound" let-label="label" let-date="date">
102
+ <div class="flex items-center gap-1 w-full">
103
+ <span class="w-[80px] shrink-0" [translate]="label"></span>
104
+ <div
105
+ class="flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors"
106
+ [class]="
107
+ invalidBounds[bound]
108
+ ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'
109
+ : 'border-gray-400 focus-within:border-main hover:border-main'
110
+ "
111
+ >
112
+ <ng-icon
113
+ name="iconoirCalendar"
114
+ class="shrink-0"
115
+ aria-hidden="true"
116
+ ></ng-icon>
117
+ <input
118
+ type="text"
119
+ inputmode="numeric"
120
+ autocomplete="off"
121
+ [gnUiAutofocus]="bound === expandedBound"
122
+ class="min-w-0 grow bg-transparent focus:outline-none"
123
+ [value]="date ? formatDate(date) : ''"
124
+ [placeholder]="'daterange.filter.datePlaceholder' | translate"
125
+ [attr.aria-label]="label | translate"
126
+ [attr.aria-invalid]="invalidBounds[bound] || null"
127
+ [attr.data-test]="bound + '-date-input'"
128
+ (change)="onDateInput(bound, $event)"
129
+ (blur)="normalizeDateInput(bound, $event)"
130
+ (click)="expandedBound !== bound && toggleBound(bound)"
131
+ />
132
+ </div>
133
+ <div class="shrink-0 flex items-center justify-end">
134
+ <gn-ui-button
135
+ type="light"
136
+ [style.--gn-ui-button-padding]="0"
137
+ [style.--gn-ui-button-width]="'24px'"
138
+ [style.--gn-ui-button-height]="'24px'"
139
+ [attr.aria-expanded]="expandedBound === bound"
140
+ [attr.data-test]="bound + '-date-toggle'"
141
+ (buttonClick)="toggleBound(bound)"
142
+ >
143
+ <ng-icon
144
+ [name]="expandedBound === bound ? 'matExpandLess' : 'matExpandMore'"
145
+ ></ng-icon>
146
+ </gn-ui-button>
147
+ </div>
148
+ </div>
149
+ </ng-template>