mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge pull request #2704 from paperless-ngx/feature-dynamic-document-counts
This commit is contained in:
		| @@ -25,7 +25,7 @@ | ||||
|       </div> | ||||
|       <div *ngIf="selectionModel.items" class="items"> | ||||
|         <ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText"> | ||||
|           <app-toggleable-dropdown-button *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" [disabled]="disabled"></app-toggleable-dropdown-button> | ||||
|           <app-toggleable-dropdown-button *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" [count]="getUpdatedDocumentCount(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" [disabled]="disabled"></app-toggleable-dropdown-button> | ||||
|         </ng-container> | ||||
|       </div> | ||||
|       <button *ngIf="editing" class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!modelIsDirty || disabled"> | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component' | ||||
| import { MatchingModel } from 'src/app/data/matching-model' | ||||
| import { Subject } from 'rxjs' | ||||
| import { SelectionDataItem } from 'src/app/services/rest/document.service' | ||||
|  | ||||
| export interface ChangedItems { | ||||
|   itemsToAdd: MatchingModel[] | ||||
| @@ -333,6 +334,15 @@ export class FilterableDropdownComponent { | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
|   documentCounts: SelectionDataItem[] | ||||
|  | ||||
|   getUpdatedDocumentCount(id: number) { | ||||
|     if (this.documentCounts) { | ||||
|       return this.documentCounts.find((c) => c.id === id)?.document_count | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   modelIsDirty: boolean = false | ||||
|  | ||||
|   constructor(private filterPipe: FilterPipe) { | ||||
|   | ||||
| @@ -20,5 +20,5 @@ | ||||
|     <app-tag *ngIf="isTag; else displayName" [tag]="item" [clickable]="false"></app-tag> | ||||
|     <ng-template #displayName><small>{{item.name}}</small></ng-template> | ||||
|   </div> | ||||
|   <div class="badge badge-light rounded-pill ms-auto me-1">{{item.document_count}}</div> | ||||
|   <div class="badge badge-light rounded-pill ms-auto me-1">{{count ?? item.document_count}}</div> | ||||
| </button> | ||||
|   | ||||
| @@ -34,6 +34,7 @@ | ||||
|         [applyOnClose]="applyOnClose" | ||||
|         (opened)="openTagsDropdown()" | ||||
|         [(selectionModel)]="tagSelectionModel" | ||||
|         [documentCounts]="tagDocumentCounts" | ||||
|         (apply)="setTags($event)"> | ||||
|       </app-filterable-dropdown> | ||||
|       <app-filterable-dropdown class="me-2 me-md-3" title="Correspondent" icon="person-fill" i18n-title | ||||
| @@ -44,6 +45,7 @@ | ||||
|         [applyOnClose]="applyOnClose" | ||||
|         (opened)="openCorrespondentDropdown()" | ||||
|         [(selectionModel)]="correspondentSelectionModel" | ||||
|         [documentCounts]="correspondentDocumentCounts" | ||||
|         (apply)="setCorrespondents($event)"> | ||||
|       </app-filterable-dropdown> | ||||
|       <app-filterable-dropdown class="me-2 me-md-3" title="Document type" icon="file-earmark-fill" i18n-title | ||||
| @@ -54,6 +56,7 @@ | ||||
|         [applyOnClose]="applyOnClose" | ||||
|         (opened)="openDocumentTypeDropdown()" | ||||
|         [(selectionModel)]="documentTypeSelectionModel" | ||||
|         [documentCounts]="documentTypeDocumentCounts" | ||||
|         (apply)="setDocumentTypes($event)"> | ||||
|       </app-filterable-dropdown> | ||||
|       <app-filterable-dropdown class="me-2 me-md-3" title="Storage path" icon="folder-fill" i18n-title | ||||
| @@ -64,6 +67,7 @@ | ||||
|         [applyOnClose]="applyOnClose" | ||||
|         (opened)="openStoragePathDropdown()" | ||||
|         [(selectionModel)]="storagePathsSelectionModel" | ||||
|         [documentCounts]="storagePathDocumentCounts" | ||||
|         (apply)="setStoragePaths($event)"> | ||||
|       </app-filterable-dropdown> | ||||
|     </div> | ||||
|   | ||||
| @@ -53,6 +53,10 @@ export class BulkEditorComponent | ||||
|   correspondentSelectionModel = new FilterableDropdownSelectionModel() | ||||
|   documentTypeSelectionModel = new FilterableDropdownSelectionModel() | ||||
|   storagePathsSelectionModel = new FilterableDropdownSelectionModel() | ||||
|   tagDocumentCounts: SelectionDataItem[] | ||||
|   correspondentDocumentCounts: SelectionDataItem[] | ||||
|   documentTypeDocumentCounts: SelectionDataItem[] | ||||
|   storagePathDocumentCounts: SelectionDataItem[] | ||||
|   awaitingDownload: boolean | ||||
|  | ||||
|   unsubscribeNotifier: Subject<any> = new Subject() | ||||
| @@ -206,6 +210,7 @@ export class BulkEditorComponent | ||||
|       .getSelectionData(Array.from(this.list.selected)) | ||||
|       .pipe(first()) | ||||
|       .subscribe((s) => { | ||||
|         this.tagDocumentCounts = s.selected_tags | ||||
|         this.applySelectionData(s.selected_tags, this.tagSelectionModel) | ||||
|       }) | ||||
|   } | ||||
| @@ -215,6 +220,7 @@ export class BulkEditorComponent | ||||
|       .getSelectionData(Array.from(this.list.selected)) | ||||
|       .pipe(first()) | ||||
|       .subscribe((s) => { | ||||
|         this.documentTypeDocumentCounts = s.selected_document_types | ||||
|         this.applySelectionData( | ||||
|           s.selected_document_types, | ||||
|           this.documentTypeSelectionModel | ||||
| @@ -227,6 +233,7 @@ export class BulkEditorComponent | ||||
|       .getSelectionData(Array.from(this.list.selected)) | ||||
|       .pipe(first()) | ||||
|       .subscribe((s) => { | ||||
|         this.correspondentDocumentCounts = s.selected_correspondents | ||||
|         this.applySelectionData( | ||||
|           s.selected_correspondents, | ||||
|           this.correspondentSelectionModel | ||||
| @@ -239,6 +246,7 @@ export class BulkEditorComponent | ||||
|       .getSelectionData(Array.from(this.list.selected)) | ||||
|       .pipe(first()) | ||||
|       .subscribe((s) => { | ||||
|         this.storagePathDocumentCounts = s.selected_storage_paths | ||||
|         this.applySelectionData( | ||||
|           s.selected_storage_paths, | ||||
|           this.storagePathsSelectionModel | ||||
|   | ||||
| @@ -82,7 +82,7 @@ | ||||
| </app-page-header> | ||||
|  | ||||
| <div class="row sticky-top pt-3 pt-sm-4 pb-2 pb-lg-4 bg-body"> | ||||
|   <app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" #filterEditor></app-filter-editor> | ||||
|   <app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" [selectionData]="list.selectionData" #filterEditor></app-filter-editor> | ||||
|   <app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor> | ||||
| </div> | ||||
|  | ||||
|   | ||||
| @@ -27,10 +27,11 @@ | ||||
|           <app-filterable-dropdown class="flex-fill" title="Tags" icon="tag-fill" i18n-title | ||||
|             filterPlaceholder="Filter tags" i18n-filterPlaceholder | ||||
|             [items]="tags" | ||||
|             [multiple]="true" | ||||
|             [(selectionModel)]="tagSelectionModel" | ||||
|             (selectionModelChange)="updateRules()" | ||||
|             [multiple]="true" | ||||
|             (opened)="onTagsDropdownOpen()" | ||||
|             [documentCounts]="tagDocumentCounts" | ||||
|             [allowSelectNone]="true"></app-filterable-dropdown> | ||||
|           <app-filterable-dropdown class="flex-fill" title="Correspondent" icon="person-fill" i18n-title | ||||
|             filterPlaceholder="Filter correspondents" i18n-filterPlaceholder | ||||
| @@ -38,20 +39,23 @@ | ||||
|             [(selectionModel)]="correspondentSelectionModel" | ||||
|             (selectionModelChange)="updateRules()" | ||||
|             (opened)="onCorrespondentDropdownOpen()" | ||||
|             [documentCounts]="correspondentDocumentCounts" | ||||
|             [allowSelectNone]="true"></app-filterable-dropdown> | ||||
|           <app-filterable-dropdown class="flex-fill" title="Document type" icon="file-earmark-fill" i18n-title | ||||
|             filterPlaceholder="Filter document types" i18n-filterPlaceholder | ||||
|             [items]="documentTypes" | ||||
|             [(selectionModel)]="documentTypeSelectionModel" | ||||
|             (opened)="onDocumentTypeDropdownOpen()" | ||||
|             (selectionModelChange)="updateRules()" | ||||
|             (opened)="onDocumentTypeDropdownOpen()" | ||||
|             [documentCounts]="documentTypeDocumentCounts" | ||||
|             [allowSelectNone]="true"></app-filterable-dropdown> | ||||
|           <app-filterable-dropdown class="me-2 flex-fill" title="Storage path" icon="folder-fill" i18n-title | ||||
|             filterPlaceholder="Filter storage paths" i18n-filterPlaceholder | ||||
|             [items]="storagePaths" | ||||
|             [(selectionModel)]="storagePathSelectionModel" | ||||
|             (opened)="onStoragePathDropdownOpen()" | ||||
|             (selectionModelChange)="updateRules()" | ||||
|             (opened)="onStoragePathDropdownOpen()" | ||||
|             [documentCounts]="storagePathDocumentCounts" | ||||
|             [allowSelectNone]="true"></app-filterable-dropdown> | ||||
|         </div> | ||||
|         <div class="d-flex flex-wrap"> | ||||
|   | ||||
| @@ -40,7 +40,11 @@ import { | ||||
| } from 'src/app/data/filter-rule-type' | ||||
| import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component' | ||||
| import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| import { | ||||
|   DocumentService, | ||||
|   SelectionData, | ||||
|   SelectionDataItem, | ||||
| } from 'src/app/services/rest/document.service' | ||||
| import { PaperlessDocument } from 'src/app/data/paperless-document' | ||||
| import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' | ||||
| import { StoragePathService } from 'src/app/services/rest/storage-path.service' | ||||
| @@ -144,6 +148,11 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|   documentTypes: PaperlessDocumentType[] = [] | ||||
|   storagePaths: PaperlessStoragePath[] = [] | ||||
|  | ||||
|   tagDocumentCounts: SelectionDataItem[] | ||||
|   correspondentDocumentCounts: SelectionDataItem[] | ||||
|   documentTypeDocumentCounts: SelectionDataItem[] | ||||
|   storagePathDocumentCounts: SelectionDataItem[] | ||||
|  | ||||
|   _textFilter = '' | ||||
|   _moreLikeId: number | ||||
|   _moreLikeDoc: PaperlessDocument | ||||
| @@ -617,6 +626,17 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|   @Output() | ||||
|   filterRulesChange = new EventEmitter<FilterRule[]>() | ||||
|  | ||||
|   @Input() | ||||
|   set selectionData(selectionData: SelectionData) { | ||||
|     this.tagDocumentCounts = selectionData?.selected_tags ?? null | ||||
|     this.documentTypeDocumentCounts = | ||||
|       selectionData?.selected_document_types ?? null | ||||
|     this.correspondentDocumentCounts = | ||||
|       selectionData?.selected_correspondents ?? null | ||||
|     this.storagePathDocumentCounts = | ||||
|       selectionData?.selected_storage_paths ?? null | ||||
|   } | ||||
|  | ||||
|   rulesModified: boolean = false | ||||
|  | ||||
|   updateRules() { | ||||
| @@ -692,6 +712,8 @@ export class FilterEditorComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   onCorrespondentDropdownOpen() { | ||||
|     console.log(this.correspondentSelectionModel) | ||||
|  | ||||
|     this.correspondentSelectionModel.apply() | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,11 @@ import { PaperlessSavedView } from '../data/paperless-saved-view' | ||||
| import { SETTINGS_KEYS } from '../data/paperless-uisettings' | ||||
| import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys' | ||||
| import { paramsFromViewState, paramsToViewState } from '../utils/query-params' | ||||
| import { DocumentService, DOCUMENT_SORT_FIELDS } from './rest/document.service' | ||||
| import { | ||||
|   DocumentService, | ||||
|   DOCUMENT_SORT_FIELDS, | ||||
|   SelectionData, | ||||
| } from './rest/document.service' | ||||
| import { SettingsService } from './settings.service' | ||||
|  | ||||
| /** | ||||
| @@ -74,6 +78,8 @@ export class DocumentListViewService { | ||||
|   rangeSelectionAnchorIndex: number | ||||
|   lastRangeSelectionToIndex: number | ||||
|  | ||||
|   selectionData?: SelectionData | ||||
|  | ||||
|   currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE) | ||||
|  | ||||
|   private listViewStates: Map<number, ListViewState> = new Map() | ||||
| @@ -222,6 +228,18 @@ export class DocumentListViewService { | ||||
|           this.isReloading = false | ||||
|           activeListViewState.collectionSize = result.count | ||||
|           activeListViewState.documents = result.results | ||||
|  | ||||
|           this.documentService | ||||
|             .getSelectionData(result.results.map((d) => d.id)) | ||||
|             .subscribe({ | ||||
|               next: (selectionData) => { | ||||
|                 this.selectionData = selectionData | ||||
|               }, | ||||
|               error: () => { | ||||
|                 this.selectionData = null | ||||
|               }, | ||||
|             }) | ||||
|  | ||||
|           if (updateQueryParams && !this._activeSavedViewId) { | ||||
|             let base = ['/documents'] | ||||
|             this.router.navigate(base, { | ||||
| @@ -247,6 +265,7 @@ export class DocumentListViewService { | ||||
|             activeListViewState.currentPage = 1 | ||||
|             this.reload() | ||||
|           } else { | ||||
|             this.selectionData = null | ||||
|             let errorMessage | ||||
|             if ( | ||||
|               typeof error.error !== 'string' && | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon