From 7359b9fe7409d8f44764f5fd40a0a710833f6607 Mon Sep 17 00:00:00 2001 From: Ross Perry Date: Tue, 15 Jul 2025 18:36:16 +0000 Subject: [PATCH 1/8] header menu actions fxnal --- .../list/grid/cell-header-menu.component.html | 75 +++++++++- .../list/grid/cell-header-menu.component.ts | 138 ++++++++++++++++-- .../list/grid/grid.component.ts | 30 ++-- .../list/inventory.component.ts | 2 +- 4 files changed, 214 insertions(+), 31 deletions(-) diff --git a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html index 90385dd6..da9bccaa 100644 --- a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html +++ b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html @@ -1,10 +1,71 @@ -
+ +
{{ params.displayName }} - + +
- -@if (menuVisible) { -
- + + + +
+ +
+ + Sort Ascending +
+ +
+ + Sort Descending +
+ + @if (sortIcon) { +
+ + Clear Sort +
+ } + + + +
+ + Pin Left +
+
+ + Pin right +
+ @if (pinState) { +
+ + Unpin +
+ } + + + + +
+ + Reset This Column +
+ +
+
+ Reset All Columns +
+
-} + +
+ diff --git a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.ts b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.ts index 8a60e43f..81ac1f5b 100644 --- a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.ts +++ b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.ts @@ -1,30 +1,142 @@ -import { Component } from '@angular/core' +import type { FlexibleConnectedPositionStrategyOrigin, OverlayRef } from '@angular/cdk/overlay' +import { Overlay } from '@angular/cdk/overlay' +import { TemplatePortal } from '@angular/cdk/portal' +import { CommonModule } from '@angular/common' +import type { AfterViewInit, TemplateRef } from '@angular/core' +import { Component, inject, ViewChild, ViewContainerRef } from '@angular/core' import { MatIconModule } from '@angular/material/icon' import type { IHeaderAngularComp } from 'ag-grid-angular' -import type { IHeaderParams } from 'ag-grid-community' +import type { Column, GridApi, IHeaderParams } from 'ag-grid-community' +import { ConfigService } from '@seed/services' +import { MatCardModule } from '@angular/material/card' +import { MatDividerModule } from '@angular/material/divider' +import { MatButtonModule } from '@angular/material/button' +import { MatSelectModule } from '@angular/material/select' +import { trueGray } from 'tailwindcss/colors' @Component({ selector: 'seed-inventory-grid-cell-header-menu', templateUrl: './cell-header-menu.component.html', - imports: [MatIconModule], + imports: [CommonModule, MatCardModule, MatDividerModule, MatButtonModule, MatIconModule, MatSelectModule], }) -export class CellHeaderMenuComponent implements IHeaderAngularComp { - // THIS COMPONENT IS STILL IN DEVELOPMENT - public params: IHeaderParams - public menuVisible = false +export class CellHeaderMenuComponent implements IHeaderAngularComp, AfterViewInit { + @ViewChild('menu') menuTemplate!: TemplateRef + @ViewChild('trigger') trigger!: FlexibleConnectedPositionStrategyOrigin + + private _configService = inject(ConfigService) + params: IHeaderParams + overlay = inject(Overlay) + vcr = inject(ViewContainerRef) + overlayRef: OverlayRef + column: Column + scheme: 'dark' | 'light' + sortIcon = '' + pinState: unknown + opened = false + gridApi: GridApi agInit(params: IHeaderParams): void { this.params = params + this.column = params.column + this.gridApi = params.api + } + + ngAfterViewInit(): void { + this.getScheme() + this.setOverlay() + this.updateSortState() + this.column.addEventListener('sortChanged', () => { + this.updateSortState() + }) + this.gridApi.addEventListener('columnPinned', () => { + this.pinState = this.column.isPinned() + }) + } + + getScheme() { + this._configService.scheme$.subscribe((scheme) => { + this.scheme = scheme + }) + } + + updateSortState() { + const state = this.gridApi.getColumnState().find((col) => col.colId === this.params.column.getColId()) + const sortDir = state?.sort ?? null + const iconMap = { + asc: 'fa-solid:arrow-up', + desc: 'fa-solid:arrow-down', + } + this.sortIcon = iconMap[sortDir] ?? '' + } + + setOverlay() { + const positionStrategy = this.overlay + .position() + .flexibleConnectedTo(this.trigger) + .withPositions([ + { + originX: 'end', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top', + }, + ]) + + this.overlayRef = this.overlay.create({ + positionStrategy, + hasBackdrop: true, + backdropClass: 'transparent-backdrop', + }) + + this.overlayRef.backdropClick().subscribe(() => { + this.overlayRef.detach() + }) + } + + toggleMenu(): void { + // event.stopPropagation() + // this.menuVisible = !this.menuVisible + if (this.overlayRef?.hasAttached()) { + this.overlayRef.detach() + } else { + const portal = new TemplatePortal(this.menuTemplate, this.vcr) + this.overlayRef?.attach(portal) + } + } + + sortCol(direction: 'asc' | 'desc' | null): void { + this.gridApi.applyColumnState({ + state: [{ colId: this.params.column.getColId(), sort: direction }], + defaultState: { sort: null }, + }) + this.detach() + } + + pinCol(direction: 'left' | 'right' | null): void { + this.gridApi.setColumnsPinned([this.params.column], direction) + this.detach() + } + + resetCol() { + const colId = this.column.getColId() + this.gridApi.applyColumnState({ state: [{ colId }], defaultState: {} }) + this.gridApi.autoSizeColumns([this.column]) + this.detach() + } + + resetAll() { + const columns = this.gridApi.getColumns() + this.gridApi.resetColumnState() + this.gridApi.autoSizeColumns(columns) + this.detach() } - toggleMenu(event: MouseEvent): void { - event.stopPropagation() - this.menuVisible = !this.menuVisible + tempAction() { + console.log('temp action') } - sortAsc(): void { - console.log('sort asc') - this.menuVisible = false + detach() { + this.overlayRef.detach() } refresh() { diff --git a/src/app/modules/inventory-list/list/grid/grid.component.ts b/src/app/modules/inventory-list/list/grid/grid.component.ts index 6495f2f4..9624f07d 100644 --- a/src/app/modules/inventory-list/list/grid/grid.component.ts +++ b/src/app/modules/inventory-list/list/grid/grid.component.ts @@ -8,7 +8,7 @@ import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community' import type { Label } from '@seed/api/label' import { ConfigService } from '@seed/services' import type { FiltersSorts, InventoryType, Pagination } from '../../../inventory/inventory.types' -// import { CellHeaderMenuComponent } from './cell-header-menu.component' +import { CellHeaderMenuComponent } from './cell-header-menu.component' import { InventoryGridControlsComponent } from './grid-controls.component' ModuleRegistry.registerModules([AllCommunityModule]) @@ -19,7 +19,7 @@ ModuleRegistry.registerModules([AllCommunityModule]) imports: [ AgGridAngular, AgGridModule, - // CellHeaderMenuComponent, + CellHeaderMenuComponent, CommonModule, InventoryGridControlsComponent, ], @@ -105,31 +105,36 @@ export class InventoryGridComponent implements OnChanges { } getColumnDefs() { + const stateColumns = this.columnDefs.map((c) => ({ ...c, headerComponent: CellHeaderMenuComponent })) + this.columnDefs = [ { headerName: 'Shortcuts', children: this.getShortcutColumns() } as ColGroupDef, - { headerName: 'Details', children: this.columnDefs } as ColGroupDef, + { headerName: 'Details', children: stateColumns } as ColGroupDef, ] } getShortcutColumns(): ColDef[] { const shortcutColumns = [ this.buildInfoCell(), - this.buildShortcutColumn('merged_indicator', 'Merged', 85, 'share'), - this.buildShortcutColumn('meters_exist_indicator', 'Meters', 80, 'bolt', 'meters'), - this.buildShortcutColumn('notes_count', 'Notes', 80, 'mode_comment', 'notes'), - this.buildShortcutColumn('groups_indicator', 'Groups', 80, 'G'), + this.buildShortcutColumn('merged_indicator', 'Merged', 82, 'share'), + this.buildShortcutColumn('meters_exist_indicator', 'Meters', 78, 'bolt', 'meters'), + this.buildShortcutColumn('notes_count', 'Notes', 71, 'mode_comment', 'notes'), + this.buildShortcutColumn('groups_indicator', 'Groups', 79, 'G'), this.buildLabelsCell(), ] return shortcutColumns } - buildShortcutColumn(field: string, headerName: string, width: number, icon: string, action: string = null): ColDef { + buildShortcutColumn(field: string, headerName: string, maxWidth: number, icon: string, action: string = null): ColDef { return { field, headerName, - width, + maxWidth, filter: false, sortable: false, + suppressMovable: true, + headerClass: 'white-space-normal', + cellClass: 'overflow-hidden', cellRenderer: ({ value }) => this.actionRenderer(value, icon, action), } } @@ -141,7 +146,9 @@ export class InventoryGridComponent implements OnChanges { headerName: 'Info', filter: false, sortable: false, - width: 60, + resizable: false, + suppressMovable: true, + maxWidth: 60, cellRenderer: ({ value }) => this.actionRenderer(value, 'info', 'detail'), } } @@ -168,6 +175,7 @@ export class InventoryGridComponent implements OnChanges { width: 80, filter: false, sortable: false, + suppressMovable: true, // labels come in as an array of ids [1,2,3]. Ag grid needs them formatted as a string valueFormatter: ({ value }: { value: number[] }) => { const labels = value @@ -194,9 +202,11 @@ export class InventoryGridComponent implements OnChanges { resetGrid = () => { if (!this.gridApi) return + const columns = this.gridApi.getColumns() this.gridApi.setFilterModel(null) this.gridApi.applyColumnState({ state: [], applyOrder: true }) this.gridApi.resetColumnState() + this.gridApi.autoSizeColumns(columns) this.gridApi.refreshClientSideRowModel() this.gridApi.refreshCells({ force: true }) this.gridReset.emit() diff --git a/src/app/modules/inventory-list/list/inventory.component.ts b/src/app/modules/inventory-list/list/inventory.component.ts index bbcd59c9..01a35067 100644 --- a/src/app/modules/inventory-list/list/inventory.component.ts +++ b/src/app/modules/inventory-list/list/inventory.component.ts @@ -180,7 +180,7 @@ export class InventoryComponent implements OnDestroy, OnInit { this.cycles = cycles this.cycle = this.cycles.find((c) => c.id === this.userSettings?.cycleId) ?? this.cycles[0] - this.cycleId = this.cycle.id + this.cycleId = this.cycle?.id this.propertyProfiles = profiles.filter((p) => p.inventory_type === 0) this.taxlotProfiles = profiles.filter((p) => p.inventory_type === 1) From a1c7395b38f9bc36da61ab7151b34b644d697c47 Mon Sep 17 00:00:00 2001 From: Ross Perry Date: Tue, 15 Jul 2025 19:00:59 +0000 Subject: [PATCH 2/8] user settings reset --- .../list/grid/cell-header-menu.component.html | 14 ++++++++++---- .../inventory-list/list/grid/grid.component.ts | 17 +++++++++++++++++ .../list/inventory.component.html | 3 +++ src/styles/styles.scss | 3 +++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html index da9bccaa..ee22cf15 100644 --- a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html +++ b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html @@ -1,15 +1,21 @@
- {{ params.displayName }} - - +
+ {{ params.displayName }} + @if (sortIcon) { + + } +
+
+ +
diff --git a/src/app/modules/inventory-list/list/grid/grid.component.ts b/src/app/modules/inventory-list/list/grid/grid.component.ts index 9624f07d..c2513611 100644 --- a/src/app/modules/inventory-list/list/grid/grid.component.ts +++ b/src/app/modules/inventory-list/list/grid/grid.component.ts @@ -5,7 +5,10 @@ import { Router } from '@angular/router' import { AgGridAngular, AgGridModule } from 'ag-grid-angular' import type { CellClickedEvent, ColDef, ColGroupDef, GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community' import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community' +import { take } from 'rxjs' import type { Label } from '@seed/api/label' +import type { OrganizationUserSettings } from '@seed/api/organization' +import { OrganizationService } from '@seed/api/organization' import { ConfigService } from '@seed/services' import type { FiltersSorts, InventoryType, Pagination } from '../../../inventory/inventory.types' import { CellHeaderMenuComponent } from './cell-header-menu.component' @@ -28,16 +31,20 @@ export class InventoryGridComponent implements OnChanges { @Input() columnDefs!: ColDef[] @Input() inventoryType: string @Input() labelMap: Record + @Input() orgId: number + @Input() orgUserId: number @Input() pagination!: Pagination @Input() rowData!: Record[] @Input() selectedViewIds: number[] @Input() type: InventoryType + @Input() userSettings: OrganizationUserSettings @Output() pageChange = new EventEmitter() @Output() filterSortChange = new EventEmitter() @Output() gridReady = new EventEmitter() @Output() selectionChanged = new EventEmitter() @Output() gridReset = new EventEmitter() private _configService = inject(ConfigService) + private _organizationService = inject(OrganizationService) private _router = inject(Router) agPageSize = 100 @@ -209,9 +216,19 @@ export class InventoryGridComponent implements OnChanges { this.gridApi.autoSizeColumns(columns) this.gridApi.refreshClientSideRowModel() this.gridApi.refreshCells({ force: true }) + this.resetUserSettings() + this.gridReset.emit() } + resetUserSettings() { + this.userSettings = {} + + this._organizationService.updateOrganizationUser(this.orgUserId, this.orgId, this.userSettings) + .pipe((take(1))) + .subscribe() + } + /* * ascending sorts formatted as 'column_id' * descending sorts formatted as '-column_id' diff --git a/src/app/modules/inventory-list/list/inventory.component.html b/src/app/modules/inventory-list/list/inventory.component.html index 06950a1f..089f9bd5 100644 --- a/src/app/modules/inventory-list/list/inventory.component.html +++ b/src/app/modules/inventory-list/list/inventory.component.html @@ -55,10 +55,13 @@ [columnDefs]="columnDefs" [inventoryType]="type" [labelMap]="labelMap" + [orgId]="orgId" + [orgUserId]="orgUserId" [pagination]="pagination" [rowData]="rowData" [selectedViewIds]="selectedViewIds" [type]="type" + [userSettings]="userSettings" (pageChange)="onPageChange($event)" (filterSortChange)="onFilterSortChange($event)" (gridReady)="onGridReady($event)" diff --git a/src/styles/styles.scss b/src/styles/styles.scss index ea3bedf1..90a99152 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -134,6 +134,9 @@ overflow: visible !important } } + .ng-star-inserted { + width: 100%; + } } .seed-dialog-panel { From fd874102b5d87647037596627d9ed6037f90e996 Mon Sep 17 00:00:00 2001 From: Ross Perry Date: Wed, 16 Jul 2025 12:23:26 +0000 Subject: [PATCH 3/8] update user settings on menu click --- .../list/grid/cell-header-menu.component.html | 2 +- .../list/grid/cell-header-menu.component.ts | 57 ++++++++++++------- .../list/grid/filter-sort-chips.component.ts | 4 +- .../list/grid/grid.component.ts | 17 +++++- .../list/inventory.component.html | 1 + 5 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html index ee22cf15..9ed6ba96 100644 --- a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html +++ b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html @@ -15,7 +15,7 @@
diff --git a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.ts b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.ts index 81ac1f5b..30723858 100644 --- a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.ts +++ b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.ts @@ -4,41 +4,53 @@ import { TemplatePortal } from '@angular/cdk/portal' import { CommonModule } from '@angular/common' import type { AfterViewInit, TemplateRef } from '@angular/core' import { Component, inject, ViewChild, ViewContainerRef } from '@angular/core' +import { MatButtonModule } from '@angular/material/button' +import { MatDividerModule } from '@angular/material/divider' import { MatIconModule } from '@angular/material/icon' +import { MatSelectModule } from '@angular/material/select' import type { IHeaderAngularComp } from 'ag-grid-angular' import type { Column, GridApi, IHeaderParams } from 'ag-grid-community' +import { take } from 'rxjs' +import { OrganizationService, type OrganizationUserSettings } from '@seed/api/organization' +import type { CurrentUser } from '@seed/api/user' import { ConfigService } from '@seed/services' -import { MatCardModule } from '@angular/material/card' -import { MatDividerModule } from '@angular/material/divider' -import { MatButtonModule } from '@angular/material/button' -import { MatSelectModule } from '@angular/material/select' -import { trueGray } from 'tailwindcss/colors' +import type { InventoryType } from 'app/modules/inventory/inventory.types' @Component({ selector: 'seed-inventory-grid-cell-header-menu', templateUrl: './cell-header-menu.component.html', - imports: [CommonModule, MatCardModule, MatDividerModule, MatButtonModule, MatIconModule, MatSelectModule], + imports: [CommonModule, MatDividerModule, MatButtonModule, MatIconModule, MatSelectModule], }) export class CellHeaderMenuComponent implements IHeaderAngularComp, AfterViewInit { @ViewChild('menu') menuTemplate!: TemplateRef @ViewChild('trigger') trigger!: FlexibleConnectedPositionStrategyOrigin private _configService = inject(ConfigService) - params: IHeaderParams - overlay = inject(Overlay) - vcr = inject(ViewContainerRef) - overlayRef: OverlayRef + private _organizationService = inject(OrganizationService) + private _overlay = inject(Overlay) + private _vcr = inject(ViewContainerRef) column: Column + gridApi: GridApi + opened = false + orgId: number + orgUserId: number + overlayRef: OverlayRef + params: IHeaderParams + pinState: unknown scheme: 'dark' | 'light' sortIcon = '' - pinState: unknown - opened = false - gridApi: GridApi + type: InventoryType + userSettings: OrganizationUserSettings - agInit(params: IHeaderParams): void { + agInit(params: IHeaderParams & { currentUser: CurrentUser; type: InventoryType }): void { this.params = params this.column = params.column this.gridApi = params.api + this.type = params.type + const { org_id, org_user_id, settings } = params.currentUser + this.orgId = org_id + this.orgUserId = org_user_id + this.userSettings = settings } ngAfterViewInit(): void { @@ -70,7 +82,7 @@ export class CellHeaderMenuComponent implements IHeaderAngularComp, AfterViewIni } setOverlay() { - const positionStrategy = this.overlay + const positionStrategy = this._overlay .position() .flexibleConnectedTo(this.trigger) .withPositions([ @@ -82,7 +94,7 @@ export class CellHeaderMenuComponent implements IHeaderAngularComp, AfterViewIni }, ]) - this.overlayRef = this.overlay.create({ + this.overlayRef = this._overlay.create({ positionStrategy, hasBackdrop: true, backdropClass: 'transparent-backdrop', @@ -99,7 +111,7 @@ export class CellHeaderMenuComponent implements IHeaderAngularComp, AfterViewIni if (this.overlayRef?.hasAttached()) { this.overlayRef.detach() } else { - const portal = new TemplatePortal(this.menuTemplate, this.vcr) + const portal = new TemplatePortal(this.menuTemplate, this._vcr) this.overlayRef?.attach(portal) } } @@ -109,6 +121,11 @@ export class CellHeaderMenuComponent implements IHeaderAngularComp, AfterViewIni state: [{ colId: this.params.column.getColId(), sort: direction }], defaultState: { sort: null }, }) + const dir = direction === 'desc' ? '-' : '' + const colDef = this.column.getColDef() + const sort = `${dir}${colDef.field}` + this.userSettings.sorts?.[this.type].push(sort) + this.detach() } @@ -131,8 +148,10 @@ export class CellHeaderMenuComponent implements IHeaderAngularComp, AfterViewIni this.detach() } - tempAction() { - console.log('temp action') + updateOrgUserSettings() { + return this._organizationService.updateOrganizationUser(this.orgUserId, this.orgId, this.userSettings) + .pipe(take(1)) + .subscribe() } detach() { diff --git a/src/app/modules/inventory-list/list/grid/filter-sort-chips.component.ts b/src/app/modules/inventory-list/list/grid/filter-sort-chips.component.ts index 1312552b..f7b79a04 100644 --- a/src/app/modules/inventory-list/list/grid/filter-sort-chips.component.ts +++ b/src/app/modules/inventory-list/list/grid/filter-sort-chips.component.ts @@ -20,8 +20,8 @@ export class FilterSortChipsComponent implements OnChanges { filterChips: FilterSortChip[] = [] sortChips: FilterSortChip[] = [] - ngOnChanges({ userSettings }: SimpleChanges) { - if (userSettings?.currentValue) { + ngOnChanges({ userSettings, columnDefs }: SimpleChanges) { + if (userSettings?.currentValue || columnDefs?.currentValue) { this.getFilterChips() this.getSortChips() } diff --git a/src/app/modules/inventory-list/list/grid/grid.component.ts b/src/app/modules/inventory-list/list/grid/grid.component.ts index c2513611..d29e0246 100644 --- a/src/app/modules/inventory-list/list/grid/grid.component.ts +++ b/src/app/modules/inventory-list/list/grid/grid.component.ts @@ -9,6 +9,7 @@ import { take } from 'rxjs' import type { Label } from '@seed/api/label' import type { OrganizationUserSettings } from '@seed/api/organization' import { OrganizationService } from '@seed/api/organization' +import type { CurrentUser } from '@seed/api/user' import { ConfigService } from '@seed/services' import type { FiltersSorts, InventoryType, Pagination } from '../../../inventory/inventory.types' import { CellHeaderMenuComponent } from './cell-header-menu.component' @@ -29,6 +30,7 @@ ModuleRegistry.registerModules([AllCommunityModule]) }) export class InventoryGridComponent implements OnChanges { @Input() columnDefs!: ColDef[] + @Input() currentUser: CurrentUser @Input() inventoryType: string @Input() labelMap: Record @Input() orgId: number @@ -112,7 +114,7 @@ export class InventoryGridComponent implements OnChanges { } getColumnDefs() { - const stateColumns = this.columnDefs.map((c) => ({ ...c, headerComponent: CellHeaderMenuComponent })) + const stateColumns = this.addHeaderMenu() this.columnDefs = [ { headerName: 'Shortcuts', children: this.getShortcutColumns() } as ColGroupDef, @@ -132,6 +134,18 @@ export class InventoryGridComponent implements OnChanges { return shortcutColumns } + addHeaderMenu() { + const stateColumns = this.columnDefs.map((c) => ({ + ...c, + headerComponent: CellHeaderMenuComponent, + headerComponentParams: { + currentUser: this.currentUser, + type: this.type, + }, + })) + return stateColumns + } + buildShortcutColumn(field: string, headerName: string, maxWidth: number, icon: string, action: string = null): ColDef { return { field, @@ -223,7 +237,6 @@ export class InventoryGridComponent implements OnChanges { resetUserSettings() { this.userSettings = {} - this._organizationService.updateOrganizationUser(this.orgUserId, this.orgId, this.userSettings) .pipe((take(1))) .subscribe() diff --git a/src/app/modules/inventory-list/list/inventory.component.html b/src/app/modules/inventory-list/list/inventory.component.html index 089f9bd5..90fc3cd7 100644 --- a/src/app/modules/inventory-list/list/inventory.component.html +++ b/src/app/modules/inventory-list/list/inventory.component.html @@ -53,6 +53,7 @@ Date: Thu, 17 Jul 2025 15:18:29 +0000 Subject: [PATCH 4/8] display filter/sort regardless of colDef --- .../list/grid/filter-sort-chips.component.ts | 47 ++++++++++++++----- .../list/inventory.component.html | 3 +- .../list/inventory.component.ts | 10 +++- src/app/modules/inventory/inventory.types.ts | 2 +- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/app/modules/inventory-list/list/grid/filter-sort-chips.component.ts b/src/app/modules/inventory-list/list/grid/filter-sort-chips.component.ts index f7b79a04..831fa00e 100644 --- a/src/app/modules/inventory-list/list/grid/filter-sort-chips.component.ts +++ b/src/app/modules/inventory-list/list/grid/filter-sort-chips.component.ts @@ -4,6 +4,7 @@ import { Component, Input } from '@angular/core' import { MatChipsModule } from '@angular/material/chips' import { MatIconModule } from '@angular/material/icon' import type { ColDef, GridApi } from 'ag-grid-community' +import type { Column } from '@seed/api/column' import type { OrganizationUserSettings } from '@seed/api/organization' import type { AgFilter, FilterSortChip, FilterType, InventoryType } from '../../../inventory/inventory.types' @@ -13,14 +14,16 @@ import type { AgFilter, FilterSortChip, FilterType, InventoryType } from '../../ imports: [CommonModule, MatChipsModule, MatIconModule], }) export class FilterSortChipsComponent implements OnChanges { - @Input() gridApi: GridApi + @Input() columns: Column[] @Input() columnDefs: ColDef[] + @Input() gridApi: GridApi @Input() userSettings: OrganizationUserSettings @Input() type: InventoryType filterChips: FilterSortChip[] = [] sortChips: FilterSortChip[] = [] - ngOnChanges({ userSettings, columnDefs }: SimpleChanges) { + ngOnChanges(changes: SimpleChanges) { + const { userSettings, columnDefs } = changes if (userSettings?.currentValue || columnDefs?.currentValue) { this.getFilterChips() this.getSortChips() @@ -39,12 +42,20 @@ export class FilterSortChipsComponent implements OnChanges { this.filterChips = [] for (const columnName of Object.keys(this.filters)) { const colDef = this.columnDefs.find(({ field }) => field === columnName) - - if (!colDef) return - - const displayName = this.buildFilterDisplayName(colDef, this.filters[columnName]) - const chip = { field: colDef.field, displayName, original: columnName } - this.filterChips.push(chip) + if (colDef) { + this.filterChips.push({ + field: colDef.field, + displayName: this.buildFilterDisplayName(colDef, this.filters[columnName]), + original: columnName, + }) + } else { + const column = this.columns.find((c) => c.name === columnName) + this.filterChips.push({ + field: columnName, + displayName: column?.display_name ?? columnName, + original: columnName, + }) + } } } @@ -54,11 +65,21 @@ export class FilterSortChipsComponent implements OnChanges { const direction = sort.startsWith('-') ? 'desc' : 'asc' sort = sort.replace(/^-/, '') const colDef = this.columnDefs.find(({ field }) => field === sort) - - if (!colDef) return - - const chip = { field: colDef.field, displayName: `${colDef.headerName} ${direction}`, original: sort } - this.sortChips.push(chip) + if (colDef) { + this.sortChips.push({ + field: colDef.field, + displayName: `${colDef.headerName} ${direction}`, + original: sort, + }) + } else { + const column = this.columns.find((c) => c.name === sort) + const displayName = column?.display_name ?? sort + this.sortChips.push({ + field: sort, + displayName: `${displayName} ${direction}`, + original: sort, + }) + } } } diff --git a/src/app/modules/inventory-list/list/inventory.component.html b/src/app/modules/inventory-list/list/inventory.component.html index 90fc3cd7..1a206c42 100644 --- a/src/app/modules/inventory-list/list/inventory.component.html +++ b/src/app/modules/inventory-list/list/inventory.component.html @@ -42,8 +42,9 @@ @if (gridApi && columnDefs) { diff --git a/src/app/modules/inventory-list/list/inventory.component.ts b/src/app/modules/inventory-list/list/inventory.component.ts index 01a35067..92ba197d 100644 --- a/src/app/modules/inventory-list/list/inventory.component.ts +++ b/src/app/modules/inventory-list/list/inventory.component.ts @@ -13,6 +13,8 @@ import { ActivatedRoute } from '@angular/router' import type { ColDef, GridApi } from 'ag-grid-community' import type { Observable } from 'rxjs' import { BehaviorSubject, catchError, combineLatest, filter, map, of, Subject, switchMap, takeUntil, tap } from 'rxjs' +import type { Column } from '@seed/api/column' +import { ColumnService } from '@seed/api/column' import type { Cycle } from '@seed/api/cycle' import { CycleService } from '@seed/api/cycle/cycle.service' import { InventoryService } from '@seed/api/inventory' @@ -34,7 +36,6 @@ import type { Profile, } from '../../inventory/inventory.types' import { ActionsComponent, ConfigSelectorComponent, FilterSortChipsComponent, InventoryGridComponent } from './grid' -// import { CellHeaderMenuComponent } from './grid/cell-header-menu.component' @Component({ selector: 'seed-inventory', @@ -61,6 +62,7 @@ import { ActionsComponent, ConfigSelectorComponent, FilterSortChipsComponent, In }) export class InventoryComponent implements OnDestroy, OnInit { private _activatedRoute = inject(ActivatedRoute) + private _columnService = inject(ColumnService) private _cycleService = inject(CycleService) private _inventoryService = inject(InventoryService) private _organizationService = inject(OrganizationService) @@ -70,6 +72,7 @@ export class InventoryComponent implements OnDestroy, OnInit { readonly tabs: InventoryType[] = ['properties', 'taxlots'] readonly type = this._activatedRoute.snapshot.paramMap.get('type') as InventoryType chunk = 100 + columns: Column[] = [] columnDefs: ColDef[] = [] currentUser: CurrentUser cycle: Cycle @@ -156,8 +159,10 @@ export class InventoryComponent implements OnDestroy, OnInit { getDependencies(org_id: number) { this.orgId = org_id this._cycleService.get(this.orgId) + const columns$ = this.type === 'taxlots' ? this._columnService.taxLotColumns$ : this._columnService.propertyColumns$ return combineLatest([ + columns$, this._userService.currentUser$, this._cycleService.cycles$, this._labelService.labels$, @@ -168,10 +173,11 @@ export class InventoryComponent implements OnDestroy, OnInit { /* * set class variables: cycles, profiles, inventory. returns profile id */ - setDependencies([currentUser, cycles, labels, profiles]: InventoryDependencies) { + setDependencies([columns, currentUser, cycles, labels, profiles]: InventoryDependencies) { if (!cycles) { return null } + this.columns = columns const { org_user_id, settings } = currentUser this.currentUser = currentUser diff --git a/src/app/modules/inventory/inventory.types.ts b/src/app/modules/inventory/inventory.types.ts index e9a88917..ed02bd54 100644 --- a/src/app/modules/inventory/inventory.types.ts +++ b/src/app/modules/inventory/inventory.types.ts @@ -123,7 +123,7 @@ export type FilterSortChip = { original: string; } -export type InventoryDependencies = [CurrentUser, Cycle[], Label[], Profile[]] +export type InventoryDependencies = [Column[], CurrentUser, Cycle[], Label[], Profile[]] type AccessLevelInstance = { id: number; From 891173d5cffc17e807461eee2cfe9e17005333b2 Mon Sep 17 00:00:00 2001 From: Ross Perry Date: Thu, 17 Jul 2025 17:21:05 +0000 Subject: [PATCH 5/8] reset menu - not hooked into grid menu --- .../list/grid/cell-header-menu.component.html | 23 +++--- .../list/grid/grid-controls.component.html | 40 ++++++++- .../list/grid/grid-controls.component.ts | 81 ++++++++++++++++++- .../list/grid/grid.component.html | 3 +- .../list/grid/grid.component.ts | 23 ------ .../list/inventory.component.html | 1 - .../list/inventory.component.ts | 6 -- src/styles/styles.scss | 4 + 8 files changed, 131 insertions(+), 50 deletions(-) diff --git a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html index 9ed6ba96..2365621a 100644 --- a/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html +++ b/src/app/modules/inventory-list/list/grid/cell-header-menu.component.html @@ -14,23 +14,23 @@
-
+ -
+ @if (sortIcon) { -
+ @@ -38,19 +38,16 @@ -
+ -
+ @if (pinState) { -
+ @@ -59,14 +56,12 @@ -
+ -
+ diff --git a/src/app/modules/inventory-list/list/grid/grid-controls.component.html b/src/app/modules/inventory-list/list/grid/grid-controls.component.html index c614ae05..1c8e4988 100644 --- a/src/app/modules/inventory-list/list/grid/grid-controls.component.html +++ b/src/app/modules/inventory-list/list/grid/grid-controls.component.html @@ -1,5 +1,43 @@
- + + + + +
+ @if (gridApi) { + + + + + + + } + + +
+ +
+
@if (selectedViewIds.length > 0) { ({{ selectedViewIds.length }} selected) diff --git a/src/app/modules/inventory-list/list/grid/grid-controls.component.ts b/src/app/modules/inventory-list/list/grid/grid-controls.component.ts index 3ccac723..a43450c3 100644 --- a/src/app/modules/inventory-list/list/grid/grid-controls.component.ts +++ b/src/app/modules/inventory-list/list/grid/grid-controls.component.ts @@ -1,18 +1,48 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core' +import { CommonModule } from '@angular/common' +import type { OnChanges, OnInit, SimpleChanges } from '@angular/core' +import { Component, EventEmitter, inject, Input, Output } from '@angular/core' +import { MatButtonModule } from '@angular/material/button' +import { MatDividerModule } from '@angular/material/divider' import { MatIconModule } from '@angular/material/icon' +import { MatMenuModule } from '@angular/material/menu' import { MatTooltipModule } from '@angular/material/tooltip' +import type { GridApi } from 'ag-grid-community' +import { take } from 'rxjs' +import type { OrganizationUserSettings } from '@seed/api/organization' +import { OrganizationService } from '@seed/api/organization' +import type { CurrentUser } from '@seed/api/user' +import { ConfigService } from '@seed/services' import type { Pagination } from '../../../inventory/inventory.types' @Component({ selector: 'seed-inventory-grid-controls', templateUrl: './grid-controls.component.html', - imports: [MatIconModule, MatTooltipModule], + imports: [ + CommonModule, + MatButtonModule, MatDividerModule, MatIconModule, MatMenuModule, MatTooltipModule], }) -export class InventoryGridControlsComponent { +export class InventoryGridControlsComponent implements OnChanges, OnInit { + @Input() currentUser!: CurrentUser + @Input() gridApi: GridApi @Input() pagination!: Pagination - @Input() resetGrid: () => void @Input() selectedViewIds: number[] @Output() pageChange = new EventEmitter() + private _configService = inject(ConfigService) + private _organizationService = inject(OrganizationService) + scheme: 'dark' | 'light' + userSettings: OrganizationUserSettings + + ngOnInit(): void { + this._configService.scheme$.subscribe((scheme) => { + this.scheme = scheme + }) + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.currentUser?.currentValue) { + this.userSettings = this.currentUser.settings + } + } onPageChange = (direction: 'first' | 'previous' | 'next' | 'last') => { const { page, num_pages } = this.pagination @@ -23,4 +53,47 @@ export class InventoryGridControlsComponent { this.pageChange.emit(newPage) } + + resetGrid() { + this.resetColumns() + this.resetFilters() + this.resetSorts() + this.gridApi.refreshClientSideRowModel() + this.gridApi.refreshCells({ force: true }) + this.resetUserSettings() + } + + resetColumns() { + const columns = this.gridApi.getColumns() + this.gridApi.resetColumnState() + this.gridApi.autoSizeColumns(columns) + } + + resetFilters() { + this.gridApi.setFilterModel(null) + this.userSettings.filters = this.currentUser.settings.filters ?? {} + this.userSettings.filters.properties = {} + this.userSettings.filters.taxlots = {} + this.updateOrgUser() + } + + resetSorts() { + this.gridApi.applyColumnState({ state: [], applyOrder: true }) + this.userSettings.sorts = this.currentUser.settings?.sorts ?? {} + this.userSettings.sorts.properties = [] + this.userSettings.sorts.taxlots = [] + this.updateOrgUser() + } + + resetUserSettings() { + this.userSettings = {} + this.updateOrgUser() + } + + updateOrgUser() { + const { org_id, org_user_id } = this.currentUser + this._organizationService.updateOrganizationUser(org_user_id, org_id, this.userSettings) + .pipe((take(1))) + .subscribe() + } } diff --git a/src/app/modules/inventory-list/list/grid/grid.component.html b/src/app/modules/inventory-list/list/grid/grid.component.html index 69a8adad..8fbe0557 100644 --- a/src/app/modules/inventory-list/list/grid/grid.component.html +++ b/src/app/modules/inventory-list/list/grid/grid.component.html @@ -1,6 +1,7 @@ diff --git a/src/app/modules/inventory-list/list/grid/grid.component.ts b/src/app/modules/inventory-list/list/grid/grid.component.ts index d29e0246..bb9809f5 100644 --- a/src/app/modules/inventory-list/list/grid/grid.component.ts +++ b/src/app/modules/inventory-list/list/grid/grid.component.ts @@ -5,7 +5,6 @@ import { Router } from '@angular/router' import { AgGridAngular, AgGridModule } from 'ag-grid-angular' import type { CellClickedEvent, ColDef, ColGroupDef, GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community' import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community' -import { take } from 'rxjs' import type { Label } from '@seed/api/label' import type { OrganizationUserSettings } from '@seed/api/organization' import { OrganizationService } from '@seed/api/organization' @@ -220,28 +219,6 @@ export class InventoryGridComponent implements OnChanges { } } - resetGrid = () => { - if (!this.gridApi) return - - const columns = this.gridApi.getColumns() - this.gridApi.setFilterModel(null) - this.gridApi.applyColumnState({ state: [], applyOrder: true }) - this.gridApi.resetColumnState() - this.gridApi.autoSizeColumns(columns) - this.gridApi.refreshClientSideRowModel() - this.gridApi.refreshCells({ force: true }) - this.resetUserSettings() - - this.gridReset.emit() - } - - resetUserSettings() { - this.userSettings = {} - this._organizationService.updateOrganizationUser(this.orgUserId, this.orgId, this.userSettings) - .pipe((take(1))) - .subscribe() - } - /* * ascending sorts formatted as 'column_id' * descending sorts formatted as '-column_id' diff --git a/src/app/modules/inventory-list/list/inventory.component.html b/src/app/modules/inventory-list/list/inventory.component.html index 1a206c42..1ffff234 100644 --- a/src/app/modules/inventory-list/list/inventory.component.html +++ b/src/app/modules/inventory-list/list/inventory.component.html @@ -68,7 +68,6 @@ (filterSortChange)="onFilterSortChange($event)" (gridReady)="onGridReady($event)" (selectionChanged)="onSelectionChanged()" - (gridReset)="onGridReset()" >
diff --git a/src/app/modules/inventory-list/list/inventory.component.ts b/src/app/modules/inventory-list/list/inventory.component.ts index 92ba197d..532354fe 100644 --- a/src/app/modules/inventory-list/list/inventory.component.ts +++ b/src/app/modules/inventory-list/list/inventory.component.ts @@ -293,12 +293,6 @@ export class InventoryComponent implements OnDestroy, OnInit { return this._organizationService.updateOrganizationUser(this.orgUserId, this.orgId, this.userSettings) } - onGridReset() { - this.userSettings.filters = {} - this.userSettings.sorts = {} - this.refreshInventory$.next() - } - onPageChange(page: number) { this.page = page this.refreshInventory$.next() diff --git a/src/styles/styles.scss b/src/styles/styles.scss index 90a99152..f262860d 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -216,3 +216,7 @@ border-radius: 25px !important; } } + +.menu-option { + @apply w-full p-2 flex items-center gap-4 rounded hover:bg-[#F4F5F4] hover:dark:bg-[#2E3B4A] text-sm cursor-pointer +} From ae8ddbe96d3c73951e8131701e5427a6f09f83bd Mon Sep 17 00:00:00 2001 From: Ross Perry Date: Fri, 18 Jul 2025 16:43:44 +0000 Subject: [PATCH 6/8] reset menu fxnal --- .../detail/header.component.ts | 11 ++++++++--- .../list/grid/grid-controls.component.ts | 18 +++++++++++------- .../inventory-list/list/grid/grid.component.ts | 1 + 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/app/modules/inventory-detail/detail/header.component.ts b/src/app/modules/inventory-detail/detail/header.component.ts index ff0205ad..10faa945 100644 --- a/src/app/modules/inventory-detail/detail/header.component.ts +++ b/src/app/modules/inventory-detail/detail/header.component.ts @@ -5,6 +5,7 @@ import { MatDialog } from '@angular/material/dialog' import { type MatSelect } from '@angular/material/select' import { AgGridAngular } from 'ag-grid-angular' import type { ColDef, GridApi, GridReadyEvent } from 'ag-grid-community' +import { filter, take, tap } from 'rxjs' import type { AccessLevelInstance, Label, Organization } from '@seed/api' import { LabelComponent } from '@seed/components' import { MaterialImports } from '@seed/materials' @@ -208,9 +209,13 @@ export class HeaderComponent implements OnInit { }, }) - dialogRef.afterClosed().subscribe((message) => { - if (message === 'refresh') this.refreshDetail.emit() - }) + dialogRef.afterClosed() + .pipe( + take(1), + filter(Boolean), + tap(() => { this.refreshDetail.emit() }), + ) + .subscribe() } trackByFn(_index: number, { id }: AccessLevelInstance) { diff --git a/src/app/modules/inventory-list/list/grid/grid-controls.component.ts b/src/app/modules/inventory-list/list/grid/grid-controls.component.ts index cbbb6e29..51d729f8 100644 --- a/src/app/modules/inventory-list/list/grid/grid-controls.component.ts +++ b/src/app/modules/inventory-list/list/grid/grid-controls.component.ts @@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common' import type { OnChanges, OnInit, SimpleChanges } from '@angular/core' import { Component, EventEmitter, inject, Input, Output } from '@angular/core' import type { GridApi } from 'ag-grid-community' -import { take } from 'rxjs' +import { take, tap } from 'rxjs' import type { CurrentUser, OrganizationUserSettings } from '@seed/api' import { OrganizationService } from '@seed/api' import { MaterialImports } from '@seed/materials' @@ -51,15 +51,11 @@ export class InventoryGridControlsComponent implements OnChanges, OnInit { this.resetColumns() this.resetFilters() this.resetSorts() - this.gridApi.refreshClientSideRowModel() - this.gridApi.refreshCells({ force: true }) this.resetUserSettings() } resetColumns() { - const columns = this.gridApi.getColumns() - this.gridApi.resetColumnState() - this.gridApi.autoSizeColumns(columns) + this.gridApi.autoSizeAllColumns() } resetFilters() { @@ -72,6 +68,7 @@ export class InventoryGridControlsComponent implements OnChanges, OnInit { resetSorts() { this.gridApi.applyColumnState({ state: [], applyOrder: true }) + this.gridApi.resetColumnState() this.userSettings.sorts = this.currentUser.settings?.sorts ?? {} this.userSettings.sorts.properties = [] this.userSettings.sorts.taxlots = [] @@ -86,7 +83,14 @@ export class InventoryGridControlsComponent implements OnChanges, OnInit { updateOrgUser() { const { org_id, org_user_id } = this.currentUser this._organizationService.updateOrganizationUser(org_user_id, org_id, this.userSettings) - .pipe((take(1))) + .pipe( + take(1), + tap(() => { + this.gridApi.refreshClientSideRowModel() + this.gridApi.refreshCells({ force: true }) + this.gridApi.onSortChanged() + }), + ) .subscribe() } } diff --git a/src/app/modules/inventory-list/list/grid/grid.component.ts b/src/app/modules/inventory-list/list/grid/grid.component.ts index 95ecc24b..e119b380 100644 --- a/src/app/modules/inventory-list/list/grid/grid.component.ts +++ b/src/app/modules/inventory-list/list/grid/grid.component.ts @@ -81,6 +81,7 @@ export class InventoryGridComponent implements OnChanges { onGridReady(params: GridReadyEvent) { this.gridApi = params.api this.gridReady.emit(this.gridApi) + this.gridApi.autoSizeAllColumns() this.gridApi.addEventListener('cellClicked', this.onCellClicked.bind(this) as (event: CellClickedEvent) => void) } From 87ceaf2a953ff4f0efdb7195b1b608c078a88766 Mon Sep 17 00:00:00 2001 From: Ross Perry Date: Fri, 18 Jul 2025 17:03:01 +0000 Subject: [PATCH 7/8] store potential menu options --- .../inventory-list/list/grid/grid-controls.component.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/modules/inventory-list/list/grid/grid-controls.component.html b/src/app/modules/inventory-list/list/grid/grid-controls.component.html index 1c8e4988..1545a297 100644 --- a/src/app/modules/inventory-list/list/grid/grid-controls.component.html +++ b/src/app/modules/inventory-list/list/grid/grid-controls.component.html @@ -11,7 +11,10 @@ Reset Grid
-