geonetwork-ui 2.11.0-dev.8442dae6e → 2.11.0-dev.9ba9c4d3d

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 (88) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1333 -691
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +228 -81
  4. package/index.d.ts.map +1 -1
  5. package/package.json +12 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  8. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +70 -10
  9. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  10. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +22 -0
  11. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +13 -1
  12. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  13. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  14. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  15. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  16. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  17. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.css +0 -0
  18. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.html +33 -0
  19. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-associated-records/form-field-associated-records.component.ts +94 -0
  20. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-temporal-extents/form-field-temporal-extents.component.html +16 -12
  21. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +6 -0
  22. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +6 -0
  23. package/src/libs/feature/editor/src/lib/fields.config.ts +5 -0
  24. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  25. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  26. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  27. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  28. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  29. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  30. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  31. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  32. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  33. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +9 -4
  34. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +46 -19
  35. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +17 -8
  36. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  37. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
  38. package/src/libs/feature/search/src/lib/utils/service/fields.ts +27 -2
  39. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  40. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  41. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  42. package/src/libs/ui/inputs/src/index.ts +2 -0
  43. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  44. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  45. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  46. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  47. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  48. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  49. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  50. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  51. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  52. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  53. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  54. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  55. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
  56. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +6 -5
  57. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  58. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  59. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  60. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  61. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  62. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  63. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  64. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  65. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  66. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  67. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  68. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  69. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  70. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  71. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  72. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  73. package/src/libs/util/shared/src/index.ts +1 -0
  74. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  75. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  76. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  77. package/src/libs/util/shared/src/lib/utils/geojson.ts +8 -0
  78. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  79. package/tailwind.base.css +5 -0
  80. package/translations/de.json +26 -3
  81. package/translations/en.json +25 -2
  82. package/translations/es.json +23 -0
  83. package/translations/fr.json +26 -3
  84. package/translations/it.json +24 -1
  85. package/translations/nl.json +23 -0
  86. package/translations/pt.json +23 -0
  87. package/translations/sk.json +24 -1
  88. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -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>
@@ -1,64 +1,201 @@
1
1
  import {
2
- AfterViewChecked,
2
+ ChangeDetectionStrategy,
3
3
  ChangeDetectorRef,
4
4
  Component,
5
- ElementRef,
6
5
  EventEmitter,
7
6
  Input,
8
7
  Output,
9
8
  ViewChild,
10
9
  inject,
11
10
  } from '@angular/core'
12
-
13
- import { MatNativeDateModule } from '@angular/material/core'
11
+ import { NgTemplateOutlet } from '@angular/common'
12
+ import {
13
+ CdkOverlayOrigin,
14
+ ConnectedPosition,
15
+ OverlayModule,
16
+ ScrollStrategyOptions,
17
+ } from '@angular/cdk/overlay'
18
+ import {
19
+ DateAdapter,
20
+ MAT_DATE_FORMATS,
21
+ MatDateFormats,
22
+ } from '@angular/material/core'
14
23
  import { MatDatepickerModule } from '@angular/material/datepicker'
24
+ import { type Locale } from 'date-fns/locale'
25
+ import { NgIcon, provideIcons, provideNgIconsConfig } from '@ng-icons/core'
26
+ import {
27
+ matClose,
28
+ matExpandLess,
29
+ matExpandMore,
30
+ } from '@ng-icons/material-icons/baseline'
31
+ import { iconoirCalendar } from '@ng-icons/iconoir'
32
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
33
+ import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
34
+ import { FieldFilterByRange } from '../../../../../../libs/common/domain/src/lib/model/search'
35
+ import {
36
+ AutofocusDirective,
37
+ propagateToDocumentOnly,
38
+ } from '../../../../../../libs/util/shared/src'
15
39
  import { ButtonComponent } from '../button/button.component'
16
- import { OverlayContainer } from '@angular/cdk/overlay'
17
- import { NgIconComponent, provideIcons } from '@ng-icons/core'
18
- import { matExpandLess, matExpandMore } from '@ng-icons/material-icons/baseline'
40
+ import { provideLocalizedDateAdapter } from '../date-adapter.providers'
41
+
42
+ export type DateRangeBound = 'start' | 'end'
19
43
 
20
44
  @Component({
21
45
  selector: 'gn-ui-date-range-dropdown',
22
46
  standalone: true,
23
47
  imports: [
24
- NgIconComponent,
25
- MatNativeDateModule,
26
- MatDatepickerModule,
48
+ AutofocusDirective,
27
49
  ButtonComponent,
50
+ MatDatepickerModule,
51
+ NgIcon,
52
+ NgTemplateOutlet,
53
+ OverlayModule,
54
+ TranslateDirective,
55
+ TranslatePipe,
28
56
  ],
29
57
  providers: [
30
58
  provideIcons({
31
- matExpandMore,
59
+ iconoirCalendar,
60
+ matClose,
32
61
  matExpandLess,
62
+ matExpandMore,
33
63
  }),
64
+ provideNgIconsConfig({
65
+ size: '1.5rem',
66
+ }),
67
+ provideLocalizedDateAdapter(),
34
68
  ],
35
69
  templateUrl: './date-range-dropdown.component.html',
36
70
  styleUrls: ['./date-range-dropdown.component.css'],
71
+ changeDetection: ChangeDetectionStrategy.OnPush,
37
72
  })
