mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-02 18:47:10 -05:00 
			
		
		
		
	partial selection model implementation
This commit is contained in:
		
							parent
							
								
									80420a99f5
								
							
						
					
					
						commit
						b8e7506de4
					
				@ -30,9 +30,15 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
  dateBefore: string
 | 
					  dateBefore: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Output()
 | 
				
			||||||
 | 
					  dateBeforeChange = new EventEmitter<string>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
  dateAfter: string
 | 
					  dateAfter: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Output()
 | 
				
			||||||
 | 
					  dateAfterChange = new EventEmitter<string>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
  title: string
 | 
					  title: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -83,6 +89,8 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onChange() {
 | 
					  onChange() {
 | 
				
			||||||
 | 
					    this.dateAfterChange.emit(this.dateAfter)
 | 
				
			||||||
 | 
					    this.dateBeforeChange.emit(this.dateBefore)
 | 
				
			||||||
    this.datesSet.emit({after: this.dateAfter, before: this.dateBefore})
 | 
					    this.datesSet.emit({after: this.dateAfter, before: this.dateBefore})
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -91,12 +99,12 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clearBefore() {
 | 
					  clearBefore() {
 | 
				
			||||||
    this.dateBefore = null;
 | 
					    this.dateBefore = null
 | 
				
			||||||
    this.onChange()
 | 
					    this.onChange()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clearAfter() {
 | 
					  clearAfter() {
 | 
				
			||||||
    this.dateAfter = null;
 | 
					    this.dateAfter = null
 | 
				
			||||||
    this.onChange()
 | 
					    this.onChange()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,14 +1,14 @@
 | 
				
			|||||||
<div class="btn-group" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown">
 | 
					<div class="btn-group" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown">
 | 
				
			||||||
  <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="type !== types.Editing && itemsSelected?.length > 0 ? 'btn-primary' : 'btn-outline-primary'">
 | 
					  <button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="type !== types.Editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'">
 | 
				
			||||||
    <div class="d-none d-md-inline">{{title}}</div>
 | 
					    <div class="d-none d-md-inline">{{title}}</div>
 | 
				
			||||||
    <div class="d-inline-block d-md-none">
 | 
					    <div class="d-inline-block d-md-none">
 | 
				
			||||||
      <svg class="toolbaricon" fill="currentColor">
 | 
					      <svg class="toolbaricon" fill="currentColor">
 | 
				
			||||||
        <use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
 | 
					        <use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
 | 
				
			||||||
      </svg>
 | 
					      </svg>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <ng-container *ngIf="type !== types.Editing && itemsSelected?.length > 0">
 | 
					    <ng-container *ngIf="type !== types.Editing && selectionModel.selectionSize() > 0">
 | 
				
			||||||
      <div class="badge bg-secondary text-light rounded-pill badge-corner">
 | 
					      <div class="badge bg-secondary text-light rounded-pill badge-corner">
 | 
				
			||||||
        {{itemsSelected?.length}}
 | 
					        {{selectionModel.selectionSize()}}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </ng-container>
 | 
					    </ng-container>
 | 
				
			||||||
  </button>
 | 
					  </button>
 | 
				
			||||||
@ -19,9 +19,9 @@
 | 
				
			|||||||
          <input class="form-control" type="text" [(ngModel)]="filterText" placeholder="Filter {{title}}" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
 | 
					          <input class="form-control" type="text" [(ngModel)]="filterText" placeholder="Filter {{title}}" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div *ngIf="toggleableItems" class="items">
 | 
					      <div *ngIf="selectionModel.items" class="items">
 | 
				
			||||||
        <ng-container *ngFor="let toggleableItem of toggleableItems | filter: filterText">
 | 
					        <ng-container *ngFor="let toggleableItem of selectionModel.items | filter: filterText">
 | 
				
			||||||
          <app-toggleable-dropdown-button [toggleableItem]="toggleableItem" (toggle)="toggleItem($event)"></app-toggleable-dropdown-button>
 | 
					          <app-toggleable-dropdown-button [toggleableItem]="toggleableItem" (toggle)="selectionModel.toggle(toggleableItem.item)"></app-toggleable-dropdown-button>
 | 
				
			||||||
        </ng-container>
 | 
					        </ng-container>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <button *ngIf="type == types.Editing" class="list-group-item list-group-item-action bg-light" (click)="dropdown.close()" [disabled]="!hasBeenToggled || (toggleableItems | filter: filterText).length == 0">
 | 
					      <button *ngIf="type == types.Editing" class="list-group-item list-group-item-action bg-light" (click)="dropdown.close()" [disabled]="!hasBeenToggled || (toggleableItems | filter: filterText).length == 0">
 | 
				
			||||||
 | 
				
			|||||||
@ -3,12 +3,55 @@ import { FilterPipe } from  'src/app/pipes/filter.pipe';
 | 
				
			|||||||
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
 | 
					import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
 | 
				
			||||||
import { ToggleableItem, ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component';
 | 
					import { ToggleableItem, ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component';
 | 
				
			||||||
import { MatchingModel } from 'src/app/data/matching-model';
 | 
					import { MatchingModel } from 'src/app/data/matching-model';
 | 
				
			||||||
 | 
					import { Subject } from 'rxjs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum FilterableDropdownType {
 | 
					export enum FilterableDropdownType {
 | 
				
			||||||
  Filtering = 'filtering',
 | 
					  Filtering = 'filtering',
 | 
				
			||||||
  Editing = 'editing'
 | 
					  Editing = 'editing'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class FilterableDropdownSelectionModel {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  changed = new Subject<FilterableDropdownSelectionModel>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  multiple = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  items: ToggleableItem[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getSelected() {
 | 
				
			||||||
 | 
					    return this.items.filter(i => i.state == ToggleableItemState.Selected).map(i => i.item)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toggle(item: MatchingModel, fireEvent = true) {
 | 
				
			||||||
 | 
					    console.log("TOGGLE TAG")
 | 
				
			||||||
 | 
					    let toggleableItem = this.items.find(i => i.item == item)
 | 
				
			||||||
 | 
					    console.log(toggleableItem)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (toggleableItem) {
 | 
				
			||||||
 | 
					      if (toggleableItem.state == ToggleableItemState.Selected) {
 | 
				
			||||||
 | 
					        toggleableItem.state = ToggleableItemState.NotSelected
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        this.items.forEach(i => {
 | 
				
			||||||
 | 
					          if (i.item == item) {
 | 
				
			||||||
 | 
					            i.state = ToggleableItemState.Selected
 | 
				
			||||||
 | 
					          } else if (!this.multiple) {
 | 
				
			||||||
 | 
					            i.state = ToggleableItemState.NotSelected
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (fireEvent) {
 | 
				
			||||||
 | 
					        this.changed.next(this)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  selectionSize() {
 | 
				
			||||||
 | 
					    return this.getSelected().length
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-filterable-dropdown',
 | 
					  selector: 'app-filterable-dropdown',
 | 
				
			||||||
  templateUrl: './filterable-dropdown.component.html',
 | 
					  templateUrl: './filterable-dropdown.component.html',
 | 
				
			||||||
@ -24,33 +67,45 @@ export class FilterableDropdownComponent {
 | 
				
			|||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
  set items(items: MatchingModel[]) {
 | 
					  set items(items: MatchingModel[]) {
 | 
				
			||||||
    if (items) {
 | 
					    if (items) {
 | 
				
			||||||
      this._toggleableItems = items.map(i => {
 | 
					      this._selectionModel.items = items.map(i => {
 | 
				
			||||||
        return {item: i, state: ToggleableItemState.NotSelected, count: i.document_count}
 | 
					        return {item: i, state: ToggleableItemState.NotSelected, count: i.document_count}
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _toggleableItems: ToggleableItem[] = []
 | 
					  get items(): MatchingModel[] {
 | 
				
			||||||
 | 
					    return this._selectionModel.items.map(i => i.item)
 | 
				
			||||||
  @Input()
 | 
					 | 
				
			||||||
  set toggleableItems (toggleableItems: ToggleableItem[]) {
 | 
					 | 
				
			||||||
    if (this.type == FilterableDropdownType.Editing && this.dropdown?.isOpen()) return
 | 
					 | 
				
			||||||
    else this._toggleableItems = toggleableItems
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get toggleableItems(): ToggleableItem[] {
 | 
					  _selectionModel = new FilterableDropdownSelectionModel()
 | 
				
			||||||
    return this._toggleableItems
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
  set itemsSelected(itemsSelected: MatchingModel[]) {
 | 
					  set selectionModel(model: FilterableDropdownSelectionModel) {
 | 
				
			||||||
    this.toggleableItems.forEach(i => {
 | 
					    if (this.selectionModel) {
 | 
				
			||||||
      i.state = (itemsSelected.find(is => is.id == i.item.id)) ? ToggleableItemState.Selected : ToggleableItemState.NotSelected
 | 
					      this.selectionModel.changed.complete()
 | 
				
			||||||
 | 
					      model.items = this.selectionModel.items
 | 
				
			||||||
 | 
					      model.multiple = this.selectionModel.multiple
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    model.changed.subscribe(updatedModel => {
 | 
				
			||||||
 | 
					      this.selectionModelChange.next(updatedModel)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					    this._selectionModel = model
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get itemsSelected(): MatchingModel[] {
 | 
					  get selectionModel(): FilterableDropdownSelectionModel {
 | 
				
			||||||
    return this.toggleableItems.filter(ti => ti.state == ToggleableItemState.Selected).map(ti => ti.item)
 | 
					    return this._selectionModel
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Output()
 | 
				
			||||||
 | 
					  selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Input()
 | 
				
			||||||
 | 
					  set multiple(value: boolean) {
 | 
				
			||||||
 | 
					    this.selectionModel.multiple = value
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get multiple() {
 | 
				
			||||||
 | 
					    return this.selectionModel.multiple
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
@ -64,50 +119,40 @@ export class FilterableDropdownComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  types = FilterableDropdownType
 | 
					  types = FilterableDropdownType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Input()
 | 
					 | 
				
			||||||
  singular: boolean = false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Output()
 | 
					 | 
				
			||||||
  toggle = new EventEmitter()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Output()
 | 
					 | 
				
			||||||
  open = new EventEmitter()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Output()
 | 
					 | 
				
			||||||
  editingComplete = new EventEmitter()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  hasBeenToggled:boolean = false
 | 
					  hasBeenToggled:boolean = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(private filterPipe: FilterPipe) { }
 | 
					  constructor(private filterPipe: FilterPipe) {
 | 
				
			||||||
 | 
					    this.selectionModel = new FilterableDropdownSelectionModel()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleItem(toggleableItem: ToggleableItem): void {
 | 
					  toggleItem(toggleableItem: ToggleableItem): void {
 | 
				
			||||||
    if (this.singular && toggleableItem.state == ToggleableItemState.Selected) {
 | 
					    // if (this.singular && toggleableItem.state == ToggleableItemState.Selected) {
 | 
				
			||||||
      this._toggleableItems.filter(ti => ti.item.id !== toggleableItem.item.id).forEach(ti => ti.state = ToggleableItemState.NotSelected)
 | 
					    //   this.selectionModel.items.filter(ti => ti.item.id !== toggleableItem.item.id).forEach(ti => ti.state = ToggleableItemState.NotSelected)
 | 
				
			||||||
    }
 | 
					    // }
 | 
				
			||||||
    this.hasBeenToggled = true
 | 
					    // this.hasBeenToggled = true
 | 
				
			||||||
    this.toggle.emit(toggleableItem.item)
 | 
					    // this.toggle.emit(toggleableItem.item)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  dropdownOpenChange(open: boolean): void {
 | 
					  dropdownOpenChange(open: boolean): void {
 | 
				
			||||||
    if (open) {
 | 
					    // if (open) {
 | 
				
			||||||
      setTimeout(() => {
 | 
					    //   setTimeout(() => {
 | 
				
			||||||
        this.listFilterTextInput.nativeElement.focus();
 | 
					    //     this.listFilterTextInput.nativeElement.focus();
 | 
				
			||||||
      }, 0)
 | 
					    //   }, 0)
 | 
				
			||||||
      this.hasBeenToggled = false
 | 
					    //   this.hasBeenToggled = false
 | 
				
			||||||
      this.open.next()
 | 
					    //   this.open.next()
 | 
				
			||||||
    } else {
 | 
					    // } else {
 | 
				
			||||||
      this.filterText = ''
 | 
					    //   this.filterText = ''
 | 
				
			||||||
      if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.toggleableItems)
 | 
					    //   if (this.type == FilterableDropdownType.Editing) this.editingComplete.emit(this.toggleableItems)
 | 
				
			||||||
    }
 | 
					    // }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  listFilterEnter(): void {
 | 
					  listFilterEnter(): void {
 | 
				
			||||||
    let filtered = this.filterPipe.transform(this.toggleableItems, this.filterText)
 | 
					    // let filtered = this.filterPipe.transform(this.toggleableItems, this.filterText)
 | 
				
			||||||
    if (filtered.length == 1) {
 | 
					    // if (filtered.length == 1) {
 | 
				
			||||||
      let toggleableItem = this.toggleableItems.find(ti => ti.item.id == filtered[0].item.id)
 | 
					    //   let toggleableItem = this.toggleableItems.find(ti => ti.item.id == filtered[0].item.id)
 | 
				
			||||||
      if (toggleableItem) toggleableItem.state = ToggleableItemState.Selected
 | 
					    //   if (toggleableItem) toggleableItem.state = ToggleableItemState.Selected
 | 
				
			||||||
      this.toggleItem(filtered[0])
 | 
					    //   this.toggleItem(filtered[0])
 | 
				
			||||||
      this.dropdown.close()
 | 
					    //   this.dropdown.close()
 | 
				
			||||||
    }
 | 
					    // }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -31,8 +31,7 @@ export class ToggleableDropdownButtonComponent {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleItem(): void {
 | 
					  toggleItem(): void {
 | 
				
			||||||
    this.toggleableItem.state = (this.toggleableItem.state == ToggleableItemState.NotSelected || this.toggleableItem.state == ToggleableItemState.PartiallySelected) ? ToggleableItemState.Selected : ToggleableItemState.NotSelected
 | 
					    this.toggle.emit()
 | 
				
			||||||
    this.toggle.emit(this.toggleableItem)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getSelectedIconName() {
 | 
					  getSelectedIconName() {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
<div class="row">
 | 
					<!-- <div class="row">
 | 
				
			||||||
  <div class="col-auto mb-2 mb-xl-0" role="group" aria-label="Select">
 | 
					  <div class="col-auto mb-2 mb-xl-0" role="group" aria-label="Select">
 | 
				
			||||||
    <button class="btn btn-sm btn-outline-danger" (click)="documentList.selectNone()">
 | 
					    <button class="btn btn-sm btn-outline-danger" (click)="documentList.selectNone()">
 | 
				
			||||||
      <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
 | 
					      <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
 | 
				
			||||||
@ -58,4 +58,4 @@
 | 
				
			|||||||
      Delete
 | 
					      Delete
 | 
				
			||||||
    </button>
 | 
					    </button>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</div>
 | 
					</div> -->
 | 
				
			||||||
 | 
				
			|||||||
@ -8,11 +8,11 @@
 | 
				
			|||||||
  <div class="w-100 d-xl-none"></div>
 | 
					  <div class="w-100 d-xl-none"></div>
 | 
				
			||||||
   <div class="col col-xl-auto mb-2 mb-xl-0">
 | 
					   <div class="col col-xl-auto mb-2 mb-xl-0">
 | 
				
			||||||
     <div class="d-flex">
 | 
					     <div class="d-flex">
 | 
				
			||||||
       <app-filterable-dropdown class="mr-2 mr-md-3" [items]="tags" [itemsSelected]="selectedTags" title="Tags" icon="tag-fill" (toggle)="toggleTag($event.id)"></app-filterable-dropdown>
 | 
					       <app-filterable-dropdown class="mr-2 mr-md-3" [items]="tags" [(selectionModel)]="tagSelectionModel" (selectionModelChange)="updateRules()" [multiple]="true" title="Tags" icon="tag-fill"></app-filterable-dropdown>
 | 
				
			||||||
       <app-filterable-dropdown class="mr-2 mr-md-3" [items]="correspondents" [itemsSelected]="selectedCorrespondents" title="Correspondents" icon="person-fill" (toggle)="toggleCorrespondent($event.id)"></app-filterable-dropdown>
 | 
					       <app-filterable-dropdown class="mr-2 mr-md-3" [items]="correspondents" [(selectionModel)]="correspondentSelectionModel" (selectionModelChange)="updateRules()" title="Correspondents" icon="person-fill"></app-filterable-dropdown>
 | 
				
			||||||
       <app-filterable-dropdown class="mr-2 mr-md-3" [items]="documentTypes" [itemsSelected]="selectedDocumentTypes" title="Document types" icon="file-earmark-fill" (toggle)="toggleDocumentType($event.id)"></app-filterable-dropdown>
 | 
					       <app-filterable-dropdown class="mr-2 mr-md-3" [items]="documentTypes" [(selectionModel)]="documentTypeSelectionModel" (selectionModelChange)="updateRules()" title="Document types" icon="file-earmark-fill"></app-filterable-dropdown>
 | 
				
			||||||
       <app-date-dropdown class="mr-2 mr-md-3" [dateBefore]="dateCreatedBefore" [dateAfter]="dateCreatedAfter" title="Created" (datesSet)="onDatesCreatedSet($event)"></app-date-dropdown>
 | 
					       <app-date-dropdown class="mr-2 mr-md-3" [(dateBefore)]="dateCreatedBefore" [(dateAfter)]="dateCreatedAfter" title="Created" (datesSet)="updateRules()"></app-date-dropdown>
 | 
				
			||||||
       <app-date-dropdown [dateBefore]="dateAddedBefore" [dateAfter]="dateAddedAfter" title="Added"  (datesSet)="onDatesAddedSet($event)"></app-date-dropdown>
 | 
					       <app-date-dropdown [(dateBefore)]="dateAddedBefore" [(dateAfter)]="dateAddedAfter" title="Added"  (datesSet)="updateRules()"></app-date-dropdown>
 | 
				
			||||||
     </div>
 | 
					     </div>
 | 
				
			||||||
   </div>
 | 
					   </div>
 | 
				
			||||||
   <div class="w-100 d-xl-none"></div>
 | 
					   <div class="w-100 d-xl-none"></div>
 | 
				
			||||||
 | 
				
			|||||||
@ -3,14 +3,13 @@ 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, filter, flatMap, mergeMap } from 'rxjs/operators';
 | 
				
			||||||
import { NgbDateParserFormatter } from '@ng-bootstrap/ng-bootstrap';
 | 
					 | 
				
			||||||
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';
 | 
				
			||||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
					import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
 | 
				
			||||||
import { FilterRule } from 'src/app/data/filter-rule';
 | 
					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';
 | 
					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';
 | 
				
			||||||
import { DateSelection } from 'src/app/components/common/date-dropdown/date-dropdown.component';
 | 
					import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-filter-editor',
 | 
					  selector: 'app-filter-editor',
 | 
				
			||||||
@ -46,37 +45,91 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  tags: PaperlessTag[] = []
 | 
					  tags: PaperlessTag[] = []
 | 
				
			||||||
  correspondents: PaperlessCorrespondent[]
 | 
					  correspondents: PaperlessCorrespondent[] = []
 | 
				
			||||||
  documentTypes: PaperlessDocumentType[] = []
 | 
					  documentTypes: PaperlessDocumentType[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _titleFilter = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  tagSelectionModel = new FilterableDropdownSelectionModel()
 | 
				
			||||||
 | 
					  correspondentSelectionModel = new FilterableDropdownSelectionModel()
 | 
				
			||||||
 | 
					  documentTypeSelectionModel = new FilterableDropdownSelectionModel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dateCreatedBefore: string
 | 
				
			||||||
 | 
					  dateCreatedAfter: string
 | 
				
			||||||
 | 
					  dateAddedBefore: string
 | 
				
			||||||
 | 
					  dateAddedAfter: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Input()
 | 
					  @Input()
 | 
				
			||||||
  filterRules: FilterRule[]
 | 
					  set filterRules (value: FilterRule[]) {
 | 
				
			||||||
 | 
					    console.log("SET FILTER RULES")
 | 
				
			||||||
 | 
					    value.forEach(rule => {
 | 
				
			||||||
 | 
					      switch (rule.rule_type) {
 | 
				
			||||||
 | 
					        case FILTER_TITLE:
 | 
				
			||||||
 | 
					          this._titleFilter = rule.value
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					        case FILTER_CREATED_AFTER:
 | 
				
			||||||
 | 
					          this.dateCreatedAfter = rule.value
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					        case FILTER_CREATED_BEFORE:
 | 
				
			||||||
 | 
					          this.dateCreatedBefore = rule.value
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					        case FILTER_ADDED_AFTER:
 | 
				
			||||||
 | 
					          this.dateAddedAfter = rule.value
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					        case FILTER_ADDED_BEFORE:
 | 
				
			||||||
 | 
					          this.dateAddedBefore = rule.value
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.tagService.getCachedMany(value.filter(v => v.rule_type == FILTER_HAS_TAG).map(rule => +rule.value)).subscribe(tags => {
 | 
				
			||||||
 | 
					      console.log(tags)
 | 
				
			||||||
 | 
					      tags.forEach(tag => this.tagSelectionModel.toggle(tag, false))
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Output()
 | 
					  @Output()
 | 
				
			||||||
  filterRulesChange = new EventEmitter<FilterRule[]>()
 | 
					  filterRulesChange = new EventEmitter<FilterRule[]>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateRules() {
 | 
				
			||||||
 | 
					    console.log("UPDATE RULES!!!")
 | 
				
			||||||
 | 
					    let filterRules: FilterRule[] = []
 | 
				
			||||||
 | 
					    if (this._titleFilter) {
 | 
				
			||||||
 | 
					      filterRules.push({rule_type: FILTER_TITLE, value: this._titleFilter})
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.tagSelectionModel.getSelected().forEach(tag => {
 | 
				
			||||||
 | 
					      filterRules.push({rule_type: FILTER_HAS_TAG, value: tag.id.toString()})
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    this.correspondentSelectionModel.getSelected().forEach(correspondent => {
 | 
				
			||||||
 | 
					      filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id.toString()})
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    this.documentTypeSelectionModel.getSelected().forEach(documentType => {
 | 
				
			||||||
 | 
					      filterRules.push({rule_type: FILTER_DOCUMENT_TYPE, value: documentType.id.toString()})
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    if (this.dateCreatedBefore) {
 | 
				
			||||||
 | 
					      filterRules.push({rule_type: FILTER_CREATED_BEFORE, value: this.dateCreatedBefore})
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.dateCreatedAfter) {
 | 
				
			||||||
 | 
					      filterRules.push({rule_type: FILTER_CREATED_AFTER, value: this.dateCreatedAfter})
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.dateAddedBefore) {
 | 
				
			||||||
 | 
					      filterRules.push({rule_type: FILTER_ADDED_BEFORE, value: this.dateAddedBefore})
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (this.dateAddedAfter) {
 | 
				
			||||||
 | 
					      filterRules.push({rule_type: FILTER_ADDED_AFTER, value: this.dateAddedAfter})
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    console.log(filterRules)
 | 
				
			||||||
 | 
					    this.filterRulesChange.next(filterRules)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  hasFilters() {
 | 
					  hasFilters() {
 | 
				
			||||||
    return this.filterRules.length > 0
 | 
					    return this._titleFilter || 
 | 
				
			||||||
  }
 | 
					      this.dateCreatedAfter || this.dateAddedBefore || this.dateCreatedAfter || this.dateCreatedBefore ||
 | 
				
			||||||
 | 
					      this.tagSelectionModel.selectionSize() || this.correspondentSelectionModel.selectionSize() || this.documentTypeSelectionModel.selectionSize()
 | 
				
			||||||
  get selectedTags(): PaperlessTag[] {
 | 
					 | 
				
			||||||
    let tagRules: FilterRule[] = this.filterRules.filter(fr => fr.rule_type == 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.rule_type == 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.rule_type == FILTER_DOCUMENT_TYPE)
 | 
					 | 
				
			||||||
    return this.documentTypes?.filter(dt => documentTypeRules.find(dtr => +dtr.value == dt.id))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  get titleFilter() {
 | 
					  get titleFilter() {
 | 
				
			||||||
    let existingRule = this.filterRules.find(rule => rule.rule_type == FILTER_TITLE)
 | 
					    return this._titleFilter
 | 
				
			||||||
    return existingRule ? existingRule.value : ''
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  set titleFilter(value) {
 | 
					  set titleFilter(value) {
 | 
				
			||||||
@ -97,142 +150,27 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
      debounceTime(400),
 | 
					      debounceTime(400),
 | 
				
			||||||
      distinctUntilChanged()
 | 
					      distinctUntilChanged()
 | 
				
			||||||
    ).subscribe(title => {
 | 
					    ).subscribe(title => {
 | 
				
			||||||
      this.setTitleRule(title)
 | 
					      this._titleFilter = title
 | 
				
			||||||
 | 
					      this.updateRules()
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ngOnDestroy() {
 | 
					  ngOnDestroy() {
 | 
				
			||||||
    this.titleFilterDebounce.complete()
 | 
					    this.titleFilterDebounce.complete()
 | 
				
			||||||
    // TODO: not sure if both is necessary
 | 
					 | 
				
			||||||
    this.subscription.unsubscribe()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  applyFilters() {
 | 
					 | 
				
			||||||
    this.filterRulesChange.next(this.filterRules)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clearSelected() {
 | 
					  clearSelected() {
 | 
				
			||||||
    this.filterRules = []
 | 
					    this._titleFilter = ""
 | 
				
			||||||
    this.applyFilters()
 | 
					    this.updateRules()
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private toggleFilterRule(filterRuleTypeID: number, value: number) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let filterRuleType = FILTER_RULE_TYPES.find(t => t.id == filterRuleTypeID)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let existingRule = this.filterRules.find(rule => rule.rule_type == filterRuleTypeID && rule.value == value?.toString())
 | 
					 | 
				
			||||||
    let existingRuleOfSameType = this.filterRules.find(rule => rule.rule_type == 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({rule_type: filterRuleTypeID, value: value?.toString()})
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      // otherwise (i.e., no multi support AND there's already a rule of this type), update the rule.
 | 
					 | 
				
			||||||
      existingRuleOfSameType.value = value?.toString()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this.applyFilters()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private setTitleRule(title: string) {
 | 
					 | 
				
			||||||
    let existingRule = this.filterRules.find(rule => rule.rule_type == FILTER_TITLE)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!existingRule && title) {
 | 
					 | 
				
			||||||
      this.filterRules.push({rule_type: FILTER_TITLE, value: title})
 | 
					 | 
				
			||||||
    } else if (existingRule && !title) {
 | 
					 | 
				
			||||||
      this.filterRules.splice(this.filterRules.findIndex(rule => rule.rule_type == FILTER_TITLE), 1)
 | 
					 | 
				
			||||||
    } else if (existingRule && title) {
 | 
					 | 
				
			||||||
      existingRule.value = title
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    this.applyFilters()
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleTag(tagId: number) {
 | 
					  toggleTag(tagId: number) {
 | 
				
			||||||
    this.toggleFilterRule(FILTER_HAS_TAG, tagId)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleCorrespondent(correspondentId: number) {
 | 
					  toggleCorrespondent(correspondentId: number) {
 | 
				
			||||||
    this.toggleFilterRule(FILTER_CORRESPONDENT, correspondentId)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  toggleDocumentType(documentTypeId: number) {
 | 
					  toggleDocumentType(documentTypeId: number) {
 | 
				
			||||||
    this.toggleFilterRule(FILTER_DOCUMENT_TYPE, documentTypeId)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Date handling
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onDatesCreatedSet(dates: DateSelection) {
 | 
					 | 
				
			||||||
    this.setDateCreatedBefore(dates.before)
 | 
					 | 
				
			||||||
    this.setDateCreatedAfter(dates.after)
 | 
					 | 
				
			||||||
    this.applyFilters()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onDatesAddedSet(dates: DateSelection) {
 | 
					 | 
				
			||||||
    this.setDateAddedBefore(dates.before)
 | 
					 | 
				
			||||||
    this.setDateAddedAfter(dates.after)
 | 
					 | 
				
			||||||
    this.applyFilters()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  get dateCreatedBefore(): string {
 | 
					 | 
				
			||||||
    let createdBeforeRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_CREATED_BEFORE)
 | 
					 | 
				
			||||||
    return createdBeforeRule ? createdBeforeRule.value : null
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  get dateCreatedAfter(): string {
 | 
					 | 
				
			||||||
    let createdAfterRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_CREATED_AFTER)
 | 
					 | 
				
			||||||
    return createdAfterRule ? createdAfterRule.value : null
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  get dateAddedBefore(): string {
 | 
					 | 
				
			||||||
    let addedBeforeRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_ADDED_BEFORE)
 | 
					 | 
				
			||||||
    return addedBeforeRule ? addedBeforeRule.value : null
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  get dateAddedAfter(): string {
 | 
					 | 
				
			||||||
    let addedAfterRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_ADDED_AFTER)
 | 
					 | 
				
			||||||
    return addedAfterRule ? addedAfterRule.value : null
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setDateCreatedBefore(date?: string) {
 | 
					 | 
				
			||||||
    if (date) this.setDateFilter(date, FILTER_CREATED_BEFORE)
 | 
					 | 
				
			||||||
    else this.clearDateFilter(FILTER_CREATED_BEFORE)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setDateCreatedAfter(date?: string) {
 | 
					 | 
				
			||||||
    if (date) this.setDateFilter(date, FILTER_CREATED_AFTER)
 | 
					 | 
				
			||||||
    else this.clearDateFilter(FILTER_CREATED_AFTER)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setDateAddedBefore(date?: string) {
 | 
					 | 
				
			||||||
    if (date) this.setDateFilter(date, FILTER_ADDED_BEFORE)
 | 
					 | 
				
			||||||
    else this.clearDateFilter(FILTER_ADDED_BEFORE)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setDateAddedAfter(date?: string) {
 | 
					 | 
				
			||||||
    if (date) this.setDateFilter(date, FILTER_ADDED_AFTER)
 | 
					 | 
				
			||||||
    else this.clearDateFilter(FILTER_ADDED_AFTER)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setDateFilter(date: string, dateRuleTypeID: number) {
 | 
					 | 
				
			||||||
    let existingRule = this.filterRules.find(rule => rule.rule_type == dateRuleTypeID)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (existingRule) {
 | 
					 | 
				
			||||||
      existingRule.value = date
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      this.filterRules.push({rule_type: dateRuleTypeID, value: date})
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  clearDateFilter(dateRuleTypeID: number) {
 | 
					 | 
				
			||||||
    let ruleIndex = this.filterRules.findIndex(rule => rule.rule_type == dateRuleTypeID)
 | 
					 | 
				
			||||||
    if (ruleIndex != -1) {
 | 
					 | 
				
			||||||
      this.filterRules.splice(ruleIndex, 1)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user