mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge branch 'dev' into feature-server-side-saved-views
This commit is contained in:
		| @@ -63,7 +63,7 @@ | |||||||
| </app-page-header> | </app-page-header> | ||||||
|  |  | ||||||
| <div class="w-100 mb-4"> | <div class="w-100 mb-4"> | ||||||
|   <app-filter-editor [(filterEditorService)]="filterEditorService" (apply)="applyFilterRules()" (clear)="clearFilterRules()" #filterEditor></app-filter-editor> |   <app-filter-editor [(filterRules)]="list.filterRules" #filterEditor></app-filter-editor> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <div class="d-flex justify-content-between align-items-center"> | <div class="d-flex justify-content-between align-items-center"> | ||||||
|   | |||||||
| @@ -2,21 +2,14 @@ import { Component, OnInit, ViewChild } from '@angular/core'; | |||||||
| import { Title } from '@angular/platform-browser'; | import { Title } from '@angular/platform-browser'; | ||||||
| import { ActivatedRoute } from '@angular/router'; | import { ActivatedRoute } from '@angular/router'; | ||||||
| import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; | ||||||
| import { cloneFilterRules, FilterRule } from 'src/app/data/filter-rule'; |  | ||||||
| import { FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type'; |  | ||||||
| import { SavedViewConfig } from 'src/app/data/saved-view-config'; | 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 { FilterEditorViewService } from 'src/app/services/filter-editor-view.service'; |  | ||||||
| import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service'; | import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service'; | ||||||
| import { SavedViewConfigService } from 'src/app/services/saved-view-config.service'; | import { SavedViewConfigService } from 'src/app/services/saved-view-config.service'; | ||||||
| import { Toast, ToastService } from 'src/app/services/toast.service'; | import { Toast, ToastService } from 'src/app/services/toast.service'; | ||||||
| import { environment } from 'src/environments/environment'; | import { environment } from 'src/environments/environment'; | ||||||
|  | import { FilterEditorComponent } from '../filter-editor/filter-editor.component'; | ||||||
| import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; | import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; | ||||||
| import { FilterEditorComponent } from 'src/app/components/filter-editor/filter-editor.component'; |  | ||||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; |  | ||||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; |  | ||||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; |  | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-document-list', |   selector: 'app-document-list', | ||||||
|   templateUrl: './document-list.component.html', |   templateUrl: './document-list.component.html', | ||||||
| @@ -27,26 +20,20 @@ export class DocumentListComponent implements OnInit { | |||||||
|   constructor( |   constructor( | ||||||
|     public list: DocumentListViewService, |     public list: DocumentListViewService, | ||||||
|     public savedViewConfigService: SavedViewConfigService, |     public savedViewConfigService: SavedViewConfigService, | ||||||
|     public filterEditorService: FilterEditorViewService, |  | ||||||
|     public route: ActivatedRoute, |     public route: ActivatedRoute, | ||||||
|     private toastService: ToastService, |     private toastService: ToastService, | ||||||
|     public modalService: NgbModal, |     public modalService: NgbModal, | ||||||
|     private titleService: Title) { } |     private titleService: Title) { } | ||||||
|  |  | ||||||
|  |   @ViewChild("filterEditor") | ||||||
|  |   private filterEditor: FilterEditorComponent | ||||||
|  |  | ||||||
|   displayMode = 'smallCards' // largeCards, smallCards, details |   displayMode = 'smallCards' // largeCards, smallCards, details | ||||||
|  |  | ||||||
|   get isFiltered() { |   get isFiltered() { | ||||||
|     return this.list.filterRules?.length > 0 |     return this.list.filterRules?.length > 0 | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   set filterRules(filterRules: FilterRule[]) { |  | ||||||
|     this.filterEditorService.filterRules = filterRules |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get filterRules(): FilterRule[] { |  | ||||||
|     return this.filterEditorService.filterRules |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   getTitle() { |   getTitle() { | ||||||
|     return this.list.savedViewTitle || "Documents" |     return this.list.savedViewTitle || "Documents" | ||||||
|   } |   } | ||||||
| @@ -66,29 +53,18 @@ export class DocumentListComponent implements OnInit { | |||||||
|     this.route.paramMap.subscribe(params => { |     this.route.paramMap.subscribe(params => { | ||||||
|       if (params.has('id')) { |       if (params.has('id')) { | ||||||
|         this.list.savedView = this.savedViewConfigService.getConfig(params.get('id')) |         this.list.savedView = this.savedViewConfigService.getConfig(params.get('id')) | ||||||
|         this.filterEditorService.filterRules = this.list.filterRules |  | ||||||
|         this.titleService.setTitle(`${this.list.savedView.title} - ${environment.appTitle}`) |         this.titleService.setTitle(`${this.list.savedView.title} - ${environment.appTitle}`) | ||||||
|       } else { |       } else { | ||||||
|         this.list.savedView = null |         this.list.savedView = null | ||||||
|         this.filterEditorService.filterRules = this.list.filterRules |  | ||||||
|         this.titleService.setTitle(`Documents - ${environment.appTitle}`) |         this.titleService.setTitle(`Documents - ${environment.appTitle}`) | ||||||
|       } |       } | ||||||
|       this.list.clear() |       this.list.clear() | ||||||
|       this.list.reload() |       this.list.reload() | ||||||
|     }) |     }) | ||||||
|     this.filterEditorService.filterRules = this.list.filterRules |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   applyFilterRules() { |  | ||||||
|     this.list.filterRules = this.filterEditorService.filterRules |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   clearFilterRules() { |  | ||||||
|     this.list.filterRules = this.filterEditorService.filterRules |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   loadViewConfig(config: SavedViewConfig) { |   loadViewConfig(config: SavedViewConfig) { | ||||||
|     this.filterEditorService.filterRules = cloneFilterRules(config.filterRules) |  | ||||||
|     this.list.load(config) |     this.list.load(config) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -113,18 +89,15 @@ export class DocumentListComponent implements OnInit { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   clickTag(tagID: number) { |   clickTag(tagID: number) { | ||||||
|     this.filterEditorService.toggleFilterByTag(tagID) |     this.filterEditor.toggleTag(tagID) | ||||||
|     this.applyFilterRules() |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   clickCorrespondent(correspondentID: number) { |   clickCorrespondent(correspondentID: number) { | ||||||
|     this.filterEditorService.toggleFilterByCorrespondent(correspondentID) |     this.filterEditor.toggleCorrespondent(correspondentID) | ||||||
|     this.applyFilterRules() |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   clickDocumentType(documentTypeID: number) { |   clickDocumentType(documentTypeID: number) { | ||||||
|     this.filterEditorService.toggleFilterByDocumentType(documentTypeID) |     this.filterEditor.toggleDocumentType(documentTypeID) | ||||||
|     this.applyFilterRules() |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,4 @@ | |||||||
| import { Component, EventEmitter, Input, Output, ElementRef, ViewChild, OnChanges, SimpleChange } from '@angular/core'; | import { Component, EventEmitter, Input, Output, ElementRef, ViewChild, SimpleChange } from '@angular/core'; | ||||||
| import { FilterRule } from 'src/app/data/filter-rule'; |  | ||||||
| import { ObjectWithId } from 'src/app/data/object-with-id'; |  | ||||||
| import { NgbDate, NgbDateStruct, NgbDatepicker } from '@ng-bootstrap/ng-bootstrap'; | import { NgbDate, NgbDateStruct, NgbDatepicker } from '@ng-bootstrap/ng-bootstrap'; | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   | |||||||
| @@ -6,14 +6,14 @@ | |||||||
|     <input class="form-control form-control-sm" type="text" [(ngModel)]="titleFilter" placeholder="Title"> |     <input class="form-control form-control-sm" type="text" [(ngModel)]="titleFilter" placeholder="Title"> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|   <app-filter-dropdown class="col-auto" [(items)]="filterEditorService.tags" [itemsSelected]="filterEditorService.selectedTags" [title]="'Tags'" (toggle)="onToggleTag($event)"></app-filter-dropdown> |   <app-filter-dropdown class="col-auto" [items]="tags" [itemsSelected]="selectedTags" title="Tags" (toggle)="toggleTag($event.id)"></app-filter-dropdown> | ||||||
|   <app-filter-dropdown class="col-auto" [(items)]="filterEditorService.correspondents" [itemsSelected]="filterEditorService.selectedCorrespondents" [title]="'Correspondents'" (toggle)="onToggleCorrespondent($event)"></app-filter-dropdown> |   <app-filter-dropdown class="col-auto" [items]="correspondents" [itemsSelected]="selectedCorrespondents" title="Correspondents" (toggle)="toggleCorrespondent($event.id)"></app-filter-dropdown> | ||||||
|   <app-filter-dropdown class="col-auto" [(items)]="filterEditorService.documentTypes" [itemsSelected]="filterEditorService.selectedDocumentTypes" [title]="'Document Types'" (toggle)="onToggleDocumentType($event)"></app-filter-dropdown> |   <app-filter-dropdown class="col-auto" [items]="documentTypes" [itemsSelected]="selectedDocumentTypes" title="Document Types" (toggle)="toggleDocumentType($event.id)"></app-filter-dropdown> | ||||||
|  |  | ||||||
|   <app-filter-dropdown-date class="col-auto" [dateBefore]="filterEditorService.dateCreatedBefore" [dateAfter]="filterEditorService.dateCreatedAfter" [title]="'Created'" (dateBeforeSet)="onDateCreatedBeforeSet($event)" (dateAfterSet)="onDateCreatedAfterSet($event)"></app-filter-dropdown-date> |   <app-filter-dropdown-date class="col-auto" [dateBefore]="dateCreatedBefore" [dateAfter]="dateCreatedAfter" title="Created" (dateBeforeSet)="onDateCreatedBeforeSet($event)" (dateAfterSet)="onDateCreatedAfterSet($event)"></app-filter-dropdown-date> | ||||||
|   <app-filter-dropdown-date class="col-auto" [dateBefore]="filterEditorService.dateAddedBefore" [dateAfter]="filterEditorService.dateAddedAfter" [title]="'Added'"  (dateBeforeSet)="onDateAddedBeforeSet($event)" (dateAfterSet)="onDateAddedAfterSet($event)"></app-filter-dropdown-date> |   <app-filter-dropdown-date class="col-auto" [dateBefore]="dateAddedBefore" [dateAfter]="dateAddedAfter" title="Added"  (dateBeforeSet)="onDateAddedBeforeSet($event)" (dateAfterSet)="onDateAddedAfterSet($event)"></app-filter-dropdown-date> | ||||||
|  |  | ||||||
|   <button class="btn btn-link btn-sm" [disabled]="!filterEditorService.hasFilters()" (click)="clearSelected()"> |   <button class="btn btn-link btn-sm" [disabled]="!hasFilters()" (click)="clearSelected()"> | ||||||
|     <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> |     <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||||
|       <path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> |       <path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> | ||||||
|     </svg> |     </svg> | ||||||
|   | |||||||
| @@ -1,11 +1,15 @@ | |||||||
| import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core'; | import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core'; | ||||||
| import { FilterEditorViewService } from 'src/app/services/filter-editor-view.service' |  | ||||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; | import { PaperlessTag } from 'src/app/data/paperless-tag'; | ||||||
| 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 { Subject, Subscription } from 'rxjs'; | import { Subject, Subscription } from 'rxjs'; | ||||||
| import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; | import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; | ||||||
| import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; | import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; | ||||||
|  | import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; | ||||||
|  | import { TagService } from 'src/app/services/rest/tag.service'; | ||||||
|  | import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; | ||||||
|  | import { FilterRule } from 'src/app/data/filter-rule'; | ||||||
|  | import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_RULE_TYPES, FILTER_TITLE } from 'src/app/data/filter-rule-type'; | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-filter-editor', |   selector: 'app-filter-editor', | ||||||
| @@ -14,19 +18,44 @@ import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; | |||||||
| }) | }) | ||||||
| export class FilterEditorComponent implements OnInit, OnDestroy { | export class FilterEditorComponent implements OnInit, OnDestroy { | ||||||
|  |  | ||||||
|   constructor() { } |   constructor( | ||||||
|  |     private documentTypeService: DocumentTypeService, | ||||||
|  |     private tagService: TagService, | ||||||
|  |     private correspondentService: CorrespondentService | ||||||
|  |   ) { } | ||||||
|  |  | ||||||
|  |   tags: PaperlessTag[] = [] | ||||||
|  |   correspondents: PaperlessCorrespondent[] | ||||||
|  |   documentTypes: PaperlessDocumentType[] = [] | ||||||
|  |  | ||||||
|   @Input() |   @Input() | ||||||
|   filterEditorService: FilterEditorViewService |   filterRules: FilterRule[] | ||||||
|  |  | ||||||
|   @Output() |   @Output() | ||||||
|   clear = new EventEmitter() |   filterRulesChange = new EventEmitter<FilterRule[]>() | ||||||
|    |    | ||||||
|   @Output() |   hasFilters() { | ||||||
|   apply = new EventEmitter() |     return this.filterRules.length > 0 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   get selectedTags(): PaperlessTag[] { | ||||||
|  |     let tagRules: FilterRule[] = this.filterRules.filter(fr => fr.type.id == FILTER_HAS_TAG) | ||||||
|  |     return this.tags?.filter(t => tagRules.find(tr => tr.value == t.id)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   get selectedCorrespondents(): PaperlessCorrespondent[] { | ||||||
|  |     let correspondentRules: FilterRule[] = this.filterRules.filter(fr => fr.type.id == FILTER_CORRESPONDENT) | ||||||
|  |     return this.correspondents?.filter(c => correspondentRules.find(cr => cr.value == c.id)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   get selectedDocumentTypes(): PaperlessDocumentType[] { | ||||||
|  |     let documentTypeRules: FilterRule[] = this.filterRules.filter(fr => fr.type.id == FILTER_DOCUMENT_TYPE) | ||||||
|  |     return this.documentTypes?.filter(dt => documentTypeRules.find(dtr => dtr.value == dt.id)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|   get titleFilter() { |   get titleFilter() { | ||||||
|     return this.filterEditorService.titleFilter |     let existingRule = this.filterRules.find(rule => rule.type.id == FILTER_TITLE) | ||||||
|  |     return existingRule ? existingRule.value : '' | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   set titleFilter(value) { |   set titleFilter(value) { | ||||||
| @@ -37,13 +66,18 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | |||||||
|   subscription: Subscription |   subscription: Subscription | ||||||
|  |  | ||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
|  |     this.tagService.listAll().subscribe(result => this.tags = result.results) | ||||||
|  |     this.correspondentService.listAll().subscribe(result => this.correspondents = result.results) | ||||||
|  |     this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results) | ||||||
|  |  | ||||||
|     this.titleFilterDebounce = new Subject<string>() |     this.titleFilterDebounce = new Subject<string>() | ||||||
|  |  | ||||||
|     this.subscription = this.titleFilterDebounce.pipe( |     this.subscription = this.titleFilterDebounce.pipe( | ||||||
|       debounceTime(400), |       debounceTime(400), | ||||||
|       distinctUntilChanged() |       distinctUntilChanged() | ||||||
|     ).subscribe(title => { |     ).subscribe(title => { | ||||||
|       this.filterEditorService.titleFilter = title |        | ||||||
|       this.applyFilters() |       this.setTitleRule(title) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -54,46 +88,159 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   applyFilters() { |   applyFilters() { | ||||||
|     this.apply.next() |     this.filterRulesChange.next(this.filterRules) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   clearSelected() { |   clearSelected() { | ||||||
|     this.filterEditorService.clear() |     this.filterRules = [] | ||||||
|     this.clear.next() |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   onToggleTag(tag: PaperlessTag) { |  | ||||||
|     this.filterEditorService.toggleFilterByTag(tag) |  | ||||||
|     this.applyFilters() |     this.applyFilters() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   onToggleCorrespondent(correspondent: PaperlessCorrespondent) { |   private toggleFilterRule(filterRuleTypeID: number, value: number) { | ||||||
|     this.filterEditorService.toggleFilterByCorrespondent(correspondent) |  | ||||||
|  |     let filterRuleType = FILTER_RULE_TYPES.find(t => t.id == filterRuleTypeID) | ||||||
|  |  | ||||||
|  |     let existingRule = this.filterRules.find(rule => rule.type.id == filterRuleTypeID && rule.value == value) | ||||||
|  |     let existingRuleOfSameType = this.filterRules.find(rule => rule.type.id == filterRuleTypeID) | ||||||
|  |      | ||||||
|  |     if (existingRule) { | ||||||
|  |       // if this exact rule already exists, remove it in all cases. | ||||||
|  |       this.filterRules.splice(this.filterRules.indexOf(existingRule), 1) | ||||||
|  |     } else if (filterRuleType.multi || !existingRuleOfSameType) { | ||||||
|  |       // if we allow multiple rules per type, or no rule of this type already exists, push a new rule. | ||||||
|  |       this.filterRules.push({type: filterRuleType, value: value}) | ||||||
|  |     } else { | ||||||
|  |       // otherwise (i.e., no multi support AND there's already a rule of this type), update the rule. | ||||||
|  |       existingRuleOfSameType.value = value | ||||||
|  |     } | ||||||
|     this.applyFilters() |     this.applyFilters() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   onToggleDocumentType(documentType: PaperlessDocumentType) { |   private setTitleRule(title: string) { | ||||||
|     this.filterEditorService.toggleFilterByDocumentType(documentType) |     let existingRule = this.filterRules.find(rule => rule.type.id == FILTER_TITLE) | ||||||
|  |  | ||||||
|  |     if (!existingRule && title) { | ||||||
|  |       this.filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_TITLE), value: title}) | ||||||
|  |     } else if (existingRule && !title) { | ||||||
|  |       this.filterRules.splice(this.filterRules.findIndex(rule => rule.type.id == FILTER_TITLE), 1) | ||||||
|  |     } else if (existingRule && title) { | ||||||
|  |       existingRule.value = title | ||||||
|  |     } | ||||||
|     this.applyFilters() |     this.applyFilters() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   toggleTag(tagId: number) { | ||||||
|  |     this.toggleFilterRule(FILTER_HAS_TAG, tagId) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   toggleCorrespondent(correspondentId: number) { | ||||||
|  |     this.toggleFilterRule(FILTER_CORRESPONDENT, correspondentId) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   toggleDocumentType(documentTypeId: number) { | ||||||
|  |     this.toggleFilterRule(FILTER_DOCUMENT_TYPE, documentTypeId) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   // Date handling | ||||||
|  |  | ||||||
|  |  | ||||||
|   onDateCreatedBeforeSet(date: NgbDateStruct) { |   onDateCreatedBeforeSet(date: NgbDateStruct) { | ||||||
|     this.filterEditorService.setDateCreatedBefore(date) |     this.setDateCreatedBefore(date) | ||||||
|     this.applyFilters() |     this.applyFilters() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   onDateCreatedAfterSet(date: NgbDateStruct) { |   onDateCreatedAfterSet(date: NgbDateStruct) { | ||||||
|     this.filterEditorService.setDateCreatedAfter(date) |     this.setDateCreatedAfter(date) | ||||||
|     this.applyFilters() |     this.applyFilters() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   onDateAddedBeforeSet(date: NgbDateStruct) { |   onDateAddedBeforeSet(date: NgbDateStruct) { | ||||||
|     this.filterEditorService.setDateAddedBefore(date) |     this.setDateAddedBefore(date) | ||||||
|     this.applyFilters() |     this.applyFilters() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   onDateAddedAfterSet(date: NgbDateStruct) { |   onDateAddedAfterSet(date: NgbDateStruct) { | ||||||
|     this.filterEditorService.setDateAddedAfter(date) |     this.setDateAddedAfter(date) | ||||||
|     this.applyFilters() |     this.applyFilters() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   get dateCreatedBefore(): NgbDateStruct { | ||||||
|  |     let createdBeforeRule: FilterRule = this.filterRules.find(fr => fr.type.id == FILTER_CREATED_BEFORE) | ||||||
|  |     return createdBeforeRule ? { | ||||||
|  |       year: createdBeforeRule.value.substring(0,4), | ||||||
|  |       month: createdBeforeRule.value.substring(5,7), | ||||||
|  |       day: createdBeforeRule.value.substring(8,10) | ||||||
|  |     } : undefined | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   get dateCreatedAfter(): NgbDateStruct { | ||||||
|  |     let createdAfterRule: FilterRule = this.filterRules.find(fr => fr.type.id == FILTER_CREATED_AFTER) | ||||||
|  |     return createdAfterRule ? { | ||||||
|  |       year: createdAfterRule.value.substring(0,4), | ||||||
|  |       month: createdAfterRule.value.substring(5,7), | ||||||
|  |       day: createdAfterRule.value.substring(8,10) | ||||||
|  |     } : undefined | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   get dateAddedBefore(): NgbDateStruct { | ||||||
|  |     let addedBeforeRule: FilterRule = this.filterRules.find(fr => fr.type.id == FILTER_ADDED_BEFORE) | ||||||
|  |     return addedBeforeRule ? { | ||||||
|  |       year: addedBeforeRule.value.substring(0,4), | ||||||
|  |       month: addedBeforeRule.value.substring(5,7), | ||||||
|  |       day: addedBeforeRule.value.substring(8,10) | ||||||
|  |     } : undefined | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   get dateAddedAfter(): NgbDateStruct { | ||||||
|  |     let addedAfterRule: FilterRule = this.filterRules.find(fr => fr.type.id == FILTER_ADDED_AFTER) | ||||||
|  |     return addedAfterRule ? { | ||||||
|  |       year: addedAfterRule.value.substring(0,4), | ||||||
|  |       month: addedAfterRule.value.substring(5,7), | ||||||
|  |       day: addedAfterRule.value.substring(8,10) | ||||||
|  |     } : undefined | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   setDateCreatedBefore(date?: NgbDateStruct) { | ||||||
|  |     if (date) this.setDateFilter(date, FILTER_CREATED_BEFORE) | ||||||
|  |     else this.clearDateFilter(FILTER_CREATED_BEFORE) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   setDateCreatedAfter(date?: NgbDateStruct) { | ||||||
|  |     if (date) this.setDateFilter(date, FILTER_CREATED_AFTER) | ||||||
|  |     else this.clearDateFilter(FILTER_CREATED_AFTER) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   setDateAddedBefore(date?: NgbDateStruct) { | ||||||
|  |     if (date) this.setDateFilter(date, FILTER_ADDED_BEFORE) | ||||||
|  |     else this.clearDateFilter(FILTER_ADDED_BEFORE) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   setDateAddedAfter(date?: NgbDateStruct) { | ||||||
|  |     if (date) this.setDateFilter(date, FILTER_ADDED_AFTER) | ||||||
|  |     else this.clearDateFilter(FILTER_ADDED_AFTER) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   setDateFilter(date: NgbDateStruct, dateRuleTypeID: number) { | ||||||
|  |     let filterRules = this.filterRules | ||||||
|  |     let existingRule = filterRules.find(rule => rule.type.id == dateRuleTypeID) | ||||||
|  |     let newValue = `${date.year}-${date.month.toString().padStart(2,'0')}-${date.day.toString().padStart(2,'0')}` // YYYY-MM-DD | ||||||
|  |  | ||||||
|  |     if (existingRule) { | ||||||
|  |       existingRule.value = newValue | ||||||
|  |     } else { | ||||||
|  |       filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == dateRuleTypeID), value: newValue}) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this.filterRules = filterRules | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   clearDateFilter(dateRuleTypeID: number) { | ||||||
|  |     let filterRules = this.filterRules | ||||||
|  |     let existingRule = filterRules.find(rule => rule.type.id == dateRuleTypeID) | ||||||
|  |     filterRules.splice(filterRules.indexOf(existingRule), 1) | ||||||
|  |     this.filterRules = filterRules | ||||||
|  |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,16 +0,0 @@ | |||||||
| import { TestBed } from '@angular/core/testing'; |  | ||||||
|  |  | ||||||
| import { FilterEditorViewService } from './filter-editor-view.service'; |  | ||||||
|  |  | ||||||
| describe('FilterEditorViewService', () => { |  | ||||||
|   let service: FilterEditorViewService; |  | ||||||
|  |  | ||||||
|   beforeEach(() => { |  | ||||||
|     TestBed.configureTestingModule({}); |  | ||||||
|     service = TestBed.inject(FilterEditorViewService); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   it('should be created', () => { |  | ||||||
|     expect(service).toBeTruthy(); |  | ||||||
|   }); |  | ||||||
| }); |  | ||||||
| @@ -1,182 +0,0 @@ | |||||||
| import { Injectable } from '@angular/core'; |  | ||||||
| import { Observable } from 'rxjs'; |  | ||||||
| import { TagService } from 'src/app/services/rest/tag.service'; |  | ||||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; |  | ||||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; |  | ||||||
| import { ObjectWithId } from 'src/app/data/object-with-id'; |  | ||||||
| import { FilterRule } from 'src/app/data/filter-rule'; |  | ||||||
| import { FilterRuleType, FILTER_RULE_TYPES, FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_TITLE, FILTER_ADDED_BEFORE, FILTER_ADDED_AFTER, FILTER_CREATED_BEFORE, FILTER_CREATED_AFTER, FILTER_CREATED_YEAR, FILTER_CREATED_MONTH, FILTER_CREATED_DAY } from 'src/app/data/filter-rule-type'; |  | ||||||
| import { Results } from 'src/app/data/results' |  | ||||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; |  | ||||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; |  | ||||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; |  | ||||||
| import { NgbDate, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; |  | ||||||
|  |  | ||||||
| @Injectable({ |  | ||||||
|   providedIn: 'root' |  | ||||||
| }) |  | ||||||
| export class FilterEditorViewService { |  | ||||||
|  |  | ||||||
|   tags: PaperlessTag[] = [] |  | ||||||
|   correspondents: PaperlessCorrespondent[] |  | ||||||
|   documentTypes: PaperlessDocumentType[] = [] |  | ||||||
|  |  | ||||||
|   filterRules: FilterRule[] = [] |  | ||||||
|  |  | ||||||
|   constructor(private tagService: TagService, private documentTypeService: DocumentTypeService, private correspondentService: CorrespondentService) { |  | ||||||
|     this.tagService.listAll().subscribe(result => this.tags = result.results) |  | ||||||
|     this.correspondentService.listAll().subscribe(result => this.correspondents = result.results) |  | ||||||
|     this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   clear() { |  | ||||||
|     this.filterRules = [] |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   hasFilters() { |  | ||||||
|     return this.filterRules.length > 0 |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   set titleFilter(title: string) { |  | ||||||
|     let existingRule = this.filterRules.find(rule => rule.type.id == FILTER_TITLE) |  | ||||||
|  |  | ||||||
|     if (!existingRule && title) { |  | ||||||
|       this.filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_TITLE), value: title}) |  | ||||||
|     } else if (existingRule && !title) { |  | ||||||
|       this.filterRules.splice(this.filterRules.findIndex(rule => rule.type.id == FILTER_TITLE), 1) |  | ||||||
|     } else if (existingRule && title) { |  | ||||||
|       existingRule.value = title |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get titleFilter(): string { |  | ||||||
|     let existingRule = this.filterRules.find(rule => rule.type.id == FILTER_TITLE) |  | ||||||
|     return existingRule ? existingRule.value : '' |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get selectedTags(): PaperlessTag[] { |  | ||||||
|     let tagRules: FilterRule[] = this.filterRules.filter(fr => fr.type.id == FILTER_HAS_TAG) |  | ||||||
|     return this.tags?.filter(t => tagRules.find(tr => tr.value == t.id)) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get selectedCorrespondents(): PaperlessCorrespondent[] { |  | ||||||
|     let correspondentRules: FilterRule[] = this.filterRules.filter(fr => fr.type.id == FILTER_CORRESPONDENT) |  | ||||||
|     return this.correspondents?.filter(c => correspondentRules.find(cr => cr.value == c.id)) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get selectedDocumentTypes(): PaperlessDocumentType[] { |  | ||||||
|     let documentTypeRules: FilterRule[] = this.filterRules.filter(fr => fr.type.id == FILTER_DOCUMENT_TYPE) |  | ||||||
|     return this.documentTypes?.filter(dt => documentTypeRules.find(dtr => dtr.value == dt.id)) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   toggleFilterByTag(tag: PaperlessTag | number) { |  | ||||||
|     if (typeof tag == 'number') tag = this.tags?.find(t => t.id == tag) |  | ||||||
|     this.toggleFilterByItem(tag, FILTER_HAS_TAG) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   toggleFilterByCorrespondent(correspondent: PaperlessCorrespondent | number) { |  | ||||||
|     if (typeof correspondent == 'number') correspondent = this.correspondents?.find(t => t.id == correspondent) |  | ||||||
|     this.toggleFilterByItem(correspondent, FILTER_CORRESPONDENT) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   toggleFilterByDocumentType(documentType: PaperlessDocumentType | number) { |  | ||||||
|     if (typeof documentType == 'number') documentType = this.documentTypes?.find(t => t.id == documentType) |  | ||||||
|     this.toggleFilterByItem(documentType, FILTER_DOCUMENT_TYPE) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private toggleFilterByItem(item: ObjectWithId, filterRuleTypeID: number) { |  | ||||||
|     let filterRules = this.filterRules |  | ||||||
|     let filterRuleType: FilterRuleType = FILTER_RULE_TYPES.find(t => t.id == filterRuleTypeID) |  | ||||||
|     let existingRules = filterRules.filter(rule => rule.type.id == filterRuleType.id) |  | ||||||
|     let existingItemRule = existingRules?.find(rule => rule.value == item.id) |  | ||||||
|  |  | ||||||
|     if (existingRules && existingItemRule) { // if exact rule exists just remove |  | ||||||
|       filterRules.splice(filterRules.indexOf(existingItemRule), 1) |  | ||||||
|     } else if (existingRules.length > 0 && filterRuleType.multi) { // e.g. tags can have multiple |  | ||||||
|       filterRules.push({type: filterRuleType, value: item.id}) |  | ||||||
|     } else if (existingRules.length > 0) { // correspondents & documentTypes can only be one |  | ||||||
|       filterRules.find(rule => rule.type.id == filterRuleType.id).value = item.id |  | ||||||
|     } else { |  | ||||||
|       filterRules.push({type: filterRuleType, value: item.id}) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     this.filterRules = filterRules |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get dateCreatedBefore(): NgbDateStruct { |  | ||||||
|     let createdBeforeRule: FilterRule = this.filterRules.find(fr => fr.type.id == FILTER_CREATED_BEFORE) |  | ||||||
|     return createdBeforeRule ? { |  | ||||||
|       year: createdBeforeRule.value.substring(0,4), |  | ||||||
|       month: createdBeforeRule.value.substring(5,7), |  | ||||||
|       day: createdBeforeRule.value.substring(8,10) |  | ||||||
|     } : undefined |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get dateCreatedAfter(): NgbDateStruct { |  | ||||||
|     let createdAfterRule: FilterRule = this.filterRules.find(fr => fr.type.id == FILTER_CREATED_AFTER) |  | ||||||
|     return createdAfterRule ? { |  | ||||||
|       year: createdAfterRule.value.substring(0,4), |  | ||||||
|       month: createdAfterRule.value.substring(5,7), |  | ||||||
|       day: createdAfterRule.value.substring(8,10) |  | ||||||
|     } : undefined |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get dateAddedBefore(): NgbDateStruct { |  | ||||||
|     let addedBeforeRule: FilterRule = this.filterRules.find(fr => fr.type.id == FILTER_ADDED_BEFORE) |  | ||||||
|     return addedBeforeRule ? { |  | ||||||
|       year: addedBeforeRule.value.substring(0,4), |  | ||||||
|       month: addedBeforeRule.value.substring(5,7), |  | ||||||
|       day: addedBeforeRule.value.substring(8,10) |  | ||||||
|     } : undefined |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   get dateAddedAfter(): NgbDateStruct { |  | ||||||
|     let addedAfterRule: FilterRule = this.filterRules.find(fr => fr.type.id == FILTER_ADDED_AFTER) |  | ||||||
|     return addedAfterRule ? { |  | ||||||
|       year: addedAfterRule.value.substring(0,4), |  | ||||||
|       month: addedAfterRule.value.substring(5,7), |  | ||||||
|       day: addedAfterRule.value.substring(8,10) |  | ||||||
|     } : undefined |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateCreatedBefore(date?: NgbDateStruct) { |  | ||||||
|     if (date) this.setDateFilter(date, FILTER_CREATED_BEFORE) |  | ||||||
|     else this.clearDateFilter(FILTER_CREATED_BEFORE) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateCreatedAfter(date?: NgbDateStruct) { |  | ||||||
|     if (date) this.setDateFilter(date, FILTER_CREATED_AFTER) |  | ||||||
|     else this.clearDateFilter(FILTER_CREATED_AFTER) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateAddedBefore(date?: NgbDateStruct) { |  | ||||||
|     if (date) this.setDateFilter(date, FILTER_ADDED_BEFORE) |  | ||||||
|     else this.clearDateFilter(FILTER_ADDED_BEFORE) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateAddedAfter(date?: NgbDateStruct) { |  | ||||||
|     if (date) this.setDateFilter(date, FILTER_ADDED_AFTER) |  | ||||||
|     else this.clearDateFilter(FILTER_ADDED_AFTER) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setDateFilter(date: NgbDateStruct, dateRuleTypeID: number) { |  | ||||||
|     let filterRules = this.filterRules |  | ||||||
|     let existingRule = filterRules.find(rule => rule.type.id == dateRuleTypeID) |  | ||||||
|     let newValue = `${date.year}-${date.month.toString().padStart(2,'0')}-${date.day.toString().padStart(2,'0')}` // YYYY-MM-DD |  | ||||||
|  |  | ||||||
|     if (existingRule) { |  | ||||||
|       existingRule.value = newValue |  | ||||||
|     } else { |  | ||||||
|       filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == dateRuleTypeID), value: newValue}) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     this.filterRules = filterRules |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   clearDateFilter(dateRuleTypeID: number) { |  | ||||||
|     let filterRules = this.filterRules |  | ||||||
|     let existingRule = filterRules.find(rule => rule.type.id == dateRuleTypeID) |  | ||||||
|     filterRules.splice(filterRules.indexOf(existingRule), 1) |  | ||||||
|     this.filterRules = filterRules |  | ||||||
|   } |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user
	 jonaswinkler
					jonaswinkler