diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index e10bdbd0c..584b7bc7a 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -40,6 +40,7 @@ import { SaveViewConfigDialogComponent } from './components/document-list/save-v import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { DateTimeComponent } from './components/common/input/date-time/date-time.component'; import { TagsComponent } from './components/common/input/tags/tags.component'; +import { SortableDirective } from './directives/sortable.directive'; @NgModule({ declarations: [ @@ -73,7 +74,8 @@ import { TagsComponent } from './components/common/input/tags/tags.component'; CheckComponent, SaveViewConfigDialogComponent, DateTimeComponent, - TagsComponent + TagsComponent, + SortableDirective ], imports: [ BrowserModule, diff --git a/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html b/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html index fc01471d7..a790a18b3 100644 --- a/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html +++ b/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html @@ -9,10 +9,10 @@ - - - - + + + + diff --git a/src-ui/src/app/components/manage/document-type-list/document-type-list.component.html b/src-ui/src/app/components/manage/document-type-list/document-type-list.component.html index 94e7aa3b7..a07f6c7e4 100644 --- a/src-ui/src/app/components/manage/document-type-list/document-type-list.component.html +++ b/src-ui/src/app/components/manage/document-type-list/document-type-list.component.html @@ -10,9 +10,9 @@
NameMatchingDocument countLast correspondenceNameMatchingDocument countLast correspondence Actions
- - - + + + diff --git a/src-ui/src/app/components/manage/generic-list/generic-list.component.ts b/src-ui/src/app/components/manage/generic-list/generic-list.component.ts index 12cf08ea9..d5477d010 100644 --- a/src-ui/src/app/components/manage/generic-list/generic-list.component.ts +++ b/src-ui/src/app/components/manage/generic-list/generic-list.component.ts @@ -1,7 +1,8 @@ -import { Directive, OnInit } from '@angular/core'; +import { Directive, OnInit, QueryList, ViewChildren } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { MatchingModel, MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'; import { ObjectWithId } from 'src/app/data/object-with-id'; +import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive'; import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'; import { DeleteDialogComponent } from '../../common/delete-dialog/delete-dialog.component'; @@ -14,12 +15,17 @@ export abstract class GenericListComponent implements On private editDialogComponent: any) { } + @ViewChildren(SortableDirective) headers: QueryList; + public data: T[] = [] public page = 1 public collectionSize = 0 + public sortField: string + public sortDirection: string + getMatching(o: MatchingModel) { if (o.matching_algorithm == MATCH_AUTO) { return "Automatic" @@ -30,12 +36,31 @@ export abstract class GenericListComponent implements On } } + onSort(event: SortEvent) { + + if (event.direction && event.direction.length > 0) { + this.sortField = event.column + this.sortDirection = event.direction + } else { + this.sortField = null + this.sortDirection = null + } + + this.headers.forEach(header => { + if (header.sortable !== this.sortField) { + header.direction = ''; + } + }); + + this.reloadData() + } + ngOnInit(): void { this.reloadData() } reloadData() { - this.service.list(this.page).subscribe(c => { + this.service.list(this.page, null, this.sortField, this.sortDirection).subscribe(c => { this.data = c.results this.collectionSize = c.count }); diff --git a/src-ui/src/app/components/manage/tag-list/tag-list.component.html b/src-ui/src/app/components/manage/tag-list/tag-list.component.html index 76ae9fb93..d06748cec 100644 --- a/src-ui/src/app/components/manage/tag-list/tag-list.component.html +++ b/src-ui/src/app/components/manage/tag-list/tag-list.component.html @@ -9,10 +9,10 @@
NameMatchingDocument countNameMatchingDocument count Actions
- + - - + + diff --git a/src-ui/src/app/directives/sortable.directive.spec.ts b/src-ui/src/app/directives/sortable.directive.spec.ts new file mode 100644 index 000000000..f77b499de --- /dev/null +++ b/src-ui/src/app/directives/sortable.directive.spec.ts @@ -0,0 +1,8 @@ +import { SortableDirective } from './sortable.directive'; + +describe('SortableDirective', () => { + it('should create an instance', () => { + const directive = new SortableDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/src-ui/src/app/directives/sortable.directive.ts b/src-ui/src/app/directives/sortable.directive.ts new file mode 100644 index 000000000..11c474dbb --- /dev/null +++ b/src-ui/src/app/directives/sortable.directive.ts @@ -0,0 +1,30 @@ +import { Directive, EventEmitter, Input, Output } from '@angular/core'; + +export interface SortEvent { + column: string; + direction: string; +} + +const rotate: {[key: string]: string} = { 'asc': 'des', 'des': '', '': 'asc' }; + +@Directive({ + selector: 'th[sortable]', + host: { + '[class.asc]': 'direction === "asc"', + '[class.des]': 'direction === "des"', + '(click)': 'rotate()' + } +}) +export class SortableDirective { + + constructor() { } + + @Input() sortable: string = ''; + @Input() direction: string = ''; + @Output() sort = new EventEmitter(); + + rotate() { + this.direction = rotate[this.direction]; + this.sort.emit({column: this.sortable, direction: this.direction}); + } +} diff --git a/src-ui/src/app/services/rest/abstract-paperless-service.ts b/src-ui/src/app/services/rest/abstract-paperless-service.ts index cdf157aaa..16064c702 100644 --- a/src-ui/src/app/services/rest/abstract-paperless-service.ts +++ b/src-ui/src/app/services/rest/abstract-paperless-service.ts @@ -21,7 +21,17 @@ export abstract class AbstractPaperlessService { return url } - list(page?: number, pageSize?: number, ordering?: string, extraParams?): Observable> { + private getOrderingQueryParam(sortField: string, sortDirection: string) { + if (sortField && sortDirection) { + return (sortDirection == 'des' ? '-' : '') + sortField + } else if (sortField) { + return sortField + } else { + return null + } + } + + list(page?: number, pageSize?: number, sortField?: string, sortDirection?: string, extraParams?): Observable> { let httpParams = new HttpParams() if (page) { httpParams = httpParams.set('page', page.toString()) @@ -29,6 +39,7 @@ export abstract class AbstractPaperlessService { if (pageSize) { httpParams = httpParams.set('page_size', pageSize.toString()) } + let ordering = this.getOrderingQueryParam(sortField, sortDirection) if (ordering) { httpParams = httpParams.set('ordering', ordering) } diff --git a/src-ui/src/app/services/rest/document.service.ts b/src-ui/src/app/services/rest/document.service.ts index bc1afb419..88a52ee04 100644 --- a/src-ui/src/app/services/rest/document.service.ts +++ b/src-ui/src/app/services/rest/document.service.ts @@ -47,16 +47,8 @@ export class DocumentService extends AbstractPaperlessService } } - private getOrderingQueryParam(sortField: string, sortDirection: string) { - if (DOCUMENT_SORT_FIELDS.find(f => f.field == sortField)) { - return (sortDirection == SORT_DIRECTION_DESCENDING ? '-' : '') + sortField - } else { - return null - } - } - list(page?: number, pageSize?: number, sortField?: string, sortDirection?: string, filterRules?: FilterRule[]): Observable> { - return super.list(page, pageSize, this.getOrderingQueryParam(sortField, sortDirection), this.filterRulesToQueryParams(filterRules)) + return super.list(page, pageSize, sortField, sortDirection, this.filterRulesToQueryParams(filterRules)) } getPreviewUrl(id: number): string { diff --git a/src-ui/src/styles.css b/src-ui/src/styles.css index 0f16a68f0..c7849912e 100644 --- a/src-ui/src/styles.css +++ b/src-ui/src/styles.css @@ -28,4 +28,34 @@ body { .form-control-dark:focus { border-color: transparent; box-shadow: 0 0 0 3px rgba(255, 255, 255, .25); +} + + +.asc { + background-color: #f8f9fa!important; +} + +.asc:after { + content: ''; + transform: rotate(180deg); + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAmxJREFUeAHtmksrRVEUx72fH8CIGQNJkpGUUmakDEiZSJRIZsRQmCkTJRmZmJgQE0kpX0D5DJKJgff7v+ru2u3O3vvc67TOvsdatdrnnP1Y///v7HvvubdbUiIhBISAEBACQkAICAEhIAQ4CXSh2DnyDfmCPEG2Iv9F9MPlM/LHyAecdyMzHYNwR3fdNK/OH9HXl1UCozD24TCvILxizEDWIEzA0FcM8woCgRrJCoS5PIwrANQSMAJX1LEI9bqpQo4JYNFFKRSvIgsxHDVnqZgIkPnNBM0rIGtYk9YOOsqgbgepRCfdbmFtqhFkVEDVPjJp0+Z6e6hRHhqBKgg6ZDCvYBygVmUoEGoh5JTRvIJwhJo1aUOoh4CLPMyvxxi7EWOMgnCGsXXI1GIXlZUYX7ucU+kbR8NW8lh3O7cue0Pk32MKndfUxQFAwxdirk3fHappAnc0oqDPzDfGTBrCfHP04dM4oTV8cxr0SVzH9FF07xD3ib6xCDE+M+aUcVygtWzzbtGX2rPBrEUYfecfQkaFzYi6HjVnGBdtL7epqAlc1+jRdAap74RrnPc4BCijttY2tRcdN0g17w7HqZrXhdJTYAuS3hd8z+vKgK3V1zWPae0mZDMykadBn1hTQBLnZNwVrJpSe/NwEeDsEwCctEOsJTsgxLvCqUl2ACftEGvJDgjxrnBqkh3ASTvEWrIDQrwrnJpkB3DSDrGW7IAQ7wqnJtkBnLRztejXXVu4+mxz/nQ9jR1w5VB86ejLTFcnnDwhzV+F6T+CHZlx6THSjn76eyyBIOPHyDakhBAQAkJACAgBISAEhIAQYCLwC8JxpAmsEGt6AAAAAElFTkSuQmCC") no-repeat; + height: 1rem; + width: 1rem; + display: block; + background-size: 1rem; + float: right; +} + +.des { + background-color: #f8f9fa!important; +} + +.des:after { + content: ''; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAmxJREFUeAHtmksrRVEUx72fH8CIGQNJkpGUUmakDEiZSJRIZsRQmCkTJRmZmJgQE0kpX0D5DJKJgff7v+ru2u3O3vvc67TOvsdatdrnnP1Y///v7HvvubdbUiIhBISAEBACQkAICAEhIAQ4CXSh2DnyDfmCPEG2Iv9F9MPlM/LHyAecdyMzHYNwR3fdNK/OH9HXl1UCozD24TCvILxizEDWIEzA0FcM8woCgRrJCoS5PIwrANQSMAJX1LEI9bqpQo4JYNFFKRSvIgsxHDVnqZgIkPnNBM0rIGtYk9YOOsqgbgepRCfdbmFtqhFkVEDVPjJp0+Z6e6hRHhqBKgg6ZDCvYBygVmUoEGoh5JTRvIJwhJo1aUOoh4CLPMyvxxi7EWOMgnCGsXXI1GIXlZUYX7ucU+kbR8NW8lh3O7cue0Pk32MKndfUxQFAwxdirk3fHappAnc0oqDPzDfGTBrCfHP04dM4oTV8cxr0SVzH9FF07xD3ib6xCDE+M+aUcVygtWzzbtGX2rPBrEUYfecfQkaFzYi6HjVnGBdtL7epqAlc1+jRdAap74RrnPc4BCijttY2tRcdN0g17w7HqZrXhdJTYAuS3hd8z+vKgK3V1zWPae0mZDMykadBn1hTQBLnZNwVrJpSe/NwEeDsEwCctEOsJTsgxLvCqUl2ACftEGvJDgjxrnBqkh3ASTvEWrIDQrwrnJpkB3DSDrGW7IAQ7wqnJtkBnLRztejXXVu4+mxz/nQ9jR1w5VB86ejLTFcnnDwhzV+F6T+CHZlx6THSjn76eyyBIOPHyDakhBAQAkJACAgBISAEhIAQYCLwC8JxpAmsEGt6AAAAAElFTkSuQmCC") no-repeat; + height: 1rem; + width: 1rem; + display: block; + background-size: 1rem; + float: right; } \ No newline at end of file
NameName ColourMatchingDocument countMatchingDocument count Actions