Unify management lists with single template

This commit is contained in:
Michael Shamoon 2022-03-22 22:01:46 -07:00
parent 65a56a1da0
commit 4c65ecbe89
27 changed files with 156 additions and 210 deletions

View File

@ -13,16 +13,16 @@ import { DocumentDetailComponent } from './components/document-detail/document-d
import { DashboardComponent } from './components/dashboard/dashboard.component'
import { TagListComponent } from './components/manage/tag-list/tag-list.component'
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
import { LogsComponent } from './components/manage/logs/logs.component'
import { SettingsComponent } from './components/manage/settings/settings.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { DatePipe, registerLocaleData } from '@angular/common'
import { NotFoundComponent } from './components/not-found/not-found.component'
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component'
import { CorrespondentEditDialogComponent } from './components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'
import { CorrespondentEditDialogComponent } from './components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { TagEditDialogComponent } from './components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from './components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
import { TagComponent } from './components/common/tag/tag.component'
import { PageHeaderComponent } from './components/common/page-header/page-header.component'
import { AppFrameComponent } from './components/app-frame/app-frame.component'
@ -58,7 +58,8 @@ import { MetadataCollapseComponent } from './components/document-detail/metadata
import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { NumberComponent } from './components/common/input/number/number.component'
import { SafePipe } from './pipes/safe.pipe'
import { SafeUrlPipe } from './pipes/safeurl.pipe'
import { SafeHtmlPipe } from './pipes/safehtml.pipe'
import { CustomDatePipe } from './pipes/custom-date.pipe'
import { DateComponent } from './components/common/input/date/date.component'
import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter'
@ -112,8 +113,8 @@ registerLocaleData(localeZh)
DocumentDetailComponent,
DashboardComponent,
TagListComponent,
CorrespondentListComponent,
DocumentTypeListComponent,
CorrespondentListComponent,
LogsComponent,
SettingsComponent,
NotFoundComponent,
@ -150,7 +151,8 @@ registerLocaleData(localeZh)
MetadataCollapseComponent,
SelectDialogComponent,
NumberComponent,
SafePipe,
SafeUrlPipe,
SafeHtmlPipe,
CustomDatePipe,
DateComponent,
ColorComponent,

View File

@ -1,8 +1,8 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'
import { PaperlessTag } from 'src/app/data/paperless-tag'
import { TagEditDialogComponent } from '../../edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { TagService } from 'src/app/services/rest/tag.service'
@Component({

View File

@ -142,11 +142,11 @@
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [render-text-mode]="2"></pdf-viewer>
</div>
<ng-template #nativePdfViewer>
<object [data]="previewUrl | safe" class="preview-sticky" width="100%"></object>
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
<object [data]="previewUrl | safe" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
</ng-container>
</ng-template>
</li>
@ -166,11 +166,11 @@
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
<ng-template #nativePdfViewer>
<object [data]="previewUrl | safe" class="preview-sticky" width="100%"></object>
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
<object [data]="previewUrl | safe" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
</ng-container>
</div>
</div>

View File

@ -19,8 +19,8 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'
import { CorrespondentEditDialogComponent } from '../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from '../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
import { PDFDocumentProxy } from 'ng2-pdf-viewer'
import { ToastService } from 'src/app/services/toast.service'
import { TextComponent } from '../common/input/text/text.component'

View File

@ -51,7 +51,7 @@
</svg>&nbsp;<span class="d-block d-md-inline" i18n>View</span>
</a>
<ng-template #previewContent>
<object [data]="previewUrl | safe" class="preview" width="100%"></object>
<object [data]="previewUrl | safeUrl" class="preview" width="100%"></object>
</ng-template>
<a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor" xmlns="http://www.w3.org/2000/svg">

View File

@ -77,7 +77,7 @@
</svg>
</a>
<ng-template #previewContent>
<object [data]="previewUrl | safe" class="preview" width="100%"></object>
<object [data]="previewUrl | safeUrl" class="preview" width="100%"></object>
</ng-template>
<a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" (click)="$event.stopPropagation()" i18n-title>
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor" xmlns="http://www.w3.org/2000/svg">

View File

@ -1,59 +0,0 @@
<app-page-header title="Correspondents" i18n-title>
<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" sortable="last_correspondence" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Last correspondence</th>
<th scope="col" i18n>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let correspondent of data">
<td scope="row">{{ correspondent.name }}</td>
<td scope="row">{{ getMatching(correspondent) }}</td>
<td scope="row">{{ correspondent.document_count }}</td>
<td scope="row">{{ correspondent.last_correspondence | customDate }}</td>
<td scope="row">
<div class="btn-group">
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(correspondent)">
<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(correspondent)">
<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(correspondent)">
<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 correspondent} other {{{collectionSize || 0}} total correspondents}}</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

