mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	Completely refactored because programming
Extracted filter editor to service Made all components actually reactive
This commit is contained in:
		@@ -29,6 +29,7 @@ import { AppFrameComponent } from './components/app-frame/app-frame.component';
 | 
			
		||||
import { ToastsComponent } from './components/common/toasts/toasts.component';
 | 
			
		||||
import { FilterEditorComponent } from './components/filter-editor/filter-editor.component';
 | 
			
		||||
import { FilterDropdownComponent } from './components/filter-editor/filter-dropdown/filter-dropdown.component';
 | 
			
		||||
import { FilterDropdownButtonComponent } from './components/filter-editor/filter-dropdown/filter-dropdown-button/filter-dropdown-button.component';
 | 
			
		||||
import { FilterDropdownDateComponent } from './components/filter-editor/filter-dropdown-date/filter-dropdown-date.component';
 | 
			
		||||
import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component';
 | 
			
		||||
import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component';
 | 
			
		||||
@@ -77,6 +78,7 @@ import { FilterPipe } from './pipes/filter.pipe';
 | 
			
		||||
    ToastsComponent,
 | 
			
		||||
    FilterEditorComponent,
 | 
			
		||||
    FilterDropdownComponent,
 | 
			
		||||
    FilterDropdownButtonComponent,
 | 
			
		||||
    FilterDropdownDateComponent,
 | 
			
		||||
    DocumentCardLargeComponent,
 | 
			
		||||
    DocumentCardSmallComponent,
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@
 | 
			
		||||
 | 
			
		||||
<div class="card w-100 mb-3">
 | 
			
		||||
  <div class="card-body">
 | 
			
		||||
    <app-filter-editor [(filterRules)]="filterRules" (apply)="applyFilterRules()" (clear)="clearFilterRules()" #filterEditor></app-filter-editor>
 | 
			
		||||
    <app-filter-editor [(filterEditorService)]="filterEditorService" (apply)="applyFilterRules()" (clear)="clearFilterRules()" #filterEditor></app-filter-editor>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ 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 { 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 { SavedViewConfigService } from 'src/app/services/saved-view-config.service';
 | 
			
		||||
import { Toast, ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
@@ -26,6 +27,7 @@ export class DocumentListComponent implements OnInit {
 | 
			
		||||
  constructor(
 | 
			
		||||
    public list: DocumentListViewService,
 | 
			
		||||
    public savedViewConfigService: SavedViewConfigService,
 | 
			
		||||
    public filterEditorService: FilterEditorViewService,
 | 
			
		||||
    public route: ActivatedRoute,
 | 
			
		||||
    private toastService: ToastService,
 | 
			
		||||
    public modalService: NgbModal,
 | 
			
		||||
@@ -33,14 +35,18 @@ export class DocumentListComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  displayMode = 'smallCards' // largeCards, smallCards, details
 | 
			
		||||
 | 
			
		||||
  filterRules: FilterRule[] = []
 | 
			
		||||
 | 
			
		||||
  @ViewChild('filterEditor') filterEditor: FilterEditorComponent
 | 
			
		||||
 | 
			
		||||
  get isFiltered() {
 | 
			
		||||
    return this.list.filterRules?.length > 0
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set filterRules(filterRules: FilterRule[]) {
 | 
			
		||||
    this.filterEditorService.filterRules = filterRules
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get filterRules(): FilterRule[] {
 | 
			
		||||
    return this.filterEditorService.filterRules
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTitle() {
 | 
			
		||||
    return this.list.savedViewTitle || "Documents"
 | 
			
		||||
  }
 | 
			
		||||
@@ -60,28 +66,29 @@ export class DocumentListComponent implements OnInit {
 | 
			
		||||
    this.route.paramMap.subscribe(params => {
 | 
			
		||||
      if (params.has('id')) {
 | 
			
		||||
        this.list.savedView = this.savedViewConfigService.getConfig(params.get('id'))
 | 
			
		||||
        this.filterRules = this.list.filterRules
 | 
			
		||||
        this.filterEditorService.filterRules = this.list.filterRules
 | 
			
		||||
        this.titleService.setTitle(`${this.list.savedView.title} - ${environment.appTitle}`)
 | 
			
		||||
      } else {
 | 
			
		||||
        this.list.savedView = null
 | 
			
		||||
        this.filterRules = this.list.filterRules
 | 
			
		||||
        this.filterEditorService.filterRules = this.list.filterRules
 | 
			
		||||
        this.titleService.setTitle(`Documents - ${environment.appTitle}`)
 | 
			
		||||
      }
 | 
			
		||||
      this.list.clear()
 | 
			
		||||
      this.list.reload()
 | 
			
		||||
    })
 | 
			
		||||
    this.filterEditorService.filterRules = this.list.filterRules
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  applyFilterRules() {
 | 
			
		||||
    this.list.filterRules = this.filterRules
 | 
			
		||||
    this.list.filterRules = this.filterEditorService.filterRules
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearFilterRules() {
 | 
			
		||||
    this.list.filterRules = this.filterRules
 | 
			
		||||
    this.list.filterRules = this.filterEditorService.filterRules
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loadViewConfig(config: SavedViewConfig) {
 | 
			
		||||
    this.filterRules = cloneFilterRules(config.filterRules)
 | 
			
		||||
    this.filterEditorService.filterRules = cloneFilterRules(config.filterRules)
 | 
			
		||||
    this.list.load(config)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -106,15 +113,18 @@ export class DocumentListComponent implements OnInit {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clickTag(tagID: number) {
 | 
			
		||||
    this.filterEditor.toggleFilterByItem(tagID, FILTER_HAS_TAG)
 | 
			
		||||
    this.filterEditorService.toggleFitlerByTagID(tagID)
 | 
			
		||||
    this.applyFilterRules()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clickCorrespondent(correspondentID: number) {
 | 
			
		||||
    this.filterEditor.toggleFilterByItem(correspondentID, FILTER_CORRESPONDENT)
 | 
			
		||||
    this.filterEditorService.toggleFitlerByCorrespondentID(correspondentID)
 | 
			
		||||
    this.applyFilterRules()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clickDocumentType(documentTypeID: number) {
 | 
			
		||||
    this.filterEditor.toggleFilterByItem(documentTypeID, FILTER_DOCUMENT_TYPE)
 | 
			
		||||
    this.filterEditorService.toggleFitlerByDocumentTypeID(documentTypeID)
 | 
			
		||||
    this.applyFilterRules()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
        <div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
 | 
			
		||||
          <div class="mb-1"><small>Before</small></div>
 | 
			
		||||
          <div class="input-group input-group-sm">
 | 
			
		||||
            <input class="form-control" type="text" placeholder="yyyy-mm-dd" name="before" [(ngModel)]="dateBefore" ngbDatepicker (dateSelect)="dateSelected($event)" #dpBefore="ngbDatepicker">
 | 
			
		||||
            <input class="form-control" type="text" placeholder="yyyy-mm-dd" name="before" [(ngModel)]="_dateBefore" ngbDatepicker (dateSelect)="onDateSelected($event)" #dpBefore="ngbDatepicker">
 | 
			
		||||
            <div class="input-group-append">
 | 
			
		||||
              <button class="btn btn-outline-secondary btn-sm" (click)="dpBefore.toggle()" type="button">
 | 
			
		||||
                <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-calendar-date" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
        <div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
 | 
			
		||||
          <div class="mb-1"><small>After</small></div>
 | 
			
		||||
          <div class="input-group">
 | 
			
		||||
            <input class="form-control form-control-sm" type="text" placeholder="yyyy-mm-dd" name="after" [(ngModel)]="dateAfter" ngbDatepicker (dateSelect)="dateSelected($event)" #dpAfter="ngbDatepicker">
 | 
			
		||||
            <input class="form-control form-control-sm" type="text" placeholder="yyyy-mm-dd" name="after" [(ngModel)]="_dateAfter" ngbDatepicker (dateSelect)="onDateSelected($event)" #dpAfter="ngbDatepicker">
 | 
			
		||||
            <div class="input-group-append">
 | 
			
		||||
              <button class="btn btn-outline-secondary btn-sm" (click)="dpAfter.toggle()" type="button">
 | 
			
		||||
                <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-calendar-date" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import { Component, EventEmitter, Input, OnInit, Output, ElementRef, ViewChild } from '@angular/core';
 | 
			
		||||
import { Component, EventEmitter, Input, Output, ElementRef, ViewChild } from '@angular/core';
 | 
			
		||||
import { FilterRule } from 'src/app/data/filter-rule';
 | 
			
		||||
import { FilterRuleType, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id';
 | 
			
		||||
import { NgbDate, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
 | 
			
		||||
@@ -12,23 +11,25 @@ import { NgbDate, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
export class FilterDropdownDateComponent {
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  filterRuleTypeIDs: number[] = []
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  selected = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  filterRuleTypes: FilterRuleType[] = []
 | 
			
		||||
  title: string
 | 
			
		||||
  dateAfter: NgbDateStruct
 | 
			
		||||
  dateBefore: NgbDateStruct
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.filterRuleTypes = this.filterRuleTypeIDs.map(id => FILTER_RULE_TYPES.find(rt => rt.id == id))
 | 
			
		||||
    this.title = this.filterRuleTypes[0].displayName
 | 
			
		||||
  }
 | 
			
		||||
  @Input()
 | 
			
		||||
  dateAfter: NgbDateStruct
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  title: string
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  dateBeforeSet = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  dateAfterSet = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  _dateBefore: NgbDateStruct
 | 
			
		||||
  _dateAfter: NgbDateStruct
 | 
			
		||||
 | 
			
		||||
  setDateQuickFilter(range: any) {
 | 
			
		||||
    this.dateAfter = this.dateBefore = undefined
 | 
			
		||||
    this._dateAfter = this._dateBefore = undefined
 | 
			
		||||
    let date = new Date()
 | 
			
		||||
    let newDate: NgbDateStruct = { year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate() }
 | 
			
		||||
    switch (typeof range) {
 | 
			
		||||
@@ -47,17 +48,12 @@ export class FilterDropdownDateComponent {
 | 
			
		||||
      default:
 | 
			
		||||
        break
 | 
			
		||||
    }
 | 
			
		||||
    this.dateAfter = newDate
 | 
			
		||||
    this.dateSelected(this.dateAfter)
 | 
			
		||||
    this._dateAfter = newDate
 | 
			
		||||
    this.onDateSelected(this._dateAfter)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  dateSelected(date:NgbDateStruct) {
 | 
			
		||||
    let isAfter = NgbDate.from(this.dateAfter).equals(date)
 | 
			
		||||
 | 
			
		||||
    let filterRuleType = this.filterRuleTypes.find(rt => rt.filtervar.indexOf(isAfter ? 'gt' : 'lt') > -1)
 | 
			
		||||
    if (filterRuleType) {
 | 
			
		||||
      let dateFilterRule:FilterRule = {value: `${date.year}-${date.month.toString().padStart(2,'0')}-${date.day.toString().padStart(2,'0')}`, type: filterRuleType}
 | 
			
		||||
      this.selected.emit(dateFilterRule)
 | 
			
		||||
    }
 | 
			
		||||
  onDateSelected(date:NgbDateStruct) {
 | 
			
		||||
    let emitter = this._dateAfter && NgbDate.from(this._dateAfter).equals(date) ? this.dateAfterSet : this.dateBeforeSet
 | 
			
		||||
    emitter.emit(date)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
<button class="list-group-item list-group-item-action d-flex align-items-center" role="menuitem" (click)="toggleItem()">
 | 
			
		||||
  <div class="selected-icon mr-1">
 | 
			
		||||
    <svg *ngIf="selected" width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-check" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
      <path fill-rule="evenodd" d="M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.236.236 0 0 1 .02-.022z"/>
 | 
			
		||||
    </svg>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="mr-1">
 | 
			
		||||
    <app-tag *ngIf="display == 'tag'; else displayName" [tag]="item" [clickable]="true" linkTitle="Filter by tag"></app-tag>
 | 
			
		||||
    <ng-template #displayName><small>{{item.name}}</small></ng-template>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="badge bg-primary text-light rounded-pill ml-auto">{{item.document_count}}</div>
 | 
			
		||||
</button>
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
.selected-icon {
 | 
			
		||||
  min-width: 1em;
 | 
			
		||||
  min-height: 1em;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { FilterDropodownButtonComponent } from './filter-dropdown-button.component';
 | 
			
		||||
 | 
			
		||||
describe('FilterDropodownButtonComponent', () => {
 | 
			
		||||
  let component: FilterDropodownButtonComponent;
 | 
			
		||||
  let fixture: ComponentFixture<FilterDropodownButtonComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ FilterDropodownButtonComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(FilterDropodownButtonComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
 | 
			
		||||
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({
 | 
			
		||||
  selector: 'app-filter-dropdown-button',
 | 
			
		||||
  templateUrl: './filter-dropdown-button.component.html',
 | 
			
		||||
  styleUrls: ['./filter-dropdown-button.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class FilterDropdownButtonComponent {
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  item: PaperlessTag | PaperlessDocumentType | PaperlessCorrespondent
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  display: string
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  selected: boolean
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  toggle = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  toggleItem(): void {
 | 
			
		||||
    this.selected = !this.selected
 | 
			
		||||
    this.toggle.emit(this.item)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,19 +3,10 @@
 | 
			
		||||
  <div class="dropdown-menu quick-filter shadow" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
 | 
			
		||||
    <div class="list-group list-group-flush">
 | 
			
		||||
      <input class="list-group-item form-control form-control-sm" type="text" [(ngModel)]="filterText" placeholder="Filter {{title}}" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
 | 
			
		||||
      <ng-container *ngIf="(items | filter: filterText).length > 0">
 | 
			
		||||
        <button class="list-group-item list-group-item-action d-flex align-items-center" role="menuitem" *ngFor="let item of items | filter: filterText; let i = index" (click)="toggleItem(item)">
 | 
			
		||||
          <div class="selected-icon mr-1">
 | 
			
		||||
            <svg *ngIf="itemsActive.includes(item)" width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-check" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
              <path fill-rule="evenodd" d="M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.236.236 0 0 1 .02-.022z"/>
 | 
			
		||||
            </svg>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="mr-1">
 | 
			
		||||
            <app-tag *ngIf="display == 'tag'; else displayName" [tag]="item" [clickable]="true" linkTitle="Filter by tag"></app-tag>
 | 
			
		||||
            <ng-template #displayName><small>{{item.name}}</small></ng-template>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="badge bg-primary text-light rounded-pill ml-auto">{{item.document_count}}</div>
 | 
			
		||||
        </button>
 | 
			
		||||
      <ng-container *ngIf="(items$ | async)?.results as items">
 | 
			
		||||
        <ng-container *ngFor="let item of items | filter: filterText; let i = index">
 | 
			
		||||
          <app-filter-dropdown-button [item]="item" [display]="display" [selected]="isItemSelected(item)" (toggle)="toggleItem($event)"></app-filter-dropdown-button>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
      </ng-container>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,4 @@
 | 
			
		||||
  min-width: 250px;
 | 
			
		||||
  max-height: 400px;
 | 
			
		||||
  overflow-y: scroll;
 | 
			
		||||
 | 
			
		||||
  .selected-icon {
 | 
			
		||||
    min-width: 1em;
 | 
			
		||||
    min-height: 1em;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { Component, EventEmitter, Input, OnInit, Output, ElementRef, ViewChild } from '@angular/core';
 | 
			
		||||
import { FilterRuleType, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { Results } from 'src/app/data/results';
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id';
 | 
			
		||||
import { FilterPipe } from  'src/app/pipes/filter.pipe';
 | 
			
		||||
 | 
			
		||||
@@ -13,29 +14,37 @@ export class FilterDropdownComponent implements OnInit {
 | 
			
		||||
  constructor(private filterPipe: FilterPipe) { }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  filterRuleTypeID: number
 | 
			
		||||
  items$: Observable<Results<ObjectWithId>>
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  itemsSelected: ObjectWithId[]
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  title: string
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  display: string
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  toggle = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  @ViewChild('listFilterTextInput') listFilterTextInput: ElementRef
 | 
			
		||||
 | 
			
		||||
  items: ObjectWithId[] = []
 | 
			
		||||
  itemsActive: ObjectWithId[] = []
 | 
			
		||||
  title: string
 | 
			
		||||
  filterText: string
 | 
			
		||||
  display: string
 | 
			
		||||
  items: ObjectWithId[]
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    let filterRuleType: FilterRuleType = FILTER_RULE_TYPES.find(t => t.id == this.filterRuleTypeID)
 | 
			
		||||
    this.title = filterRuleType.displayName
 | 
			
		||||
    this.display = filterRuleType.datatype
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.items$.subscribe(result => this.items = result.results)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleItem(item: ObjectWithId): void {
 | 
			
		||||
    this.toggle.emit(item)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isItemSelected(item: ObjectWithId): boolean {
 | 
			
		||||
    return this.itemsSelected?.find(i => i.id == item.id) !== undefined
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  dropdownOpenChange(open: boolean): void {
 | 
			
		||||
    if (open) {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,17 @@
 | 
			
		||||
    <div class="text-muted mt-1">Filter by:</div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="col">
 | 
			
		||||
    <input class="form-control form-control-sm" type="text" [(ngModel)]="filterText" placeholder="Title" #filterTextInput>
 | 
			
		||||
    <input class="form-control form-control-sm" type="text" [(ngModel)]="filterEditorService.filterText" placeholder="Title" #filterTextInput>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <app-filter-dropdown class="col-auto" *ngFor="let quickFilterRuleTypeID of quickFilterRuleTypeIDs" [filterRuleTypeID]="quickFilterRuleTypeID" (toggle)="toggleFilterByItem($event, quickFilterRuleTypeID)"></app-filter-dropdown>
 | 
			
		||||
  <app-filter-dropdown class="col-auto" [(items$)]="filterEditorService.tags$" [itemsSelected]="filterEditorService.selectedTags" [title]="'Tags'" [display]="'tag'" (toggle)="onToggleTag($event)"></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$)]="filterEditorService.documentTypes$" [itemsSelected]="filterEditorService.selectedDocumentTypes" [title]="'Document Types'" (toggle)="onToggleDocumentType($event)"></app-filter-dropdown>
 | 
			
		||||
 | 
			
		||||
  <app-filter-dropdown-date class="col-auto" *ngFor="let dateAddedFilterRuleTypeID of dateAddedFilterRuleTypeIDs" [filterRuleTypeIDs]="dateAddedFilterRuleTypeID" (selected)="setDateFilter($event)"></app-filter-dropdown-date>
 | 
			
		||||
  <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]="filterEditorService.dateAddedBefore" [dateAfter]="filterEditorService.dateAddedAfter" [title]="'Added'"  (dateBeforeSet)="onDateAddedBeforeSet($event)" (dateAfterSet)="onDateAddedAfterSet($event)"></app-filter-dropdown-date>
 | 
			
		||||
 | 
			
		||||
  <button class="btn btn-link btn-sm" [disabled]="!hasFilters()" (click)="clearSelected()">
 | 
			
		||||
  <button class="btn btn-link btn-sm" [disabled]="!filterEditorService.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">
 | 
			
		||||
      <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>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,10 @@
 | 
			
		||||
import { Component, EventEmitter, Input, OnInit, Output, ElementRef, AfterViewInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
 | 
			
		||||
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 { Component, EventEmitter, Input, Output, ElementRef, AfterViewInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
 | 
			
		||||
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service';
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id';
 | 
			
		||||
import { FilterEditorViewService } from 'src/app/services/filter-editor-view.service'
 | 
			
		||||
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 { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service';
 | 
			
		||||
import { ObjectWithId } from 'src/app/data/object-with-id';
 | 
			
		||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
			
		||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service';
 | 
			
		||||
import { TagService } from 'src/app/services/rest/tag.service';
 | 
			
		||||
import { FilterDropdownComponent } from './filter-dropdown/filter-dropdown.component'
 | 
			
		||||
import { FilterDropdownDateComponent } from './filter-dropdown-date/filter-dropdown-date.component'
 | 
			
		||||
import { fromEvent } from 'rxjs';
 | 
			
		||||
@@ -20,38 +16,20 @@ import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
  templateUrl: './filter-editor.component.html',
 | 
			
		||||
  styleUrls: ['./filter-editor.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class FilterEditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
export class FilterEditorComponent implements AfterViewInit {
 | 
			
		||||
 | 
			
		||||
  constructor(private documentTypeService: DocumentTypeService, private tagService: TagService, private correspondentService: CorrespondentService) { }
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  filterEditorService: FilterEditorViewService
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  clear = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  filterRules: FilterRule[] = []
 | 
			
		||||
 | 
			
		||||
  @Output()
 | 
			
		||||
  apply = new EventEmitter()
 | 
			
		||||
 | 
			
		||||
  @ViewChild('filterTextInput') filterTextInput: ElementRef;
 | 
			
		||||
  @ViewChildren(FilterDropdownComponent) quickFilterDropdowns!: QueryList<FilterDropdownComponent>;
 | 
			
		||||
  @ViewChildren(FilterDropdownDateComponent) quickDateFilterDropdowns!: QueryList<FilterDropdownDateComponent>;
 | 
			
		||||
 | 
			
		||||
  quickFilterRuleTypeIDs: number[] = [FILTER_HAS_TAG, FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE]
 | 
			
		||||
  dateAddedFilterRuleTypeIDs: any[] = [[FILTER_ADDED_BEFORE, FILTER_ADDED_AFTER], [FILTER_CREATED_BEFORE, FILTER_CREATED_AFTER]]
 | 
			
		||||
 | 
			
		||||
  correspondents: PaperlessCorrespondent[] = []
 | 
			
		||||
  tags: PaperlessTag[] = []
 | 
			
		||||
  documentTypes: PaperlessDocumentType[] = []
 | 
			
		||||
 | 
			
		||||
  filterText: string
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.updateTextFilterInput()
 | 
			
		||||
    this.tagService.listAll().subscribe(result => this.setDropdownItems(result.results, FILTER_HAS_TAG))
 | 
			
		||||
    this.correspondentService.listAll().subscribe(result => this.setDropdownItems(result.results, FILTER_CORRESPONDENT))
 | 
			
		||||
    this.documentTypeService.listAll().subscribe(result => this.setDropdownItems(result.results, FILTER_DOCUMENT_TYPE))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit() {
 | 
			
		||||
    fromEvent(this.filterTextInput.nativeElement,'keyup').pipe(
 | 
			
		||||
@@ -59,120 +37,52 @@ export class FilterEditorComponent implements OnInit, AfterViewInit {
 | 
			
		||||
      distinctUntilChanged(),
 | 
			
		||||
      tap()
 | 
			
		||||
    ).subscribe((event: Event) => {
 | 
			
		||||
      this.filterText = (event.target as HTMLInputElement).value
 | 
			
		||||
      this.onTextFilterInput()
 | 
			
		||||
      this.filterEditorService.filterText = (event.target as HTMLInputElement).value
 | 
			
		||||
      this.applyFilters()
 | 
			
		||||
    })
 | 
			
		||||
    this.quickDateFilterDropdowns.forEach(d => this.updateDateDropdown(d))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDropdownItems(items: ObjectWithId[], filterRuleTypeID: number): void {
 | 
			
		||||
    let dropdown: FilterDropdownComponent = this.getDropdownByFilterRuleTypeID(filterRuleTypeID)
 | 
			
		||||
    if (dropdown) {
 | 
			
		||||
      dropdown.items = items
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateDropdownActiveItems(dropdown: FilterDropdownComponent): void {
 | 
			
		||||
    let activeRulesValues = this.filterRules.filter(r => r.type.id == dropdown.filterRuleTypeID).map(r => r.value)
 | 
			
		||||
    let activeItems = []
 | 
			
		||||
    if (activeRulesValues.length > 0) {
 | 
			
		||||
      activeItems = dropdown.items.filter(i => activeRulesValues.includes(i.id))
 | 
			
		||||
    }
 | 
			
		||||
    dropdown.itemsActive = activeItems
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateDateDropdown(dateDropdown: FilterDropdownDateComponent) {
 | 
			
		||||
    let activeRules = this.filterRules.filter(r => dateDropdown.filterRuleTypeIDs.includes(r.type.id))
 | 
			
		||||
    if (activeRules.length > 0) {
 | 
			
		||||
      activeRules.forEach(rule => {
 | 
			
		||||
        let date = { year: rule.value.substring(0,4), month: rule.value.substring(5,7), day: rule.value.substring(8,10) }
 | 
			
		||||
        rule.type.filtervar.indexOf('gt') > -1 ? dateDropdown.dateAfter = date : dateDropdown.dateBefore = date
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      dateDropdown.dateAfter = dateDropdown.dateBefore = undefined
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getDropdownByFilterRuleTypeID(filterRuleTypeID: number): FilterDropdownComponent {
 | 
			
		||||
    return this.quickFilterDropdowns.find(d => d.filterRuleTypeID == filterRuleTypeID)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  applySelected() {
 | 
			
		||||
  applyFilters() {
 | 
			
		||||
    this.apply.next()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearSelected() {
 | 
			
		||||
    this.filterRules.splice(0,this.filterRules.length)
 | 
			
		||||
    this.updateTextFilterInput()
 | 
			
		||||
    this.quickFilterDropdowns.forEach(d => this.updateDropdownActiveItems(d))
 | 
			
		||||
    this.quickDateFilterDropdowns.forEach(d => this.updateDateDropdown(d))
 | 
			
		||||
    this.filterEditorService.clear()
 | 
			
		||||
    this.clear.next()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  hasFilters() {
 | 
			
		||||
    return this.filterRules.length > 0
 | 
			
		||||
  onToggleTag(tag: PaperlessTag) {
 | 
			
		||||
    this.filterEditorService.toggleFitlerByTag(tag)
 | 
			
		||||
    this.applyFilters()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateTextFilterInput() {
 | 
			
		||||
    let existingTextRule = this.filterRules.find(rule => rule.type.id == FILTER_TITLE)
 | 
			
		||||
    if (existingTextRule) this.filterText = existingTextRule.value
 | 
			
		||||
    else this.filterText = ''
 | 
			
		||||
  onToggleCorrespondent(correspondent: PaperlessCorrespondent) {
 | 
			
		||||
    this.filterEditorService.toggleFitlerByCorrespondent(correspondent)
 | 
			
		||||
    this.applyFilters()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onTextFilterInput() {
 | 
			
		||||
    let text = this.filterText
 | 
			
		||||
    let filterRules = this.filterRules
 | 
			
		||||
    let existingRule = filterRules.find(rule => rule.type.id == FILTER_TITLE)
 | 
			
		||||
    if (existingRule && existingRule.value == text) {
 | 
			
		||||
      return
 | 
			
		||||
    } else if (existingRule) {
 | 
			
		||||
      existingRule.value = text
 | 
			
		||||
    } else {
 | 
			
		||||
      filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_TITLE), value: text})
 | 
			
		||||
    }
 | 
			
		||||
    this.filterRules = filterRules
 | 
			
		||||
    this.applySelected()
 | 
			
		||||
  onToggleDocumentType(documentType: PaperlessDocumentType) {
 | 
			
		||||
    this.filterEditorService.toggleFitlerByDocumentType(documentType)
 | 
			
		||||
    this.applyFilters()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleFilterByItem(item: any, filterRuleTypeID: number) {
 | 
			
		||||
    let dropdown = this.getDropdownByFilterRuleTypeID(filterRuleTypeID)
 | 
			
		||||
    if (typeof item == 'number') {
 | 
			
		||||
      item = dropdown.items.find(i => i.id == item)
 | 
			
		||||
    }
 | 
			
		||||
    let filterRules = this.filterRules
 | 
			
		||||
    let filterRuleType: FilterRuleType = FILTER_RULE_TYPES.find(t => t.id == filterRuleTypeID)
 | 
			
		||||
    let existingRule = filterRules.find(rule => rule.type.id == filterRuleType.id)
 | 
			
		||||
 | 
			
		||||
    if (existingRule && existingRule.value == item.id) {
 | 
			
		||||
      filterRules.splice(filterRules.indexOf(existingRule), 1)
 | 
			
		||||
    } else if (existingRule && filterRuleType.id == FILTER_HAS_TAG) {
 | 
			
		||||
      filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == filterRuleType.id), value: item.id})
 | 
			
		||||
    } else if (existingRule && existingRule.value == item.id) {
 | 
			
		||||
      return
 | 
			
		||||
    } else if (existingRule) {
 | 
			
		||||
      existingRule.value = item.id
 | 
			
		||||
    } else {
 | 
			
		||||
      filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == filterRuleType.id), value: item.id})
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.updateDropdownActiveItems(dropdown)
 | 
			
		||||
 | 
			
		||||
    this.filterRules = filterRules
 | 
			
		||||
    this.applySelected()
 | 
			
		||||
  onDateCreatedBeforeSet(date: NgbDateStruct) {
 | 
			
		||||
    this.filterEditorService.setDateCreatedBefore(date)
 | 
			
		||||
    this.applyFilters()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateFilter(newFilterRule: FilterRule) {
 | 
			
		||||
    let filterRules = this.filterRules
 | 
			
		||||
    let existingRule = filterRules.find(rule => rule.type.id == newFilterRule.type.id)
 | 
			
		||||
 | 
			
		||||
    if (existingRule) {
 | 
			
		||||
      existingRule.value = newFilterRule.value
 | 
			
		||||
    } else {
 | 
			
		||||
      filterRules.push(newFilterRule)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.filterRules = filterRules
 | 
			
		||||
    this.applySelected()
 | 
			
		||||
  onDateCreatedAfterSet(date: NgbDateStruct) {
 | 
			
		||||
    this.filterEditorService.setDateCreatedAfter(date)
 | 
			
		||||
    this.applyFilters()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onDateAddedBeforeSet(date: NgbDateStruct) {
 | 
			
		||||
    this.filterEditorService.setDateAddedBefore(date)
 | 
			
		||||
    this.applyFilters()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onDateAddedAfterSet(date: NgbDateStruct) {
 | 
			
		||||
    this.filterEditorService.setDateAddedAfter(date)
 | 
			
		||||
    this.applyFilters()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import { DocumentService } from './rest/document.service';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This service manages the document list which is displayed using the document list view.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * This service also serves saved views by transparently switching between the document list
 | 
			
		||||
 * and saved views on request. See below.
 | 
			
		||||
 */
 | 
			
		||||
@@ -25,7 +25,7 @@ export class DocumentListViewService {
 | 
			
		||||
  currentPage = 1
 | 
			
		||||
  currentPageSize: number = +localStorage.getItem(GENERAL_SETTINGS.DOCUMENT_LIST_SIZE) || GENERAL_SETTINGS.DOCUMENT_LIST_SIZE_DEFAULT
 | 
			
		||||
  collectionSize: number
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * This is the current config for the document list. The service will always remember the last settings used for the document list.
 | 
			
		||||
   */
 | 
			
		||||
@@ -192,7 +192,7 @@ export class DocumentListViewService {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  constructor(private documentService: DocumentService) { 
 | 
			
		||||
  constructor(private documentService: DocumentService) {
 | 
			
		||||
    let documentListViewConfigJson = sessionStorage.getItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG)
 | 
			
		||||
    if (documentListViewConfigJson) {
 | 
			
		||||
      try {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								src-ui/src/app/services/filter-editor-view.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src-ui/src/app/services/filter-editor-view.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
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();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										188
									
								
								src-ui/src/app/services/filter-editor-view.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								src-ui/src/app/services/filter-editor-view.service.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
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$: Observable<Results<PaperlessTag>>
 | 
			
		||||
  correspondents$: Observable<Results<PaperlessCorrespondent>>
 | 
			
		||||
  documentTypes$: Observable<Results<PaperlessDocumentType>>
 | 
			
		||||
 | 
			
		||||
  tags: PaperlessTag[] = []
 | 
			
		||||
  correspondents: PaperlessCorrespondent[]
 | 
			
		||||
  documentTypes: PaperlessDocumentType[] = []
 | 
			
		||||
 | 
			
		||||
  filterRules: FilterRule[] = []
 | 
			
		||||
 | 
			
		||||
  constructor(private tagService: TagService, private documentTypeService: DocumentTypeService, private correspondentService: CorrespondentService) {
 | 
			
		||||
    this.tags$ = this.tagService.listAll()
 | 
			
		||||
    this.tags$.subscribe(result => this.tags = result.results)
 | 
			
		||||
    this.correspondents$ = this.correspondentService.listAll()
 | 
			
		||||
    this.correspondents$.subscribe(result => this.correspondents = result.results)
 | 
			
		||||
    this.documentTypes$ = this.documentTypeService.listAll()
 | 
			
		||||
    this.documentTypes$.subscribe(result => this.documentTypes = result.results)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clear() {
 | 
			
		||||
    this.filterRules = []
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  hasFilters() {
 | 
			
		||||
    return this.filterRules.length > 0
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set filterText(text: string) {
 | 
			
		||||
    let filterRules = this.filterRules
 | 
			
		||||
    let existingRule = filterRules.find(rule => rule.type.id == FILTER_TITLE)
 | 
			
		||||
    if (existingRule && existingRule.value == text) {
 | 
			
		||||
      return
 | 
			
		||||
    } else if (existingRule) {
 | 
			
		||||
      existingRule.value = text
 | 
			
		||||
    } else {
 | 
			
		||||
      filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_TITLE), value: text})
 | 
			
		||||
    }
 | 
			
		||||
    this.filterRules = filterRules
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get filterText(): 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))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleFitlerByTag(tag: PaperlessTag) {
 | 
			
		||||
    this.toggleFilterByItem(tag, FILTER_HAS_TAG)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleFitlerByCorrespondent(tag: PaperlessCorrespondent) {
 | 
			
		||||
    this.toggleFilterByItem(tag, FILTER_CORRESPONDENT)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleFitlerByDocumentType(tag: PaperlessDocumentType) {
 | 
			
		||||
    this.toggleFilterByItem(tag, FILTER_DOCUMENT_TYPE)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleFitlerByTagID(tagID: number) {
 | 
			
		||||
    this.toggleFitlerByTag(this.tags?.find(t => t.id == tagID))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleFitlerByCorrespondentID(correspondentID: number) {
 | 
			
		||||
    this.toggleFitlerByCorrespondent(this.correspondents?.find(t => t.id == correspondentID))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleFitlerByDocumentTypeID(documentTypeID: number) {
 | 
			
		||||
    this.toggleFitlerByDocumentType(this.documentTypes?.find(t => t.id == documentTypeID))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private toggleFilterByItem(item: ObjectWithId, filterRuleTypeID: number) {
 | 
			
		||||
    let filterRules = this.filterRules
 | 
			
		||||
    let filterRuleType: FilterRuleType = FILTER_RULE_TYPES.find(t => t.id == filterRuleTypeID)
 | 
			
		||||
    let existingRule = filterRules.find(rule => rule.type.id == filterRuleType.id)
 | 
			
		||||
 | 
			
		||||
    if (existingRule && existingRule.value == item.id) {
 | 
			
		||||
      filterRules.splice(filterRules.indexOf(existingRule), 1)
 | 
			
		||||
    } else if (existingRule && filterRuleType.id == FILTER_HAS_TAG) {
 | 
			
		||||
      filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == filterRuleType.id), value: item.id})
 | 
			
		||||
    } else if (existingRule && existingRule.value == item.id) {
 | 
			
		||||
      return
 | 
			
		||||
    } else if (existingRule) {
 | 
			
		||||
      existingRule.value = item.id
 | 
			
		||||
    } else {
 | 
			
		||||
      filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == filterRuleType.id), 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) {
 | 
			
		||||
    this.setDate(date, FILTER_CREATED_BEFORE)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateCreatedAfter(date: NgbDateStruct) {
 | 
			
		||||
    this.setDate(date, FILTER_CREATED_AFTER)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateAddedBefore(date: NgbDateStruct) {
 | 
			
		||||
    this.setDate(date, FILTER_ADDED_BEFORE)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateAddedAfter(date: NgbDateStruct) {
 | 
			
		||||
    this.setDate(date, FILTER_ADDED_AFTER)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDate(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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user