diff --git a/src/lib/filtering/filter-card/filter-card.component.html b/src/lib/filtering/filter-card/filter-card.component.html
new file mode 100644
index 0000000..db78647
--- /dev/null
+++ b/src/lib/filtering/filter-card/filter-card.component.html
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ filter?.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/filtering/filter-card/filter-card.component.scss b/src/lib/filtering/filter-card/filter-card.component.scss
new file mode 100644
index 0000000..b5caea8
--- /dev/null
+++ b/src/lib/filtering/filter-card/filter-card.component.scss
@@ -0,0 +1,46 @@
+@use '../../../assets/styles/common-mixins' as mixins;
+
+.filter-menu-options,
+.filter-menu-header {
+ display: flex;
+ justify-content: space-between;
+ padding: 8px 16px 16px 16px;
+ min-width: var(--filter-card-min-width);
+
+ .actions {
+ display: flex;
+
+ > *:not(:last-child) {
+ margin-right: 8px;
+ }
+ }
+}
+
+.filter-content {
+ @include mixins.scroll-bar;
+ max-height: 570px;
+ overflow: auto;
+}
+
+.filter-menu-options {
+ margin-top: 8px;
+ padding: 16px 16px 3px;
+}
+
+.filter-options {
+ background-color: var(--iqser-side-nav);
+ padding-bottom: 8px;
+}
+
+iqser-input-with-action {
+ padding: 0 8px 8px 8px;
+}
+
+::ng-deep .filter-menu-checkbox {
+ width: 100%;
+
+ label {
+ width: 100%;
+ height: 100%;
+ }
+}
diff --git a/src/lib/filtering/filter-card/filter-card.component.ts b/src/lib/filtering/filter-card/filter-card.component.ts
new file mode 100644
index 0000000..4d265b8
--- /dev/null
+++ b/src/lib/filtering/filter-card/filter-card.component.ts
@@ -0,0 +1,132 @@
+import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, TemplateRef } from '@angular/core';
+import { combineLatest, Observable, pipe } from 'rxjs';
+import { IFilter } from '../models/filter.model';
+import { INestedFilter } from '../models/nested-filter.model';
+import { IFilterGroup } from '../models/filter-group.model';
+import { handleCheckedValue } from '../filter-utils';
+import { FilterService } from '../filter.service';
+import { SearchService } from '../../search';
+import { Filter } from '../models/filter';
+import { map } from 'rxjs/operators';
+import { shareDistinctLast, shareLast } from '../../utils';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
+
+const areExpandable = (nestedFilter: INestedFilter) => !!nestedFilter?.children?.length;
+const atLeastOneIsExpandable = pipe(
+ map(group => !!group?.filters.some(areExpandable)),
+ shareDistinctLast(),
+);
+
+@Component({
+ selector: 'iqser-filter-card [primaryFiltersSlug]',
+ templateUrl: './filter-card.component.html',
+ styleUrls: ['./filter-card.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [
+ {
+ provide: MAT_CHECKBOX_DEFAULT_OPTIONS,
+ useValue: {
+ clickAction: 'noop',
+ color: 'primary',
+ },
+ },
+ ],
+})
+export class FilterCardComponent implements OnInit {
+ @Input() primaryFiltersSlug!: string;
+ @Input() actionsTemplate?: TemplateRef;
+ @Input() secondaryFiltersSlug = '';
+ @Input() primaryFiltersLabel: string = _('filter-menu.filter-types');
+ @Input() minWidth = 350;
+
+ primaryFilterGroup$!: Observable;
+ secondaryFilterGroup$!: Observable;
+ primaryFilters$!: Observable;
+
+ atLeastOneFilterIsExpandable$?: Observable;
+ atLeastOneSecondaryFilterIsExpandable$?: Observable;
+
+ constructor(
+ readonly filterService: FilterService,
+ readonly searchService: SearchService,
+ private readonly _elementRef: ElementRef,
+ ) {}
+
+ private get _primaryFilters$(): Observable {
+ return combineLatest([this.primaryFilterGroup$, this.searchService.valueChanges$]).pipe(
+ map(([group]) => this.searchService.searchIn(group?.filters ?? [])),
+ shareLast(),
+ );
+ }
+
+ ngOnInit() {
+ this.primaryFilterGroup$ = this.filterService.getGroup$(this.primaryFiltersSlug).pipe(shareLast());
+ this.secondaryFilterGroup$ = this.filterService.getGroup$(this.secondaryFiltersSlug).pipe(shareLast());
+ this.primaryFilters$ = this._primaryFilters$;
+
+ this.atLeastOneFilterIsExpandable$ = atLeastOneIsExpandable(this.primaryFilterGroup$);
+ this.atLeastOneSecondaryFilterIsExpandable$ = atLeastOneIsExpandable(this.secondaryFilterGroup$);
+
+ (this._elementRef.nativeElement as HTMLElement).style.setProperty('--filter-card-min-width', `${this.minWidth}px`);
+ }
+
+ filterCheckboxClicked($event: MouseEvent, nestedFilter: INestedFilter, filterGroup: IFilterGroup, parent?: INestedFilter): void {
+ $event.stopPropagation();
+
+ if (filterGroup.singleSelect) {
+ this.deactivateFilters(nestedFilter.id);
+ }
+
+ // eslint-disable-next-line no-param-reassign
+ nestedFilter.checked = !nestedFilter.checked;
+
+ if (parent) {
+ handleCheckedValue(parent);
+ } else {
+ // eslint-disable-next-line no-param-reassign
+ if (nestedFilter.indeterminate) {
+ nestedFilter.checked = false;
+ }
+ // eslint-disable-next-line no-param-reassign
+ nestedFilter.indeterminate = false;
+ // eslint-disable-next-line no-return-assign,no-param-reassign
+ nestedFilter.children?.forEach(f => (f.checked = !!nestedFilter.checked));
+ }
+
+ this.filterService.refresh();
+ }
+
+ activatePrimaryFilters(): void {
+ this._setFilters(this.primaryFiltersSlug, true);
+ }
+
+ deactivateFilters(exceptedFilterId?: string): void {
+ this._setFilters(this.primaryFiltersSlug, false, exceptedFilterId);
+ if (this.secondaryFiltersSlug) {
+ this._setFilters(this.secondaryFiltersSlug, false, exceptedFilterId);
+ }
+ }
+
+ toggleFilterExpanded($event: MouseEvent, nestedFilter: INestedFilter): void {
+ $event.stopPropagation();
+ // eslint-disable-next-line no-param-reassign
+ nestedFilter.expanded = !nestedFilter.expanded;
+ this.filterService.refresh();
+ }
+
+ private _setFilters(filterGroup: string, checked = false, exceptedFilterId?: string) {
+ const filters = this.filterService.getGroup(filterGroup)?.filters;
+ filters?.forEach(f => {
+ if (f.id !== exceptedFilterId) {
+ // eslint-disable-next-line no-param-reassign
+ f.checked = checked;
+ // eslint-disable-next-line no-param-reassign
+ f.indeterminate = false;
+ // eslint-disable-next-line no-return-assign,no-param-reassign
+ f.children?.forEach(ff => (ff.checked = checked));
+ }
+ });
+ this.filterService.refresh();
+ }
+}
diff --git a/src/lib/filtering/filter.service.ts b/src/lib/filtering/filter.service.ts
index 19d7246..5f88b84 100644
--- a/src/lib/filtering/filter.service.ts
+++ b/src/lib/filtering/filter.service.ts
@@ -102,6 +102,15 @@ export class FilterService {
);
}
+ updateFilterGroups(newFilters: IFilterGroup[]): void {
+ const filters = this.filterGroups.map(oldFilter => {
+ const newFilter = newFilters.find(f => f.slug === oldFilter.slug);
+ return newFilter ? newFilter : oldFilter;
+ });
+
+ this.#filterGroups$.next(filters);
+ }
+
addSingleFilter(filter: IFilter) {
if (!this.#singleFilters.has(filter.id)) {
return this.#singleFilters.set(filter.id, new BehaviorSubject(filter));
diff --git a/src/lib/filtering/filters.module.ts b/src/lib/filtering/filters.module.ts
index aa445f8..f3143f8 100644
--- a/src/lib/filtering/filters.module.ts
+++ b/src/lib/filtering/filters.module.ts
@@ -10,10 +10,11 @@ import { IqserIconsModule } from '../icons';
import { IqserInputsModule } from '../inputs';
import { IqserHelpModeModule } from '../help-mode';
import { SingleFilterComponent } from './single-filter/single-filter.component';
+import { FilterCardComponent } from './filter-card/filter-card.component';
const matModules = [MatCheckboxModule, MatMenuModule];
const modules = [TranslateModule, IqserButtonsModule, IqserIconsModule, IqserInputsModule, IqserHelpModeModule];
-const components = [QuickFiltersComponent, PopupFilterComponent, SingleFilterComponent];
+const components = [QuickFiltersComponent, PopupFilterComponent, SingleFilterComponent, FilterCardComponent];
@NgModule({
declarations: [...components],
diff --git a/src/lib/filtering/popup-filter/popup-filter.component.html b/src/lib/filtering/popup-filter/popup-filter.component.html
index bc55ca3..ec41649 100644
--- a/src/lib/filtering/popup-filter/popup-filter.component.html
+++ b/src/lib/filtering/popup-filter/popup-filter.component.html
@@ -5,7 +5,7 @@
[class.disabled]="primaryFiltersDisabled$ | async"
[disabled]="primaryFiltersDisabled$ | async"
[icon]="primaryGroup.icon"
- [label]="primaryGroup.label || ('filter-menu.label' | translate)"
+ [label]="primaryGroup.label?.capitalize() || ('filter-menu.label' | translate)"
[matMenuTriggerFor]="filterMenu"
[showDot]="hasActiveFilters$ | async"
id="{{ primaryGroup.slug }}"
@@ -17,7 +17,7 @@
[attr.aria-expanded]="expanded$ | async"
[class.disabled]="primaryFiltersDisabled$ | async"
[disabled]="primaryFiltersDisabled$ | async"
- [label]="primaryGroup.label || ('filter-menu.label' | translate)"
+ [label]="primaryGroup.label?.capitalize() || ('filter-menu.label' | translate)"
[matMenuTriggerFor]="filterMenu"
[showDot]="hasActiveFilters$ | async"
>
@@ -30,106 +30,12 @@
xPosition="before"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
- {{ filter?.label }}
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/lib/filtering/popup-filter/popup-filter.component.scss b/src/lib/filtering/popup-filter/popup-filter.component.scss
index 2e05059..add3363 100644
--- a/src/lib/filtering/popup-filter/popup-filter.component.scss
+++ b/src/lib/filtering/popup-filter/popup-filter.component.scss
@@ -1,50 +1,5 @@
@use '../../../assets/styles/common-mixins' as mixins;
-.filter-menu-options,
-.filter-menu-header {
- display: flex;
- justify-content: space-between;
- padding: 8px 16px 16px 16px;
- min-width: 350px;
-
- .actions {
- display: flex;
-
- > *:not(:last-child) {
- margin-right: 8px;
- }
- }
-}
-
-.filter-content {
- @include mixins.scroll-bar;
- max-height: 570px;
- overflow: auto;
-}
-
-.filter-menu-options {
- margin-top: 8px;
- padding: 16px 16px 3px;
-}
-
-.filter-options {
- background-color: var(--iqser-side-nav);
- padding-bottom: 8px;
-}
-
-::ng-deep .filter-menu-checkbox {
- width: 100%;
-
- label {
- width: 100%;
- height: 100%;
- }
-}
-
-iqser-input-with-action {
- padding: 0 8px 8px 8px;
-}
-
.disabled {
pointer-events: none;
}
diff --git a/src/lib/filtering/popup-filter/popup-filter.component.ts b/src/lib/filtering/popup-filter/popup-filter.component.ts
index 79e7431..0270e9a 100644
--- a/src/lib/filtering/popup-filter/popup-filter.component.ts
+++ b/src/lib/filtering/popup-filter/popup-filter.component.ts
@@ -1,37 +1,19 @@
import { ChangeDetectionStrategy, Component, Input, OnInit, TemplateRef } from '@angular/core';
-import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
-import { BehaviorSubject, combineLatest, Observable, pipe } from 'rxjs';
+import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { delay, map } from 'rxjs/operators';
import { any, shareDistinctLast, shareLast } from '../../utils';
-import { handleCheckedValue } from '../filter-utils';
import { FilterService } from '../filter.service';
import { IFilterGroup } from '../models/filter-group.model';
-import { INestedFilter } from '../models/nested-filter.model';
import { SearchService } from '../../search';
import { Filter, IFilter } from '..';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-const areExpandable = (nestedFilter: INestedFilter) => !!nestedFilter?.children?.length;
-const atLeastOneIsExpandable = pipe(
- map(group => !!group?.filters.some(areExpandable)),
- shareDistinctLast(),
-);
-
@Component({
selector: 'iqser-popup-filter [primaryFiltersSlug]',
templateUrl: './popup-filter.component.html',
styleUrls: ['./popup-filter.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
- providers: [
- {
- provide: MAT_CHECKBOX_DEFAULT_OPTIONS,
- useValue: {
- clickAction: 'noop',
- color: 'primary',
- },
- },
- SearchService,
- ],
+ providers: [SearchService],
})
export class PopupFilterComponent implements OnInit {
@Input() primaryFiltersSlug!: string;
@@ -39,9 +21,8 @@ export class PopupFilterComponent implements OnInit {
@Input() secondaryFiltersSlug = '';
@Input() primaryFiltersLabel: string = _('filter-menu.filter-types');
- atLeastOneFilterIsExpandable$?: Observable;
- atLeastOneSecondaryFilterIsExpandable$?: Observable;
hasActiveFilters$?: Observable;
+
readonly expanded = new BehaviorSubject(false);
readonly expanded$ = this.expanded.asObservable().pipe(delay(200));
@@ -82,66 +63,5 @@ export class PopupFilterComponent implements OnInit {
this.hasActiveFilters$ = this._hasActiveFilters$;
this.primaryFiltersDisabled$ = this._primaryFiltersDisabled$;
- this.atLeastOneFilterIsExpandable$ = atLeastOneIsExpandable(this.primaryFilterGroup$);
- this.atLeastOneSecondaryFilterIsExpandable$ = atLeastOneIsExpandable(this.secondaryFilterGroup$);
- }
-
- filterCheckboxClicked($event: MouseEvent, nestedFilter: INestedFilter, filterGroup: IFilterGroup, parent?: INestedFilter): void {
- $event.stopPropagation();
-
- if (filterGroup.singleSelect) {
- this.deactivateFilters(nestedFilter.id);
- }
-
- // eslint-disable-next-line no-param-reassign
- nestedFilter.checked = !nestedFilter.checked;
-
- if (parent) {
- handleCheckedValue(parent);
- } else {
- // eslint-disable-next-line no-param-reassign
- if (nestedFilter.indeterminate) {
- nestedFilter.checked = false;
- }
- // eslint-disable-next-line no-param-reassign
- nestedFilter.indeterminate = false;
- // eslint-disable-next-line no-return-assign,no-param-reassign
- nestedFilter.children?.forEach(f => (f.checked = !!nestedFilter.checked));
- }
-
- this.filterService.refresh();
- }
-
- activatePrimaryFilters(): void {
- this._setFilters(this.primaryFiltersSlug, true);
- }
-
- deactivateFilters(exceptedFilterId?: string): void {
- this._setFilters(this.primaryFiltersSlug, false, exceptedFilterId);
- if (this.secondaryFiltersSlug) {
- this._setFilters(this.secondaryFiltersSlug, false, exceptedFilterId);
- }
- }
-
- toggleFilterExpanded($event: MouseEvent, nestedFilter: INestedFilter): void {
- $event.stopPropagation();
- // eslint-disable-next-line no-param-reassign
- nestedFilter.expanded = !nestedFilter.expanded;
- this.filterService.refresh();
- }
-
- private _setFilters(filterGroup: string, checked = false, exceptedFilterId?: string) {
- const filters = this.filterService.getGroup(filterGroup)?.filters;
- filters?.forEach(f => {
- if (f.id !== exceptedFilterId) {
- // eslint-disable-next-line no-param-reassign
- f.checked = checked;
- // eslint-disable-next-line no-param-reassign
- f.indeterminate = false;
- // eslint-disable-next-line no-return-assign,no-param-reassign
- f.children?.forEach(ff => (ff.checked = checked));
- }
- });
- this.filterService.refresh();
}
}
diff --git a/src/lib/inputs/form-field/form-field-component.directive.ts b/src/lib/inputs/form-field/form-field-component.directive.ts
index 08ae8e4..db7b09e 100644
--- a/src/lib/inputs/form-field/form-field-component.directive.ts
+++ b/src/lib/inputs/form-field/form-field-component.directive.ts
@@ -1,4 +1,4 @@
-import { Directive } from '@angular/core';
+import { ChangeDetectorRef, Directive, inject } from '@angular/core';
import { ControlValueAccessor, ValidationErrors, Validator } from '@angular/forms';
@Directive()
@@ -6,6 +6,8 @@ export abstract class FormFieldComponent implements ControlValueAccessor, Val
touched = false;
disabled = false;
+ protected readonly _changeRef = inject(ChangeDetectorRef);
+
protected _value: I | undefined;
get value(): I | undefined {
@@ -38,6 +40,7 @@ export abstract class FormFieldComponent implements ControlValueAccessor, Val
writeValue(option: I): void {
this._value = option;
+ this._changeRef.markForCheck();
}
validate(): ValidationErrors | null {
diff --git a/src/lib/listing/models/entity.model.ts b/src/lib/listing/models/entity.model.ts
index b8fc65f..fb4d63a 100644
--- a/src/lib/listing/models/entity.model.ts
+++ b/src/lib/listing/models/entity.model.ts
@@ -1,7 +1,8 @@
import { IListable } from './listable';
+import { Id } from './trackable';
export abstract class Entity implements IListable {
- abstract readonly id: string;
+ abstract readonly id: Id;
abstract readonly routerLink?: string;
abstract readonly searchKey: string;
diff --git a/src/lib/listing/models/trackable.ts b/src/lib/listing/models/trackable.ts
index b752b41..66c3fb4 100644
--- a/src/lib/listing/models/trackable.ts
+++ b/src/lib/listing/models/trackable.ts
@@ -1,3 +1,5 @@
+export type Id = string | number;
+
export interface ITrackable {
- readonly id: string;
+ readonly id: Id;
}
diff --git a/src/lib/listing/services/entities.service.ts b/src/lib/listing/services/entities.service.ts
index 218d49d..ab843a6 100644
--- a/src/lib/listing/services/entities.service.ts
+++ b/src/lib/listing/services/entities.service.ts
@@ -4,6 +4,7 @@ import { filter, map, startWith, tap } from 'rxjs/operators';
import { IListable } from '../models';
import { GenericService, QueryParam } from '../../services';
import { getLength, List, mapEach, shareDistinctLast, shareLast } from '../../utils';
+import { Id } from '../models/trackable';
@Injectable()
/**
@@ -47,7 +48,7 @@ export class EntitiesService extends
);
}
- getEntityChanged$(entityId: string): Observable {
+ getEntityChanged$(entityId: Id): Observable {
return this._entityChanged$.pipe(
filter(entity => entity.id === entityId),
startWith(this.find(entityId)),
@@ -55,7 +56,7 @@ export class EntitiesService extends
);
}
- getEntityDeleted$(entityId: string): Observable {
+ getEntityDeleted$(entityId: Id): Observable {
return this._entityDeleted$.pipe(filter(entity => entity.id === entityId));
}
@@ -88,7 +89,7 @@ export class EntitiesService extends
}
}
- remove(id: string) {
+ remove(id: Id) {
const entity = this.all.find(item => item.id === id);
if (entity) {
this.#all$.next(this.all.filter(item => item.id !== id));
@@ -96,11 +97,11 @@ export class EntitiesService extends
}
}
- find(id: string): E | undefined {
+ find(id: Id): E | undefined {
return this.all.find(entity => entity.id === id);
}
- has(id: string): boolean {
+ has(id: Id): boolean {
return this.all.some(entity => entity.id === id);
}
diff --git a/src/lib/services/entities-map.service.ts b/src/lib/services/entities-map.service.ts
index 220d47f..4642c15 100644
--- a/src/lib/services/entities-map.service.ts
+++ b/src/lib/services/entities-map.service.ts
@@ -3,12 +3,13 @@ import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { filter, startWith } from 'rxjs/operators';
import { Entity } from '../listing';
import { RequiredParam, shareLast, Validate } from '../utils';
+import { Id } from '../listing/models/trackable';
@Injectable({ providedIn: 'root' })
export abstract class EntitiesMapService, I> {
protected abstract readonly _primaryKey: string;
- protected readonly _map = new Map>();
+ protected readonly _map = new Map>();
readonly #entityChanged$ = new Subject();
readonly #entityDeleted$ = new Subject();
@@ -16,11 +17,11 @@ export abstract class EntitiesMapService, I> {
return this._map.size === 0;
}
- delete(keys: string[]): void {
+ delete(keys: Id[]): void {
keys.forEach(key => this._map.delete(key));
}
- get$(key: string) {
+ get$(key: Id) {
if (!this._map.has(key)) {
this._map.set(key, new BehaviorSubject([]));
}
@@ -28,13 +29,13 @@ export abstract class EntitiesMapService, I> {
return this._getBehaviourSubject(key).asObservable();
}
- has(parentId: string) {
+ has(parentId: Id) {
return this._map.has(parentId);
}
- get(key: string): E[];
- get(key: string, id: string): E | undefined;
- get(key: string, id?: string): E | E[] | undefined {
+ get(key: Id): E[];
+ get(key: Id, id: Id): E | undefined;
+ get(key: Id, id?: Id): E | E[] | undefined {
const value = this._getBehaviourSubject(key)?.value;
if (!id) {
return value ?? [];
@@ -42,7 +43,7 @@ export abstract class EntitiesMapService, I> {
return value?.find(item => item.id === id);
}
- set(key: string, entities: E[]): void {
+ set(key: Id, entities: E[]): void {
if (!this._map.has(key)) {
this._map.set(key, new BehaviorSubject(entities));
return entities.forEach(entity => this.#entityChanged$.next(entity));
@@ -96,7 +97,7 @@ export abstract class EntitiesMapService, I> {
}
@Validate()
- watch$(@RequiredParam() key: string, @RequiredParam() entityId: string): Observable {
+ watch$(@RequiredParam() key: Id, @RequiredParam() entityId: Id): Observable {
return this.#entityChanged$.pipe(
filter(entity => entity.id === entityId),
startWith(this.get(key, entityId) as E),
@@ -104,17 +105,17 @@ export abstract class EntitiesMapService, I> {
);
}
- watchDeleted$(entityId: string): Observable {
+ watchDeleted$(entityId: Id): Observable {
return this.#entityDeleted$.pipe(filter(entity => entity.id === entityId));
}
- private _pluckPrimaryKey(entity: E): string {
+ private _pluckPrimaryKey(entity: E): Id {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
- return entity[this._primaryKey] as string;
+ return entity[this._primaryKey] as Id;
}
- private _getBehaviourSubject(key: string): BehaviorSubject {
+ private _getBehaviourSubject(key: Id): BehaviorSubject {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this._map.get(key)!;
}
diff --git a/src/lib/utils/functions.ts b/src/lib/utils/functions.ts
index f7c5408..510f3ad 100644
--- a/src/lib/utils/functions.ts
+++ b/src/lib/utils/functions.ts
@@ -1,4 +1,4 @@
-import { ITrackable } from '../listing/models/trackable';
+import { Id, ITrackable } from '../listing/models/trackable';
import { UntypedFormGroup } from '@angular/forms';
import { forOwn, has, isEqual, isPlainObject, transform } from 'lodash-es';
import dayjs, { Dayjs } from 'dayjs';
@@ -34,7 +34,7 @@ export function toNumber(str: string): number {
}
export function trackByFactory() {
- return (_index: number, item: T): string => item.id;
+ return (_index: number, item: T): Id => item.id;
}
export function hasFormChanged(form: UntypedFormGroup, initialFormValue: Record): boolean {