38
- export class DateRangeDropdownComponent implements AfterViewChecked {
39
- private overlayContainer = inject(OverlayContainer)
73
+ export class DateRangeDropdownComponent {
74
+ private scrollStrategies = inject(ScrollStrategyOptions)
75
+ private dateAdapter = inject<DateAdapter<Date, Locale>>(DateAdapter)
76
+ private dateFormats = inject<MatDateFormats>(MAT_DATE_FORMATS)
40
77
  private cdr = inject(ChangeDetectorRef)
41
78
 
42
79
  @Input() title: string
43
- @Input() startDate: Date
44
- @Input() endDate: Date
45
- @Output() startDateChange = new EventEmitter<Date>()
46
- @Output() endDateChange = new EventEmitter<Date>()
80
+ @Input() dateRange: FieldFilterByRange = {}
81
+ @Output() dateRangeChange = new EventEmitter<FieldFilterByRange>()
82
+
83
+ @ViewChild('overlayOrigin') overlayOrigin: CdkOverlayOrigin
84
+
85
+ overlayPositions: ConnectedPosition[] = [
86
+ {
87
+ originX: 'start',
88
+ originY: 'bottom',
89
+ overlayX: 'start',
90
+ overlayY: 'top',
91
+ offsetY: 8,
92
+ },
93
+ {
94
+ originX: 'start',
95
+ originY: 'top',
96
+ overlayX: 'start',
97
+ overlayY: 'bottom',
98
+ offsetY: -8,
99
+ },
100
+ ]
101
+ scrollStrategy = this.scrollStrategies.reposition()
102
+ overlayOpen = false
103
+ expandedBound: DateRangeBound | null = 'start'
104
+ invalidBounds: Record<DateRangeBound, boolean> = { start: false, end: false }
105
+
106
+ constructor() {
107
+ // redraw when the lazily loaded UI locale reaches the adapter
108
+ this.dateAdapter.localeChanges
109
+ .pipe(takeUntilDestroyed())
110
+ .subscribe(() => this.cdr.markForCheck())
111
+ }
112
+
113
+ get startDate() {
114
+ return this.dateRange.start
115
+ }
116
+
117
+ get endDate() {
118
+ return this.dateRange.end
119
+ }
47
120
 
48
- @ViewChild('picker') picker: ElementRef
49
- isPickerDisplayed = false
121
+ get selectedDatesCount() {
122
+ return (this.startDate ? 1 : 0) + (this.endDate ? 1 : 0)
123
+ }
124
+
125
+ openOverlay() {
126
+ this.expandedBound = 'start'
127
+ this.overlayOpen = true
128
+ }
129
+
130
+ closeOverlay() {
131
+ this.overlayOpen = false
132
+ }
133
+
134
+ toggleBound(bound: DateRangeBound) {
135
+ this.expandedBound = this.expandedBound === bound ? null : bound
136
+ }
137
+
138
+ formatDate(date: Date) {
139
+ return this.dateAdapter.format(date, this.dateFormats.display.dateInput)
140
+ }
141
+
142
+ onDateInput(bound: DateRangeBound, event: Event) {
143
+ const typedText = (event.target as HTMLInputElement).value.trim()
144
+ const date = typedText
145
+ ? this.dateAdapter.parse(typedText, this.dateFormats.parse.dateInput)
146
+ : null
147
+ if (!this.isAcceptable(bound, typedText, date)) {
148
+ // keep the text as typed and flag it; the filter stays on its last value
149
+ this.invalidBounds = { ...this.invalidBounds, [bound]: true }
150
+ return
151
+ }
152
+ if (bound === 'start') this.setStartDate(date)
153
+ else this.setEndDate(date)
154
+ }
155
+
156
+ normalizeDateInput(bound: DateRangeBound, event: Event) {
157
+ if (this.invalidBounds[bound]) return
158
+ const date = bound === 'start' ? this.startDate : this.endDate
159
+ const input = event.target as HTMLInputElement
160
+ input.value = date ? this.formatDate(date) : ''
161
+ }
162
+
163
+ private isAcceptable(
164
+ bound: DateRangeBound,
165
+ typedText: string,
166
+ date: Date | null
167
+ ) {
168
+ if (!typedText) return true
169
+ if (!date || !this.dateAdapter.isValid(date)) return false
170
+ return bound === 'start'
171
+ ? !this.endDate || date <= this.endDate
172
+ : !this.startDate || date >= this.startDate
173
+ }
174
+
175
+ setStartDate(date: Date) {
176
+ this.invalidBounds = { ...this.invalidBounds, start: false }
177
+ this.applyDateRange({ ...this.dateRange, start: date })
178
+ this.expandedBound = 'end'
179
+ }
180
+
181
+ setEndDate(date: Date) {
182
+ this.invalidBounds = { ...this.invalidBounds, end: false }
183
+ this.applyDateRange({ ...this.dateRange, end: date })
184
+ this.expandedBound = null
185
+ }
50
186
 
51
- ngAfterViewChecked() {
52
- this.checkPickerOverlay()
187
+ clearDates(event: Event) {
188
+ this.invalidBounds = { start: false, end: false }
189
+ this.expandedBound = 'start'
190
+ this.applyDateRange({})
191
+ propagateToDocumentOnly(event)
53
192
  }
54
193
 
55
- checkPickerOverlay() {
56
- const overlayContainerElement = this.overlayContainer.getContainerElement()
57
- setTimeout(() => {
58
- this.isPickerDisplayed =
59
- overlayContainerElement.querySelector('.mat-datepicker-content') !==
60
- null
61
- this.cdr.detectChanges()
62
- }, 200) // FIXME: find a better way to deal with animation delay
194
+ private applyDateRange(dateRange: FieldFilterByRange) {
195
+ this.dateRange = {
196
+ ...(dateRange.start && { start: dateRange.start }),
197
+ ...(dateRange.end && { end: dateRange.end }),
198
+ }
199
+ this.dateRangeChange.emit(this.dateRange)
63
200
  }
64
201
  }
@@ -5,7 +5,6 @@ import {
5
5
  Input,
6
6
  Output,
7
7
  } from '@angular/core'
8
- import { MatNativeDateModule } from '@angular/material/core'
9
8
  import { MatDatepickerModule } from '@angular/material/datepicker'
10
9
  import { ButtonComponent } from '../button/button.component'
11
10
  import {
@@ -14,6 +13,7 @@ import {
14
13
  provideNgIconsConfig,
15
14
  } from '@ng-icons/core'
16
15
  import { iconoirCalendar } from '@ng-icons/iconoir'
16
+ import { provideLocalizedDateAdapter } from '../date-adapter.providers'
17
17
 
18
18
  @Component({
19
19
  selector: 'gn-ui-date-range-picker',
@@ -21,17 +21,13 @@ import { iconoirCalendar } from '@ng-icons/iconoir'
21
21
  styleUrls: ['./date-range-picker.component.css'],
22
22
  changeDetection: ChangeDetectionStrategy.OnPush,
23
23
  standalone: true,
24
- imports: [
25
- MatNativeDateModule,
26
- MatDatepickerModule,
27
- ButtonComponent,
28
- NgIconComponent,
29
- ],
24
+ imports: [MatDatepickerModule, ButtonComponent, NgIconComponent],
30
25
  providers: [
31
26
  provideIcons({ iconoirCalendar }),
32
27
  provideNgIconsConfig({
33
28
  size: '1.5rem',
34
29
  }),
30
+ provideLocalizedDateAdapter(),
35
31
  ],
36
32
  })
37
33
  export class DateRangePickerComponent {
@@ -1,4 +1,5 @@
1
1
  ngx-dropzone {
2
2
  height: auto;
3
3
  border: none;
4
+ overflow: hidden;
4
5
  }
@@ -1,18 +1,31 @@
1
- <div class="flex h-full">
1
+ <div
2
+ class="flex h-full items-center"
3
+ [class]="extraClass"
4
+ (click)="openFileSelector()"
5
+ >
2
6
  <ngx-dropzone
3
- class="flex-1"
7
+ class="flex-1 overflow-hidden"
4
8
  [multiple]="false"
5
- (change)="selectFile($event)"
9
+ [disableClick]="true"
6
10
  [accept]="accept"
11
+ [maxFileSize]="maxFileSizeBytes"
12
+ [style.background-color]="dropzoneBackgroundColor"
13
+ (change)="selectFile($event)"
7
14
  >
8
- @if (!fileName) {
9
- <div class="text-gray-900 pl-2 py-2" translate="">
15
+ @if (!fileName || !showFileName) {
16
+ <div [class]="textClass">
10
17
  {{ placeholder }}
11
18
  </div>
12
19
  }
13
20
 
14
- @if (fileName) {
15
- <div class="text-gray-900 pl-2 py-2">{{ fileName }}</div>
21
+ @if (fileName && showFileName) {
22
+ <div [class]="textClass">
23
+ {{ fileName }}
24
+ </div>
16
25
  }
17
26
  </ngx-dropzone>
27
+
28
+ @if (icon) {
29
+ <ng-icon [name]="icon" class="shrink-0 text-gray-950"></ng-icon>
30
+ }
18
31
  </div>