@cauca-911/datagrid
Version:
Run `npm install @cauca-911/datagrid --save` to add this library to your project
1 lines • 186 kB
Source Map (JSON)
{"version":3,"file":"cauca-911-datagrid.mjs","sources":["../../../projects/cauca-datagrid/src/lib/data-grid/models/data-grid-row-drop-event.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/base-grid-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/components/data-grid-header/data-grid-header.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/components/data-grid-header/data-grid-header.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/models/density-enum.ts","../../../projects/cauca-datagrid/src/lib/services/preference.ts","../../../projects/cauca-datagrid/src/lib/services/mat-paginator-localization.service.ts","../../../projects/cauca-datagrid/src/lib/services/preference.service.ts","../../../projects/cauca-datagrid/src/lib/provide-cauca-grid.ts","../../../projects/cauca-datagrid/src/lib/data-grid/base-data-grid.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/components/button-popover/button-popover.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/components/button-popover/button-popover.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/components/data-grid-table-options/data-grid-table-options.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/components/data-grid-table-options/data-grid-table-options.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/components/data-grid-progress-spiner/data-grid-progress-spiner.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/components/data-grid-progress-spiner/data-grid-progress-spiner.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/data-grid/data-grid.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/data-grid/data-grid.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/odata-grid/odata-listing.service.ts","../../../projects/cauca-datagrid/src/lib/data-grid/odata-grid/grid-url-state.ts","../../../projects/cauca-datagrid/src/lib/data-grid/odata-grid/odata-grid.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/odata-grid/odata-grid.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/grid-state-restoration.service.ts","../../../projects/cauca-datagrid/src/lib/data-grid/provide-grid-state-restoration.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/color-data-grid-column/color-data-grid-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/color-data-grid-column/color-data-grid-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/components/check-icon/check-icon.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-check-column/data-grid-check-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-check-column/data-grid-check-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-column/data-grid-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-column/data-grid-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-command-icon-column/data-grid-command-icon-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-command-icon-column/data-grid-command-icon-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-commands-column/data-grid-commands-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-commands-column/data-grid-commands-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-icon-column/data-grid-icon-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-icon-column/data-grid-icon-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-menu-column/data-grid-menu-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/data-grid-menu-column/data-grid-menu-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/components/time-label/time-label.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/components/time-label/time-label.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/date-grid-column/date-grid-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/date-grid-column/date-grid-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/sortable-data-grid-check-column/sortable-data-grid-check-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/sortable-data-grid-check-column/sortable-data-grid-check-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/components/column-label/column-label.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/components/column-label/column-label.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/sortable-data-grid-column/sortable-data-grid-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/sortable-data-grid-column/sortable-data-grid-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/translation-grid-column/translation-grid-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/translation-grid-column/translation-grid-column.component.html","../../../projects/cauca-datagrid/src/lib/data-grid/columns/time-span-data-grid-column/time-span-data-grid-column.component.ts","../../../projects/cauca-datagrid/src/lib/data-grid/columns/time-span-data-grid-column/time-span-data-grid-column.component.html","../../../projects/cauca-datagrid/src/public-api.ts","../../../projects/cauca-datagrid/src/cauca-911-datagrid.ts"],"sourcesContent":["export class DataGridRowDropEvent {\n public item: unknown;\n public data: unknown[];\n public fromIndex: number;\n public toIndex: number;\n\n public constructor(data: {\n item: unknown;\n data: unknown[];\n fromIndex: number;\n toIndex: number;\n }) {\n Object.assign(this, data);\n }\n}\n","import { Component, HostBinding, OnDestroy, OnInit, Optional, ViewChild, ViewEncapsulation, input, ChangeDetectionStrategy } from '@angular/core';\r\nimport { MatCellDef, MatColumnDef, MatFooterCellDef, MatHeaderCellDef, MatTable } from '@angular/material/table';\r\n\r\n@Component({ template: '' ,\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.None\r\n})\r\nexport abstract class BaseGridColumnComponent implements OnDestroy, OnInit {\r\n\r\n public readonly prop = input('');\r\n public readonly showLabel = input(true);\r\n public readonly label = input<string | undefined>(undefined);\r\n public readonly isInColumnChooser = input(true);\r\n public readonly width = input('');\r\n public readonly showToolTip = input(false);\r\n\r\n @HostBinding('attr.ariaHidden') public ariaHidden!: true;\r\n @HostBinding('class') public classes!: 'column-template cdk-visually-hidden';\r\n\r\n @ViewChild(MatColumnDef, { static: true }) public columnDef!: MatColumnDef;\r\n @ViewChild(MatCellDef, { static: true }) public cellDef!: MatCellDef;\r\n @ViewChild(MatHeaderCellDef, { static: true }) public headerCellDef!: MatHeaderCellDef;\r\n @ViewChild(MatFooterCellDef, { static: true }) public footerCellDef!: MatFooterCellDef;\r\n\r\n public selected: boolean = true;\r\n\r\n public constructor(@Optional() public table: MatTable<unknown>) {\r\n }\r\n\r\n public ngOnInit(): void {\r\n if (this.columnDef) {\r\n if (!this.columnDef.name) {\r\n this.columnDef.name = this.prop();\r\n }\r\n this.columnDef.cell = this.cellDef;\r\n this.columnDef.headerCell = this.headerCellDef;\r\n this.columnDef.footerCell = this.footerCellDef;\r\n }\r\n }\r\n\r\n public ngOnDestroy(): void {\r\n if (this.table) {\r\n this.table.removeColumnDef(this.columnDef);\r\n }\r\n }\r\n\r\n public capitalize(value: string): string {\r\n return value.charAt(0).toUpperCase() + value.slice(1);\r\n }\r\n}\r\n","import { Component, OnDestroy, OnInit, input, output, signal, ChangeDetectionStrategy } from '@angular/core';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatIcon } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { IconButtonComponent, RaisedButtonComponent } from '@cauca-911/material';\nimport { TranslatePipe } from '@ngx-translate/core';\nimport { Subject, debounceTime, takeUntil } from 'rxjs';\n\n@Component({\n selector: 'cauca-data-grid-header',\n templateUrl: './data-grid-header.component.html',\n styleUrls: ['./data-grid-header.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MatFormFieldModule, IconButtonComponent, RaisedButtonComponent, MatInputModule, ReactiveFormsModule, TranslatePipe, MatIcon],\n})\nexport class DataGridHeaderComponent implements OnInit, OnDestroy {\n\n public filterControl: FormControl<string> = new FormControl<string>('');\n\n /** Written from the debounced valueChanges subscription and by OdataGridComponent when it\n * restores persisted state, so it is a signal: neither is a change-detection trigger\n * under zoneless (ADR-0004). */\n public readonly showClearButton = signal(false);\n\n public readonly showAddButton = input(false);\n public readonly showRefreshButton = input(false);\n public readonly addButtonLabel = input('');\n public readonly disableAddButton = input(false);\n public readonly filterChange = output<string>();\n public readonly addButtonClick = output<void>();\n public readonly refreshRequest = output<void>();\n\n protected _onDestroy = new Subject<void>();\n\n public ngOnInit(): void {\n this.filterControl.valueChanges\n .pipe(\n debounceTime(500),\n takeUntil(this._onDestroy))\n .subscribe((filter: string) => {\n this.filterChange.emit(filter);\n this.showClearButton.set(!!filter);\n });\n }\n\n public ngOnDestroy(): void {\n this._onDestroy.next();\n this._onDestroy.complete();\n }\n\n public onRefresh(): void {\n this.refreshRequest.emit();\n }\n\n public clearSearchValue(): void {\n this.filterControl.setValue('');\n }\n\n public onAddButtonClick(): void {\n this.addButtonClick.emit();\n }\n}\n","<div class=\"grid-header\">\n\n <mat-form-field appearance=\"outline\" class=\"full-width-field grid-search-box\" subscriptSizing=\"dynamic\">\n @if (showClearButton()) { <cauca-icon-button matPrefix (buttonClick)=\"clearSearchValue()\" icon=\"close\" /> }\n <mat-label>{{ 'dataGrid.keywordsLabel' | translate}}</mat-label>\n <input matInput [formControl]=\"filterControl\">\n <mat-icon matSuffix>search</mat-icon>\n </mat-form-field>\n\n <ng-content select=\"[filterRow]\" ngProjectAs=\"[filterRow]\" />\n\n @if (showRefreshButton()) { <cauca-icon-button (buttonClick)=\"onRefresh()\" icon=\"refresh\" class=\"grid-refresh-button\" /> }\n\n @if (showAddButton()) { <cauca-raised-button icon=\"add\" color=\"primary\" [label]=\"addButtonLabel()\" [translateLabel]=\"false\" class=\"grid-new-button\" [isDisabled]=\"disableAddButton()\" (buttonClick)=\"onAddButtonClick()\" /> }\n</div>","export enum Density {\n Compact = \"compact\",\n Standard = \"standard\",\n Comfortable = \"comfortable\"\n}","import { Density } from \"../data-grid/models/density-enum\";\n\nexport class Preference {\n public gridDensity: Density;\n}\n\nexport class GridConfiguration {\n public name: string;\n public visibleColumns: string[];\n}\n","import { Injectable } from \"@angular/core\";\r\nimport { MatPaginatorIntl } from \"@angular/material/paginator\";\r\nimport { TranslateService } from \"@cauca-911/core\";\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class MatPaginatorLocalizationService extends MatPaginatorIntl {\r\n public itemsPerPageLabel: string = 'Items per page';\r\n public nextPageLabel: string = 'Next page';\r\n public previousPageLabel: string = 'Previous page';\r\n public ofLabel: string = 'of'\r\n\r\n public constructor(\r\n private readonly translateService: TranslateService) {\r\n super();\r\n this.translateService.onLangChange.subscribe(() => this.loadTranslations());\r\n this.loadTranslations();\r\n }\r\n\r\n public getRangeLabel = (page, pageSize, length): string => {\r\n if (length == 0 || pageSize == 0) {\r\n return `0 ${this.ofLabel} ${length}`;\r\n }\r\n\r\n length = Math.max(length, 0);\r\n const startIndex = page * pageSize;\r\n const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize;\r\n return `${startIndex + 1} – ${endIndex} ${this.ofLabel} ${length}`;\r\n };\r\n\r\n private loadTranslations(): any {\r\n this.translateService.get([\r\n 'paginator.itemsPerPage',\r\n 'paginator.nextPage',\r\n 'paginator.previousPage',\r\n 'paginator.of'\r\n ])\r\n .subscribe(translation => {\r\n this.itemsPerPageLabel = translation['paginator.itemsPerPage'];\r\n this.nextPageLabel = translation['paginator.nextPage'];\r\n this.previousPageLabel = translation['paginator.previousPage'];\r\n this.ofLabel = translation['paginator.of'];\r\n this.changes.next();\r\n });\r\n }\r\n}\r\n","import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable, of } from 'rxjs';\nimport { GridConfiguration, Preference } from './preference';\nimport { Density } from '../data-grid/models/density-enum';\nimport { BasePreferenceService } from './base-preference-service';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class PreferenceService implements BasePreferenceService {\n public preference: BehaviorSubject<Preference> = new BehaviorSubject<Preference>(new Preference());\n\n public gridConfiguration: GridConfiguration[] = [];\n\n public constructor() {\n const data = localStorage.getItem('GridPreference');\n const configuration = localStorage.getItem('GridConfiguration');\n if (data) {\n this.preference.next(JSON.parse(data));\n }\n if (configuration) {\n this.gridConfiguration = JSON.parse(configuration);\n }\n }\n\n public getPreference(): Preference {\n return this.preference.value;\n }\n\n public setPreferenceDensity(density: Density) {\n const preference = this.getPreference();\n if (density === preference.gridDensity) return;\n preference.gridDensity = density;\n this.preference.next(preference);\n this.addOrUpdatePreference();\n }\n\n public addOrUpdatePreference(): void {\n localStorage.setItem('GridPreference', JSON.stringify(this.getPreference()));\n }\n\n public getPreferenceForGrid(gridName: string): Observable<GridConfiguration> {\n return of(this.getPreferenceForGridNotObservable(gridName));\n }\n\n public addOrUpdateGridPreference(gridName: string, visibleColumns: string[]): Observable<void> {\n const configuration = this.getPreferenceForGridNotObservable(gridName);\n configuration.visibleColumns = visibleColumns || [];\n localStorage.setItem('GridConfiguration', JSON.stringify(this.gridConfiguration));\n return of();\n }\n\n private getPreferenceForGridNotObservable(gridName: string): GridConfiguration {\n const configuration = this.gridConfiguration.find(g => g.name == gridName);\n if (configuration) {\n return configuration;\n }\n const newConfiguration = new GridConfiguration();\n newConfiguration.name = gridName;\n this.gridConfiguration.push(newConfiguration);\n return newConfiguration;\n }\n}","import { CDK_DRAG_CONFIG } from \"@angular/cdk/drag-drop\";\nimport { Provider, EnvironmentProviders, Type, InjectionToken } from \"@angular/core\";\nimport { MatPaginatorIntl } from \"@angular/material/paginator\";\nimport { MatPaginatorLocalizationService } from \"./services/mat-paginator-localization.service\";\nimport { PreferenceService } from \"./services/preference.service\";\nimport { BasePreferenceService } from \"./services/base-preference-service\";\n\nexport interface CaucaGridOptions {\n /** If not provided, a default implementation will be used which will use localStorage to store the grid preferences. */\n gridPreferenceService?: Type<BasePreferenceService>\n}\n\nexport const PREFERENCE_SERVICE_TOKEN = new InjectionToken<BasePreferenceService>('CaucaGridPreferenceService');\n\nexport function provideCaucaGrid(options?: CaucaGridOptions): (Provider|EnvironmentProviders) {\n return [\n { provide: CDK_DRAG_CONFIG, useValue: { previewContainer: 'parent' } },\n { provide: MatPaginatorIntl, useClass: MatPaginatorLocalizationService },\n { provide: PREFERENCE_SERVICE_TOKEN, useClass: options?.gridPreferenceService || PreferenceService }\n ];\n}\n","import {\n AfterContentInit, Component, ContentChild,\n ContentChildren, Inject, input, linkedSignal, output, QueryList, signal, ViewChild,\n ChangeDetectionStrategy\n} from '@angular/core';\r\nimport { MatColumnDef, MatFooterRowDef, MatHeaderRowDef, MatNoDataRow, MatRowDef, MatTable } from '@angular/material/table';\r\nimport { MatPaginator } from '@angular/material/paginator';\r\nimport { BaseGridColumnComponent } from './columns/base-grid-column.component';\r\nimport { DataGridHeaderComponent } from './components/data-grid-header/data-grid-header.component';\r\nimport { MatSort } from '@angular/material/sort';\r\nimport { Density } from './models/density-enum';\r\nimport { Preference } from '../services/preference';\r\nimport { TranslateService } from '@ngx-translate/core';\r\nimport { BasePreferenceService } from '../services/base-preference-service';\r\nimport { PREFERENCE_SERVICE_TOKEN } from '../provide-cauca-grid';\r\n\r\n@Component({ changeDetection: ChangeDetectionStrategy.OnPush,\r\n template: '' })\r\nexport abstract class BaseDataGridComponent implements AfterContentInit {\r\n public readonly resourceName = input.required<string>();\r\n public readonly displayedColumns = input<string[]>([]);\r\n public readonly showAddButton = input(false);\r\n public readonly disableAddButton = input(false);\r\n public readonly addButtonLabel = input('');\r\n public readonly searchableColumns = input<string[]>();\r\n public readonly showTableOptions = input(false);\r\n public readonly showColumnViewer = input(true);\r\n public readonly showPaginator = input(false);\r\n public readonly hidePageSize = input(false);\r\n public readonly showExpandButton = input(false);\r\n public readonly addButtonClick = output<void>();\r\n public expandedElement = signal<any>(null);\r\n\r\n @ViewChild(DataGridHeaderComponent) public header!: DataGridHeaderComponent;\r\n @ViewChild(MatTable, { static: true }) public table: MatTable<unknown>;\r\n @ContentChildren(BaseGridColumnComponent) public regularColumns: QueryList<BaseGridColumnComponent>;\r\n @ContentChildren(MatColumnDef) public columnDefs: QueryList<MatColumnDef>;\r\n @ContentChild(MatHeaderRowDef) public headerRowDef: QueryList<MatHeaderRowDef>;\r\n @ContentChild(MatFooterRowDef) public footerRowDef: QueryList<MatFooterRowDef>;\r\n @ContentChildren(MatRowDef) public rowDefs: QueryList<MatRowDef<unknown>>;\r\n @ContentChild(MatNoDataRow) public noDataRow: MatNoDataRow;\r\n @ViewChild(MatPaginator) public paginator: MatPaginator;\r\n\r\n public defaultAddButtonLabel: string = '';\r\n\r\n /**\r\n * Derived from the column inputs, but overwritten when the user picks columns in the table\r\n * options — which is exactly what linkedSignal models. It replaces the ngOnChanges mirroring\r\n * this class used to do (ADR-0003).\r\n */\r\n public readonly allDisplayedColumns = linkedSignal(() => this.computeDisplayedColumns());\r\n public preference: Preference;\r\n\r\n protected sort: MatSort = null;\r\n\r\n public get density(): typeof Density {\r\n return Density;\r\n }\r\n\r\n \r\n public constructor(translateService: TranslateService,\r\n @Inject(PREFERENCE_SERVICE_TOKEN) private preferenceService: BasePreferenceService,) {\r\n this.defaultAddButtonLabel = translateService.instant('dataGrid.defaultAddButtonLabel');\r\n }\r\n\r\n public ngAfterContentInit(): void {\r\n this.setUpPaginator(this.paginator, this.showPaginator());\r\n this.setUpFilterPredicate();\r\n if (this.table && this.regularColumns && this.rowDefs && this.columnDefs) {\r\n this.applyColumns();\r\n }\r\n\r\n if (this.showTableOptions()) {\r\n this.preferenceService.preference.subscribe((preference) => {\r\n this.preference = preference;\r\n });\r\n } else {\r\n this.preference = new Preference();\r\n }\r\n }\r\n\r\n public onExpand(element: any): void {\r\n if (this.expandedElement() === element) {\r\n this.expandedElement.set(null);\r\n } else {\r\n this.expandedElement.set(element);\r\n }\r\n }\r\n\r\n public clearFilter(): void {\r\n this.header?.clearSearchValue();\r\n }\r\n\r\n public renderRows(): void {\r\n this.forceReloadFilter();\r\n this.applyColumns();\r\n this.table.renderRows();\r\n }\r\n\r\n public onDensityChanged(density: Density): void {\r\n this.preferenceService.setPreferenceDensity(density);\r\n }\r\n\r\n public abstract getData(): unknown[];\r\n protected abstract setUpFilterPredicate(): void;\r\n protected abstract setUpSort(sort: MatSort): void;\r\n protected abstract forceReloadFilter(): void;\r\n\r\n /** The set of columns to render, before the user's own column selection is applied. */\r\n protected computeDisplayedColumns(): string[] {\r\n return [...this.displayedColumns()];\r\n }\r\n\r\n protected setUpPaginator(_paginator: MatPaginator, _showPaginator: boolean): void {\r\n // nothing\r\n }\r\n\r\n protected onColumnsChanged(columns: string[]): void {\r\n this.allDisplayedColumns.set([...columns]);\r\n const columnsToSave = this.removeMandatoryColumns([...columns]);\r\n this.preferenceService.addOrUpdateGridPreference(this.resourceName(), columnsToSave).subscribe();\r\n }\r\n\r\n protected applyColumns(): void {\r\n this.regularColumns.forEach(dynColumn => this.table.addColumnDef(dynColumn.columnDef));\r\n this.rowDefs.forEach(rowDef => this.table.addRowDef(rowDef));\r\n this.table.setNoDataRow(this.noDataRow);\r\n this.columnDefs.forEach(columnDef => {\r\n this.table.addColumnDef(columnDef);\r\n this.sort.register({ id: columnDef.name, start: 'desc', disableClear: false });\r\n });\r\n }\r\n\r\n private removeMandatoryColumns(columns: string[]): string[] {\r\n let index = columns.indexOf(\"\");\r\n if (index > -1) {\r\n columns.splice(index, 1);\r\n }\r\n\r\n index = columns.indexOf(\"position\");\r\n if (index > -1) {\r\n columns.splice(index, 1);\r\n }\r\n\r\n index = columns.indexOf(\"buttons\");\r\n if (index > -1) {\r\n columns.splice(index, 1);\r\n }\r\n\r\n index = columns.indexOf(\"menu\");\r\n if (index > -1) {\r\n columns.splice(index, 1);\r\n }\r\n\r\n index = columns.indexOf(\"expand\");\r\n if (index > -1) {\r\n columns.splice(index, 1);\r\n }\r\n\r\n return columns;\r\n }\r\n}\r\n","import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';\nimport { ChangeDetectionStrategy, Component, input, signal } from '@angular/core';\nimport { BasicButtonComponent } from '@cauca-911/material';\n\n@Component({\n selector: 'cauca-button-popover',\n templateUrl: './button-popover.component.html',\n styleUrls: ['./button-popover.component.scss'],\n imports: [CdkConnectedOverlay, BasicButtonComponent, CdkOverlayOrigin],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ButtonPopoverComponent {\n public buttonLabel = input<string>(null);\n public buttonIcon = input<string>('');;\n public color = input<string>('primary');\n\n public isActive = signal<boolean>(false);\n\n public toggle() {\n this.isActive.set(!this.isActive());\n }\n\n public close() {\n if (this.isActive()) {\n this.isActive.set(false);\n }\n }\n}\n","<cauca-basic-button [icon]=\"buttonIcon()\" [color]=\"color()\" [label]=\"buttonLabel()\" [translateLabel]=\"false\" (buttonClick)=\"toggle()\" cdkOverlayOrigin #popoverTrigger=\"cdkOverlayOrigin\" />\n\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"popoverTrigger\"\n [cdkConnectedOverlayOpen]=\"isActive()\"\n [cdkConnectedOverlayHasBackdrop]=\"isActive()\"\n [cdkConnectedOverlayBackdropClass]=\"'cdk-overlay-transparent-backdrop-cs'\"\n (backdropClick)=\"toggle()\">\n <ng-content></ng-content>\n</ng-template>","import { Component, inject, OnInit, QueryList, ViewChild, input, output, ChangeDetectionStrategy } from '@angular/core';\nimport { Density } from '../../models/density-enum';\nimport { BaseGridColumnComponent } from '../../columns/base-grid-column.component';\nimport { ButtonPopoverComponent } from '../button-popover/button-popover.component';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { BasicButtonComponent, MenuItemComponent, RaisedButtonComponent, StrokedButtonComponent } from '@cauca-911/material';\nimport { FormsModule } from '@angular/forms';\nimport { MatMenuModule } from '@angular/material/menu';\nimport { TranslatePipe } from '@ngx-translate/core';\nimport { PREFERENCE_SERVICE_TOKEN } from '../../../provide-cauca-grid';\n\n@Component({\n selector: 'cauca-data-grid-table-options',\n templateUrl: './data-grid-table-options.component.html',\n styleUrls: ['./data-grid-table-options.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MatCardModule, ButtonPopoverComponent, MatCheckboxModule, StrokedButtonComponent, RaisedButtonComponent, BasicButtonComponent, MatMenuModule, FormsModule, TranslatePipe, MenuItemComponent],\n})\nexport class DataGridTableOptionsComponent implements OnInit {\n public readonly gridName = input.required<string>();\n public readonly showColumnViewer = input(true);\n public readonly allColumns = input<QueryList<BaseGridColumnComponent>>(new QueryList());\n\n public readonly densityChanged = output<Density>();\n public readonly columnsChanged = output<string[]>();\n\n @ViewChild(ButtonPopoverComponent, { static: true }) public columnViewer: ButtonPopoverComponent;\n\n public columnsSearch: string = '';\n\n private readonly preferenceService = inject(PREFERENCE_SERVICE_TOKEN);\n\n public get density(): typeof Density {\n return Density;\n }\n\n public get columns(): BaseGridColumnComponent[] {\n return this.allColumns().filter(c => c.prop() !== 'buttons' && c.prop() !== 'menu' && c.prop() !== '' && c.isInColumnChooser());\n }\n\n public get currentDensityIcon(): string {\n const currentDensity = this.preferenceService.getPreference()?.gridDensity;\n if (currentDensity === Density.Compact) {\n return 'density_small';\n }\n\n if (currentDensity === Density.Comfortable) {\n return 'density_large';\n }\n\n return 'density_medium';\n }\n\n public ngOnInit(): void {\n this.preferenceService.getPreferenceForGrid(this.gridName()).subscribe({\n next: configuration => {\n if (configuration?.visibleColumns) {\n this.selectOnlyUserColumns(configuration.visibleColumns);\n } else {\n this.selectAllColumns();\n }\n this.onColumnsChanged();\n }\n });\n }\n\n public closeColumnViewer(): void {\n if (this.columnViewer) {\n this.columnViewer.close();\n }\n }\n\n public changeDensity(newDensity: Density): void {\n this.densityChanged.emit(newDensity);\n }\n\n public onColumnsChanged(): void {\n const newColumns = this.allColumns().filter(column => column.selected);\n this.columnsChanged.emit(newColumns.map((column) => column.prop()));\n }\n\n public resetColumns(): void {\n this.allColumns().forEach((column => column.selected = true));\n this.columnsChanged.emit(this.allColumns().map((column) => column.prop()));\n }\n\n private selectOnlyUserColumns(configurationVisibleColumns: string[]): void {\n this.allColumns().forEach((column => {\n const index = configurationVisibleColumns.findIndex(c => c === column.prop());\n if (index === -1 && column.prop() !== 'buttons' && column.prop() !== 'menu' && column.prop() !== '') {\n column.selected = false;\n }\n }));\n }\n\n private selectAllColumns(): void {\n this.allColumns().forEach((column => {\n column.selected = true;\n }));\n }\n}\n","<div class=\"table-options-bar\">\n <div class=\"table-options\">\n @if (showColumnViewer()) { <cauca-button-popover [buttonLabel]=\"'dataGrid.columns' | translate\" buttonIcon=\"view_column\">\n <mat-card>\n <mat-card-content>\n <div class=\"columns\">\n @for (column of columns; track column) {\n <mat-checkbox color=\"primary\" [(ngModel)]=\"column.selected\"\n (change)=\"onColumnsChanged()\">\n {{column.label()}}\n </mat-checkbox>\n }\n </div>\n </mat-card-content>\n <mat-card-actions>\n <cauca-stroked-button [small]=\"true\" color=\"primary\" (buttonClick)=\"resetColumns()\" label=\"dataGrid.columnChooser.reset\" [translateLabel]=\"true\" />\n <cauca-raised-button [small]=\"true\" color=\"primary\" (buttonClick)=\"resetColumns()\" label=\"dataGrid.columnChooser.showAll\" [translateLabel]=\"true\" />\n </mat-card-actions>\n </mat-card>\n </cauca-button-popover> }\n <cauca-basic-button color=\"primary\" [matMenuTriggerFor]=\"densityMenu\" class=\"open-menu-button\" [icon]=\"currentDensityIcon\" label=\"dataGrid.density.title\" [translateLabel]=\"true\" />\n </div>\n\n <ng-content select=\"[customFilter]\"> </ng-content>\n</div>\n\n<mat-menu #densityMenu=\"matMenu\">\n <cauca-menu-item label=\"dataGrid.density.compact\" [translateLabel]=\"true\" icon=\"density_small\" (buttonClick)=\"changeDensity(density.Compact)\" class=\"density-compact\" />\n <cauca-menu-item label=\"dataGrid.density.standard\" [translateLabel]=\"true\" icon=\"density_medium\" (buttonClick)=\"changeDensity(density.Standard)\" class=\"density-standard\" />\n <cauca-menu-item label=\"dataGrid.density.comfortable\" [translateLabel]=\"true\" icon=\"density_large\" (buttonClick)=\"changeDensity(density.Comfortable)\" class=\"density-comfortable\" />\n</mat-menu>","import { ChangeDetectionStrategy, Component, input } from '@angular/core';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\n\n@Component({\n selector: 'cauca-data-grid-progress-spiner',\n templateUrl: './data-grid-progress-spiner.component.html',\n styleUrls: ['./data-grid-progress-spiner.component.scss'],\n imports: [MatProgressSpinnerModule],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class DataGridProgressSpinerComponent {\n\n public isLoading = input<boolean>(false);\n public useBackground = input<boolean>(true);\n}\n","@if (isLoading()) {\n <div class=\"spiner-container\" [class.use-background]=\"useBackground()\">\n <mat-progress-spinner\n color=\"primary\"\n mode=\"indeterminate\">\n </mat-progress-spinner>\n </div>\n}","import {\n AfterContentInit, AfterViewInit, ChangeDetectionStrategy, Component,\n ContentChild, effect, inject, input, output,\n TemplateRef\n} from '@angular/core';\nimport { MatCellDef, MatColumnDef, MatFooterCell, MatFooterCellDef, MatFooterRow, MatFooterRowDef, MatHeaderCell, MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatNoDataRow, MatRow, MatRowDef, MatTable, MatTableDataSource } from '@angular/material/table';\nimport { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';\nimport { DataGridRowDropEvent } from '../models/data-grid-row-drop-event';\nimport { toLowerCaseAndWithoutDiacritics } from '@cauca-911/core';\nimport { BaseDataGridComponent } from '../base-data-grid.component';\nimport { MatSort } from '@angular/material/sort';\nimport { MatPaginator } from '@angular/material/paginator';\nimport { DataGridHeaderComponent } from '../components/data-grid-header/data-grid-header.component';\nimport { DataGridTableOptionsComponent } from '../components/data-grid-table-options/data-grid-table-options.component';\nimport { IconButtonComponent } from '@cauca-911/material';\nimport { MatIcon } from '@angular/material/icon';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { DataGridProgressSpinerComponent } from '../components/data-grid-progress-spiner/data-grid-progress-spiner.component';\nimport { TranslateService } from '@ngx-translate/core';\nimport { PREFERENCE_SERVICE_TOKEN } from '../../provide-cauca-grid';\nimport { StatusMessageComponent } from '@cauca-911/material';\n\n@Component({\n changeDetection: ChangeDetectionStrategy.OnPush,\n selector: 'cauca-data-grid',\n templateUrl: './data-grid.component.html',\n styleUrls: ['./data-grid.component.scss'],\n imports: [\n NgTemplateOutlet, // still needed\n // Material Table structure:\n MatTable, MatSort, MatColumnDef,\n MatHeaderCell, MatHeaderCellDef, MatCellDef,\n MatFooterCell, MatFooterCellDef,\n MatRow, MatRowDef,\n MatHeaderRow, MatHeaderRowDef,\n MatFooterRow, MatFooterRowDef,\n MatNoDataRow,\n // Drag and Drop:\n CdkDropList, CdkDrag, CdkDragHandle,\n // Material extras:\n MatIcon, MatPaginator,\n // Custom Components:\n DataGridHeaderComponent,\n DataGridTableOptionsComponent,\n DataGridProgressSpinerComponent,\n IconButtonComponent,\n StatusMessageComponent\n]\n})\nexport class DataGridComponent extends BaseDataGridComponent implements AfterContentInit, AfterViewInit {\n\n public readonly data = input<any[]>([]);\n public readonly showLoadingError = input(false);\n public readonly isLoading = input(false);\n public readonly allowReordering = input(false);\n public readonly filterPredicate = input<(data: unknown, filter: string) => boolean>();\n public readonly showFooter = input(false);\n public readonly showHeader = input(true);\n public readonly headerColor = input<string | undefined>(undefined);\n public readonly showRefreshButton = input(false);\n public readonly rowDrop = output<DataGridRowDropEvent>();\n public readonly refreshData = output<void>();\n @ContentChild(TemplateRef) public rowDetailTemplate!: TemplateRef<any>;\n\n public dataSource: MatTableDataSource<unknown[]>;\n public dragDisabled: boolean = true;\n public currentFilter: string = '';\n\n\n public constructor(\n translateService: TranslateService,\n ) {\n super(translateService, inject(PREFERENCE_SERVICE_TOKEN));\n this.dataSource = new MatTableDataSource([]);\n\n // Replaces the ngOnChanges hook that mirrored the `data` input into the datasource.\n effect(() => this.setData(this.data()));\n }\n\n public onRefreshData(): void {\n this.dataSource.data = [];\n this.refreshData.emit();\n }\n\n public ngAfterViewInit(): void {\n this.setUpPaginator(this.paginator, this.showPaginator());\n }\n\n public applyFilter(filter: string): void {\n if (this.paginator) {\n this.paginator.firstPage();\n }\n this.dataSource.filter = toLowerCaseAndWithoutDiacritics(filter);\n }\n\n public override setUpSort(sort: MatSort): void {\n this.sort = sort;\n this.dataSource.sort = this.sort;\n }\n\n public override getData(): unknown[] {\n return this.dataSource.data;\n }\n\n public getFilteredData(): unknown[] {\n return this.dataSource.filteredData;\n }\n\n public refreshDisplayedColumns(columns: string[]): void {\n this.onColumnsChanged(columns);\n }\n\n public rowDropped(event: CdkDragDrop<unknown>): void {\n const data: unknown[] = this.getData();\n const filteredData: any[] = this.getFilteredData();\n const fromIndex = data.findIndex((d) => d === filteredData[event.previousIndex]);\n const toIndex = data.findIndex((d) => d === filteredData[event.currentIndex]);\n if (fromIndex >= 0 && toIndex >= 0) {\n moveItemInArray(data, fromIndex, toIndex);\n this.renderRows();\n this.rowDrop.emit(\n new DataGridRowDropEvent({\n item: data[fromIndex],\n data,\n fromIndex,\n toIndex,\n })\n );\n }\n }\n\n protected override setUpPaginator(paginator: MatPaginator, showPaginator: boolean): void {\n this.dataSource.paginator = paginator && showPaginator ? paginator : null;\n }\n\n protected override computeDisplayedColumns(): string[] {\n return this.decorateColumns(this.displayedColumns());\n }\n\n protected onColumnsChanged(columns: string[]): void {\n super.onColumnsChanged(columns);\n this.allDisplayedColumns.set(this.decorateColumns(columns));\n }\n\n protected override setUpFilterPredicate(): void {\n const filterPredicate = this.filterPredicate();\n if (filterPredicate) {\n this.dataSource.filterPredicate = filterPredicate;\n } else if (this.searchableColumns()?.length > 0) {\n this.dataSource.filterPredicate = (data: unknown, filter: string) => {\n return this.searchableColumns().some((column) => {\n const value = data[column];\n return value?.toString().toLowerCaseAndWithoutDiacritics().includes(filter);\n });\n };\n }\n }\n\n protected override forceReloadFilter(): void {\n // eslint-disable-next-line no-self-assign\n this.dataSource.filter = this.dataSource.filter;\n }\n\n /** `position` and `expand` are grid-owned columns, not part of the caller's list. */\n private decorateColumns(columns: string[]): string[] {\n let decorated = [...columns];\n if (this.allowReordering() && !decorated.includes('position')) {\n decorated = ['position', ...decorated];\n }\n if (this.showExpandButton() && !decorated.includes('expand')) {\n decorated = ['expand', ...decorated];\n }\n return decorated;\n }\n\n private setData(data: any[]): void {\n if (!Array.isArray(data)) {\n return;\n }\n this.dataSource.data = data;\n setTimeout(() => {\n this.table?.setNoDataRow(this.noDataRow);\n this.table?.renderRows();\n }, 0);\n }\n}","@if (showHeader()) {\n <cauca-data-grid-header [disableAddButton]=\"disableAddButton()\" [showAddButton]=\"showAddButton()\" [addButtonLabel]=\"addButtonLabel() || defaultAddButtonLabel\"\n (addButtonClick)=\"addButtonClick.emit($event)\" (filterChange)=\"applyFilter($event)\"\n [showRefreshButton]=\"showRefreshButton()\" (refreshRequest)=\"onRefreshData()\">\n <ng-content select=\"[filterRow]\" ngProjectAs=\"[filterRow]\" />\n </cauca-data-grid-header>\n}\n\n@if (showTableOptions()) {\n <cauca-data-grid-table-options [showColumnViewer]=\"showColumnViewer()\" [gridName]=\"resourceName()\" (densityChanged)=\"onDensityChanged($event)\" (columnsChanged)=\"onColumnsChanged($event)\" [allColumns]=\"regularColumns\">\n <ng-content\n select=\"[customFilter]\"\n ngProjectAs=\"[customFilter]\">\n </ng-content>\n </cauca-data-grid-table-options>\n}\n\n<table matSort mat-table [dataSource]=\"dataSource\" cdkDropList [cdkDropListData]=\"dataSource\"\n (cdkDropListDropped)=\"rowDropped($event)\" aria-describedby=\"Data Grid\" multiTemplateDataRows>\n\n @if (showExpandButton()) {\n <ng-container matColumnDef=\"expand\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell *matCellDef=\"let row\">\n <div class=\"align-commands\">\n <cauca-icon-button (buttonClick)=\"onExpand(row)\" color=\"primary\" class=\"button-icon\" [icon]=\"row === expandedElement() ? 'keyboard_arrow_up' : 'keyboard_arrow_down'\" />\n </div>\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n }\n\n @if (allowReordering()) {\n <ng-container matColumnDef=\"position\">\n <th mat-header-cell *matHeaderCellDef></th>\n <td mat-cell (mousedown)=\"dragDisabled = false\" (mouseup)=\"dragDisabled = true\" *matCellDef=\"let group\">\n <mat-icon class=\"reorder-icon\" cdkDragHandle>drag_indicator</mat-icon>\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n }\n\n <ng-content></ng-content>\n\n <tr class=\"grid-header\" [style.background-color]=\"headerColor()\" mat-header-row *matHeaderRowDef=\"allDisplayedColumns(); sticky: true\"></tr>\n\n <tr mat-row *matRowDef=\"let row; columns: allDisplayedColumns();\" cdkDrag [cdkDragData]=\"row\"\n [cdkDragDisabled]=\"dragDisabled\" (cdkDragReleased)=\"dragDisabled = true\"\n [class.compact]=\"preference.gridDensity === density.Compact\" [class.standard]=\"preference.gridDensity === density.Standard\"\n [class.comfortable]=\"preference.gridDensity === density.Comfortable\"></tr>\n\n <ng-container matColumnDef=\"expandedDetail\">\n <td mat-cell *matCellDef=\"let row\" [attr.colspan]=\"allDisplayedColumns().length\">\n <div class=\"grid-element-detail-wrapper\" [class.grid-element-detail-wrapper-expanded]=\"row === expandedElement()\">\n <div class=\"grid-element-detail\">\n @if (row === expandedElement()) {\n <template [ngTemplateOutlet]=\"rowDetailTemplate\" [ngTemplateOutletContext]=\"{ row: expandedElement() }\"></template>\n }\n </div>\n </div>\n </td>\n </ng-container>\n\n <tr mat-row *matRowDef=\"let row; columns: ['expandedDetail']\" class=\"grid-detail-row\"></tr>\n\n @if (showFooter()) {\n <tr mat-footer-row *matFooterRowDef=\"allDisplayedColumns(); sticky: false\"></tr>\n }\n\n @if (!isLoading()) {\n <tr class=\"mat-row\" *matNoDataRow>\n @if (!showLoadingError() && !isLoading()) {\n <td class=\"mat-cell data-grid-message\"\n [attr.colspan]=\"allDisplayedColumns().length\">\n <cauca-status-message type=\"noData\" />\n </td>\n }\n @if (showLoadingError()) {\n <td class=\"mat-cell data-grid-message error-message\" [attr.colspan]=\"allDisplayedColumns().length\">\n <cauca-status-message type=\"error\" />\n </td>\n }\n </tr>\n }\n\n</table>\n\n<cauca-data-grid-progress-spiner [isLoading]=\"isLoading()\" />\n\n@if (showPaginator()) {\n <mat-paginator #paginator [hidePageSize]=\"hidePageSize()\" [pageSizeOptions]=\"[5, 10, 25, 50]\" [pageSize]=\"10\" />\n}","import {EventEmitter, Injectable, signal} from \"@angular/core\";\nimport {ODataDataSource, ODataFilter} from \"odata-data-source\";\nimport {distinctUntilChanged, Subscription} from \"rxjs\";\nimport {HttpClient} from \"@angular/common/http\";\n\nexport interface Paginator {\n pageIndex: number;\n pageSize: number;\n length: number;\n page: EventEmitter<any>;\n}\n\ninterface SingleSort {\n active: string;\n direction: 'asc' | 'desc' | '';\n sortChange: EventEmitter<any>;\n}\ninterface MultiSort {\n sortedBy: {\n id: string;\n direction: 'asc' | 'desc';\n }[];\n multiSortChange: EventEmitter<any>;\n}\n\nexport type Sort = SingleSort | MultiSort;\n\ninterface Props {\n baseUrl: string;\n resourceName: string;\n paginator: Paginator\n sort?: Sort;\n initialSort?: string[];\n filters: ODataFilter[]\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class OdataListingService<T = any> {\n public dataSource: ODataDataSource | null = null;\n public error = signal(false);\n public loading = signal(false);\n private errorSubscription: Subscription | null = null;\n private loadingSubscription: Subscription | null = null;\n\n public get data(): T[] | null {\n return this.dataSource?.data ?? null;\n }\n\n public constructor(private readonly httpClient: HttpClient) {}\n\n public initialize({baseUrl, resourceName, paginator, sort, initialSort, filters}: Props): ODataDataSource {\n const newDataSource = new ODataDataSource(this.httpClient, `${baseUrl}odata/${resourceName}`);\n\n newDataSource.filters = filters;\n if (initialSort){\n newDataSource.initialSort = initialSort;\n }\n if (sort) {\n newDataSource.sort = sort;\n }\n newDataSource.paginator = paginator;\n this.dataSource = newDataSource;\n\n this.dataSource.refresh();\n this.errorSubscription = this.dataSource.errors.subscribe((errors) => this.error.set(!!errors));\n\n this.loadingSubscription = this.dataSource.loading\n .pipe(distinctUntilChanged())\n .subscribe((isLoading: boolean) => {\n if (isLoading) {\n this.loading.set(false);\n }\n this.loading.set(isLoading);\n });\n\n return newDataSource;\n }\n\n public updateFilters(filters: ODataFilter[]) {\n if (this.dataSource) {\n this.dataSource.filters = filters;\n }\n }\n\n public forceReloadFilter() {\n if (this.dataSource) {\n // eslint-disable-next-line no-self-assign\n this.dataSource.filters = this.dataSource.filters;\n }\n }\n\n public isInitialized() {\n return this.dataSource !== null;\n }\n\n public refresh() {\n this.dataSource?.refresh();\n }\n\n public destroy() {\n this.errorSubscription?.unsubscribe();\n this.loadingSubscription?.unsubscribe();\n }\n}\n","import { ParamMap, Params } from '@angular/router';\n\nexport interface GridUrlState {\n filter?: string;\n sort?: string;\n direction?: 'asc' | 'desc' | '';\n pageIndex?: number;\n pageSize?: number;\n}\n\nconst PARAM_BASE_NAMES = {\n filter: 'filter',\n sort: 'sort',\n direction: 'dir',\n pageIndex: 'page',\n pageSize: 'size',\n} as const;\n\n/**\n * Builds the query param name for a given base name and optional grid state key.\n * With a key, the scheme is `<key><CapitalizedBaseName>` (e.g. key \"alerts\" + base \"filter\" -> \"alertsFilter\").\n * Without a key, the plain base name is used (e.g. \"filter\").\n */\nfunction paramName(baseName: string, key: string): string {\n if (!key) {\n return baseName;\n }\n return `${key}${baseName.charAt(0).toUpperCase()}${baseName.slice(1)}`;\n}\n\nexport function toQueryParams(state: GridUrlState, key: string = ''): Params {\n const params: Params = {};\n\n if (state.filter) {\n params[paramName(PARAM_BASE_NAMES.filter, key)] = state.filter;\n }\n if (state.sort) {\n params[paramName(PARAM_BASE_NAMES.sort, key)] = state.sort;\n }\n if (state.direction) {\n params[paramName(PARAM_BASE_NAMES.direction, key)] = state.direction;\n }\n if (state.pageIndex) {\n params[paramName(PARAM_BASE_NAMES.pageIndex, key)] = state.pageIndex;\n }\n if (state.pageSize) {\n params[paramName(PARAM_BASE_NAMES.pageSize, key)] = state.pageSize;\n }\n\n return params;\n}\n\nexport function fromQueryParams(map: ParamMap, key: string = ''): GridUrlState {\n const state: GridUrlState = {};\n\n const filter = map.get(paramName(PARAM_BASE_NAMES.filter, key));\n if (filter) {\n state.filter = filter;\n }\n\n const sort = map.get(paramName(PARAM_BASE_NAMES.sort, key));\n if (sort) {\n state.sort = sort;\n }\n\n const direction = map.get(paramName(PARAM_BASE_NAMES.direction, key));\n if (direction === 'asc' || direction === 'desc') {\n state.direction = direction;\n }\n\n const pageIndex = parsePositiveInteger(map.get(paramName(PARAM_BASE_NAMES.pageIndex, key)));\n if (pageIndex !== undefined) {\n state.pageIndex = pageIndex;\n }\n\n const pageSize = parsePositiveInteger(map.get(paramName(PARAM_BASE_NAMES.pageSize, key)));\n if (pageSize !== undefined) {\n state.pageSize = pageSize;\n }\n\n return state;\n}\n\n/**\n * Returns a Params object with every grid-state param name mapped to `null` for the given key.\n * Combined with `queryParamsHandling: 'merge'`, this lets the grid explicitly clear stale\n * params (e.g. a previously-set filter) instead of leaving them stuck in the URL.\n */\nexport function emptyQueryParams(key: string = ''): Params {\n const params: Params = {};\n Object.values(PARAM_BASE_NAMES).forEach((baseName) => {\n params[paramName(baseName, key)] = null;\n });\n return params;\n}\n\nexport function paramsEqual(a: Params, b: Params): boolean {\n const aKeys = Object.keys(a).filter((paramKey) => a[paramKey] !== null && a[paramKey] !== undefined);\n const bKeys = Object.keys(b).filter((paramKey) => b[paramKey] !== null && b[paramKey] !== undefined);\n\n if (aKeys.length !== bKeys.length) {\n return false;\n }\n\n return aKeys.every((paramKey) => String(a[paramKey]) === String(b[paramKey]));\n}\n\nfunction parsePositiveInteger(value: string | null): number | undefined {\n if (!value) {\n return undefined;\n }\n const parsed = Number(value);\n if (Number.isNaN(parsed) || parsed < 0) {\n return undefined;\n }\n return parsed;\n}\n","import { AfterViewInit, ChangeDetectionStrategy, Component, ContentChild, DestroyRef, EventEmitter, inject, input, OnDestroy, output, TemplateRef, ViewChild } from '@angular/core';\nimport { outputToObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ODataFilter } from 'odata-data-source';\nimport { MatSort } from '@angular/material/sort';\nimport { ActivatedRoute, ParamMap, Params, Router } from '@angular/router';\nimport { BaseDataGridComponent } from '../base-data-grid.component';\nimport { CaucaCoreService, toLowerCaseAndWithoutDiacritics } from '@cauca-911/core';\nimport { MatPaginator } from '@angular/material/paginator';\nimport { OdataListingServi