diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index af2c46492..6a847494a 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -28,6 +28,7 @@ import { PageHeaderComponent } from './components/common/page-header/page-header import { AppFrameComponent } from './components/app-frame/app-frame.component'; import { ToastsComponent } from './components/common/toasts/toasts.component'; import { FilterEditorComponent } from './components/filter-editor/filter-editor.component'; +import { FilterDropdownComponent } from './components/filter-editor/filter-dropdown/filter-dropdown.component'; import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component'; import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component'; import { NgxFileDropModule } from 'ngx-file-drop'; @@ -74,6 +75,7 @@ import { FilterPipe } from './pipes/filter.pipe'; AppFrameComponent, ToastsComponent, FilterEditorComponent, + FilterDropdownComponent, DocumentCardLargeComponent, DocumentCardSmallComponent, TextComponent, diff --git a/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.html b/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.html new file mode 100644 index 000000000..b135caff0 --- /dev/null +++ b/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.html @@ -0,0 +1,19 @@ +
+ + +
diff --git a/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.scss b/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.scss new file mode 100644 index 000000000..05df7b213 --- /dev/null +++ b/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.scss @@ -0,0 +1,10 @@ +.quick-filter { + min-width: 250px; + max-height: 400px; + overflow-y: scroll; + + .selected-icon { + min-width: 1em; + min-height: 1em; + } +} diff --git a/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.spec.ts b/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.spec.ts new file mode 100644 index 000000000..29edd7c45 --- /dev/null +++ b/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FilterDropodownComponent } from './filter-dropdown.component'; + +describe('FilterDropodownComponent', () => { + let component: FilterDropodownComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FilterDropodownComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(FilterDropodownComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.ts b/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.ts new file mode 100644 index 000000000..443fd30e4 --- /dev/null +++ b/src-ui/src/app/components/filter-editor/filter-dropdown/filter-dropdown.component.ts @@ -0,0 +1,34 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FilterRuleType, FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_TITLE, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type'; +import { ObjectWithId } from 'src/app/data/object-with-id'; +import { MatchingModel } from 'src/app/data/matching-model'; + +@Component({ + selector: 'app-filter-dropdown', + templateUrl: './filter-dropdown.component.html', + styleUrls: ['./filter-dropdown.component.scss'] +}) +export class FilterDropdownComponent implements OnInit { + + constructor() { } + + @Input() + filterRuleTypeID: number + + @Output() + toggle = new EventEmitter() + + items: MatchingModel[] = [] + itemsActive: MatchingModel[] = [] + title: string + filterText: string + + ngOnInit(): void { + let filterRuleType: FilterRuleType = FILTER_RULE_TYPES.find(t => t.id == this.filterRuleTypeID) + this.title = filterRuleType.name + } + + toggleItem(item: ObjectWithId) { + this.toggle.emit(item, this.filterRuleTypeID) + } +} diff --git a/src-ui/src/app/components/filter-editor/filter-editor.component.html b/src-ui/src/app/components/filter-editor/filter-editor.component.html index 5e2077116..153f32644 100644 --- a/src-ui/src/app/components/filter-editor/filter-editor.component.html +++ b/src-ui/src/app/components/filter-editor/filter-editor.component.html @@ -6,65 +6,10 @@ -
- - -
- -
- - -
- -
- - -
+ +
diff --git a/src-ui/src/app/components/filter-editor/filter-editor.component.ts b/src-ui/src/app/components/filter-editor/filter-editor.component.ts index ac89d9b88..b79fbaf12 100644 --- a/src-ui/src/app/components/filter-editor/filter-editor.component.ts +++ b/src-ui/src/app/components/filter-editor/filter-editor.component.ts @@ -1,12 +1,15 @@ -import { Component, EventEmitter, Input, OnInit, Output, ElementRef, AfterViewInit, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, ElementRef, AfterViewInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; import { FilterRule } from 'src/app/data/filter-rule'; -import { FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_TITLE, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type'; +import { FilterRuleType, FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_TITLE, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type'; import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; import { PaperlessTag } from 'src/app/data/paperless-tag'; +import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'; +import { ObjectWithId } from 'src/app/data/object-with-id'; import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; import { TagService } from 'src/app/services/rest/tag.service'; +import { FilterDropdownComponent } from './filter-dropdown/filter-dropdown.component' import { fromEvent } from 'rxjs'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; @@ -29,6 +32,9 @@ export class FilterEditorComponent implements OnInit, AfterViewInit { apply = new EventEmitter() @ViewChild('filterTextInput') input: ElementRef; + @ViewChildren(FilterDropdownComponent) quickFilterDropdowns!: QueryList; + + quickFilterRuleTypeIDs: number[] = [FILTER_HAS_TAG, FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE] correspondents: PaperlessCorrespondent[] = [] tags: PaperlessTag[] = [] @@ -39,25 +45,11 @@ export class FilterEditorComponent implements OnInit, AfterViewInit { filterCorrespondentsText: string filterDocumentTypesText: string - applySelected() { - this.apply.next() - } - - clearSelected() { - this.filterRules.splice(0,this.filterRules.length) - this.updateTextFilterInput() - this.clear.next() - } - - hasFilters() { - return this.filterRules.length > 0 - } - ngOnInit(): void { - this.correspondentService.listAll().subscribe(result => {this.correspondents = result.results}) - this.tagService.listAll().subscribe(result => this.tags = result.results) - this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results) this.updateTextFilterInput() + this.tagService.listAll().subscribe(result => this.setDropdownItems(result.results, FILTER_HAS_TAG)) + this.correspondentService.listAll().subscribe(result => this.setDropdownItems(result.results, FILTER_CORRESPONDENT)) + this.documentTypeService.listAll().subscribe(result => this.setDropdownItems(result.results, FILTER_DOCUMENT_TYPE)) } ngAfterViewInit() { @@ -73,8 +65,29 @@ export class FilterEditorComponent implements OnInit, AfterViewInit { }); } - findRuleIndex(type_id: number, value: any) { - return this.filterRules.findIndex(rule => rule.type.id == type_id && rule.value == value) + setDropdownItems(items: ObjectWithId[], filterRuleTypeID: number) { + let dropdown: FilterDropdownComponent = this.getDropdownByFilterRuleTypeID(filterRuleTypeID) + if (dropdown) { + dropdown.items = items + } + } + + getDropdownByFilterRuleTypeID(filterRuleTypeID: number): FilterDropdownComponent { + return this.quickFilterDropdowns.find(d => d.filterRuleTypeID == filterRuleTypeID) + } + + applySelected() { + this.apply.next() + } + + clearSelected() { + this.filterRules.splice(0,this.filterRules.length) + this.updateTextFilterInput() + this.clear.next() + } + + hasFilters() { + return this.filterRules.length > 0 } updateTextFilterInput() { @@ -98,60 +111,22 @@ export class FilterEditorComponent implements OnInit, AfterViewInit { this.applySelected() } - toggleFilterByTag(tag_id: number) { - let existingRuleIndex = this.findRuleIndex(FILTER_HAS_TAG, tag_id) + toggleFilterByItem(item: ObjectWithId, filterRuleTypeID: number) { let filterRules = this.filterRules - if (existingRuleIndex !== -1) { - filterRules.splice(existingRuleIndex, 1) - } else { - filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_HAS_TAG), value: tag_id}) - } - this.filterRules = filterRules - this.applySelected() - } + let filterRuleType: FilterRuleType = FILTER_RULE_TYPES.find(t => t.id == filterRuleTypeID) + let existingRule = filterRules.find(rule => rule.type.id == filterRuleType.id) - toggleFilterByCorrespondent(correspondent_id: number) { - let filterRules = this.filterRules - let existingRule = filterRules.find(rule => rule.type.id == FILTER_CORRESPONDENT) - if (existingRule && existingRule.value == correspondent_id) { + if (existingRule && existingRule.value == item.id && filterRuleType.id == FILTER_HAS_TAG) { + filterRules.splice(filterRules.indexOf(existingRule), 1) + } else if (existingRule && existingRule.value == item.id) { return } else if (existingRule) { - existingRule.value = correspondent_id + existingRule.value = item.id } else { - filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_CORRESPONDENT), value: correspondent_id}) + filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == filterRuleType.id), value: item.id}) } this.filterRules = filterRules this.applySelected() } - toggleFilterByDocumentType(document_type_id: number) { - let filterRules = this.filterRules - let existingRule = filterRules.find(rule => rule.type.id == FILTER_DOCUMENT_TYPE) - if (existingRule && existingRule.value == document_type_id) { - return - } else if (existingRule) { - existingRule.value = document_type_id - } else { - filterRules.push({type: FILTER_RULE_TYPES.find(t => t.id == FILTER_DOCUMENT_TYPE), value: document_type_id}) - } - this.filterRules = filterRules - this.applySelected() - } - - 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 - } - } diff --git a/src-ui/src/app/data/filter-rule-type.ts b/src-ui/src/app/data/filter-rule-type.ts index a35759f69..1a174ce57 100644 --- a/src-ui/src/app/data/filter-rule-type.ts +++ b/src-ui/src/app/data/filter-rule-type.ts @@ -22,15 +22,15 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ {id: FILTER_TITLE, name: "Title contains", filtervar: "title__icontains", datatype: "string", multi: false, default: ""}, {id: FILTER_CONTENT, name: "Content contains", filtervar: "content__icontains", datatype: "string", multi: false, default: ""}, - - {id: FILTER_ASN, name: "ASN is", filtervar: "archive_serial_number", datatype: "number", multi: false}, - - {id: FILTER_CORRESPONDENT, name: "Correspondent is", filtervar: "correspondent__id", datatype: "correspondent", multi: false}, - {id: FILTER_DOCUMENT_TYPE, name: "Document type is", filtervar: "document_type__id", datatype: "document_type", multi: false}, - {id: FILTER_IS_IN_INBOX, name: "Is in Inbox", filtervar: "is_in_inbox", datatype: "boolean", multi: false, default: true}, - {id: FILTER_HAS_TAG, name: "Has tag", filtervar: "tags__id__all", datatype: "tag", multi: true}, - {id: FILTER_DOES_NOT_HAVE_TAG, name: "Does not have tag", filtervar: "tags__id__none", datatype: "tag", multi: true}, + {id: FILTER_ASN, name: "ASN is", filtervar: "archive_serial_number", datatype: "number", multi: false}, + + {id: FILTER_CORRESPONDENT, name: "Correspondents", filtervar: "correspondent__id", datatype: "correspondent", multi: false}, + {id: FILTER_DOCUMENT_TYPE, name: "Document types", filtervar: "document_type__id", datatype: "document_type", multi: false}, + + {id: FILTER_IS_IN_INBOX, name: "Is in Inbox", filtervar: "is_in_inbox", datatype: "boolean", multi: false, default: true}, + {id: FILTER_HAS_TAG, name: "Tags", filtervar: "tags__id__all", datatype: "tag", multi: true}, + {id: FILTER_DOES_NOT_HAVE_TAG, name: "Does not have tag", filtervar: "tags__id__none", datatype: "tag", multi: true}, {id: FILTER_HAS_ANY_TAG, name: "Has any tag", filtervar: "is_tagged", datatype: "boolean", multi: false, default: true}, {id: FILTER_CREATED_BEFORE, name: "Created before", filtervar: "created__date__lt", datatype: "date", multi: false}, @@ -42,7 +42,7 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ {id: FILTER_ADDED_BEFORE, name: "Added before", filtervar: "added__date__lt", datatype: "date", multi: false}, {id: FILTER_ADDED_AFTER, name: "Added after", filtervar: "added__date__gt", datatype: "date", multi: false}, - + {id: FILTER_MODIFIED_BEFORE, name: "Modified before", filtervar: "modified__date__lt", datatype: "date", multi: false}, {id: FILTER_MODIFIED_AFTER, name: "Modified after", filtervar: "modified__date__gt", datatype: "date", multi: false}, ] @@ -54,4 +54,4 @@ export interface FilterRuleType { datatype: string //number, string, boolean, date multi: boolean default?: any -} \ No newline at end of file +}