mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Saved views, some refactoring
This commit is contained in:
parent
6afdf666fd
commit
d1e10754a5
@ -19,7 +19,7 @@ const routes: Routes = [
|
|||||||
{path: '', component: AppFrameComponent, children: [
|
{path: '', component: AppFrameComponent, children: [
|
||||||
{path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuardService] },
|
{path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuardService] },
|
||||||
{path: 'documents', component: DocumentListComponent, canActivate: [AuthGuardService] },
|
{path: 'documents', component: DocumentListComponent, canActivate: [AuthGuardService] },
|
||||||
{path: 'view/:name', component: DocumentListComponent, canActivate: [AuthGuardService] },
|
{path: 'view/:id', component: DocumentListComponent, canActivate: [AuthGuardService] },
|
||||||
{path: 'search', component: SearchComponent, canActivate: [AuthGuardService] },
|
{path: 'search', component: SearchComponent, canActivate: [AuthGuardService] },
|
||||||
{path: 'documents/:id', component: DocumentDetailComponent, canActivate: [AuthGuardService] },
|
{path: 'documents/:id', component: DocumentDetailComponent, canActivate: [AuthGuardService] },
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ import { NgxFileDropModule } from 'ngx-file-drop';
|
|||||||
import { TextComponent } from './components/common/input/text/text.component';
|
import { TextComponent } from './components/common/input/text/text.component';
|
||||||
import { SelectComponent } from './components/common/input/select/select.component';
|
import { SelectComponent } from './components/common/input/select/select.component';
|
||||||
import { CheckComponent } from './components/common/input/check/check.component';
|
import { CheckComponent } from './components/common/input/check/check.component';
|
||||||
|
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -66,7 +67,8 @@ import { CheckComponent } from './components/common/input/check/check.component'
|
|||||||
DocumentCardSmallComponent,
|
DocumentCardSmallComponent,
|
||||||
TextComponent,
|
TextComponent,
|
||||||
SelectComponent,
|
SelectComponent,
|
||||||
CheckComponent
|
CheckComponent,
|
||||||
|
SaveViewConfigDialogComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -43,6 +43,20 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted" *ngIf='viewConfigService.getSideBarConfigs().length > 0'>
|
||||||
|
<span>Saved filters</span>
|
||||||
|
</h6>
|
||||||
|
<ul class="nav flex-column mb-2">
|
||||||
|
<li class="nav-item" *ngFor='let config of viewConfigService.getSideBarConfigs()'>
|
||||||
|
<a class="nav-link" routerLink="view/{{config.id}}" routerLinkActive="active">
|
||||||
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#funnel"/>
|
||||||
|
</svg>
|
||||||
|
{{config.title}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
|
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
|
||||||
<span>Open documents</span>
|
<span>Open documents</span>
|
||||||
</h6>
|
</h6>
|
||||||
|
@ -7,6 +7,7 @@ import { PaperlessDocument } from 'src/app/data/paperless-document';
|
|||||||
import { AuthService } from 'src/app/services/auth.service';
|
import { AuthService } from 'src/app/services/auth.service';
|
||||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
|
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
|
||||||
import { SearchService } from 'src/app/services/rest/search.service';
|
import { SearchService } from 'src/app/services/rest/search.service';
|
||||||
|
import { SavedViewConfigService } from 'src/app/services/saved-view-config.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-app-frame',
|
selector: 'app-app-frame',
|
||||||
@ -15,7 +16,13 @@ import { SearchService } from 'src/app/services/rest/search.service';
|
|||||||
})
|
})
|
||||||
export class AppFrameComponent implements OnInit, OnDestroy {
|
export class AppFrameComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
constructor (public router: Router, private openDocumentsService: OpenDocumentsService, private authService: AuthService, private searchService: SearchService) {
|
constructor (
|
||||||
|
public router: Router,
|
||||||
|
private openDocumentsService: OpenDocumentsService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private searchService: SearchService,
|
||||||
|
public viewConfigService: SavedViewConfigService
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
searchField = new FormControl('')
|
searchField = new FormControl('')
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { Form, FormGroup } from '@angular/forms';
|
import { FormGroup } from '@angular/forms';
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { MatchingModel } from 'src/app/data/matching-model';
|
import { MATCHING_ALGORITHMS } from 'src/app/data/matching-model';
|
||||||
import { ObjectWithId } from 'src/app/data/object-with-id';
|
import { ObjectWithId } from 'src/app/data/object-with-id';
|
||||||
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service';
|
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service';
|
||||||
import { Toast, ToastService } from 'src/app/services/toast.service';
|
import { Toast, ToastService } from 'src/app/services/toast.service';
|
||||||
@ -47,7 +47,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMatchingAlgorithms() {
|
getMatchingAlgorithms() {
|
||||||
return MatchingModel.MATCHING_ALGORITHMS
|
return MATCHING_ALGORITHMS
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
|
||||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { AbstractInputComponent } from '../abstract-input';
|
import { AbstractInputComponent } from '../abstract-input';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
|
import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tag',
|
selector: 'app-tag',
|
||||||
@ -23,7 +23,7 @@ export class TagComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getColour() {
|
getColour() {
|
||||||
return PaperlessTag.COLOURS.find(c => c.id == this.tag.colour)
|
return TAG_COLOURS.find(c => c.id == this.tag.colour)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
|
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
|
||||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
|
import { PaperlessDocument } from 'src/app/data/paperless-document';
|
||||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
|
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
|
||||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
|
import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
|
||||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
|
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
|
||||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
|
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
|
||||||
@ -17,6 +17,7 @@ import { DeleteDialogComponent } from '../common/delete-dialog/delete-dialog.com
|
|||||||
import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-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 { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component';
|
||||||
import { TagEditDialogComponent } from '../manage/tag-list/tag-edit-dialog/tag-edit-dialog.component';
|
import { TagEditDialogComponent } from '../manage/tag-list/tag-edit-dialog/tag-edit-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-document-detail',
|
selector: 'app-document-detail',
|
||||||
templateUrl: './document-detail.component.html',
|
templateUrl: './document-detail.component.html',
|
||||||
@ -116,7 +117,7 @@ export class DocumentDetailComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getColour(id: number) {
|
getColour(id: number) {
|
||||||
return PaperlessTag.COLOURS.find(c => c.id == this.getTag(id).colour)
|
return TAG_COLOURS.find(c => c.id == this.getTag(id).colour)
|
||||||
}
|
}
|
||||||
|
|
||||||
addTag(id: number) {
|
addTag(id: number) {
|
||||||
|
@ -1,74 +1,83 @@
|
|||||||
<app-page-header title="Documents">
|
<app-page-header [title]="docs.viewConfig ? docs.viewConfig.title : 'Documents'">
|
||||||
|
|
||||||
<div class="btn-group btn-group-toggle mr-2" ngbRadioGroup [(ngModel)]="displayMode" (ngModelChange)="saveDisplayMode()">
|
<div class="btn-group btn-group-toggle mr-2" ngbRadioGroup [(ngModel)]="displayMode"
|
||||||
|
(ngModelChange)="saveDisplayMode()">
|
||||||
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
||||||
<input ngbButton type="radio" class="btn btn-sm" value="details">
|
<input ngbButton type="radio" class="btn btn-sm" value="details">
|
||||||
<svg class="toolbaricon" fill="currentColor">
|
<svg class="toolbaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#list-ul"/>
|
<use xlink:href="assets/bootstrap-icons.svg#list-ul" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
||||||
<input ngbButton type="radio" class="btn btn-sm" value="smallCards">
|
<input ngbButton type="radio" class="btn btn-sm" value="smallCards">
|
||||||
<svg class="toolbaricon" fill="currentColor">
|
<svg class="toolbaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#grid"/>
|
<use xlink:href="assets/bootstrap-icons.svg#grid" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
||||||
<input ngbButton type="radio" class="btn btn-sm" value="largeCards">
|
<input ngbButton type="radio" class="btn btn-sm" value="largeCards">
|
||||||
<svg class="toolbaricon" fill="currentColor">
|
<svg class="toolbaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#hdd-stack"/>
|
<use xlink:href="assets/bootstrap-icons.svg#hdd-stack" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group btn-group-toggle mr-2" ngbRadioGroup [(ngModel)]="docs.currentSortDirection" (ngModelChange)="reload()">
|
<div class="btn-group btn-group-toggle mr-2" ngbRadioGroup [(ngModel)]="docs.currentSortDirection"
|
||||||
|
(ngModelChange)="reload()"
|
||||||
|
*ngIf="!docs.viewConfig">
|
||||||
<div ngbDropdown class="btn-group">
|
<div ngbDropdown class="btn-group">
|
||||||
<button class="btn btn-outline-secondary btn-sm" id="dropdownBasic1" ngbDropdownToggle>Sort by</button>
|
<button class="btn btn-outline-secondary btn-sm" id="dropdownBasic1" ngbDropdownToggle>Sort by</button>
|
||||||
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
||||||
<button *ngFor="let f of getSortFields()" ngbDropdownItem (click)="setSort(f.field)" [class.active]="docs.currentSortField == f.field">{{f.name}}</button>
|
<button *ngFor="let f of getSortFields()" ngbDropdownItem (click)="setSort(f.field)"
|
||||||
|
[class.active]="docs.currentSortField == f.field">{{f.name}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
||||||
<input ngbButton type="radio" class="btn btn-sm" value="asc">
|
<input ngbButton type="radio" class="btn btn-sm" value="asc">
|
||||||
<svg class="toolbaricon" fill="currentColor">
|
<svg class="toolbaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#sort-alpha-down"/>
|
<use xlink:href="assets/bootstrap-icons.svg#sort-alpha-down" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
|
||||||
<input ngbButton type="radio" class="btn btn-sm" value="des">
|
<input ngbButton type="radio" class="btn btn-sm" value="des">
|
||||||
<svg class="toolbaricon" fill="currentColor">
|
<svg class="toolbaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#sort-alpha-up-alt"/>
|
<use xlink:href="assets/bootstrap-icons.svg#sort-alpha-up-alt" />
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" (click)="showFilter=!showFilter">
|
<div class="btn-group" *ngIf="!docs.viewConfig">
|
||||||
<svg class="toolbaricon" fill="currentColor">
|
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#funnel"/>
|
<button type="button" class="btn btn-sm btn-outline-secondary" (click)="showFilter=!showFilter">
|
||||||
</svg>
|
<svg class="toolbaricon" fill="currentColor">
|
||||||
Filter
|
<use xlink:href="assets/bootstrap-icons.svg#funnel" />
|
||||||
</button>
|
</svg>
|
||||||
|
Filter
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="btn-group" ngbDropdown role="group">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary dropdown-toggle-split" ngbDropdownToggle></button>
|
||||||
|
<div class="dropdown-menu" ngbDropdownMenu>
|
||||||
|
<button ngbDropdownItem *ngFor="let config of savedViewConfigService.getConfigs()" (click)="loadViewConfig(config)">{{config.title}}</button>
|
||||||
|
<div class="dropdown-divider" *ngIf="savedViewConfigService.getConfigs().length > 0"></div>
|
||||||
|
<button ngbDropdownItem (click)="saveViewConfig()">Save current view</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
</app-page-header>
|
</app-page-header>
|
||||||
|
|
||||||
<div class="card w-100 mb-3" [hidden]="!showFilter">
|
<div class="card w-100 mb-3" [hidden]="!showFilter">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">Filter</h5>
|
<h5 class="card-title">Filter</h5>
|
||||||
<app-filter-editor [(ruleSet)]="filter" (apply)="applyFilter()"></app-filter-editor>
|
<app-filter-editor [(filterRules)]="filterRules" (apply)="applyFilterRules()"></app-filter-editor>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ngb-pagination
|
<ngb-pagination [pageSize]="25" [collectionSize]="docs.collectionSize" [(page)]="docs.currentPage" [maxSize]="5"
|
||||||
[pageSize]="25"
|
[rotate]="true" [boundaryLinks]="true" (pageChange)="reload()" aria-label="Default pagination"></ngb-pagination>
|
||||||
[collectionSize]="docs.collectionSize"
|
|
||||||
[(page)]="docs.currentPage"
|
|
||||||
[maxSize]="5"
|
|
||||||
[rotate]="true"
|
|
||||||
[boundaryLinks]="true"
|
|
||||||
(pageChange)="reload()"
|
|
||||||
aria-label="Default pagination"></ngb-pagination>
|
|
||||||
|
|
||||||
<div *ngIf="displayMode == 'largeCards'">
|
<div *ngIf="displayMode == 'largeCards'">
|
||||||
<app-document-card-large *ngFor="let d of docs.documents"
|
<app-document-card-large *ngFor="let d of docs.documents" [document]="d" [details]="d.content">
|
||||||
[document]="d"
|
|
||||||
[details]="d.content">
|
|
||||||
</app-document-card-large>
|
</app-document-card-large>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { cloneFilterRules, FilterRule } from 'src/app/data/filter-rule';
|
||||||
|
import { SavedViewConfig } from 'src/app/data/saved-view-config';
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
|
||||||
import { FilterRuleSet } from '../filter-editor/filter-editor.component';
|
import { SavedViewConfigService } from 'src/app/services/saved-view-config.service';
|
||||||
|
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-document-list',
|
selector: 'app-document-list',
|
||||||
@ -10,11 +15,14 @@ import { FilterRuleSet } from '../filter-editor/filter-editor.component';
|
|||||||
export class DocumentListComponent implements OnInit {
|
export class DocumentListComponent implements OnInit {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public docs: DocumentListViewService) { }
|
public docs: DocumentListViewService,
|
||||||
|
public savedViewConfigService: SavedViewConfigService,
|
||||||
|
public route: ActivatedRoute,
|
||||||
|
public modalService: NgbModal) { }
|
||||||
|
|
||||||
displayMode = 'smallCards' // largeCards, smallCards, details
|
displayMode = 'smallCards' // largeCards, smallCards, details
|
||||||
|
|
||||||
filter = new FilterRuleSet()
|
filterRules: FilterRule[] = []
|
||||||
showFilter = false
|
showFilter = false
|
||||||
|
|
||||||
getSortFields() {
|
getSortFields() {
|
||||||
@ -34,18 +42,47 @@ export class DocumentListComponent implements OnInit {
|
|||||||
if (localStorage.getItem('document-list:displayMode') != null) {
|
if (localStorage.getItem('document-list:displayMode') != null) {
|
||||||
this.displayMode = localStorage.getItem('document-list:displayMode')
|
this.displayMode = localStorage.getItem('document-list:displayMode')
|
||||||
}
|
}
|
||||||
this.filter = this.docs.currentFilter.clone()
|
this.route.paramMap.subscribe(params => {
|
||||||
this.showFilter = this.filter.rules.length > 0
|
if (params.has('id')) {
|
||||||
this.reload()
|
this.docs.viewConfig = this.savedViewConfigService.getConfig(params.get('id'))
|
||||||
|
} else {
|
||||||
|
this.filterRules = cloneFilterRules(this.docs.currentFilterRules)
|
||||||
|
this.showFilter = this.filterRules.length > 0
|
||||||
|
this.docs.viewConfig = null
|
||||||
|
}
|
||||||
|
this.reload()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
this.docs.reload()
|
this.docs.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
applyFilter() {
|
applyFilterRules() {
|
||||||
this.docs.setFilter(this.filter.clone())
|
this.docs.setFilterRules(this.filterRules)
|
||||||
this.reload()
|
this.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadViewConfig(config: SavedViewConfig) {
|
||||||
|
this.filterRules = config.filterRules
|
||||||
|
this.docs.setFilterRules(config.filterRules)
|
||||||
|
this.docs.currentSortField = config.sortField
|
||||||
|
this.docs.currentSortDirection = config.sortDirection
|
||||||
|
this.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
saveViewConfig() {
|
||||||
|
let modal = this.modalService.open(SaveViewConfigDialogComponent, {backdrop: 'static'})
|
||||||
|
modal.componentInstance.saveClicked.subscribe(formValue => {
|
||||||
|
this.savedViewConfigService.saveConfig({
|
||||||
|
filterRules: cloneFilterRules(this.filterRules),
|
||||||
|
title: formValue.title,
|
||||||
|
showInDashboard: formValue.showInDashboard,
|
||||||
|
showInSideBar: formValue.showInSideBar,
|
||||||
|
sortDirection: this.docs.currentSortDirection,
|
||||||
|
sortField: this.docs.currentSortField
|
||||||
|
})
|
||||||
|
modal.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
<form [formGroup]="saveViewConfigForm" class="needs-validation" novalidate (ngSubmit)="save()">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-basic-title">Save current view</h4>
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="cancel()">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<app-input-text title="Title" formControlName="title"></app-input-text>
|
||||||
|
<app-input-check title="Show in side bar" formControlName="showInSideBar"></app-input-check>
|
||||||
|
<app-input-check title="Show in dashboard" formControlName="showInDashboard"></app-input-check>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-dark" (click)="cancel()">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component';
|
||||||
|
|
||||||
|
describe('SaveViewConfigDialogComponent', () => {
|
||||||
|
let component: SaveViewConfigDialogComponent;
|
||||||
|
let fixture: ComponentFixture<SaveViewConfigDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ SaveViewConfigDialogComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SaveViewConfigDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,33 @@
|
|||||||
|
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-save-view-config-dialog',
|
||||||
|
templateUrl: './save-view-config-dialog.component.html',
|
||||||
|
styleUrls: ['./save-view-config-dialog.component.css']
|
||||||
|
})
|
||||||
|
export class SaveViewConfigDialogComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(private modal: NgbActiveModal) { }
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
public saveClicked = new EventEmitter()
|
||||||
|
|
||||||
|
saveViewConfigForm = new FormGroup({
|
||||||
|
title: new FormControl(''),
|
||||||
|
showInSideBar: new FormControl(false),
|
||||||
|
showInDashboard: new FormControl(false),
|
||||||
|
})
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
this.saveClicked.emit(this.saveViewConfigForm.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.modal.close()
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<div *ngFor="let rule of ruleSet.rules" class="form-row form-group">
|
<div *ngFor="let rule of filterRules" class="form-row form-group">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<select class="form-control form-control-sm" [(ngModel)]="rule.type" (change)="rule.value = null">
|
<select class="form-control form-control-sm" [(ngModel)]="rule.type" (change)="rule.value = null">
|
||||||
<option *ngFor="let ruleType of getRuleTypes()" [ngValue]="ruleType">{{ruleType.name}}</option>
|
<option *ngFor="let ruleType of getRuleTypes()" [ngValue]="ruleType">{{ruleType.name}}</option>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
import { FilterRule } from 'src/app/data/filter-rule';
|
||||||
|
import { FilterRuleType, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type';
|
||||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
|
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
|
||||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
|
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
|
||||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
|
import { PaperlessTag } from 'src/app/data/paperless-tag';
|
||||||
@ -6,66 +8,6 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
|
|||||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
|
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
|
||||||
import { TagService } from 'src/app/services/rest/tag.service';
|
import { TagService } from 'src/app/services/rest/tag.service';
|
||||||
|
|
||||||
export interface FilterRuleType {
|
|
||||||
name: string
|
|
||||||
filtervar: string
|
|
||||||
datatype: string //number, string, boolean, date
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FilterRule {
|
|
||||||
type: FilterRuleType
|
|
||||||
value: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FilterRuleSet {
|
|
||||||
|
|
||||||
static RULE_TYPES: FilterRuleType[] = [
|
|
||||||
{name: "Title contains", filtervar: "title__icontains", datatype: "string"},
|
|
||||||
{name: "Content contains", filtervar: "content__icontains", datatype: "string"},
|
|
||||||
|
|
||||||
{name: "ASN is", filtervar: "archive_serial_number", datatype: "number"},
|
|
||||||
|
|
||||||
{name: "Correspondent is", filtervar: "correspondent__id", datatype: "correspondent"},
|
|
||||||
{name: "Document type is", filtervar: "document_type__id", datatype: "document_type"},
|
|
||||||
{name: "Has tag", filtervar: "tags__id", datatype: "tag"},
|
|
||||||
|
|
||||||
{name: "Has any tag", filtervar: "is_tagged", datatype: "boolean"},
|
|
||||||
|
|
||||||
{name: "Date created before", filtervar: "created__date__lt", datatype: "date"},
|
|
||||||
{name: "Date created after", filtervar: "created__date__gt", datatype: "date"},
|
|
||||||
|
|
||||||
{name: "Year created is", filtervar: "created__year", datatype: "number"},
|
|
||||||
{name: "Month created is", filtervar: "created__month", datatype: "number"},
|
|
||||||
{name: "Day created is", filtervar: "created__day", datatype: "number"},
|
|
||||||
|
|
||||||
{name: "Date added before", filtervar: "added__date__lt", datatype: "date"},
|
|
||||||
{name: "Date added after", filtervar: "added__date__gt", datatype: "date"},
|
|
||||||
|
|
||||||
{name: "Date modified before", filtervar: "modified__date__lt", datatype: "date"},
|
|
||||||
{name: "Date modified after", filtervar: "modified__date__gt", datatype: "date"},
|
|
||||||
]
|
|
||||||
|
|
||||||
rules: FilterRule[] = []
|
|
||||||
|
|
||||||
toQueryParams() {
|
|
||||||
let params = {}
|
|
||||||
for (let rule of this.rules) {
|
|
||||||
params[rule.type.filtervar] = rule.value
|
|
||||||
}
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
clone(): FilterRuleSet {
|
|
||||||
let newRuleSet = new FilterRuleSet()
|
|
||||||
for (let rule of this.rules) {
|
|
||||||
newRuleSet.rules.push({type: rule.type, value: rule.value})
|
|
||||||
}
|
|
||||||
return newRuleSet
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-filter-editor',
|
selector: 'app-filter-editor',
|
||||||
@ -77,28 +19,25 @@ export class FilterEditorComponent implements OnInit {
|
|||||||
constructor(private documentTypeService: DocumentTypeService, private tagService: TagService, private correspondentService: CorrespondentService) { }
|
constructor(private documentTypeService: DocumentTypeService, private tagService: TagService, private correspondentService: CorrespondentService) { }
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
ruleSet = new FilterRuleSet()
|
filterRules: FilterRule[] = []
|
||||||
|
|
||||||
@Output()
|
|
||||||
ruleSetChange = new EventEmitter<FilterRuleSet>()
|
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
apply = new EventEmitter()
|
apply = new EventEmitter()
|
||||||
|
|
||||||
selectedRuleType: FilterRuleType = FilterRuleSet.RULE_TYPES[0]
|
selectedRuleType: FilterRuleType = FILTER_RULE_TYPES[0]
|
||||||
|
|
||||||
correspondents: PaperlessCorrespondent[] = []
|
correspondents: PaperlessCorrespondent[] = []
|
||||||
tags: PaperlessTag[] = []
|
tags: PaperlessTag[] = []
|
||||||
documentTypes: PaperlessDocumentType[] = []
|
documentTypes: PaperlessDocumentType[] = []
|
||||||
|
|
||||||
newRuleClicked() {
|
newRuleClicked() {
|
||||||
this.ruleSet.rules.push({type: this.selectedRuleType, value: null})
|
this.filterRules.push({type: this.selectedRuleType, value: null})
|
||||||
}
|
}
|
||||||
|
|
||||||
removeRuleClicked(rule) {
|
removeRuleClicked(rule) {
|
||||||
let index = this.ruleSet.rules.findIndex(r => r == rule)
|
let index = this.filterRules.findIndex(r => r == rule)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.ruleSet.rules.splice(index, 1)
|
this.filterRules.splice(index, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +46,7 @@ export class FilterEditorComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearClicked() {
|
clearClicked() {
|
||||||
this.ruleSet.rules.splice(0,this.ruleSet.rules.length)
|
this.filterRules.splice(0,this.filterRules.length)
|
||||||
this.apply.next()
|
this.apply.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,6 +57,7 @@ export class FilterEditorComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRuleTypes() {
|
getRuleTypes() {
|
||||||
return FilterRuleSet.RULE_TYPES
|
return FILTER_RULE_TYPES
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Directive, OnInit } from '@angular/core';
|
import { Directive, OnInit } from '@angular/core';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { MatchingModel } from 'src/app/data/matching-model';
|
import { MatchingModel, MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model';
|
||||||
import { ObjectWithId } from 'src/app/data/object-with-id';
|
import { ObjectWithId } from 'src/app/data/object-with-id';
|
||||||
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service';
|
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service';
|
||||||
import { DeleteDialogComponent } from '../../common/delete-dialog/delete-dialog.component';
|
import { DeleteDialogComponent } from '../../common/delete-dialog/delete-dialog.component';
|
||||||
@ -21,10 +21,10 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
|
|||||||
public collectionSize = 0
|
public collectionSize = 0
|
||||||
|
|
||||||
getMatching(o: MatchingModel) {
|
getMatching(o: MatchingModel) {
|
||||||
if (o.matching_algorithm == MatchingModel.MATCH_AUTO) {
|
if (o.matching_algorithm == MATCH_AUTO) {
|
||||||
return "Automatic"
|
return "Automatic"
|
||||||
} else if (o.match && o.match.length > 0) {
|
} else if (o.match && o.match.length > 0) {
|
||||||
return `${o.match} (${MatchingModel.MATCHING_ALGORITHMS.find(a => a.id == o.matching_algorithm).name})`
|
return `${o.match} (${MATCHING_ALGORITHMS.find(a => a.id == o.matching_algorithm).name})`
|
||||||
} else {
|
} else {
|
||||||
return "-"
|
return "-"
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,38 @@
|
|||||||
|
|
||||||
</app-page-header>
|
</app-page-header>
|
||||||
|
|
||||||
<p>items per page, documents per view type</p>
|
<!-- <p>items per page, documents per view type</p> -->
|
||||||
|
<ul ngbNav #nav="ngbNav" class="nav-tabs">
|
||||||
|
<li [ngbNavItem]="1">
|
||||||
|
<a ngbNavLink>Document List Settings</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
|
</ng-template>
|
||||||
|
</li>
|
||||||
|
<li [ngbNavItem]="2">
|
||||||
|
<a ngbNavLink>Saved views</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Title</th>
|
||||||
|
<th scope="col">Show in dashboard</th>
|
||||||
|
<th scope="col">Show in sidebar</th>
|
||||||
|
<th scope="col">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let config of savedViewConfigService.getConfigs()">
|
||||||
|
<td>{{ config.title }}</td>
|
||||||
|
<td>{{ config.showInDashboard }}</td>
|
||||||
|
<td>{{ config.showInSideBar }}</td>
|
||||||
|
<td><button type="button" class="btn btn-sm btn-outline-danger" (click)="deleteViewConfig(config)">Delete</button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</ng-template>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
@ -1,4 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { SavedViewConfig } from 'src/app/data/saved-view-config';
|
||||||
|
import { SavedViewConfigService } from 'src/app/services/saved-view-config.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: 'app-settings',
|
||||||
@ -7,9 +9,17 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit {
|
export class SettingsComponent implements OnInit {
|
||||||
|
|
||||||
constructor() { }
|
constructor(
|
||||||
|
private savedViewConfigService: SavedViewConfigService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
active
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteViewConfig(config: SavedViewConfig) {
|
||||||
|
this.savedViewConfigService.deleteConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { Component } from '@angular/core';
|
|||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component';
|
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component';
|
||||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
|
import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
|
||||||
import { TagService } from 'src/app/services/rest/tag.service';
|
import { TagService } from 'src/app/services/rest/tag.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
@ -29,11 +29,11 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getColours() {
|
getColours() {
|
||||||
return PaperlessTag.COLOURS
|
return TAG_COLOURS
|
||||||
}
|
}
|
||||||
|
|
||||||
getColor(id: number) {
|
getColor(id: number) {
|
||||||
return PaperlessTag.COLOURS.find(c => c.id == id)
|
return TAG_COLOURS.find(c => c.id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { PaperlessTag } from 'src/app/data/paperless-tag';
|
import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
|
||||||
import { TagService } from 'src/app/services/rest/tag.service';
|
import { TagService } from 'src/app/services/rest/tag.service';
|
||||||
import { CorrespondentEditDialogComponent } from '../correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component';
|
import { CorrespondentEditDialogComponent } from '../correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component';
|
||||||
import { GenericListComponent } from '../generic-list/generic-list.component';
|
import { GenericListComponent } from '../generic-list/generic-list.component';
|
||||||
@ -18,7 +18,7 @@ export class TagListComponent extends GenericListComponent<PaperlessTag> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getColor(id) {
|
getColor(id) {
|
||||||
return PaperlessTag.COLOURS.find(c => c.id == id)
|
return TAG_COLOURS.find(c => c.id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
getObjectName(object: PaperlessTag) {
|
getObjectName(object: PaperlessTag) {
|
||||||
|
31
src-ui/src/app/data/filter-rule-type.ts
Normal file
31
src-ui/src/app/data/filter-rule-type.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export const FILTER_RULE_TYPES: FilterRuleType[] = [
|
||||||
|
{name: "Title contains", filtervar: "title__icontains", datatype: "string"},
|
||||||
|
{name: "Content contains", filtervar: "content__icontains", datatype: "string"},
|
||||||
|
|
||||||
|
{name: "ASN is", filtervar: "archive_serial_number", datatype: "number"},
|
||||||
|
|
||||||
|
{name: "Correspondent is", filtervar: "correspondent__id", datatype: "correspondent"},
|
||||||
|
{name: "Document type is", filtervar: "document_type__id", datatype: "document_type"},
|
||||||
|
{name: "Has tag", filtervar: "tags__id", datatype: "tag"},
|
||||||
|
|
||||||
|
{name: "Has any tag", filtervar: "is_tagged", datatype: "boolean"},
|
||||||
|
|
||||||
|
{name: "Date created before", filtervar: "created__date__lt", datatype: "date"},
|
||||||
|
{name: "Date created after", filtervar: "created__date__gt", datatype: "date"},
|
||||||
|
|
||||||
|
{name: "Year created is", filtervar: "created__year", datatype: "number"},
|
||||||
|
{name: "Month created is", filtervar: "created__month", datatype: "number"},
|
||||||
|
{name: "Day created is", filtervar: "created__day", datatype: "number"},
|
||||||
|
|
||||||
|
{name: "Date added before", filtervar: "added__date__lt", datatype: "date"},
|
||||||
|
{name: "Date added after", filtervar: "added__date__gt", datatype: "date"},
|
||||||
|
|
||||||
|
{name: "Date modified before", filtervar: "modified__date__lt", datatype: "date"},
|
||||||
|
{name: "Date modified after", filtervar: "modified__date__gt", datatype: "date"},
|
||||||
|
]
|
||||||
|
|
||||||
|
export interface FilterRuleType {
|
||||||
|
name: string
|
||||||
|
filtervar: string
|
||||||
|
datatype: string //number, string, boolean, date
|
||||||
|
}
|
23
src-ui/src/app/data/filter-rule.ts
Normal file
23
src-ui/src/app/data/filter-rule.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { FilterRuleType } from './filter-rule-type';
|
||||||
|
|
||||||
|
|
||||||
|
export function filterRulesToQueryParams(filterRules: FilterRule[]) {
|
||||||
|
let params = {}
|
||||||
|
for (let rule of filterRules) {
|
||||||
|
params[rule.type.filtervar] = rule.value
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] {
|
||||||
|
let newRules: FilterRule[] = []
|
||||||
|
for (let rule of filterRules) {
|
||||||
|
newRules.push({type: rule.type, value: rule.value})
|
||||||
|
}
|
||||||
|
return newRules
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterRule {
|
||||||
|
type: FilterRuleType
|
||||||
|
value: any
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
import { MatchingModel } from './matching-model';
|
|
||||||
|
|
||||||
describe('MatchingModel', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new MatchingModel()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,22 +1,23 @@
|
|||||||
import { ObjectWithId } from './object-with-id';
|
import { ObjectWithId } from './object-with-id';
|
||||||
|
|
||||||
export class MatchingModel extends ObjectWithId {
|
|
||||||
|
|
||||||
static MATCH_ANY = 1
|
export const MATCH_ANY = 1
|
||||||
static MATCH_ALL = 2
|
export const MATCH_ALL = 2
|
||||||
static MATCH_LITERAL = 3
|
export const MATCH_LITERAL = 3
|
||||||
static MATCH_REGEX = 4
|
export const MATCH_REGEX = 4
|
||||||
static MATCH_FUZZY = 5
|
export const MATCH_FUZZY = 5
|
||||||
static MATCH_AUTO = 6
|
export const MATCH_AUTO = 6
|
||||||
|
|
||||||
static MATCHING_ALGORITHMS = [
|
export const MATCHING_ALGORITHMS = [
|
||||||
{id: MatchingModel.MATCH_ANY, name: "Any"},
|
{id: MATCH_ANY, name: "Any"},
|
||||||
{id: MatchingModel.MATCH_ALL, name: "All"},
|
{id: MATCH_ALL, name: "All"},
|
||||||
{id: MatchingModel.MATCH_LITERAL, name: "Literal"},
|
{id: MATCH_LITERAL, name: "Literal"},
|
||||||
{id: MatchingModel.MATCH_REGEX, name: "Regular Expression"},
|
{id: MATCH_REGEX, name: "Regular Expression"},
|
||||||
{id: MatchingModel.MATCH_FUZZY, name: "Fuzzy Match"},
|
{id: MATCH_FUZZY, name: "Fuzzy Match"},
|
||||||
{id: MatchingModel.MATCH_AUTO, name: "Auto"},
|
{id: MATCH_AUTO, name: "Auto"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export interface MatchingModel extends ObjectWithId {
|
||||||
|
|
||||||
name?: string
|
name?: string
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { ObjectWithId } from './object-with-id';
|
|
||||||
|
|
||||||
describe('ObjectWithId', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new ObjectWithId()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,4 +1,4 @@
|
|||||||
export class ObjectWithId {
|
export interface ObjectWithId {
|
||||||
|
|
||||||
id?: number
|
id?: number
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { PaperlessCorrespondent } from './paperless-correspondent';
|
|
||||||
|
|
||||||
describe('PaperlessCorrespondent', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new PaperlessCorrespondent()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,6 +1,6 @@
|
|||||||
import { MatchingModel } from './matching-model';
|
import { MatchingModel } from './matching-model';
|
||||||
|
|
||||||
export class PaperlessCorrespondent extends MatchingModel {
|
export interface PaperlessCorrespondent extends MatchingModel {
|
||||||
|
|
||||||
document_count?: number
|
document_count?: number
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { PaperlessDocumentType } from './paperless-document-type';
|
|
||||||
|
|
||||||
describe('PaperlessDocumentType', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new PaperlessDocumentType()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,6 +1,6 @@
|
|||||||
import { MatchingModel } from './matching-model';
|
import { MatchingModel } from './matching-model';
|
||||||
|
|
||||||
export class PaperlessDocumentType extends MatchingModel {
|
export interface PaperlessDocumentType extends MatchingModel {
|
||||||
|
|
||||||
document_count?: number
|
document_count?: number
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { PaperlessDocument } from './paperless-document';
|
|
||||||
|
|
||||||
describe('PaperlessDocument', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new PaperlessDocument()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -3,7 +3,7 @@ import { ObjectWithId } from './object-with-id'
|
|||||||
import { PaperlessTag } from './paperless-tag'
|
import { PaperlessTag } from './paperless-tag'
|
||||||
import { PaperlessDocumentType } from './paperless-document-type'
|
import { PaperlessDocumentType } from './paperless-document-type'
|
||||||
|
|
||||||
export class PaperlessDocument extends ObjectWithId {
|
export interface PaperlessDocument extends ObjectWithId {
|
||||||
|
|
||||||
correspondent?: PaperlessCorrespondent
|
correspondent?: PaperlessCorrespondent
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { PaperlessLog } from './paperless-log';
|
|
||||||
|
|
||||||
describe('PaperlessLog', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new PaperlessLog()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,2 +1,2 @@
|
|||||||
export class PaperlessLog {
|
export interface PaperlessLog {
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { PaperlessTag } from './paperless-tag';
|
|
||||||
|
|
||||||
describe('PaperlessTag', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new PaperlessTag()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,23 +1,24 @@
|
|||||||
import { MatchingModel } from './matching-model';
|
import { MatchingModel } from './matching-model';
|
||||||
import { ObjectWithId } from './object-with-id';
|
import { ObjectWithId } from './object-with-id';
|
||||||
|
|
||||||
export class PaperlessTag extends MatchingModel {
|
|
||||||
|
|
||||||
static COLOURS = [
|
export const TAG_COLOURS = [
|
||||||
{id: 1, value: "#a6cee3", name: "Light Blue", textColor: "#000000"},
|
{id: 1, value: "#a6cee3", name: "Light Blue", textColor: "#000000"},
|
||||||
{id: 2, value: "#1f78b4", name: "Blue", textColor: "#ffffff"},
|
{id: 2, value: "#1f78b4", name: "Blue", textColor: "#ffffff"},
|
||||||
{id: 3, value: "#b2df8a", name: "Light Green", textColor: "#000000"},
|
{id: 3, value: "#b2df8a", name: "Light Green", textColor: "#000000"},
|
||||||
{id: 4, value: "#33a02c", name: "Green", textColor: "#000000"},
|
{id: 4, value: "#33a02c", name: "Green", textColor: "#000000"},
|
||||||
{id: 5, value: "#fb9a99", name: "Light Red", textColor: "#000000"},
|
{id: 5, value: "#fb9a99", name: "Light Red", textColor: "#000000"},
|
||||||
{id: 6, value: "#e31a1c", name: "Red ", textColor: "#ffffff"},
|
{id: 6, value: "#e31a1c", name: "Red ", textColor: "#ffffff"},
|
||||||
{id: 7, value: "#fdbf6f", name: "Light Orange", textColor: "#000000"},
|
{id: 7, value: "#fdbf6f", name: "Light Orange", textColor: "#000000"},
|
||||||
{id: 8, value: "#ff7f00", name: "Orange", textColor: "#000000"},
|
{id: 8, value: "#ff7f00", name: "Orange", textColor: "#000000"},
|
||||||
{id: 9, value: "#cab2d6", name: "Light Violet", textColor: "#000000"},
|
{id: 9, value: "#cab2d6", name: "Light Violet", textColor: "#000000"},
|
||||||
{id: 10, value: "#6a3d9a", name: "Violet", textColor: "#ffffff"},
|
{id: 10, value: "#6a3d9a", name: "Violet", textColor: "#ffffff"},
|
||||||
{id: 11, value: "#b15928", name: "Brown", textColor: "#000000"},
|
{id: 11, value: "#b15928", name: "Brown", textColor: "#000000"},
|
||||||
{id: 12, value: "#000000", name: "Black", textColor: "#ffffff"},
|
{id: 12, value: "#000000", name: "Black", textColor: "#ffffff"},
|
||||||
{id: 13, value: "#cccccc", name: "Light Grey", textColor: "#000000"}
|
{id: 13, value: "#cccccc", name: "Light Grey", textColor: "#000000"}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export interface PaperlessTag extends MatchingModel {
|
||||||
|
|
||||||
colour?: number
|
colour?: number
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import { Results } from './results';
|
|
||||||
|
|
||||||
describe('Results', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new Results()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,4 +1,4 @@
|
|||||||
export class Results<T> {
|
export interface Results<T> {
|
||||||
|
|
||||||
count: number
|
count: number
|
||||||
|
|
||||||
|
19
src-ui/src/app/data/saved-view-config.ts
Normal file
19
src-ui/src/app/data/saved-view-config.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { FilterRule } from './filter-rule';
|
||||||
|
|
||||||
|
export interface SavedViewConfig {
|
||||||
|
|
||||||
|
id?: string
|
||||||
|
|
||||||
|
filterRules: FilterRule[]
|
||||||
|
|
||||||
|
sortField: string
|
||||||
|
|
||||||
|
sortDirection: string
|
||||||
|
|
||||||
|
title: string
|
||||||
|
|
||||||
|
showInSideBar: boolean
|
||||||
|
|
||||||
|
showInDashboard: boolean
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { FilterRuleSet } from '../components/filter-editor/filter-editor.component';
|
import { cloneFilterRules, FilterRule, filterRulesToQueryParams } from '../data/filter-rule';
|
||||||
import { PaperlessDocument } from '../data/paperless-document';
|
import { PaperlessDocument } from '../data/paperless-document';
|
||||||
|
import { SavedViewConfig } from '../data/saved-view-config';
|
||||||
import { DocumentService } from './rest/document.service';
|
import { DocumentService } from './rest/document.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -24,17 +25,28 @@ export class DocumentListViewService {
|
|||||||
currentPage = 1
|
currentPage = 1
|
||||||
collectionSize: number
|
collectionSize: number
|
||||||
|
|
||||||
currentFilter = new FilterRuleSet()
|
currentFilterRules: FilterRule[] = []
|
||||||
|
|
||||||
currentSortDirection = 'des'
|
currentSortDirection = 'des'
|
||||||
currentSortField = DocumentListViewService.DEFAULT_SORT_FIELD
|
currentSortField = DocumentListViewService.DEFAULT_SORT_FIELD
|
||||||
|
|
||||||
|
viewConfig: SavedViewConfig
|
||||||
|
|
||||||
reload(onFinish?) {
|
reload(onFinish?) {
|
||||||
|
let ordering: string
|
||||||
|
let filterRules: FilterRule[]
|
||||||
|
if (this.viewConfig) {
|
||||||
|
ordering = this.getOrderingQueryParam(this.viewConfig.sortField, this.viewConfig.sortDirection)
|
||||||
|
filterRules = this.viewConfig.filterRules
|
||||||
|
} else {
|
||||||
|
ordering = this.getOrderingQueryParam(this.currentSortField, this.currentSortDirection)
|
||||||
|
filterRules = this.currentFilterRules
|
||||||
|
}
|
||||||
|
|
||||||
this.documentService.list(
|
this.documentService.list(
|
||||||
this.currentPage,
|
this.currentPage,
|
||||||
null,
|
null,
|
||||||
this.getOrderingQueryParam(),
|
ordering,
|
||||||
this.currentFilter.toQueryParams()).subscribe(
|
filterRulesToQueryParams(filterRules)).subscribe(
|
||||||
result => {
|
result => {
|
||||||
this.collectionSize = result.count
|
this.collectionSize = result.count
|
||||||
this.documents = result.results
|
this.documents = result.results
|
||||||
@ -50,16 +62,17 @@ export class DocumentListViewService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderingQueryParam() {
|
getOrderingQueryParam(sortField: string, sortDirection: string) {
|
||||||
if (DocumentListViewService.SORT_FIELDS.find(f => f.field == this.currentSortField)) {
|
if (DocumentListViewService.SORT_FIELDS.find(f => f.field == sortField)) {
|
||||||
return (this.currentSortDirection == 'des' ? '-' : '') + this.currentSortField
|
return (sortDirection == 'des' ? '-' : '') + sortField
|
||||||
} else {
|
} else {
|
||||||
return DocumentListViewService.DEFAULT_SORT_FIELD
|
return DocumentListViewService.DEFAULT_SORT_FIELD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilter(filter: FilterRuleSet) {
|
//TODO: refactor
|
||||||
this.currentFilter = filter
|
setFilterRules(filterRules: FilterRule[]) {
|
||||||
|
this.currentFilterRules = cloneFilterRules(filterRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastPage(): number {
|
getLastPage(): number {
|
||||||
|
16
src-ui/src/app/services/saved-view-config.service.spec.ts
Normal file
16
src-ui/src/app/services/saved-view-config.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SavedViewConfigService } from './saved-view-config.service';
|
||||||
|
|
||||||
|
describe('SavedViewConfigService', () => {
|
||||||
|
let service: SavedViewConfigService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(SavedViewConfigService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
54
src-ui/src/app/services/saved-view-config.service.ts
Normal file
54
src-ui/src/app/services/saved-view-config.service.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { SavedViewConfig } from '../data/saved-view-config';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class SavedViewConfigService {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
let savedConfigs = localStorage.getItem('saved-view-config-service:savedConfigs')
|
||||||
|
if (savedConfigs) {
|
||||||
|
this.configs = JSON.parse(savedConfigs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private configs: SavedViewConfig[] = []
|
||||||
|
|
||||||
|
getConfigs(): SavedViewConfig[] {
|
||||||
|
return this.configs
|
||||||
|
}
|
||||||
|
|
||||||
|
getDashboardConfigs(): SavedViewConfig[] {
|
||||||
|
return this.configs.filter(sf => sf.showInDashboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
getSideBarConfigs(): SavedViewConfig[] {
|
||||||
|
return this.configs.filter(sf => sf.showInSideBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(id: string): SavedViewConfig {
|
||||||
|
return this.configs.find(sf => sf.id == id)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveConfig(config: SavedViewConfig) {
|
||||||
|
config.id = uuidv4()
|
||||||
|
this.configs.push(config)
|
||||||
|
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
private save() {
|
||||||
|
localStorage.setItem('saved-view-config-service:savedConfigs', JSON.stringify(this.configs))
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteConfig(config: SavedViewConfig) {
|
||||||
|
let index = this.configs.findIndex(vc => vc.id == config.id)
|
||||||
|
if (index != -1) {
|
||||||
|
this.configs.splice(index, 1)
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user