Unify management lists with single template

This commit is contained in:
Michael Shamoon
2022-03-22 22:01:46 -07:00
parent 6790532d8f
commit 8565a2c3ed
27 changed files with 156 additions and 210 deletions

View File

@@ -0,0 +1,62 @@
<app-page-header title="{{ typeName | titlecase }}s">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" i18n>Create</button>
</app-page-header>
<div class="row">
<div class="col-md mb-2 mb-xl-0">
<div class="form-inline d-flex align-items-center">
<label class="text-muted me-2 mb-0" i18n>Filter by:</label>
<input class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder>
</div>
</div>
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
</div>
<table class="table table-striped align-middle border shadow-sm">
<thead>
<tr>
<th scope="col" sortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
<th scope="col" sortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
<th scope="col" sortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
<th scope="col" *ngFor="let column of extraColumns" sortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
<th scope="col" i18n>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let object of data">
<td scope="row">{{ object.name }}</td>
<td scope="row">{{ getMatching(object) }}</td>
<td scope="row">{{ object.document_count }}</td>
<td scope="row" *ngFor="let column of extraColumns">
<div *ngIf="column.rendersHtml; else colValue" [innerHtml]="column.valueFn.call(null, object) | safeHtml"></div>
<ng-template #colValue>{{ column.valueFn.call(null, object) }}</ng-template>
</td>
<td scope="row">
<div class="btn-group">
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
</svg>&nbsp;<ng-container i18n>Documents</ng-container>
</button>
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object)">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
</svg>&nbsp;<ng-container i18n>Edit</ng-container>
</button>
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
</svg>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="d-flex">
<div i18n *ngIf="collectionSize > 0">{collectionSize, plural, =1 {One {{typeName}}} other {{{collectionSize || 0}} total {{typeName}}s}}</div>
<ngb-pagination *ngIf="collectionSize > 20" class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
</div>

View File

@@ -0,0 +1,187 @@
import {
Directive,
OnDestroy,
OnInit,
QueryList,
ViewChildren,
} from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subject, Subscription } from 'rxjs'
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
import {
MatchingModel,
MATCHING_ALGORITHMS,
MATCH_AUTO,
} from 'src/app/data/matching-model'
import { ObjectWithId } from 'src/app/data/object-with-id'
import {
SortableDirective,
SortEvent,
} from 'src/app/directives/sortable.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
export interface ManagementListColumn {
key: string
name: string
valueFn: any
rendersHtml?: boolean
}
@Directive()
export abstract class ManagementListComponent<T extends ObjectWithId>
implements OnInit, OnDestroy
{
constructor(
private service: AbstractNameFilterService<T>,
private modalService: NgbModal,
private editDialogComponent: any,
private toastService: ToastService,
private list: DocumentListViewService,
protected filterRuleType: number,
public typeName: string,
public extraColumns: ManagementListColumn[]
) {}
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
public data: T[] = []
public page = 1
public collectionSize = 0
public sortField: string
public sortReverse: boolean
private nameFilterDebounce: Subject<string>
private subscription: Subscription
private _nameFilter: string
ngOnInit(): void {
this.reloadData()
this.nameFilterDebounce = new Subject<string>()
this.subscription = this.nameFilterDebounce
.pipe(debounceTime(400), distinctUntilChanged())
.subscribe((title) => {
this._nameFilter = title
this.page = 1
this.reloadData()
})
}
ngOnDestroy() {
this.subscription.unsubscribe()
}
getMatching(o: MatchingModel) {
if (o.matching_algorithm == MATCH_AUTO) {
return $localize`Automatic`
} else if (o.match && o.match.length > 0) {
return `${
MATCHING_ALGORITHMS.find((a) => a.id == o.matching_algorithm).shortName
}: ${o.match}`
} else {
return '-'
}
}
onSort(event: SortEvent) {
this.sortField = event.column
this.sortReverse = event.reverse
this.reloadData()
}
reloadData() {
this.service
.listFiltered(
this.page,
null,
this.sortField,
this.sortReverse,
this._nameFilter
)
.subscribe((c) => {
this.data = c.results
this.collectionSize = c.count
})
}
openCreateDialog() {
var activeModal = this.modalService.open(this.editDialogComponent, {
backdrop: 'static',
})
activeModal.componentInstance.dialogMode = 'create'
activeModal.componentInstance.success.subscribe((o) => {
this.reloadData()
})
}
openEditDialog(object: T) {
var activeModal = this.modalService.open(this.editDialogComponent, {
backdrop: 'static',
})
activeModal.componentInstance.object = object
activeModal.componentInstance.dialogMode = 'edit'
activeModal.componentInstance.success.subscribe((o) => {
this.reloadData()
})
}
getDeleteMessage(object: T) {
return $localize`Do you really want to delete the ${this.typeName}?`
}
filterDocuments(object: ObjectWithId) {
this.list.quickFilter([
{ rule_type: this.filterRuleType, value: object.id.toString() },
])
}
openDeleteDialog(object: T) {
var activeModal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})
activeModal.componentInstance.title = $localize`Confirm delete`
activeModal.componentInstance.messageBold = this.getDeleteMessage(object)
activeModal.componentInstance.message = $localize`Associated documents will not be deleted.`
activeModal.componentInstance.btnClass = 'btn-danger'
activeModal.componentInstance.btnCaption = $localize`Delete`
activeModal.componentInstance.confirmClicked.subscribe(() => {
activeModal.componentInstance.buttonsEnabled = false
this.service.delete(object).subscribe(
(_) => {
activeModal.close()
this.reloadData()
},
(error) => {
activeModal.componentInstance.buttonsEnabled = true
this.toastService.showError(
$localize`Error while deleting element: ${JSON.stringify(
error.error
)}`
)
}
)
})
}
get nameFilter() {
return this._nameFilter
}
set nameFilter(nameFilter: string) {
this.nameFilterDebounce.next(nameFilter)
}
onNameFilterKeyUp(event: KeyboardEvent) {
if (event.code == 'Escape') this.nameFilterDebounce.next(null)
}
}