@ -2,39 +2,48 @@ import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { ToastService } from 'src/app/services/toast.service'
import { GenericListComponent } from '../generic-list/generic-list.component'
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/correspondent-edit-dialog.component'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { ManagementListComponent } from '../management-list/management-list.component'
@Component({
selector: 'app-correspondent-list',
templateUrl: './correspondent-list.component.html',
styleUrls: ['./correspondent-list.component.scss'],
templateUrl: './../management-list/management-list.component.html',
styleUrls: ['./../management-list/management-list.component.scss'],
providers: [{ provide: CustomDatePipe }],
})
export class CorrespondentListComponent extends GenericListComponent<PaperlessCorrespondent> {
export class CorrespondentListComponent extends ManagementListComponent<PaperlessCorrespondent> {
constructor(
correspondentsService: CorrespondentService,
modalService: NgbModal,
private list: DocumentListViewService,
toastService: ToastService
toastService: ToastService,
list: DocumentListViewService,
private datePipe: CustomDatePipe
) {
super(
correspondentsService,
modalService,
CorrespondentEditDialogComponent,
toastService
toastService,
list,
FILTER_CORRESPONDENT,
$localize`correspondent`,
[
{
key: 'last_correspondence',
name: $localize`Last correspondence`,
valueFn: (c: PaperlessCorrespondent) => {
return this.datePipe.transform(c.last_correspondence)
},
},
]
)
}
getDeleteMessage(object: PaperlessCorrespondent) {
return $localize`Do you really want to delete the correspondent "${object.name}"?`
}
filterDocuments(object: PaperlessCorrespondent) {
this.list.quickFilter([
{ rule_type: FILTER_CORRESPONDENT, value: object.id.toString() },
])
}
}

View File

@ -5,31 +5,34 @@ import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { ToastService } from 'src/app/services/toast.service'
import { GenericListComponent } from '../generic-list/generic-list.component'
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/document-type-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
import { ManagementListComponent } from '../management-list/management-list.component'
@Component({
selector: 'app-document-type-list',
templateUrl: './document-type-list.component.html',
styleUrls: ['./document-type-list.component.scss'],
templateUrl: './../management-list/management-list.component.html',
styleUrls: ['./../management-list/management-list.component.scss'],
})
export class DocumentTypeListComponent extends GenericListComponent<PaperlessDocumentType> {
export class DocumentTypeListComponent extends ManagementListComponent<PaperlessDocumentType> {
constructor(
service: DocumentTypeService,
documentTypeService: DocumentTypeService,
modalService: NgbModal,
private list: DocumentListViewService,
toastService: ToastService
toastService: ToastService,
list: DocumentListViewService
) {
super(service, modalService, DocumentTypeEditDialogComponent, toastService)
super(
documentTypeService,
modalService,
DocumentTypeEditDialogComponent,
toastService,
list,
FILTER_DOCUMENT_TYPE,
$localize`document type`,
[]
)
}
getDeleteMessage(object: PaperlessDocumentType) {
return $localize`Do you really want to delete the document type "${object.name}"?`
}
filterDocuments(object: PaperlessDocumentType) {
this.list.quickFilter([
{ rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString() },
])
}
}

View File

@ -1,4 +1,4 @@
<app-page-header title="Document types" i18n-title>
<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>
@ -19,27 +19,32 @@
<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 document_type of data">
<td scope="row">{{ document_type.name }}</td>
<td scope="row">{{ getMatching(document_type) }}</td>
<td scope="row">{{ document_type.document_count }}</td>
<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(document_type)">
<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(document_type)">
<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(document_type)">
<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"/>
@ -52,6 +57,6 @@
</table>
<div class="d-flex">
<div i18n *ngIf="collectionSize > 0">{collectionSize, plural, =1 {One document type} other {{{collectionSize || 0}} total document types}}</div>
<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

@ -18,19 +18,34 @@ 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 GenericListComponent<T extends ObjectWithId>
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 toastService: ToastService,
private list: DocumentListViewService,
protected filterRuleType: number,
public typeName: string,
public extraColumns: ManagementListColumn[]
) {}
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
@ -48,24 +63,6 @@ export abstract class GenericListComponent<T extends ObjectWithId>
private subscription: Subscription
private _nameFilter: string
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()
}
ngOnInit(): void {
this.reloadData()
@ -84,6 +81,24 @@ export abstract class GenericListComponent<T extends ObjectWithId>
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(
@ -121,7 +136,13 @@ export abstract class GenericListComponent<T extends ObjectWithId>
}
getDeleteMessage(object: T) {
return $localize`Do you really want to delete this element?`
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) {

View File

@ -1,60 +0,0 @@
<app-page-header title="Tags" i18n-title>
<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" i18n>Color</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" i18n>Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let tag of data">
<td scope="row">{{ tag.name }}</td>
<td scope="row"><span class="badge" [style.color]="tag.text_color"
[style.background-color]="tag.color">{{tag.color}}</span></td>
<td scope="row">{{ getMatching(tag) }}</td>
<td scope="row">{{ tag.document_count }}</td>
<td scope="row">
<div class="btn-group">
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(tag)">
<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(tag)">
<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(tag)">
<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 tag} other {{{collectionSize || 0}} total tags}}</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

