mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Basic tags, correspondents & document type dropdowns
This commit is contained in:
		| @@ -48,6 +48,7 @@ import { WidgetFrameComponent } from './components/dashboard/widgets/widget-fram | ||||
| import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component'; | ||||
| import { YesNoPipe } from './pipes/yes-no.pipe'; | ||||
| import { FileSizePipe } from './pipes/file-size.pipe'; | ||||
| import { FilterPipe } from './pipes/filter.pipe'; | ||||
|  | ||||
| @NgModule({ | ||||
|   declarations: [ | ||||
| @@ -88,7 +89,8 @@ import { FileSizePipe } from './pipes/file-size.pipe'; | ||||
|     WidgetFrameComponent, | ||||
|     WelcomeWidgetComponent, | ||||
|     YesNoPipe, | ||||
|     FileSizePipe | ||||
|     FileSizePipe, | ||||
|     FilterPipe | ||||
|   ], | ||||
|   imports: [ | ||||
|     BrowserModule, | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <app-page-header [title]="getTitle()"> | ||||
|  | ||||
|   <div class="btn-group btn-group-toggle" ngbRadioGroup [(ngModel)]="displayMode" | ||||
|     (ngModelChange)="saveDisplayMode()"> | ||||
|     <label ngbButtonLabel class="btn-outline-primary btn-sm"> | ||||
| @@ -21,6 +20,7 @@ | ||||
|       </svg> | ||||
|     </label> | ||||
|   </div> | ||||
|  | ||||
|   <div class="btn-group btn-group-toggle ml-2" ngbRadioGroup [(ngModel)]="list.sortDirection"> | ||||
|     <div ngbDropdown class="btn-group"> | ||||
|       <button class="btn btn-outline-primary btn-sm" id="dropdownBasic1" ngbDropdownToggle>Sort by</button> | ||||
| @@ -42,13 +42,14 @@ | ||||
|       </svg> | ||||
|     </label> | ||||
|   </div> | ||||
|  | ||||
|   <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> | ||||
|       Filter | ||||
|       Advanced Filters | ||||
|     </button> | ||||
|  | ||||
|     <div class="btn-group" ngbDropdown role="group"> | ||||
| @@ -58,18 +59,69 @@ | ||||
|           <button ngbDropdownItem *ngFor="let config of savedViewConfigService.getConfigs()" (click)="loadViewConfig(config)">{{config.title}}</button> | ||||
|           <div class="dropdown-divider" *ngIf="savedViewConfigService.getConfigs().length > 0"></div> | ||||
|         </ng-container> | ||||
|          | ||||
|  | ||||
|         <button ngbDropdownItem (click)="saveViewConfig()" *ngIf="list.savedViewId">Save "{{list.savedViewTitle}}"</button> | ||||
|         <button ngbDropdownItem (click)="saveViewConfigAs()">Save as...</button> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|   </div> | ||||
|  | ||||
| </app-page-header> | ||||
|  | ||||
| <div class="row pb-1 mb-3 align-items-right" > | ||||
|   <div class="btn-toolbar col-auto"> | ||||
|     <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" ngbDropdownMenu aria-labelledby="dropdownTags"> | ||||
|         <div class="list-group list-group-flush"> | ||||
|           <input class="list-group-item form-control" type="text" [(ngModel)]="searchText" placeholder="Filter tags"> | ||||
|           <ng-container *ngIf="(tags | filter: searchText).length > 0"> | ||||
|             <button class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" role="menuitem" *ngFor="let tag of tags | filter: searchText; let i = index" (click)="filterByTag(tag.id, true)"> | ||||
|               {{tag.name}} | ||||
|               <span class="badge bg-primary text-light rounded-pill">{{tag.document_count}}</span> | ||||
|             </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" ngbDropdownMenu aria-labelledby="dropdownCorrespondents"> | ||||
|         <div class="list-group list-group-flush"> | ||||
|           <input class="list-group-item form-control" type="text" [(ngModel)]="searchText" placeholder="Filter correspondents"> | ||||
|           <ng-container *ngIf="(correspondents | filter: searchText).length > 0"> | ||||
|             <button class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" role="menuitem" *ngFor="let correspondent of correspondents | filter: searchText; let i = index" (click)="filterByCorrespondent(correspondent.id, true)"> | ||||
|               {{correspondent.name}} | ||||
|               <span class="badge bg-primary text-light rounded-pill">{{correspondent.document_count}}</span> | ||||
|             </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" ngbDropdownMenu aria-labelledby="dropdownDocumentTypes"> | ||||
|         <div class="list-group list-group-flush"> | ||||
|           <input class="list-group-item form-control" type="text" [(ngModel)]="searchText" placeholder="Filter tags"> | ||||
|           <ng-container *ngIf="(documentTypes | filter: searchText).length > 0"> | ||||
|             <button class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" role="menuitem" *ngFor="let documentType of documentTypes | filter: searchText; let i = index" (click)="filterByDocumentType(documentType.id, true)"> | ||||
|               {{documentType.name}} | ||||
|               <span class="badge bg-primary text-light rounded-pill">{{documentType.document_count}}</span> | ||||
|             </button> | ||||
|           </ng-container> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| <div class="card w-100 mb-3" [hidden]="!showFilter"> | ||||
|   <div class="card-body"> | ||||
|     <h5 class="card-title">Filter</h5> | ||||
|     <h5 class="card-title">Advanced Filters</h5> | ||||
|     <app-filter-editor [(filterRules)]="filterRules" (apply)="applyFilterRules()" (clear)="clearFilterRules()"></app-filter-editor> | ||||
|   </div> | ||||
| </div> | ||||
| @@ -125,5 +177,12 @@ | ||||
|  | ||||
|  | ||||
| <div class=" m-n2 row" *ngIf="displayMode == 'smallCards'"> | ||||
|   <app-document-card-small [document]="d" *ngFor="let d of list.documents" (clickTag)="filterByTag($event)" (clickCorrespondent)="filterByCorrespondent($event)"></app-document-card-small>     | ||||
|   <app-document-card-small [document]="d" *ngFor="let d of list.documents" (clickTag)="filterByTag($event)" (clickCorrespondent)="filterByCorrespondent($event)"></app-document-card-small> | ||||
| </div> | ||||
|  | ||||
| <script type="text/ng-template" id="customTemplate.html"> | ||||
|   <a> | ||||
|       <img ng-src="http://upload.wikimedia.org/wikipedia/commons/thumb/{{match.model.flag}}" width="16"> | ||||
|       <span ng-bind-html="match.label | uibTypeaheadHighlight:query"></span> | ||||
|   </a> | ||||
| </script> | ||||
|   | ||||
| @@ -11,6 +11,12 @@ import { SavedViewConfigService } from 'src/app/services/saved-view-config.servi | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service'; | ||||
| import { environment } from 'src/environments/environment'; | ||||
| import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag'; | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; | ||||
| 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'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-list', | ||||
| @@ -25,13 +31,20 @@ export class DocumentListComponent implements OnInit { | ||||
|     public route: ActivatedRoute, | ||||
|     private toastService: ToastService, | ||||
|     public modalService: NgbModal, | ||||
|     private titleService: Title) { } | ||||
|     private titleService: Title, | ||||
|     private tagService: TagService, | ||||
|     private correspondentService: CorrespondentService, | ||||
|     private documentTypeService: DocumentTypeService) { } | ||||
|  | ||||
|   displayMode = 'smallCards' // largeCards, smallCards, details | ||||
|  | ||||
|   filterRules: FilterRule[] = [] | ||||
|   showFilter = false | ||||
|  | ||||
|   tags: PaperlessTag[] = [] | ||||
|   correspondents: PaperlessCorrespondent[] = [] | ||||
|   documentTypes: PaperlessDocumentType[] = [] | ||||
|  | ||||
|   get isFiltered() { | ||||
|     return this.list.filterRules?.length > 0 | ||||
|   } | ||||
| @@ -67,6 +80,9 @@ export class DocumentListComponent implements OnInit { | ||||
|       this.list.clear() | ||||
|       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() { | ||||
| @@ -103,8 +119,8 @@ export class DocumentListComponent implements OnInit { | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   filterByTag(tag_id: number) { | ||||
|     let filterRules = this.list.filterRules | ||||
|   filterByTag(tag_id: number, singleton: boolean = false) { | ||||
|     let filterRules = singleton ? [] : this.list.filterRules | ||||
|     if (filterRules.find(rule => rule.type.id == FILTER_HAS_TAG && rule.value == tag_id)) { | ||||
|       return | ||||
|     } | ||||
| @@ -114,8 +130,8 @@ export class DocumentListComponent implements OnInit { | ||||
|     this.applyFilterRules() | ||||
|   } | ||||
|  | ||||
|   filterByCorrespondent(correspondent_id: number) { | ||||
|     let filterRules = this.list.filterRules | ||||
|   filterByCorrespondent(correspondent_id: number, singleton: boolean = false) { | ||||
|     let filterRules = singleton ? [] : this.list.filterRules | ||||
|     let existing_rule = filterRules.find(rule => rule.type.id == FILTER_CORRESPONDENT) | ||||
|     if (existing_rule && existing_rule.value == correspondent_id) { | ||||
|       return | ||||
| @@ -128,8 +144,8 @@ export class DocumentListComponent implements OnInit { | ||||
|     this.applyFilterRules() | ||||
|   } | ||||
|  | ||||
|   filterByDocumentType(document_type_id: number) { | ||||
|     let filterRules = this.list.filterRules | ||||
|   filterByDocumentType(document_type_id: number, singleton: boolean = false) { | ||||
|     let filterRules = singleton ? [] : this.list.filterRules | ||||
|     let existing_rule = filterRules.find(rule => rule.type.id == FILTER_DOCUMENT_TYPE) | ||||
|     if (existing_rule && existing_rule.value == document_type_id) { | ||||
|       return | ||||
|   | ||||
							
								
								
									
										17
									
								
								src-ui/src/app/pipes/filter.pipe.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src-ui/src/app/pipes/filter.pipe.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| import { Pipe, PipeTransform } from '@angular/core'; | ||||
|  | ||||
| @Pipe({ | ||||
|   name: 'filter' | ||||
| }) | ||||
| export class FilterPipe implements PipeTransform { | ||||
|   transform(items: any[], searchText: string): any[] { | ||||
|     if (!items) return []; | ||||
|     if (!searchText) return items; | ||||
|  | ||||
|     return items.filter(item => { | ||||
|       return Object.keys(item).some(key => { | ||||
|         return String(item[key]).toLowerCase().includes(searchText.toLowerCase()); | ||||
|       }); | ||||
|     }); | ||||
|    } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon