diff --git a/src/lib/listing/listing-component.directive.ts b/src/lib/listing/listing-component.directive.ts index b3c67e5..c8af962 100644 --- a/src/lib/listing/listing-component.directive.ts +++ b/src/lib/listing/listing-component.directive.ts @@ -23,7 +23,6 @@ export abstract class ListingComponent extends AutoUnsubscri readonly listingMode$: Observable; readonly routerLinkFn?: (entity: T) => string | string[]; - // TODO: These should be somewhere in table listing, not generic listing abstract readonly tableColumnConfigs: readonly TableColumnConfig[]; abstract readonly tableHeaderLabel: string; diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index 45b39a4..9475848 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -12,7 +12,7 @@ [class.list-source]="isSource(column)" [style.--color]="column.color" class="column"> -
{{ column.label | translate }} ({{column.entities.length}})
+
{{ column.label | translate }} ({{column.entities?.length || 0}})
+
+ +
diff --git a/src/lib/listing/workflow/workflow.component.scss b/src/lib/listing/workflow/workflow.component.scss index cb6dfb0..42f52ee 100644 --- a/src/lib/listing/workflow/workflow.component.scss +++ b/src/lib/listing/workflow/workflow.component.scss @@ -38,8 +38,7 @@ } > .heading { - margin-left: 18px; - margin-bottom: 16px; + margin: 0 18px 16px 18px; } &.dragging { @@ -60,7 +59,7 @@ var(--iqser-white) 8px ); - > .heading, ::ng-deep.cdk-drag > * { + > .heading, .add-btn, ::ng-deep.cdk-drag > * { opacity: 0.3; } @@ -76,11 +75,9 @@ min-height: calc(100% - 36px); } + .cdk-drag { background-color: var(--iqser-white); - transition: background-color 0.2s, box-shadow 0.2s; - border-radius: 8px; - margin: 0 8px 4px 8px; &:last-child { margin-bottom: 8px; @@ -93,6 +90,31 @@ &:hover { background-color: var(--iqser-grey-6); + } +} + +.add-btn { + background-color: var(--iqser-grey-6); + padding: 10px; + text-align: center; + cursor: pointer; + + mat-icon { + width: 14px; + height: 14px; + } + + &:hover { + background-color: var(--iqser-grey-4); + } +} + +.cdk-drag, .add-btn { + transition: background-color 0.2s, box-shadow 0.2s; + border-radius: 8px; + margin: 0 8px 4px 8px; + + &:hover { box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15); } } diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts index 695e8d5..8c94baf 100644 --- a/src/lib/listing/workflow/workflow.component.ts +++ b/src/lib/listing/workflow/workflow.component.ts @@ -1,4 +1,16 @@ -import { ChangeDetectionStrategy, Component, forwardRef, Inject, Input, OnInit, QueryList, TemplateRef, ViewChildren } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + forwardRef, + Inject, + Input, + OnInit, + Output, + QueryList, + TemplateRef, + ViewChildren +} from '@angular/core'; import { ListingComponent } from '../listing-component.directive'; import { Listable, ListingModes } from '../models'; import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop'; @@ -16,6 +28,7 @@ interface WorkflowColumn { export interface WorkflowConfig { key: string; + itemVersionFn: (entity: T) => string; columns: WorkflowColumn[]; } @@ -30,23 +43,22 @@ export class WorkflowComponent implements @Input() headerTemplate?: TemplateRef; @Input() @Required() itemTemplate!: TemplateRef; @Input() @Required() config!: WorkflowConfig; + @Input() @Required() itemHeight!: string; @Input() itemClasses?: { [key: string]: (e: T) => boolean }; + @Input() addElementIcon?: string; + @Input() addElementColumn?: K; + @Output() readonly addElement = new EventEmitter(); dragging = false; sourceColumn?: WorkflowColumn; @ViewChildren(CdkDropList) private readonly _dropLists!: QueryList; + private _existingEntities: { [key: string]: T } = {}; constructor( @Inject(forwardRef(() => ListingComponent)) private _parent: ListingComponent, private readonly _loadingService: LoadingService ) {} - _itemHeight = 0; - - get itemHeight(): string { - return `${this._itemHeight}px`; - } - get listingComponent(): ListingComponent { return this._parent; } @@ -106,31 +118,65 @@ export class WorkflowComponent implements return this._dropLists?.find(list => list.id === id); } - private _computeItemHeight(): void { - const items = document.getElementsByClassName('cdk-drag'); - if (items.length) { - this._itemHeight = items[0].getBoundingClientRect().height; - } - } - private _updateConfigItems(entities: T[]) { // Disable updating while dragging if (this.dragging) { return; } - // TODO ... - this.config.columns.forEach(column => { - column.entities = []; - }); + // Remove deleted entities + const updatedIds = entities.map(entity => entity.id); + for (const id of Object.keys(this._existingEntities)) { + if (!updatedIds.includes(id)) { + this._removeEntity(this._existingEntities[id]); + } + } + + // Add or move updated entities entities.forEach(entity => { - const column = this._getColumnByKey(entity[this.config.key]); - if (column) { - column.entities?.push(entity); + const shouldAdd = this._shouldAdd(entity); + const shouldMove = this._shouldMove(entity); + + if (shouldMove) { + this._removeEntity(entity); + } + + if (shouldAdd || shouldMove) { + this._addEntity(entity); } }); - this._computeItemHeight(); + // this._computeItemHeight(); + } + + private _addEntity(entity: T): void { + this._existingEntities[entity.id] = entity; + const column = this._getColumnByKey(entity[this.config.key] as K); + if (column) { + if (!column.entities) { + column.entities = []; + } + column.entities.push(entity); + } + } + + private _removeEntity(entity: T): void { + const existingEntity = this._existingEntities[entity.id]; + const column = this._getColumnByKey(existingEntity[this.config.key] as K); + if (column) { + const idx = (column.entities as T[]).findIndex(item => item.id === entity.id); + column.entities?.splice(idx, 1); + } + delete this._existingEntities[entity.id]; + } + + private _shouldMove(entity: T): boolean { + const existingEntity = this._existingEntities[entity.id]; + return existingEntity && this.config.itemVersionFn(entity) !== this.config.itemVersionFn(existingEntity); + } + + private _shouldAdd(entity: T): boolean { + return !this._existingEntities[entity.id]; } private _getColumnByKey(key: K): WorkflowColumn {