diff --git a/src/lib/listing/index.ts b/src/lib/listing/index.ts index 2c3e281..208abd9 100644 --- a/src/lib/listing/index.ts +++ b/src/lib/listing/index.ts @@ -15,3 +15,7 @@ export * from './listing-component.directive'; export * from './page-header/page-header.component'; export * from './page-header/models'; + +export * from './workflow/models/entity-wrapper.model'; +export * from './workflow/models/workflow-config.model'; +export * from './workflow/models/workflow-column.model'; diff --git a/src/lib/listing/table-content/table-content.component.ts b/src/lib/listing/table-content/table-content.component.ts index 57f48b3..dd7635b 100644 --- a/src/lib/listing/table-content/table-content.component.ts +++ b/src/lib/listing/table-content/table-content.component.ts @@ -1,7 +1,7 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, forwardRef, Inject, Input, OnDestroy, ViewChild } from '@angular/core'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { delay, tap } from 'rxjs/operators'; -import { AutoUnsubscribe, trackBy } from '../../utils'; +import { AutoUnsubscribe, trackByFactory } from '../../utils'; import { IListable } from '../models'; import { ListingComponent, ListingService } from '../index'; import { HasScrollbarDirective } from '../../scrollbar'; @@ -20,7 +20,7 @@ export class TableContentComponent extends AutoUnsubscribe @Input() tableItemClasses?: Record boolean>; @Input() selectionEnabled!: boolean; @Input() helpModeKey?: 'dossier-list' | 'document-list'; - readonly trackBy = trackBy(); + readonly trackBy = trackByFactory(); @ViewChild(CdkVirtualScrollViewport, { static: true }) readonly scrollViewport!: CdkVirtualScrollViewport; @ViewChild(HasScrollbarDirective, { static: true }) readonly hasScrollbarDirective!: HasScrollbarDirective; diff --git a/src/lib/listing/workflow/column-header/column-header.component.ts b/src/lib/listing/workflow/column-header/column-header.component.ts index 5403981..aaae18a 100644 --- a/src/lib/listing/workflow/column-header/column-header.component.ts +++ b/src/lib/listing/workflow/column-header/column-header.component.ts @@ -15,8 +15,8 @@ import { filter, map, tap } from 'rxjs/operators'; import { CircleButtonTypes } from '../../../buttons'; import { IListable } from '../../models'; import { AutoUnsubscribe, Debounce } from '../../../utils'; -import { WorkflowColumn } from '../workflow.component'; import { ListingService } from '../../services'; +import { WorkflowColumn } from '../models/workflow-column.model'; @Component({ selector: 'iqser-column-header [column] [selectionColumn]', @@ -105,6 +105,6 @@ export class ColumnHeaderComponent extend this._updateBulkActionsContainerWidth(entries[0]); }); - observer.observe(this.bulkActionsContainer?.nativeElement); + observer.observe(this.bulkActionsContainer?.nativeElement as Element); } } diff --git a/src/lib/listing/workflow/models/entity-wrapper.model.ts b/src/lib/listing/workflow/models/entity-wrapper.model.ts new file mode 100644 index 0000000..8155e7e --- /dev/null +++ b/src/lib/listing/workflow/models/entity-wrapper.model.ts @@ -0,0 +1,25 @@ +import { IListable } from '@iqser/common-ui'; +import { BehaviorSubject, Observable } from 'rxjs'; + +export class EntityWrapper { + readonly classes$: BehaviorSubject>; + + constructor( + readonly entity: T, + private readonly _itemClasses: Record boolean>, + readonly isSelected$: Observable, + ) { + this.classes$ = new BehaviorSubject>(this._getItemClasses(entity)); + } + + private _getItemClasses(entity: T): Record { + const classes: { [key: string]: boolean } = {}; + for (const key in this._itemClasses) { + if (Object.prototype.hasOwnProperty.call(this._itemClasses, key)) { + classes[key] = this._itemClasses[key](entity); + } + } + classes.item = true; + return classes; + } +} diff --git a/src/lib/listing/workflow/models/workflow-column.model.ts b/src/lib/listing/workflow/models/workflow-column.model.ts new file mode 100644 index 0000000..537e0bf --- /dev/null +++ b/src/lib/listing/workflow/models/workflow-column.model.ts @@ -0,0 +1,11 @@ +import { IListable } from '@iqser/common-ui'; +import { BehaviorSubject } from 'rxjs'; + +export interface WorkflowColumn { + key: K; + label: string; + color: string; + enterFn: (entities: T[]) => Promise | void; + enterPredicate: (entities: T[]) => boolean; + entities: BehaviorSubject; +} diff --git a/src/lib/listing/workflow/models/workflow-config.model.ts b/src/lib/listing/workflow/models/workflow-config.model.ts new file mode 100644 index 0000000..ed9e360 --- /dev/null +++ b/src/lib/listing/workflow/models/workflow-config.model.ts @@ -0,0 +1,8 @@ +import { IListable } from '@iqser/common-ui'; +import { WorkflowColumn } from './workflow-column.model'; + +export interface WorkflowConfig { + columnIdentifierFn: (entity: T) => K; + itemVersionFn: (entity: T) => string; + columns: WorkflowColumn[]; +} diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index 58c087e..336d191 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -1,4 +1,4 @@ - + @@ -27,49 +27,53 @@ class="column" > -
+
- - - - -
-
-
- -
- -
- -
+
+ + + + +
+
+ + + +
+ +
+
+
+
+ +
+
-
- -
-
+
diff --git a/src/lib/listing/workflow/workflow.component.scss b/src/lib/listing/workflow/workflow.component.scss index 2e58e78..a98fcc7 100644 --- a/src/lib/listing/workflow/workflow.component.scss +++ b/src/lib/listing/workflow/workflow.component.scss @@ -58,7 +58,6 @@ > .heading, .add-btn, ::ng-deep.item > * > * { opacity: 0.3; } - } } } @@ -68,6 +67,10 @@ overflow-y: auto; @include no-scroll-bar; min-height: calc(100% - 36px); + + &.multi-select-active { + min-height: calc(100% - 86px); + } } .item { @@ -124,3 +127,8 @@ border-radius: 8px; margin: 0 8px 4px 8px; } + +cdk-virtual-scroll-viewport { + height: 100%; + @include no-scroll-bar; +} diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts index 7932f7f..eff1ee0 100644 --- a/src/lib/listing/workflow/workflow.component.ts +++ b/src/lib/listing/workflow/workflow.component.ts @@ -15,61 +15,25 @@ import { } from '@angular/core'; import { ListingComponent } from '../listing-component.directive'; import { CdkDragDrop, CdkDragStart, CdkDropList } from '@angular/cdk/drag-drop'; -import { AutoUnsubscribe, Debounce, Required } from '../../utils'; -import { LoadingService } from '../../loading'; +import { AutoUnsubscribe, Debounce, trackByFactory } from '../../utils'; import { IListable } from '../models'; import { EntitiesService, ListingService } from '../services'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { filter, tap } from 'rxjs/operators'; - -export interface WorkflowColumn { - key: K; - label: string; - color: string; - enterFn: (entities: T[]) => Promise | void; - enterPredicate: (entities: T[]) => boolean; - entities: BehaviorSubject; -} - -export interface WorkflowConfig { - columnIdentifierFn: (entity: T) => K; - itemVersionFn: (entity: T) => string; - columns: WorkflowColumn[]; -} - -class EntityWrapper { - readonly classes$: BehaviorSubject>; - - constructor( - readonly entity: T, - private readonly _itemClasses: Record boolean>, - readonly isSelected$: Observable, - ) { - this.classes$ = new BehaviorSubject>(this._getItemClasses(entity)); - } - - private _getItemClasses(entity: T): Record { - const classes: { [key: string]: boolean } = {}; - for (const key in this._itemClasses) { - if (Object.prototype.hasOwnProperty.call(this._itemClasses, key)) { - classes[key] = this._itemClasses[key](entity); - } - } - classes.item = true; - return classes; - } -} +import { WorkflowConfig } from './models/workflow-config.model'; +import { WorkflowColumn } from './models/workflow-column.model'; +import { EntityWrapper } from './models/entity-wrapper.model'; @Component({ - selector: 'iqser-workflow', + selector: 'iqser-workflow [itemTemplate] [config]', templateUrl: './workflow.component.html', styleUrls: ['./workflow.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class WorkflowComponent extends AutoUnsubscribe implements OnInit { @Input() headerTemplate?: TemplateRef; - @Input() @Required() itemTemplate!: TemplateRef; - @Input() @Required() config!: WorkflowConfig; + @Input() itemTemplate!: TemplateRef; + @Input() config!: WorkflowConfig; @Input() itemClasses!: Record boolean>; @Input() addElementIcon?: string; @Input() addElementColumn?: K; @@ -82,6 +46,7 @@ export class WorkflowComponent extends Au @Output() readonly noDataAction = new EventEmitter(); @Output() readonly addElement = new EventEmitter(); + readonly trackBy = trackByFactory(); itemHeight?: number; itemWidth?: number; dragging = false; @@ -90,13 +55,12 @@ export class WorkflowComponent extends Au readonly draggingEntities$ = new BehaviorSubject([]); all: { [key: string]: EntityWrapper } = {}; @ViewChildren(CdkDropList) private readonly _dropLists!: QueryList; - private _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { + private readonly _observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { this._updateItemSize(entries[0]); }); constructor( @Inject(forwardRef(() => ListingComponent)) readonly listingComponent: ListingComponent, - private readonly _loadingService: LoadingService, private readonly _changeRef: ChangeDetectorRef, private readonly _elementRef: ElementRef, readonly listingService: ListingService, @@ -105,10 +69,6 @@ export class WorkflowComponent extends Au super(); } - get tableHeaderLabel(): string | undefined { - return this.listingComponent.tableHeaderLabel; - } - async move(event: CdkDragDrop): Promise { if (event.previousContainer !== event.container) { const column = this._getColumnByKey((event.container.id) as K); @@ -135,9 +95,9 @@ export class WorkflowComponent extends Au return () => column.enterPredicate(this.draggingEntities$.value); } - startDragging(column: WorkflowColumn, $event: CdkDragStart): void { - const entity: T = $event.source.data as T; - if (this.listingService.selected.includes(entity)) { + startDragging(column: WorkflowColumn, $event: CdkDragStart): void { + const entity = $event.source.data; + if (this.listingService.selectedIds.includes(entity.id)) { this.draggingEntities$.next(this.listingService.selected); } else { this.draggingEntities$.next([entity]); @@ -165,8 +125,9 @@ export class WorkflowComponent extends Au return !!this._getListById(column.key)?._dropListRef.isReceiving(); } + @Debounce(30) private _setupResizeObserver(): void { - const cdkDragElement: Element = this._elementRef.nativeElement.querySelector('.cdk-drag') as Element; + const cdkDragElement = this._elementRef.nativeElement.querySelector('.cdk-drag') as Element; if (cdkDragElement) { this._observer.observe(cdkDragElement); } @@ -214,7 +175,7 @@ export class WorkflowComponent extends Au } }); - this._changeRef.detectChanges(); + this._changeRef.markForCheck(); } private _addEntity(entity: T): void { @@ -246,12 +207,14 @@ export class WorkflowComponent extends Au private _removeEntity(entity: T): void { const existingEntity = this.all[entity.id]; const column = this._getColumnByKey(this.config.columnIdentifierFn(existingEntity.entity)); + if (column) { const entities = column.entities.value; const idx = entities.findIndex(item => item.id === entity.id); entities.splice(idx, 1); column.entities.next(entities); } + delete this.all[entity.id]; } diff --git a/src/lib/utils/functions.ts b/src/lib/utils/functions.ts index 302c4d7..912c7f9 100644 --- a/src/lib/utils/functions.ts +++ b/src/lib/utils/functions.ts @@ -31,6 +31,6 @@ export function toNumber(str: string): number { } } -export function trackBy() { +export function trackByFactory() { return (index: number, item: T): string => item.id; }