diff --git a/src/assets/styles/common-components.scss b/src/assets/styles/common-components.scss index 1678858..7733b63 100644 --- a/src/assets/styles/common-components.scss +++ b/src/assets/styles/common-components.scss @@ -110,3 +110,27 @@ border-radius: 50%; position: absolute; } + +.multi-select { + min-height: 40px; + background: var(--iqser-primary); + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 8px 0 16px; + color: var(--iqser-white); + + .selected-wrapper { + display: flex; + align-items: center; + + iqser-round-checkbox .wrapper.inactive { + cursor: default; + } + + .all-caps-label { + margin: 0 16px 0 8px; + opacity: 1; + } + } +} diff --git a/src/lib/listing/listing.module.ts b/src/lib/listing/listing.module.ts index 2efa848..7b8b264 100644 --- a/src/lib/listing/listing.module.ts +++ b/src/lib/listing/listing.module.ts @@ -21,6 +21,7 @@ import { IqserButtonsModule } from '../buttons'; import { IqserHelpModeModule } from '../help-mode'; import { TableContentComponent } from './table-content/table-content.component'; import { TableItemComponent } from './table-content/table-item/table-item.component'; +import { ColumnHeaderComponent } from './workflow/column-header/column-header.component'; const matModules = [MatTooltipModule]; const components = [ @@ -30,6 +31,9 @@ const components = [ TableColumnNameComponent, ScrollButtonComponent, PageHeaderComponent, + TableContentComponent, + TableItemComponent, + ColumnHeaderComponent, ]; const modules = [ DragDropModule, @@ -47,7 +51,7 @@ const modules = [ const utils = [SyncWidthDirective]; @NgModule({ - declarations: [...components, ...utils, TableContentComponent, TableItemComponent], + declarations: [...components, ...utils], exports: [...components, ...utils], imports: [CommonModule, ...modules, ...matModules], }) diff --git a/src/lib/listing/services/listing.service.ts b/src/lib/listing/services/listing.service.ts index 4f713b1..083bf21 100644 --- a/src/lib/listing/services/listing.service.ts +++ b/src/lib/listing/services/listing.service.ts @@ -92,7 +92,6 @@ export class ListingService { } isSelected(entity: E): boolean { - console.log('is selected'); return this.selectedIds.indexOf(entity.id) !== -1; } diff --git a/src/lib/listing/workflow/column-header/column-header.component.css b/src/lib/listing/workflow/column-header/column-header.component.css new file mode 100644 index 0000000..3ce2e65 --- /dev/null +++ b/src/lib/listing/workflow/column-header/column-header.component.css @@ -0,0 +1,10 @@ +.heading { + margin: 0 18px 16px 18px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.mr-10 { + margin-right: 10px; +} diff --git a/src/lib/listing/workflow/column-header/column-header.component.html b/src/lib/listing/workflow/column-header/column-header.component.html new file mode 100644 index 0000000..4702c24 --- /dev/null +++ b/src/lib/listing/workflow/column-header/column-header.component.html @@ -0,0 +1,34 @@ + +
+ {{ column.label | translate }} ({{entities.length || 0}}) + +
+ + +
+
+
+
+ + + +
+ + +
+
diff --git a/src/lib/listing/workflow/column-header/column-header.component.ts b/src/lib/listing/workflow/column-header/column-header.component.ts new file mode 100644 index 0000000..7df66c1 --- /dev/null +++ b/src/lib/listing/workflow/column-header/column-header.component.ts @@ -0,0 +1,77 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { combineLatest, Observable } from 'rxjs'; +import { filter, map, tap } from 'rxjs/operators'; +import { CircleButtonTypes } from '../../../buttons'; +import { IListable } from '../../models'; +import { AutoUnsubscribe, Required } from '../../../utils'; +import { WorkflowColumn } from '../workflow.component'; +import { ListingService } from '../../services'; + +@Component({ + selector: 'iqser-column-header', + templateUrl: './column-header.component.html', + styleUrls: ['./column-header.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ColumnHeaderComponent extends AutoUnsubscribe implements OnInit { + readonly circleButtonTypes = CircleButtonTypes; + + @Input() @Required() column!: WorkflowColumn; + @Input() @Required() selectionColumn?: WorkflowColumn; + @Output() @Required() readonly selectionColumnChange = new EventEmitter | undefined>(); + + allSelected$!: Observable; + indeterminate$!: Observable; + + constructor(readonly listingService: ListingService) { + super(); + } + + get activeSelection(): boolean { + return this.selectionColumn === this.column; + } + + ngOnInit(): void { + this.allSelected$ = combineLatest([this.listingService.selectedLength$, this.column.entities]).pipe( + map(([length, columnEntities]) => length > 0 && length === columnEntities.length), + ); + this.indeterminate$ = combineLatest([this.listingService.selectedLength$, this.column.entities]).pipe( + map(([length, columnEntities]) => length > 0 && length !== columnEntities.length), + ); + this.addSubscription = this.column.entities + .pipe( + filter(entities => entities.length <= 1), + tap(() => this.disableSelection()), + ) + .subscribe(); + } + + toggleSelectAll(): void { + if (this.listingService.selected.length === 0) { + this.selectAll(); + } else { + this.selectNone(); + } + } + + enableSelection(): void { + this.selectionColumn = this.column; + this.selectionColumnChange.emit(this.selectionColumn); + } + + disableSelection(): void { + if (this.activeSelection) { + this.selectionColumn = undefined; + this.selectionColumnChange.emit(this.selectionColumn); + this.selectNone(); + } + } + + selectAll(): void { + this.listingService.setSelected(this.column.entities.value || []); + } + + selectNone(): void { + this.listingService.setSelected([]); + } +} diff --git a/src/lib/listing/workflow/workflow.component.html b/src/lib/listing/workflow/workflow.component.html index ac2b1b9..1f2c182 100644 --- a/src/lib/listing/workflow/workflow.component.html +++ b/src/lib/listing/workflow/workflow.component.html @@ -15,24 +15,26 @@ [text]="noDataText" > -
+
-
{{ 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 7aec9ef..a7978cb 100644 --- a/src/lib/listing/workflow/workflow.component.scss +++ b/src/lib/listing/workflow/workflow.component.scss @@ -37,10 +37,6 @@ margin-right: 8px; } - > .heading { - margin: 0 18px 16px 18px; - } - &.dragging { .cdk-drag { pointer-events: none; @@ -76,6 +72,7 @@ .cdk-drag { background-color: var(--iqser-white); + border: 2px solid var(--iqser-white); &:last-child { margin-bottom: 8px; @@ -85,6 +82,10 @@ background-color: var(--iqser-grey-6); color: var(--iqser-disabled); } + + &.selected { + border-color: var(--iqser-primary); + } } .add-btn { @@ -116,11 +117,5 @@ .cdk-drag-placeholder { border: 1px dashed var(--iqser-grey-5); border-radius: 8px; - min-height: var(--height); margin: 0 8px 4px 8px; } - - -.cdk-drag-preview { - max-height: var(--height); -} diff --git a/src/lib/listing/workflow/workflow.component.ts b/src/lib/listing/workflow/workflow.component.ts index 1635c84..1532cfd 100644 --- a/src/lib/listing/workflow/workflow.component.ts +++ b/src/lib/listing/workflow/workflow.component.ts @@ -19,14 +19,15 @@ import { AutoUnsubscribe, Debounce, Required } from '../../utils'; import { LoadingService } from '../../loading'; import { IListable } from '../models'; import { EntitiesService, ListingService } from '../services'; +import { BehaviorSubject } from 'rxjs'; -interface WorkflowColumn { +export interface WorkflowColumn { key: K; label: string; color: string; enterFn: (entity: T) => Promise | void; enterPredicate: (entity: T) => boolean; - entities?: T[]; + entities: BehaviorSubject; } export interface WorkflowConfig { @@ -60,6 +61,7 @@ export class WorkflowComponent extends Au itemHeight?: number; dragging = false; sourceColumn?: WorkflowColumn; + selectionColumn?: WorkflowColumn; @ViewChildren(CdkDropList) private readonly _dropLists!: QueryList; private _existingEntities: { [key: string]: T } = {}; @@ -96,7 +98,6 @@ export class WorkflowComponent extends Au } ngOnInit(): void { - this.config.columns.forEach(c => (c.entities = [])); this.addSubscription = this.listingService.displayed$.subscribe(entities => this._updateConfigItems(entities)); this._setupResizeObserver(); } @@ -182,19 +183,18 @@ export class WorkflowComponent extends Au if (!column) { return; } - if (!column.entities) { - column.entities = []; - } this._existingEntities[entity.id] = entity; - column.entities.push(entity); + column.entities.next([...column.entities.value, entity]); } private _removeEntity(entity: T): void { const existingEntity = this._existingEntities[entity.id]; const column = this._getColumnByKey(this.config.columnIdentifierFn(existingEntity)); if (column) { - const idx = (column.entities as T[]).findIndex(item => item.id === entity.id); - column.entities?.splice(idx, 1); + const entities = column.entities.value; + const idx = entities.findIndex(item => item.id === entity.id); + entities.splice(idx, 1); + column.entities.next(entities); } delete this._existingEntities[entity.id]; } @@ -208,7 +208,7 @@ export class WorkflowComponent extends Au return !this._existingEntities[entity.id]; } - private _getColumnByKey(key: K): WorkflowColumn { + private _getColumnByKey(key: K | undefined): WorkflowColumn { return this.config.columns.find(col => col.key === key) as WorkflowColumn; } }