@ -5,31 +5,43 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { TagService } from 'src/app/services/rest/tag.service'
import { ToastService } from 'src/app/services/toast.service'
import { GenericListComponent } from '../generic-list/generic-list.component'
import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.component'
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { ManagementListComponent } from '../management-list/management-list.component'
@Component({
selector: 'app-tag-list',
templateUrl: './tag-list.component.html',
styleUrls: ['./tag-list.component.scss'],
templateUrl: './../management-list/management-list.component.html',
styleUrls: ['./../management-list/management-list.component.scss'],
})
export class TagListComponent extends GenericListComponent<PaperlessTag> {
export class TagListComponent extends ManagementListComponent<PaperlessTag> {
constructor(
tagService: TagService,
modalService: NgbModal,
private list: DocumentListViewService,
toastService: ToastService
toastService: ToastService,
list: DocumentListViewService
) {
super(tagService, modalService, TagEditDialogComponent, toastService)
super(
tagService,
modalService,
TagEditDialogComponent,
toastService,
list,
FILTER_HAS_TAGS_ALL,
$localize`tag`,
[
{
key: 'color',
name: $localize`Color`,
rendersHtml: true,
valueFn: (t: PaperlessTag) => {
return `<span class="badge" style="color: ${t.text_color}; background-color: ${t.color}">${t.color}</span>`
},
},
]
)
}
getDeleteMessage(object: PaperlessTag) {
return $localize`Do you really want to delete the tag "${object.name}"?`
}
filterDocuments(object: PaperlessTag) {
this.list.quickFilter([
{ rule_type: FILTER_HAS_TAGS_ALL, value: object.id.toString() },
])
}
}

View File

@ -0,0 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser'
@Pipe({
name: 'safeHtml',
})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(html) {
return this.sanitizer.bypassSecurityTrustHtml(html)
}
}

View File

@ -2,9 +2,9 @@ import { Pipe, PipeTransform } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser'
@Pipe({
name: 'safe',
name: 'safeurl',
})
export class SafePipe implements PipeTransform {
export class SafeUrlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(url) {