mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Moved quick filters to filter editor
This commit is contained in:
		| @@ -45,15 +45,8 @@ | |||||||
|  |  | ||||||
|   <div class="btn-group ml-2"> |   <div class="btn-group ml-2"> | ||||||
|  |  | ||||||
|     <button type="button" class="btn btn-sm" [ngClass]="isFiltered ? 'btn-primary' : 'btn-outline-primary'" (click)="showFilter=!showFilter"> |  | ||||||
|       <svg class="toolbaricon" fill="currentColor"> |  | ||||||
|         <use xlink:href="assets/bootstrap-icons.svg#funnel" /> |  | ||||||
|       </svg> |  | ||||||
|       {{ showFilter ? 'Hide' : 'Show' }} Filter Editor |  | ||||||
|     </button> |  | ||||||
|  |  | ||||||
|     <div class="btn-group" ngbDropdown role="group"> |     <div class="btn-group" ngbDropdown role="group"> | ||||||
|       <button class="btn btn-sm btn-outline-primary dropdown-toggle-split" ngbDropdownToggle></button> |       <button class="btn btn-sm btn-outline-primary dropdown-toggle" ngbDropdownToggle>Saved Views</button> | ||||||
|       <div class="dropdown-menu" ngbDropdownMenu> |       <div class="dropdown-menu" ngbDropdownMenu> | ||||||
|         <ng-container *ngIf="!list.savedViewId"> |         <ng-container *ngIf="!list.savedViewId"> | ||||||
|           <button ngbDropdownItem *ngFor="let config of savedViewConfigService.getConfigs()" (click)="loadViewConfig(config)">{{config.title}}</button> |           <button ngbDropdownItem *ngFor="let config of savedViewConfigService.getConfigs()" (click)="loadViewConfig(config)">{{config.title}}</button> | ||||||
| @@ -69,82 +62,8 @@ | |||||||
|  |  | ||||||
| </app-page-header> | </app-page-header> | ||||||
|  |  | ||||||
| <div class="row pb-1 mb-3 align-items-right"> | <div class="card w-100 mb-3"> | ||||||
|   <div class="btn-toolbar col-auto"> |  | ||||||
|     <span class="text-muted mt-1 mr-2">Quick Filters:</span> |  | ||||||
|     <div class="btn-group ml-2" ngbDropdown role="group"> |  | ||||||
|       <button class="btn btn-outline-primary btn-sm" id="dropdownTags" ngbDropdownToggle>Tags</button> |  | ||||||
|       <div class="dropdown-menu quick-filter" ngbDropdownMenu aria-labelledby="dropdownTags"> |  | ||||||
|         <div class="list-group list-group-flush"> |  | ||||||
|           <input class="list-group-item form-control" type="text" [(ngModel)]="filterTagsText" placeholder="Filter tags"> |  | ||||||
|           <ng-container *ngIf="(tags | filter: filterTagsText).length > 0"> |  | ||||||
|             <button class="list-group-item list-group-item-action d-flex align-items-center" role="menuitem" *ngFor="let tag of tags | filter: filterTagsText; let i = index" (click)="toggleFilterByTag(tag.id)"> |  | ||||||
|               <div class="selected-icon mr-1"> |  | ||||||
|                 <svg *ngIf="currentViewIncludesTag(tag.id)" 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 [tag]="tag" [clickable]="true" linkTitle="Filter by tag"></app-tag></div> |  | ||||||
|               <div class="badge bg-primary text-light rounded-pill ml-auto">{{tag.document_count}}</div> |  | ||||||
|             </button> |  | ||||||
|           </ng-container> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|  |  | ||||||
|     <div class="btn-group ml-2" ngbDropdown role="group"> |  | ||||||
|       <button class="btn btn-outline-primary btn-sm" id="dropdownCorrespondents" ngbDropdownToggle>Correspondents</button> |  | ||||||
|       <div class="dropdown-menu quick-filter" ngbDropdownMenu aria-labelledby="dropdownCorrespondents"> |  | ||||||
|         <div class="list-group list-group-flush"> |  | ||||||
|           <input class="list-group-item form-control" type="text" [(ngModel)]="filterCorrespondentsText" placeholder="Filter correspondents"> |  | ||||||
|           <ng-container *ngIf="(correspondents | filter: filterCorrespondentsText).length > 0"> |  | ||||||
|             <button class="list-group-item list-group-item-action d-flex align-items-center" role="menuitem" *ngFor="let correspondent of correspondents | filter: filterCorrespondentsText; let i = index" (click)="toggleFilterByCorrespondent(correspondent.id)"> |  | ||||||
|               <div class="selected-icon mr-1"> |  | ||||||
|                 <svg *ngIf="currentViewIncludesCorrespondent(correspondent.id)" 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">{{correspondent.name}}</div> |  | ||||||
|               <div class="badge bg-primary text-light rounded-pill ml-auto">{{correspondent.document_count}}</div> |  | ||||||
|             </button> |  | ||||||
|           </ng-container> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|  |  | ||||||
|     <div class="btn-group ml-2" ngbDropdown role="group"> |  | ||||||
|       <button class="btn btn-outline-primary btn-sm" id="dropdownDocumentTypes" ngbDropdownToggle>Document Types</button> |  | ||||||
|       <div class="dropdown-menu quick-filter" ngbDropdownMenu aria-labelledby="dropdownDocumentTypes"> |  | ||||||
|         <div class="list-group list-group-flush"> |  | ||||||
|           <input class="list-group-item form-control" type="text" [(ngModel)]="filterDocumentTypesText" placeholder="Filter document types"> |  | ||||||
|           <ng-container *ngIf="(documentTypes | filter: filterDocumentTypesText).length > 0"> |  | ||||||
|             <button class="list-group-item list-group-item-action d-flex align-items-center" role="menuitem" *ngFor="let documentType of documentTypes | filter: filterDocumentTypesText; let i = index" (click)="toggleFilterByDocumentType(documentType.id)"> |  | ||||||
|               <div class="selected-icon mr-1"> |  | ||||||
|                 <svg *ngIf="currentViewIncludesDocumentType(documentType.id)" 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">{{documentType.name}}</div> |  | ||||||
|               <div class="badge bg-primary text-light rounded-pill ml-auto">{{documentType.document_count}}</div> |  | ||||||
|             </button> |  | ||||||
|           </ng-container> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|  |  | ||||||
|     <button class="btn-link border-0 bg-transparent ml-3 text-muted" *ngIf="currentViewIncludesQuickFilter()" (click)="filterEditor.clearClicked()"> |  | ||||||
|       <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> |  | ||||||
|       Clear |  | ||||||
|     </button> |  | ||||||
|  |  | ||||||
|   </div> |  | ||||||
| </div> |  | ||||||
|  |  | ||||||
| <div class="card w-100 mb-3" [hidden]="!showFilter"> |  | ||||||
|   <div class="card-body"> |   <div class="card-body"> | ||||||
|     <h5 class="card-title">Filter Editor</h5> |  | ||||||
|     <app-filter-editor [(filterRules)]="filterRules" (apply)="applyFilterRules()" (clear)="clearFilterRules()"></app-filter-editor> |     <app-filter-editor [(filterRules)]="filterRules" (apply)="applyFilterRules()" (clear)="clearFilterRules()"></app-filter-editor> | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -1,10 +0,0 @@ | |||||||
| .quick-filter { |  | ||||||
|   min-width: 250px; |  | ||||||
|   max-height: 400px; |  | ||||||
|   overflow-y: scroll; |  | ||||||
|    |  | ||||||
|   .selected-icon { |  | ||||||
|     min-width: 1em; |  | ||||||
|     min-height: 1em; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { Component, OnInit, ViewChild } from '@angular/core'; | import { Component, OnInit } 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'; | ||||||
| @@ -14,9 +14,6 @@ import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-vi | |||||||
| 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 { 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 { FilterEditorComponent } from 'src/app/components/filter-editor/filter-editor.component'; | import { FilterEditorComponent } from 'src/app/components/filter-editor/filter-editor.component'; | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
| @@ -32,25 +29,12 @@ export class DocumentListComponent implements OnInit { | |||||||
|     public route: ActivatedRoute, |     public route: ActivatedRoute, | ||||||
|     private toastService: ToastService, |     private toastService: ToastService, | ||||||
|     public modalService: NgbModal, |     public modalService: NgbModal, | ||||||
|     private titleService: Title, |     private titleService: Title) { } | ||||||
|     private tagService: TagService, |  | ||||||
|     private correspondentService: CorrespondentService, |  | ||||||
|     private documentTypeService: DocumentTypeService) { } |  | ||||||
|  |  | ||||||
|   displayMode = 'smallCards' // largeCards, smallCards, details |   displayMode = 'smallCards' // largeCards, smallCards, details | ||||||
|  |  | ||||||
|   filterRules: FilterRule[] = [] |   filterRules: FilterRule[] = [] | ||||||
|   showFilter = false |  | ||||||
|  |  | ||||||
|   tags: PaperlessTag[] = [] |  | ||||||
|   correspondents: PaperlessCorrespondent[] = [] |  | ||||||
|   documentTypes: PaperlessDocumentType[] = [] |  | ||||||
|   filterTagsText: string |  | ||||||
|   filterCorrespondentsText: string |  | ||||||
|   filterDocumentTypesText: string |  | ||||||
|  |  | ||||||
|   @ViewChild(FilterEditorComponent) filterEditor; |  | ||||||
|    |  | ||||||
|   get isFiltered() { |   get isFiltered() { | ||||||
|     return this.list.filterRules?.length > 0 |     return this.list.filterRules?.length > 0 | ||||||
|   } |   } | ||||||
| @@ -75,20 +59,15 @@ export class DocumentListComponent implements OnInit { | |||||||
|       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.filterRules = this.list.filterRules |         this.filterRules = this.list.filterRules | ||||||
|         this.showFilter = false |  | ||||||
|         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.filterRules = this.list.filterRules |         this.filterRules = this.list.filterRules | ||||||
|         this.showFilter = this.filterRules.length > 0 |  | ||||||
|         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.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) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   applyFilterRules() { |   applyFilterRules() { | ||||||
| @@ -97,7 +76,6 @@ export class DocumentListComponent implements OnInit { | |||||||
|  |  | ||||||
|   clearFilterRules() { |   clearFilterRules() { | ||||||
|     this.list.filterRules = this.filterRules |     this.list.filterRules = this.filterRules | ||||||
|     this.showFilter = false |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   loadViewConfig(config: SavedViewConfig) { |   loadViewConfig(config: SavedViewConfig) { | ||||||
| @@ -164,60 +142,4 @@ export class DocumentListComponent implements OnInit { | |||||||
|     this.applyFilterRules() |     this.applyFilterRules() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   findRuleIndex(type_id: number, value: any) { |  | ||||||
|     return this.list.filterRules.findIndex(rule => rule.type.id == type_id && rule.value == value) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   toggleFilterByTag(tag_id: number) { |  | ||||||
|     let existingRuleIndex = this.findRuleIndex(FILTER_HAS_TAG, tag_id) |  | ||||||
|     if (existingRuleIndex !== -1) { |  | ||||||
|       let filterRules = this.list.filterRules |  | ||||||
|       filterRules.splice(existingRuleIndex, 1) |  | ||||||
|       this.filterRules = filterRules |  | ||||||
|       this.applyFilterRules() |  | ||||||
|     } else { |  | ||||||
|       this.filterByTag(tag_id) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   toggleFilterByCorrespondent(correspondent_id: number) { |  | ||||||
|     let existingRuleIndex = this.findRuleIndex(FILTER_CORRESPONDENT, correspondent_id) |  | ||||||
|     if (existingRuleIndex !== -1) { |  | ||||||
|       let filterRules = this.list.filterRules |  | ||||||
|       filterRules.splice(existingRuleIndex, 1) |  | ||||||
|       this.filterRules = filterRules |  | ||||||
|       this.applyFilterRules() |  | ||||||
|     } else { |  | ||||||
|       this.filterByCorrespondent(correspondent_id) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   toggleFilterByDocumentType(document_type_id: number) { |  | ||||||
|     let existingRuleIndex = this.findRuleIndex(FILTER_DOCUMENT_TYPE, document_type_id) |  | ||||||
|     if (existingRuleIndex !== -1) { |  | ||||||
|       let filterRules = this.list.filterRules |  | ||||||
|       filterRules.splice(existingRuleIndex, 1) |  | ||||||
|       this.filterRules = filterRules |  | ||||||
|       this.applyFilterRules() |  | ||||||
|     } else { |  | ||||||
|       this.filterByDocumentType(document_type_id) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   currentViewIncludesTag(tag_id: number) { |  | ||||||
|     return this.findRuleIndex(FILTER_HAS_TAG, tag_id) !== -1 |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   currentViewIncludesCorrespondent(correspondent_id: number) { |  | ||||||
|     return this.findRuleIndex(FILTER_CORRESPONDENT, correspondent_id) !== -1 |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   currentViewIncludesDocumentType(document_type_id: number) { |  | ||||||
|     return this.findRuleIndex(FILTER_DOCUMENT_TYPE, document_type_id) !== -1 |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   currentViewIncludesQuickFilter() { |  | ||||||
|     return this.list.filterRules.find(rule => rule.type.id == FILTER_HAS_TAG || rule.type.id == FILTER_CORRESPONDENT || rule.type.id == FILTER_DOCUMENT_TYPE) !== undefined |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,52 +1,83 @@ | |||||||
| <div *ngFor="let rule of filterRules" class="form-row form-group"> | <div class="form-row form-group mb-0"> | ||||||
|   <div class="col-md-3 col-form-label"> |   <div class="col-auto"> | ||||||
|     <span>{{rule.type.name}}</span> |     <div class="text-muted mt-1">Filter by:</div> | ||||||
|   </div> |   </div> | ||||||
|   <div class="col"> |   <div class="col"> | ||||||
|     <input *ngIf="rule.type.datatype == 'string'" type="text" class="form-control form-control-sm" [(ngModel)]="rule.value"> |     <input class="form-control form-control-sm" type="text" placeholder="Title / content"> | ||||||
|     <input *ngIf="rule.type.datatype == 'number'" type="number" class="form-control form-control-sm" [(ngModel)]="rule.value"> |  | ||||||
|     <input *ngIf="rule.type.datatype == 'date'" type="date" class="form-control form-control-sm" [(ngModel)]="rule.value"> |  | ||||||
|  |  | ||||||
|     <select *ngIf="rule.type.datatype == 'tag'" class="form-control form-control-sm" [(ngModel)]="rule.value"> |  | ||||||
|       <option *ngFor="let t of tags" [ngValue]="t.id">{{t.name}}</option> |  | ||||||
|     </select> |  | ||||||
|  |  | ||||||
|     <select *ngIf="rule.type.datatype == 'document_type'" class="form-control form-control-sm" [(ngModel)]="rule.value"> |  | ||||||
|       <option *ngFor="let dt of documentTypes" [ngValue]="dt.id">{{dt.name}}</option> |  | ||||||
|     </select> |  | ||||||
|  |  | ||||||
|     <select *ngIf="rule.type.datatype == 'correspondent'" class="form-control form-control-sm" [(ngModel)]="rule.value"> |  | ||||||
|       <option *ngFor="let c of correspondents" [ngValue]="c.id">{{c.name}}</option> |  | ||||||
|     </select> |  | ||||||
|  |  | ||||||
|     <select *ngIf="rule.type.datatype == 'boolean'" class="form-control form-control-sm" [(ngModel)]="rule.value"> |  | ||||||
|       <option [ngValue]="true">Yes</option> |  | ||||||
|       <option [ngValue]="false">No</option> |  | ||||||
|     </select> |  | ||||||
|  |  | ||||||
|   </div> |   </div> | ||||||
|   <div class="col-auto"> |  | ||||||
|     <button class="btn btn-sm btn-outline-secondary" (click)="removeRuleClicked(rule)"> |   <div class="btn-group col-auto" ngbDropdown role="group"> | ||||||
|       <svg class="toolbaricon" fill="currentColor"> |     <button class="btn btn-outline-primary btn-sm" id="dropdownTags" ngbDropdownToggle>Tags</button> | ||||||
|         <use xlink:href="assets/bootstrap-icons.svg#x"/> |     <div class="dropdown-menu quick-filter" ngbDropdownMenu aria-labelledby="dropdownTags"> | ||||||
|       </svg> |       <div class="list-group list-group-flush"> | ||||||
|     </button> |         <input class="list-group-item form-control" type="text" [(ngModel)]="filterTagsText" placeholder="Filter tags"> | ||||||
|  |         <ng-container *ngIf="(tags | filter: filterTagsText).length > 0"> | ||||||
|  |           <button class="list-group-item list-group-item-action d-flex align-items-center" role="menuitem" *ngFor="let tag of tags | filter: filterTagsText; let i = index" (click)="toggleFilterByTag(tag.id)"> | ||||||
|  |             <div class="selected-icon mr-1"> | ||||||
|  |               <svg *ngIf="currentViewIncludesTag(tag.id)" 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 [tag]="tag" [clickable]="true" linkTitle="Filter by tag"></app-tag></div> | ||||||
|  |             <div class="badge bg-primary text-light rounded-pill ml-auto">{{tag.document_count}}</div> | ||||||
|  |           </button> | ||||||
|  |         </ng-container> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |  | ||||||
|  |   <div class="btn-group col-auto" ngbDropdown role="group"> | ||||||
|  |     <button class="btn btn-outline-primary btn-sm" id="dropdownCorrespondents" ngbDropdownToggle>Correspondents</button> | ||||||
|  |     <div class="dropdown-menu quick-filter" ngbDropdownMenu aria-labelledby="dropdownCorrespondents"> | ||||||
|  |       <div class="list-group list-group-flush"> | ||||||
|  |         <input class="list-group-item form-control" type="text" [(ngModel)]="filterCorrespondentsText" placeholder="Filter correspondents"> | ||||||
|  |         <ng-container *ngIf="(correspondents | filter: filterCorrespondentsText).length > 0"> | ||||||
|  |           <button class="list-group-item list-group-item-action d-flex align-items-center" role="menuitem" *ngFor="let correspondent of correspondents | filter: filterCorrespondentsText; let i = index" (click)="toggleFilterByCorrespondent(correspondent.id)"> | ||||||
|  |             <div class="selected-icon mr-1"> | ||||||
|  |               <svg *ngIf="currentViewIncludesCorrespondent(correspondent.id)" 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">{{correspondent.name}}</div> | ||||||
|  |             <div class="badge bg-primary text-light rounded-pill ml-auto">{{correspondent.document_count}}</div> | ||||||
|  |           </button> | ||||||
|  |         </ng-container> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |  | ||||||
|  |   <div class="btn-group col-auto" ngbDropdown role="group"> | ||||||
|  |     <button class="btn btn-outline-primary btn-sm" id="dropdownDocumentTypes" ngbDropdownToggle>Document Types</button> | ||||||
|  |     <div class="dropdown-menu quick-filter" ngbDropdownMenu aria-labelledby="dropdownDocumentTypes"> | ||||||
|  |       <div class="list-group list-group-flush"> | ||||||
|  |         <input class="list-group-item form-control" type="text" [(ngModel)]="filterDocumentTypesText" placeholder="Filter document types"> | ||||||
|  |         <ng-container *ngIf="(documentTypes | filter: filterDocumentTypesText).length > 0"> | ||||||
|  |           <button class="list-group-item list-group-item-action d-flex align-items-center" role="menuitem" *ngFor="let documentType of documentTypes | filter: filterDocumentTypesText; let i = index" (click)="toggleFilterByDocumentType(documentType.id)"> | ||||||
|  |             <div class="selected-icon mr-1"> | ||||||
|  |               <svg *ngIf="currentViewIncludesDocumentType(documentType.id)" 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">{{documentType.name}}</div> | ||||||
|  |             <div class="badge bg-primary text-light rounded-pill ml-auto">{{documentType.document_count}}</div> | ||||||
|  |           </button> | ||||||
|  |         </ng-container> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |  | ||||||
|  |   <div class="btn-group col-auto" ngbDropdown role="group"> | ||||||
|  |     <button class="btn btn-outline-primary btn-sm" id="dropdownCreated" ngbDropdownToggle>Created</button> | ||||||
|  |   </div> | ||||||
|  |  | ||||||
|  |   <div class="btn-group col-auto" ngbDropdown role="group"> | ||||||
|  |     <button class="btn btn-outline-primary btn-sm" id="dropdownAdded" ngbDropdownToggle>Added</button> | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <div class="form-row form-group"> | <button class="btn-link border-0 bg-transparent ml-3 text-muted" *ngIf="hasFilters()" (click)="clearClicked()"> | ||||||
|   <div class="col"> |   <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||||
|     <select [(ngModel)]="selectedRuleType" class="form-control form-control-sm"> |     <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"/> | ||||||
|       <option *ngFor="let ruleType of getRuleTypes()" [ngValue]="ruleType">{{ruleType.name}}</option> |   </svg> | ||||||
|     </select> |   Clear | ||||||
|   </div> | </button> | ||||||
|   <div class="col-auto"> |  | ||||||
|     <button (click)="newRuleClicked()" class="btn btn-sm btn-outline-secondary">Add</button> |  | ||||||
|   </div> |  | ||||||
|   <div class="col-auto"> |  | ||||||
|     <button (click)="clearClicked()" class="btn btn-sm btn-outline-secondary">Clear</button> |  | ||||||
|   </div> |  | ||||||
|   <div class="col-auto"> |  | ||||||
|     <button (click)="applyClicked()" class="btn btn-sm btn-outline-secondary">Apply</button> |  | ||||||
|   </div> |  | ||||||
| </div> |  | ||||||
|   | |||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | .quick-filter { | ||||||
|  |   min-width: 250px; | ||||||
|  |   max-height: 400px; | ||||||
|  |   overflow-y: scroll; | ||||||
|  |  | ||||||
|  |   .selected-icon { | ||||||
|  |     min-width: 1em; | ||||||
|  |     min-height: 1em; | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; | ||||||
| import { FilterRule } from 'src/app/data/filter-rule'; | import { FilterRule } from 'src/app/data/filter-rule'; | ||||||
| import { FilterRuleType, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type'; | import { FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type'; | ||||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; | import { PaperlessTag } from 'src/app/data/paperless-tag'; | ||||||
| @@ -27,15 +27,16 @@ export class FilterEditorComponent implements OnInit { | |||||||
|   @Output() |   @Output() | ||||||
|   apply = new EventEmitter() |   apply = new EventEmitter() | ||||||
|  |  | ||||||
|   selectedRuleType: FilterRuleType = FILTER_RULE_TYPES[0] |  | ||||||
|  |  | ||||||
|   correspondents: PaperlessCorrespondent[] = [] |   correspondents: PaperlessCorrespondent[] = [] | ||||||
|   tags: PaperlessTag[] = [] |   tags: PaperlessTag[] = [] | ||||||
|   documentTypes: PaperlessDocumentType[] = [] |   documentTypes: PaperlessDocumentType[] = [] | ||||||
|  |  | ||||||
|  |   filterTagsText: string | ||||||
|  |   filterCorrespondentsText: string | ||||||
|  |   filterDocumentTypesText: string | ||||||
|  |  | ||||||
|   newRuleClicked() { |   newRuleClicked() { | ||||||
|     this.filterRules.push({type: this.selectedRuleType, value: this.selectedRuleType.default}) |     this.filterRules.push({type: this.selectedRuleType, value: this.selectedRuleType.default}) | ||||||
|     this.selectedRuleType = this.getRuleTypes().length > 0 ? this.getRuleTypes()[0] : null |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   removeRuleClicked(rule) { |   removeRuleClicked(rule) { | ||||||
| @@ -54,14 +55,70 @@ export class FilterEditorComponent implements OnInit { | |||||||
|     this.clear.next() |     this.clear.next() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   hasFilters() { | ||||||
|  |     return this.filterRules.length > 0 | ||||||
|  |   } | ||||||
|  |  | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.correspondentService.listAll().subscribe(result => {this.correspondents = result.results}) |     this.correspondentService.listAll().subscribe(result => {this.correspondents = result.results}) | ||||||
|     this.tagService.listAll().subscribe(result => this.tags = result.results) |     this.tagService.listAll().subscribe(result => this.tags = result.results) | ||||||
|     this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results) |     this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getRuleTypes() { |   findRuleIndex(type_id: number, value: any) { | ||||||
|     return FILTER_RULE_TYPES.filter(rt => rt.multi || !this.filterRules.find(r => r.type == rt)) |     return this.filterRules.findIndex(rule => rule.type.id == type_id && rule.value == value) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   toggleFilterByTag(tag_id: number) { | ||||||
|  |     let existingRuleIndex = this.findRuleIndex(FILTER_HAS_TAG, tag_id) | ||||||
|  |     if (existingRuleIndex !== -1) { | ||||||
|  |       let filterRules = this.filterRules | ||||||
|  |       filterRules.splice(existingRuleIndex, 1) | ||||||
|  |       this.filterRules = filterRules | ||||||
|  |       this.applyFilterRules() | ||||||
|  |     } else { | ||||||
|  |       this.filterByTag(tag_id) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   toggleFilterByCorrespondent(correspondent_id: number) { | ||||||
|  |     let existingRuleIndex = this.findRuleIndex(FILTER_CORRESPONDENT, correspondent_id) | ||||||
|  |     if (existingRuleIndex !== -1) { | ||||||
|  |       let filterRules = this.filterRules | ||||||
|  |       filterRules.splice(existingRuleIndex, 1) | ||||||
|  |       this.filterRules = filterRules | ||||||
|  |       this.applyFilterRules() | ||||||
|  |     } else { | ||||||
|  |       this.filterByCorrespondent(correspondent_id) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   toggleFilterByDocumentType(document_type_id: number) { | ||||||
|  |     let existingRuleIndex = this.findRuleIndex(FILTER_DOCUMENT_TYPE, document_type_id) | ||||||
|  |     if (existingRuleIndex !== -1) { | ||||||
|  |       let filterRules = this.filterRules | ||||||
|  |       filterRules.splice(existingRuleIndex, 1) | ||||||
|  |       this.filterRules = filterRules | ||||||
|  |       this.applyFilterRules() | ||||||
|  |     } else { | ||||||
|  |       this.filterByDocumentType(document_type_id) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   currentViewIncludesTag(tag_id: number) { | ||||||
|  |     return this.findRuleIndex(FILTER_HAS_TAG, tag_id) !== -1 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   currentViewIncludesCorrespondent(correspondent_id: number) { | ||||||
|  |     return this.findRuleIndex(FILTER_CORRESPONDENT, correspondent_id) !== -1 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   currentViewIncludesDocumentType(document_type_id: number) { | ||||||
|  |     return this.findRuleIndex(FILTER_DOCUMENT_TYPE, document_type_id) !== -1 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   currentViewIncludesQuickFilter() { | ||||||
|  |     return this.filterRules.find(rule => rule.type.id == FILTER_HAS_TAG || rule.type.id == FILTER_CORRESPONDENT || rule.type.id == FILTER_DOCUMENT_TYPE) !== undefined | ||||||
|   } |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon