mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Add frontend owner filtering
Add owner to doc cards, table Frontend testing for owner filtering
This commit is contained in:
@@ -106,6 +106,12 @@
|
||||
</svg>
|
||||
<small>{{document.created_date | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.owner && document.owner !== settingsService.currentUser.id" class="list-group-item bg-light text-dark p-1 border-0">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock"/>
|
||||
</svg>
|
||||
<small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.__search_hit__?.score" class="list-group-item bg-light text-dark border-0 d-flex p-0 ps-4 search-score">
|
||||
<small class="text-muted" i18n>Score:</small>
|
||||
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
|
||||
|
@@ -23,7 +23,7 @@ import { ComponentWithPermissions } from '../../with-permissions/with-permission
|
||||
export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
||||
constructor(
|
||||
private documentService: DocumentService,
|
||||
private settingsService: SettingsService
|
||||
public settingsService: SettingsService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
@@ -38,15 +38,15 @@
|
||||
<div class="list-group list-group-flush border-0 pt-1 pb-2 card-info">
|
||||
<button *ngIf="document.document_type" type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle document type filter" i18n-title
|
||||
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted bi bi-file-earmark" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#file-earmark"/>
|
||||
</svg>
|
||||
<small>{{(document.document_type$ | async)?.name}}</small>
|
||||
</button>
|
||||
<button *ngIf="document.storage_path" type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle storage path filter" i18n-title
|
||||
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||
<svg class="metadata-icon me-2 text-muted bi bi-folder" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/>
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
|
||||
</svg>
|
||||
<small>{{(document.storage_path$ | async)?.name}}</small>
|
||||
</button>
|
||||
@@ -59,18 +59,23 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
|
||||
<svg class="metadata-icon me-2 text-muted bi bi-calendar-event" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z"/>
|
||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#calendar-event"/>
|
||||
</svg>
|
||||
<small>{{document.created_date | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.archive_serial_number" class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted bi bi-upc-scan" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M1.5 1a.5.5 0 0 0-.5.5v3a.5.5 0 0 1-1 0v-3A1.5 1.5 0 0 1 1.5 0h3a.5.5 0 0 1 0 1h-3zM11 .5a.5.5 0 0 1 .5-.5h3A1.5 1.5 0 0 1 16 1.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 1-.5-.5zM.5 11a.5.5 0 0 1 .5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 1 0 1h-3A1.5 1.5 0 0 1 0 14.5v-3a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v3a1.5 1.5 0 0 1-1.5 1.5h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 1 .5-.5zM3 4.5a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7zm2 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-7zm3 0a.5.5 0 0 1 1 0v7a.5.5 0 0 1-1 0v-7z"/>
|
||||
</svg>
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="document.archive_serial_number" class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#upc-scan"/>
|
||||
</svg>
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
<div *ngIf="document.owner && document.owner !== settingsService.currentUser.id" class="ps-0 p-1">
|
||||
<svg class="metadata-icon me-2 text-muted" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock"/>
|
||||
</svg>
|
||||
<small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
|
@@ -24,7 +24,7 @@ import { ComponentWithPermissions } from '../../with-permissions/with-permission
|
||||
export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
||||
constructor(
|
||||
private documentService: DocumentService,
|
||||
private settingsService: SettingsService
|
||||
public settingsService: SettingsService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
@@ -142,6 +142,13 @@
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Title</th>
|
||||
<th class="d-none d-xl-table-cell"
|
||||
appSortable="owner"
|
||||
title="Sort by owner" i18n-title
|
||||
[currentSortField]="list.sortField"
|
||||
[currentSortReverse]="list.sortReverse"
|
||||
(sort)="onSort($event)"
|
||||
i18n>Owner</th>
|
||||
<th *ngIf="notesEnabled" class="d-none d-xl-table-cell"
|
||||
appSortable="num_notes"
|
||||
title="Sort by notes" i18n-title
|
||||
@@ -198,6 +205,9 @@
|
||||
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
|
||||
<app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></app-tag>
|
||||
</td>
|
||||
<td>
|
||||
{{d.owner | username}}
|
||||
</td>
|
||||
<td *ngIf="notesEnabled" class="d-none d-xl-table-cell">
|
||||
<a *ngIf="d.notes.length" routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
|
||||
<span class="badge rounded-pill bg-light border text-primary">
|
||||
|
@@ -58,20 +58,26 @@
|
||||
[documentCounts]="storagePathDocumentCounts"
|
||||
[allowSelectNone]="true"></app-filterable-dropdown>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap">
|
||||
<div class="d-flex flex-wrap mb-2 mb-xxl-0">
|
||||
<app-date-dropdown class="mb-2 mb-xl-0"
|
||||
title="Created" i18n-title
|
||||
(datesSet)="updateRules()"
|
||||
[(dateBefore)]="dateCreatedBefore"
|
||||
[(dateAfter)]="dateCreatedAfter"
|
||||
[(relativeDate)]="dateCreatedRelativeDate"></app-date-dropdown>
|
||||
<app-date-dropdown class="mb-2 mb-xl-0"
|
||||
<app-date-dropdown class="mb-2 mb-xl-0 me-2"
|
||||
title="Added" i18n-title
|
||||
(datesSet)="updateRules()"
|
||||
[(dateBefore)]="dateAddedBefore"
|
||||
[(dateAfter)]="dateAddedAfter"
|
||||
[(relativeDate)]="dateAddedRelativeDate"></app-date-dropdown>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap">
|
||||
<app-permissions-filter-dropdown class="mb-2 mb-xl-0"
|
||||
title="Permissions" i18n-title
|
||||
(ownerFilterSet)="updateRules()"
|
||||
[(selectionModel)]="permissionsSelectionModel"></app-permissions-filter-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-100 d-xxl-none"></div>
|
||||
|
@@ -43,6 +43,10 @@ import {
|
||||
FILTER_DOCUMENT_TYPE,
|
||||
FILTER_CORRESPONDENT,
|
||||
FILTER_STORAGE_PATH,
|
||||
FILTER_OWNER,
|
||||
FILTER_OWNER_DOES_NOT_INCLUDE,
|
||||
FILTER_OWNER_ISNULL,
|
||||
FILTER_OWNER_ANY,
|
||||
} from 'src/app/data/filter-rule-type'
|
||||
import {
|
||||
FilterableDropdownSelectionModel,
|
||||
@@ -59,6 +63,11 @@ 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'
|
||||
import { RelativeDate } from '../../common/date-dropdown/date-dropdown.component'
|
||||
import {
|
||||
OwnerFilterType,
|
||||
PermissionsSelectionModel,
|
||||
} from '../../common/permissions-filter-dropdown/permissions-filter-dropdown.component'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
|
||||
const TEXT_FILTER_TARGET_TITLE = 'title'
|
||||
const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content'
|
||||
@@ -136,6 +145,15 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
|
||||
case FILTER_ASN:
|
||||
return $localize`ASN: ${rule.value}`
|
||||
|
||||
case FILTER_OWNER:
|
||||
return $localize`Owner: ${rule.value}`
|
||||
|
||||
case FILTER_OWNER_DOES_NOT_INCLUDE:
|
||||
return $localize`Owner not in: ${rule.value}`
|
||||
|
||||
case FILTER_OWNER_ISNULL:
|
||||
return $localize`Without an owner`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +165,8 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
private tagService: TagService,
|
||||
private correspondentService: CorrespondentService,
|
||||
private documentService: DocumentService,
|
||||
private storagePathService: StoragePathService
|
||||
private storagePathService: StoragePathService,
|
||||
private settingsService: SettingsService
|
||||
) {}
|
||||
|
||||
@ViewChild('textFilterInput')
|
||||
@@ -241,6 +260,8 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
dateCreatedRelativeDate: RelativeDate
|
||||
dateAddedRelativeDate: RelativeDate
|
||||
|
||||
permissionsSelectionModel = new PermissionsSelectionModel()
|
||||
|
||||
_unmodifiedFilterRules: FilterRule[] = []
|
||||
_filterRules: FilterRule[] = []
|
||||
|
||||
@@ -274,6 +295,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
this.dateCreatedRelativeDate = null
|
||||
this.dateAddedRelativeDate = null
|
||||
this.textFilterModifier = TEXT_FILTER_MODIFIER_EQUALS
|
||||
this.permissionsSelectionModel.clear()
|
||||
|
||||
value.forEach((rule) => {
|
||||
switch (rule.rule_type) {
|
||||
@@ -441,6 +463,35 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
this.textFilterModifier = TEXT_FILTER_MODIFIER_LT
|
||||
this._textFilter = rule.value
|
||||
break
|
||||
case FILTER_OWNER:
|
||||
this.permissionsSelectionModel.ownerFilter = OwnerFilterType.SELF
|
||||
this.permissionsSelectionModel.hideUnowned = false
|
||||
if (rule.value)
|
||||
this.permissionsSelectionModel.userID = parseInt(rule.value, 10)
|
||||
break
|
||||
case FILTER_OWNER_ANY:
|
||||
this.permissionsSelectionModel.ownerFilter = OwnerFilterType.OTHERS
|
||||
if (rule.value)
|
||||
this.permissionsSelectionModel.includeUsers.push(
|
||||
parseInt(rule.value, 10)
|
||||
)
|
||||
break
|
||||
case FILTER_OWNER_DOES_NOT_INCLUDE:
|
||||
this.permissionsSelectionModel.ownerFilter = OwnerFilterType.NOT_SELF
|
||||
if (rule.value)
|
||||
this.permissionsSelectionModel.excludeUsers.push(
|
||||
parseInt(rule.value, 10)
|
||||
)
|
||||
break
|
||||
case FILTER_OWNER_ISNULL:
|
||||
if (rule.value === 'true' || rule.value === '1') {
|
||||
this.permissionsSelectionModel.hideUnowned = false
|
||||
this.permissionsSelectionModel.ownerFilter = OwnerFilterType.UNOWNED
|
||||
} else {
|
||||
this.permissionsSelectionModel.hideUnowned =
|
||||
rule.value === 'false' || rule.value === '0'
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
this.rulesModified = filterRulesDiffer(
|
||||
@@ -702,6 +753,40 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.permissionsSelectionModel.ownerFilter == OwnerFilterType.SELF) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER,
|
||||
value: this.permissionsSelectionModel.userID.toString(),
|
||||
})
|
||||
} else if (
|
||||
this.permissionsSelectionModel.ownerFilter == OwnerFilterType.NOT_SELF
|
||||
) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER_DOES_NOT_INCLUDE,
|
||||
value: this.permissionsSelectionModel.excludeUsers?.join(','),
|
||||
})
|
||||
} else if (
|
||||
this.permissionsSelectionModel.ownerFilter == OwnerFilterType.OTHERS
|
||||
) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER_ANY,
|
||||
value: this.permissionsSelectionModel.includeUsers?.join(','),
|
||||
})
|
||||
} else if (
|
||||
this.permissionsSelectionModel.ownerFilter == OwnerFilterType.UNOWNED
|
||||
) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER_ISNULL,
|
||||
value: 'true',
|
||||
})
|
||||
}
|
||||
|
||||
if (this.permissionsSelectionModel.hideUnowned) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_OWNER_ISNULL,
|
||||
value: 'false',
|
||||
})
|
||||
}
|
||||
return filterRules
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user