Customizable documents view
Fix
This commit is contained in:
parent
ffae52c9ac
commit
eb1eab1dfc
File diff suppressed because it is too large
Load Diff
@ -352,19 +352,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@if (savedViewGroup.get(view.id.toString()).get('show_on_dashboard').value) {
|
||||
<div class="col">
|
||||
<pngx-input-number i18n-title title="Widget list limit" [showAdd]="false" formControlName="page_size"></pngx-input-number>
|
||||
<pngx-input-number i18n-title title="Documents page size" [showAdd]="false" formControlName="page_size"></pngx-input-number>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label" for="display_mode_{{view.id}}" i18n>Widget display</label>
|
||||
<label class="form-label" for="display_mode_{{view.id}}" i18n>Display as</label>
|
||||
<select class="form-select" formControlName="display_mode">
|
||||
<option [ngValue]="DashboardViewMode.TABLE" i18n>Table</option>
|
||||
<option [ngValue]="DashboardViewMode.SMALL_CARDS" i18n>Small Cards</option>
|
||||
<option [ngValue]="DashboardViewMode.LARGE_CARDS" i18n>Large Cards</option>
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
@if (documentDisplayFields) {
|
||||
<pngx-input-drag-drop-select i18n-title title="Show" [items]="documentDisplayFields" formControlName="document_display_fields"></pngx-input-drag-drop-select>
|
||||
}
|
||||
|
@ -547,7 +547,6 @@ export class SettingsComponent
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.store.next(this.settingsForm.value)
|
||||
this.documentListViewService.updatePageSize()
|
||||
this.settings.updateAppearanceSettings()
|
||||
let savedToast: Toast = {
|
||||
content: $localize`Settings were saved successfully.`,
|
||||
|
@ -14,7 +14,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
@for (column of savedView.document_display_fields; track column; let i = $index) {
|
||||
@if (activeDisplayFields.has(column)) {
|
||||
@if (activeDisplayFields.includes(column)) {
|
||||
<th
|
||||
scope="col"
|
||||
[ngClass]="{
|
||||
@ -31,7 +31,7 @@
|
||||
@for (doc of documents; track doc.id) {
|
||||
<tr>
|
||||
@for (column of savedView.document_display_fields; track column; let i = $index) {
|
||||
@if (activeDisplayFields.has(column)) {
|
||||
@if (activeDisplayFields.includes(column)) {
|
||||
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': i > 1 }">
|
||||
@switch (column) {
|
||||
@case (DashboardViewTableColumn.ADDED) {
|
||||
|
@ -36,7 +36,6 @@ import {
|
||||
} from 'src/app/services/permissions.service'
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||
import { Results } from 'src/app/data/results'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-saved-view-widget',
|
||||
@ -81,11 +80,11 @@ export class SavedViewWidgetComponent
|
||||
mouseOnPreview = false
|
||||
popoverHidden = true
|
||||
|
||||
activeDisplayFields: Set<DocumentDisplayField> = new Set([
|
||||
activeDisplayFields: DocumentDisplayField[] = [
|
||||
DocumentDisplayField.TITLE,
|
||||
DocumentDisplayField.CREATED,
|
||||
DocumentDisplayField.ADDED,
|
||||
])
|
||||
]
|
||||
|
||||
ngOnInit(): void {
|
||||
this.reload()
|
||||
@ -121,7 +120,7 @@ export class SavedViewWidgetComponent
|
||||
type &&
|
||||
this.permissionsService.currentUserCan(PermissionAction.View, type)
|
||||
)
|
||||
this.activeDisplayFields.add(column)
|
||||
this.activeDisplayFields.push(column)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title">
|
||||
@if (displayFields.has(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
|
||||
@if (displayFields.includes(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
|
||||
@if (clickCorrespondent.observers.length ) {
|
||||
<a title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name}}</a>
|
||||
} @else {
|
||||
@ -23,10 +23,10 @@
|
||||
}
|
||||
:
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.TITLE)) {
|
||||
@if (displayFields.includes(DocumentDisplayField.TITLE)) {
|
||||
{{document.title | documentTitle}}
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.TAGS)) {
|
||||
@if (displayFields.includes(DocumentDisplayField.TAGS)) {
|
||||
@for (t of document.tags$ | async; track t) {
|
||||
<pngx-tag [tag]="t" linkTitle="Filter by tag" i18n-linkTitle class="ms-1" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="clickTag.observers.length"></pngx-tag>
|
||||
}
|
||||
@ -34,7 +34,7 @@
|
||||
</h5>
|
||||
</div>
|
||||
<p class="card-text">
|
||||
@if (document.__search_hit__ && document.__search_hit__.highlights) {
|
||||
@if (document.__search_hit__?.score && document.__search_hit__.highlights) {
|
||||
<span [innerHtml]="document.__search_hit__.highlights"></span>
|
||||
}
|
||||
@for (highlight of searchNoteHighlights; track highlight) {
|
||||
@ -43,7 +43,7 @@
|
||||
<span [innerHtml]="highlight"></span>
|
||||
</span>
|
||||
}
|
||||
@if (!document.__search_hit__) {
|
||||
@if (!document.__search_hit__?.score) {
|
||||
<span class="result-content">{{contentTrimmed}}</span>
|
||||
}
|
||||
</p>
|
||||
@ -70,29 +70,29 @@
|
||||
</div>
|
||||
|
||||
<div class="list-group list-group-horizontal border-0 card-info ms-md-auto mt-2 mt-md-0">
|
||||
@if (displayFields.has(DocumentDisplayField.NOTES) && notesEnabled && document.notes.length) {
|
||||
@if (displayFields.includes(DocumentDisplayField.NOTES) && notesEnabled && document.notes.length) {
|
||||
<button routerLink="/documents/{{document.id}}/notes" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="View notes" i18n-title>
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="chat-left-text"></i-bs><small i18n>{{document.notes.length}} Notes</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.DOCUMENT_TYPE) && document.document_type) {
|
||||
@if (displayFields.includes(DocumentDisplayField.DOCUMENT_TYPE) && document.document_type) {
|
||||
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by document type" i18n-title
|
||||
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="file-earmark"></i-bs><small>{{(document.document_type$ | async)?.name}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.STORAGE_PATH) && document.storage_path) {
|
||||
@if (displayFields.includes(DocumentDisplayField.STORAGE_PATH) && document.storage_path) {
|
||||
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by storage path" i18n-title
|
||||
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="archive"></i-bs><small>{{(document.storage_path$ | async)?.name}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.ASN) && document.archive_serial_number | isNumber) {
|
||||
@if (displayFields.includes(DocumentDisplayField.ASN) && document.archive_serial_number | isNumber) {
|
||||
<div class="list-group-item me-2 bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="upc-scan"></i-bs><small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.CREATED) || displayFields.has(DocumentDisplayField.ADDED)) {
|
||||
@if (displayFields.includes(DocumentDisplayField.CREATED) || displayFields.includes(DocumentDisplayField.ADDED)) {
|
||||
<ng-template #dateTooltip>
|
||||
<div class="d-flex flex-column text-light">
|
||||
<span i18n>Created: {{ document.created | customDate }}</span>
|
||||
@ -100,19 +100,23 @@
|
||||
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
@if (displayFields.includes(DocumentDisplayField.CREATED)) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>
|
||||
@if (displayFields.has(DocumentDisplayField.CREATED)) { {{document.created_date | customDate:'mediumDate'}} }
|
||||
@else { {{document.added | customDate:'mediumDate'}} }
|
||||
</small>
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created_date | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
@if (displayFields.includes(DocumentDisplayField.ADDED)) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.added | customDate:'mediumDate'}}</small>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (displayFields.includes(DocumentDisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.SHARED) && document.is_shared_by_requester) {
|
||||
@if (displayFields.includes(DocumentDisplayField.SHARED) && document.is_shared_by_requester) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="people-fill"></i-bs><small i18n>Shared</small>
|
||||
</div>
|
||||
@ -124,7 +128,7 @@
|
||||
</div>
|
||||
}
|
||||
@for (field of document.custom_fields; track field.id) {
|
||||
@if (displayFields.has(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
|
||||
@if (displayFields.includes(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
|
||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
|
||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="ui-radios"></i-bs>
|
||||
<small>
|
||||
|
@ -35,9 +35,7 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
||||
selected = false
|
||||
|
||||
@Input()
|
||||
displayFields: Set<DocumentDisplayField | string> = new Set(
|
||||
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
)
|
||||
displayFields: string[] = DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
|
||||
@Output()
|
||||
toggleSelected = new EventEmitter()
|
||||
|
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (displayFields?.has(DocumentDisplayField.TAGS)) {
|
||||
@if (displayFields?.includes(DocumentDisplayField.TAGS)) {
|
||||
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
|
||||
@for (t of getTagsLimited$() | async; track t) {
|
||||
<pngx-tag [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
|
||||
@ -24,7 +24,7 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (displayFields.has(DocumentDisplayField.NOTES) && notesEnabled && document.notes.length) {
|
||||
@if (displayFields.includes(DocumentDisplayField.NOTES) && notesEnabled && document.notes.length) {
|
||||
<a routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
|
||||
<span class="badge rounded-pill bg-light border text-primary">
|
||||
<i-bs width="1.2em" height="1.2em" class="ms-1 me-1" name="chat-left-text"></i-bs>
|
||||
@ -34,31 +34,31 @@
|
||||
|
||||
<div class="card-body bg-light p-2">
|
||||
<p class="card-text">
|
||||
@if (displayFields.has(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
|
||||
@if (displayFields.includes(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
|
||||
<a title="Toggle correspondent filter" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name ?? privateName}}</a>:
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.TITLE)) {
|
||||
@if (displayFields.includes(DocumentDisplayField.TITLE)) {
|
||||
{{document.title | documentTitle}}
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer pt-0 pb-2 px-2">
|
||||
<div class="list-group list-group-flush border-0 pt-1 pb-2 card-info">
|
||||
@if (displayFields.has(DocumentDisplayField.DOCUMENT_TYPE) && document.document_type) {
|
||||
@if (displayFields.includes(DocumentDisplayField.DOCUMENT_TYPE) && document.document_type) {
|
||||
<button 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()">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="file-earmark"></i-bs>
|
||||
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.STORAGE_PATH) && document.storage_path) {
|
||||
@if (displayFields.includes(DocumentDisplayField.STORAGE_PATH) && document.storage_path) {
|
||||
<button 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()">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="folder"></i-bs>
|
||||
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
|
||||
</button>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.CREATED) || displayFields.has(DocumentDisplayField.ADDED)) {
|
||||
@if (displayFields.includes(DocumentDisplayField.CREATED) || displayFields.includes(DocumentDisplayField.ADDED)) {
|
||||
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
|
||||
<ng-template #dateTooltip>
|
||||
<div class="d-flex flex-column text-light">
|
||||
@ -70,32 +70,32 @@
|
||||
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
|
||||
<small>
|
||||
@if (displayFields.has(DocumentDisplayField.CREATED)) { {{document.created | customDate:'mediumDate'}} }
|
||||
@if (displayFields.includes(DocumentDisplayField.CREATED)) { {{document.created | customDate:'mediumDate'}} }
|
||||
@else { {{document.added | customDate:'mediumDate'}} }
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.ASN) && document.archive_serial_number | isNumber) {
|
||||
@if (displayFields.includes(DocumentDisplayField.ASN) && document.archive_serial_number | isNumber) {
|
||||
<div class="ps-0 p-1">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="upc-scan"></i-bs>
|
||||
<small>#{{document.archive_serial_number}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
@if (displayFields.includes(DocumentDisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
||||
<div class="ps-0 p-1">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
|
||||
<small>{{document.owner | username}}</small>
|
||||
</div>
|
||||
}
|
||||
@if (displayFields.has(DocumentDisplayField.SHARED) && document.is_shared_by_requester) {
|
||||
@if (displayFields.includes(DocumentDisplayField.SHARED) && document.is_shared_by_requester) {
|
||||
<div class="ps-0 p-1">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="people-fill"></i-bs>
|
||||
<small i18n>Shared</small>
|
||||
</div>
|
||||
}
|
||||
@for (field of document.custom_fields; track field.id) {
|
||||
@if (displayFields.has(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
|
||||
@if (displayFields.includes(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
|
||||
<div class="ps-0 p-1">
|
||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="ui-radios"></i-bs>
|
||||
<pngx-custom-field-display [document]="document" [fieldId]="field.field"></pngx-custom-field-display>
|
||||
|
@ -42,9 +42,7 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
||||
document: Document
|
||||
|
||||
@Input()
|
||||
displayFields: Set<DocumentDisplayField | string> = new Set(
|
||||
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
)
|
||||
displayFields: string[] = DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
|
||||
@Output()
|
||||
dblClickDocument = new EventEmitter()
|
||||
|
@ -20,7 +20,7 @@
|
||||
<div class="px-3">
|
||||
@for (field of settingsService.allDocumentDisplayFields; track field.id) {
|
||||
<div class="form-check small">
|
||||
<input type="checkbox" class="form-check-input" id="displayField{{field.id}}" [checked]="activeDisplayFields.has(field.id)" (change)="toggleDisplayField(field.id)">
|
||||
<input type="checkbox" class="form-check-input" id="displayField{{field.id}}" [checked]="activeDisplayFields.includes(field.id)" (change)="toggleDisplayField(field.id)">
|
||||
<label class="form-check-label small" for="displayField{{field.id}}">{{field.name}}</label>
|
||||
</div>
|
||||
}
|
||||
@ -28,15 +28,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group flex-fill" role="group">
|
||||
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="table" (ngModelChange)="saveDisplayMode()" id="displayModeDetails" name="displayModeDetails">
|
||||
<input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="table" id="displayModeDetails" name="displayModeDetails">
|
||||
<label for="displayModeDetails" class="btn btn-outline-primary btn-sm">
|
||||
<i-bs name="list-ul"></i-bs>
|
||||
</label>
|
||||
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="smallCards" (ngModelChange)="saveDisplayMode()" id="displayModeSmall" name="displayModeSmall">
|
||||
<input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="smallCards" id="displayModeSmall" name="displayModeSmall">
|
||||
<label for="displayModeSmall" class="btn btn-outline-primary btn-sm">
|
||||
<i-bs name="grid"></i-bs>
|
||||
</label>
|
||||
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="largeCards" (ngModelChange)="saveDisplayMode()" id="displayModeLarge" name="displayModeLarge">
|
||||
<input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="largeCards" id="displayModeLarge" name="displayModeLarge">
|
||||
<label for="displayModeLarge" class="btn btn-outline-primary btn-sm">
|
||||
<i-bs name="hdd-stack"></i-bs>
|
||||
</label>
|
||||
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
<div>
|
||||
@for (f of getSortFields(); track f) {
|
||||
<button ngbDropdownItem (click)="setSortField(f.field)"
|
||||
<button ngbDropdownItem (click)="list.sortField = f.field"
|
||||
[class.active]="list.sortField === f.field">{{f.name}}
|
||||
</button>
|
||||
}
|
||||
@ -125,7 +125,7 @@
|
||||
}
|
||||
</div>
|
||||
@if (list.collectionSize) {
|
||||
<ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
|
||||
<ngb-pagination [pageSize]="list.pageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
|
||||
[rotate]="true" aria-label="Default pagination" size="sm"></ngb-pagination>
|
||||
}
|
||||
</div>
|
||||
@ -138,7 +138,7 @@
|
||||
@if (list.error ) {
|
||||
<div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
|
||||
} @else {
|
||||
@if (displayMode === 'largeCards') {
|
||||
@if (list.displayMode === DisplayMode.LARGE_CARDS) {
|
||||
<div>
|
||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||
<pngx-document-card-large
|
||||
@ -156,11 +156,11 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (displayMode === 'details' || displayMode === 'table') {
|
||||
@if (list.displayMode === DisplayMode.TABLE) {
|
||||
<table class="table table-sm align-middle border shadow-sm">
|
||||
<thead>
|
||||
<th></th>
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.ASN)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.ASN)) {
|
||||
<th class="d-none d-lg-table-cell"
|
||||
pngxSortable="archive_serial_number"
|
||||
title="Sort by ASN" i18n-title
|
||||
@ -169,7 +169,7 @@
|
||||
(sort)="onSort($event)"
|
||||
i18n>ASN</th>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
|
||||
<th class="d-none d-md-table-cell"
|
||||
pngxSortable="correspondent__name"
|
||||
title="Sort by correspondent" i18n-title
|
||||
@ -178,7 +178,7 @@
|
||||
(sort)="onSort($event)"
|
||||
i18n>Correspondent</th>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.TITLE)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.TITLE)) {
|
||||
<th
|
||||
pngxSortable="title"
|
||||
title="Sort by title" i18n-title
|
||||
@ -187,7 +187,7 @@
|
||||
(sort)="onSort($event)"
|
||||
i18n>Title</th>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.OWNER)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="owner"
|
||||
title="Sort by owner" i18n-title
|
||||
@ -196,7 +196,7 @@
|
||||
(sort)="onSort($event)"
|
||||
i18n>Owner</th>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.NOTES) && notesEnabled) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.NOTES) && notesEnabled) {
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="num_notes"
|
||||
title="Sort by notes" i18n-title
|
||||
@ -205,7 +205,7 @@
|
||||
(sort)="onSort($event)"
|
||||
i18n>Notes</th>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="document_type__name"
|
||||
title="Sort by document type" i18n-title
|
||||
@ -214,7 +214,7 @@
|
||||
(sort)="onSort($event)"
|
||||
i18n>Document type</th>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
|
||||
<th class="d-none d-xl-table-cell"
|
||||
pngxSortable="storage_path__name"
|
||||
title="Sort by storage path" i18n-title
|
||||
@ -223,7 +223,7 @@
|
||||
(sort)="onSort($event)"
|
||||
i18n>Storage path</th>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.CREATED)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.CREATED)) {
|
||||
<th
|
||||
pngxSortable="created"
|
||||
title="Sort by created date" i18n-title
|
||||
@ -232,7 +232,7 @@
|
||||
(sort)="onSort($event)"
|
||||
i18n>Created</th>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.ADDED)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.ADDED)) {
|
||||
<th
|
||||
pngxSortable="added"
|
||||
title="Sort by added date" i18n-title
|
||||
@ -256,12 +256,12 @@
|
||||
<label class="form-check-label" for="docCheck{{d.id}}"></label>
|
||||
</div>
|
||||
</td>
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.ASN)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.ASN)) {
|
||||
<td class="d-none d-xl-table-cell">
|
||||
{{d.archive_serial_number}}
|
||||
</td>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
|
||||
<td class="d-none d-xl-table-cell">
|
||||
@if (d.correspondent) {
|
||||
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
|
||||
@ -269,21 +269,21 @@
|
||||
</td>
|
||||
}
|
||||
<td>
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.TITLE)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.TITLE)) {
|
||||
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.TAGS)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.TAGS)) {
|
||||
@for (t of d.tags$ | async; track t) {
|
||||
<pngx-tag [tag]="t" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.OWNER)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
|
||||
<td>
|
||||
{{d.owner | username}}
|
||||
</td>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.NOTES) && notesEnabled) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.NOTES) && notesEnabled) {
|
||||
<td class="d-none d-xl-table-cell">
|
||||
@if (d.notes.length) {
|
||||
<a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
|
||||
@ -294,26 +294,26 @@
|
||||
}
|
||||
</td>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
|
||||
<td class="d-none d-xl-table-cell">
|
||||
@if (d.document_type) {
|
||||
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
|
||||
<td class="d-none d-xl-table-cell">
|
||||
@if (d.storage_path) {
|
||||
<a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.CREATED)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.CREATED)) {
|
||||
<td>
|
||||
{{d.created_date | customDate}}
|
||||
</td>
|
||||
}
|
||||
@if (activeDisplayFields.has(DocumentDisplayField.ADDED)) {
|
||||
@if (activeDisplayFields.includes(DocumentDisplayField.ADDED)) {
|
||||
<td>
|
||||
{{d.added | customDate}}
|
||||
</td>
|
||||
@ -328,7 +328,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
@if (displayMode === 'smallCards') {
|
||||
@if (list.displayMode === DisplayMode.SMALL_CARDS) {
|
||||
<div class="row row-cols-paperless-cards">
|
||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||
<pngx-document-card-small class="p-0"
|
||||
|
@ -80,3 +80,7 @@ $paperless-card-breakpoints: (
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
pngx-page-header .dropdown-menu {
|
||||
--bs-dropdown-min-width: 12em;
|
||||
}
|
||||
|
@ -34,7 +34,12 @@ import {
|
||||
import { Subject, of, throwError } from 'rxjs'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
|
||||
import { DocumentDisplayField, SavedView } from 'src/app/data/saved-view'
|
||||
import {
|
||||
DEFAULT_DOCUMENT_DISPLAY_FIELDS,
|
||||
DisplayMode,
|
||||
DocumentDisplayField,
|
||||
SavedView,
|
||||
} from 'src/app/data/saved-view'
|
||||
import {
|
||||
FILTER_FULLTEXT_MORELIKE,
|
||||
FILTER_FULLTEXT_QUERY,
|
||||
@ -169,17 +174,6 @@ describe('DocumentListComponent', () => {
|
||||
component = fixture.componentInstance
|
||||
})
|
||||
|
||||
it('should load display mode from local storage', () => {
|
||||
window.localStorage.setItem('document-list:displayMode', 'largeCards')
|
||||
fixture.detectChanges()
|
||||
expect(component.displayMode).toEqual('largeCards')
|
||||
component.displayMode = 'smallCards'
|
||||
component.saveDisplayMode()
|
||||
expect(window.localStorage.getItem('document-list:displayMode')).toEqual(
|
||||
'smallCards'
|
||||
)
|
||||
})
|
||||
|
||||
it('should reload on new document consumed', () => {
|
||||
const reloadSpy = jest.spyOn(documentListService, 'reload')
|
||||
const fileStatusSubject = new Subject<FileStatus>()
|
||||
@ -297,18 +291,18 @@ describe('DocumentListComponent', () => {
|
||||
const displayModeButtons = fixture.debugElement.queryAll(
|
||||
By.css('input[type="radio"]')
|
||||
)
|
||||
expect(component.displayMode).toEqual('smallCards')
|
||||
expect(component.list.displayMode).toEqual('smallCards')
|
||||
|
||||
displayModeButtons[0].nativeElement.checked = true
|
||||
displayModeButtons[0].triggerEventHandler('change')
|
||||
fixture.detectChanges()
|
||||
expect(component.displayMode).toEqual('table')
|
||||
expect(component.list.displayMode).toEqual('table')
|
||||
expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(3)
|
||||
|
||||
displayModeButtons[1].nativeElement.checked = true
|
||||
displayModeButtons[1].triggerEventHandler('change')
|
||||
fixture.detectChanges()
|
||||
expect(component.displayMode).toEqual('smallCards')
|
||||
expect(component.list.displayMode).toEqual('smallCards')
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.directive(DocumentCardSmallComponent))
|
||||
).toHaveLength(3)
|
||||
@ -316,7 +310,7 @@ describe('DocumentListComponent', () => {
|
||||
displayModeButtons[2].nativeElement.checked = true
|
||||
displayModeButtons[2].triggerEventHandler('change')
|
||||
fixture.detectChanges()
|
||||
expect(component.displayMode).toEqual('largeCards')
|
||||
expect(component.list.displayMode).toEqual('largeCards')
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.directive(DocumentCardLargeComponent))
|
||||
).toHaveLength(3)
|
||||
@ -337,7 +331,7 @@ describe('DocumentListComponent', () => {
|
||||
})
|
||||
|
||||
it('should support setting sort field by table head', () => {
|
||||
component.activeDisplayFields = new Set([DocumentDisplayField.ASN])
|
||||
component.activeDisplayFields = [DocumentDisplayField.ASN]
|
||||
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
||||
fixture.detectChanges()
|
||||
expect(documentListService.sortField).toEqual('created')
|
||||
@ -348,7 +342,7 @@ describe('DocumentListComponent', () => {
|
||||
detailsDisplayModeButton.nativeElement.checked = true
|
||||
detailsDisplayModeButton.triggerEventHandler('change')
|
||||
fixture.detectChanges()
|
||||
expect(component.displayMode).toEqual('table')
|
||||
expect(component.list.displayMode).toEqual(DisplayMode.TABLE)
|
||||
|
||||
const sortTh = fixture.debugElement.query(By.directive(SortableDirective))
|
||||
sortTh.triggerEventHandler('click')
|
||||
@ -547,6 +541,42 @@ describe('DocumentListComponent', () => {
|
||||
expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
|
||||
})
|
||||
|
||||
it('should detect saved view changes', () => {
|
||||
const view: SavedView = {
|
||||
id: 10,
|
||||
name: 'Saved View 10',
|
||||
sort_field: 'added',
|
||||
sort_reverse: true,
|
||||
filter_rules: [
|
||||
{
|
||||
rule_type: FILTER_HAS_TAGS_ANY,
|
||||
value: '20',
|
||||
},
|
||||
],
|
||||
page_size: 5,
|
||||
display_mode: DisplayMode.SMALL_CARDS,
|
||||
document_display_fields: [DocumentDisplayField.TITLE],
|
||||
}
|
||||
jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
|
||||
const queryParams = { view: view.id.toString() }
|
||||
jest
|
||||
.spyOn(activatedRoute, 'queryParamMap', 'get')
|
||||
.mockReturnValue(of(convertToParamMap(queryParams)))
|
||||
activatedRoute.snapshot.queryParams = queryParams
|
||||
router.routerState.snapshot.url = '/view/10/'
|
||||
fixture.detectChanges()
|
||||
expect(documentListService.activeSavedViewId).toEqual(10)
|
||||
|
||||
component.list.documentDisplayFields = [DocumentDisplayField.ASN]
|
||||
expect(component.savedViewIsModified).toBeTruthy()
|
||||
component.list.documentDisplayFields = [DocumentDisplayField.TITLE]
|
||||
expect(component.savedViewIsModified).toBeFalsy()
|
||||
component.list.displayMode = DisplayMode.TABLE
|
||||
expect(component.savedViewIsModified).toBeTruthy()
|
||||
component.list.displayMode = DisplayMode.SMALL_CARDS
|
||||
expect(component.savedViewIsModified).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should navigate to a document', () => {
|
||||
fixture.detectChanges()
|
||||
const routerSpy = jest.spyOn(router, 'navigate')
|
||||
@ -559,12 +589,15 @@ describe('DocumentListComponent', () => {
|
||||
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
||||
expect(documentListService.sortField).toEqual('created')
|
||||
|
||||
component.displayMode = 'table'
|
||||
component.list.displayMode = DisplayMode.TABLE
|
||||
component.list.documentDisplayFields = DEFAULT_DOCUMENT_DISPLAY_FIELDS.map(
|
||||
(f) => f.id
|
||||
)
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||
).toHaveLength(8)
|
||||
).toHaveLength(9)
|
||||
|
||||
expect(component.notesEnabled).toBeTruthy()
|
||||
settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
|
||||
@ -572,7 +605,7 @@ describe('DocumentListComponent', () => {
|
||||
expect(component.notesEnabled).toBeFalsy()
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||
).toHaveLength(7)
|
||||
).toHaveLength(8)
|
||||
|
||||
// insufficient perms
|
||||
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
|
||||
@ -600,30 +633,16 @@ describe('DocumentListComponent', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should load display fields from local storage', () => {
|
||||
window.localStorage.setItem('document-list:displayFields', '["asn"]')
|
||||
fixture.detectChanges()
|
||||
expect(component.activeDisplayFields).toEqual(
|
||||
new Set([DocumentDisplayField.ASN])
|
||||
)
|
||||
component.activeDisplayFields = new Set([DocumentDisplayField.TITLE])
|
||||
component.saveDisplayFields()
|
||||
expect(
|
||||
JSON.parse(window.localStorage.getItem('document-list:displayFields'))
|
||||
).toEqual([DocumentDisplayField.TITLE])
|
||||
})
|
||||
|
||||
it('should support toggling display fields', () => {
|
||||
fixture.detectChanges()
|
||||
component.activeDisplayFields = new Set([DocumentDisplayField.ASN])
|
||||
component.activeDisplayFields = [DocumentDisplayField.ASN]
|
||||
component.toggleDisplayField(DocumentDisplayField.TITLE)
|
||||
expect(component.activeDisplayFields).toEqual(
|
||||
new Set([DocumentDisplayField.ASN, DocumentDisplayField.TITLE])
|
||||
)
|
||||
expect(component.activeDisplayFields).toEqual([
|
||||
DocumentDisplayField.ASN,
|
||||
DocumentDisplayField.TITLE,
|
||||
])
|
||||
component.toggleDisplayField(DocumentDisplayField.ASN)
|
||||
expect(component.activeDisplayFields).toEqual(
|
||||
new Set([DocumentDisplayField.TITLE])
|
||||
)
|
||||
expect(component.activeDisplayFields).toEqual([DocumentDisplayField.TITLE])
|
||||
})
|
||||
|
||||
it('should get custom field title', () => {
|
||||
|
@ -51,6 +51,7 @@ export class DocumentListComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
DocumentDisplayField = DocumentDisplayField
|
||||
DisplayMode = DisplayMode
|
||||
|
||||
constructor(
|
||||
public list: DocumentListViewService,
|
||||
@ -72,27 +73,12 @@ export class DocumentListComponent
|
||||
|
||||
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
|
||||
|
||||
displayMode: string = DisplayMode.SMALL_CARDS // largeCards, smallCards, table
|
||||
|
||||
_activeDisplayFields: Set<DocumentDisplayField | string> = new Set([
|
||||
DocumentDisplayField.TITLE,
|
||||
DocumentDisplayField.CORRESPONDENT,
|
||||
DocumentDisplayField.CREATED,
|
||||
DocumentDisplayField.TAGS,
|
||||
DocumentDisplayField.DOCUMENT_TYPE,
|
||||
DocumentDisplayField.STORAGE_PATH,
|
||||
DocumentDisplayField.NOTES,
|
||||
DocumentDisplayField.OWNER,
|
||||
DocumentDisplayField.ASN,
|
||||
DocumentDisplayField.SHARED,
|
||||
])
|
||||
|
||||
get activeDisplayFields(): Set<DocumentDisplayField | string> {
|
||||
return this._activeDisplayFields
|
||||
get activeDisplayFields(): DocumentDisplayField[] {
|
||||
return this.list.documentDisplayFields
|
||||
}
|
||||
|
||||
set activeDisplayFields(fields: Set<DocumentDisplayField | string>) {
|
||||
this._activeDisplayFields = fields
|
||||
set activeDisplayFields(fields: DocumentDisplayField[]) {
|
||||
this.list.documentDisplayFields = fields
|
||||
this.updateDisplayCustomFields()
|
||||
}
|
||||
activeDisplayCustomFields: Set<string> = new Set()
|
||||
@ -118,6 +104,11 @@ export class DocumentListComponent
|
||||
return (
|
||||
this.unmodifiedSavedView.sort_field !== this.list.sortField ||
|
||||
this.unmodifiedSavedView.sort_reverse !== this.list.sortReverse ||
|
||||
(this.unmodifiedSavedView.page_size &&
|
||||
this.unmodifiedSavedView.page_size !== this.list.pageSize) ||
|
||||
this.unmodifiedSavedView.display_mode !== this.list.displayMode ||
|
||||
this.unmodifiedSavedView.document_display_fields.join(',') !==
|
||||
this.activeDisplayFields.join(',') ||
|
||||
filterRulesDiffer(
|
||||
this.unmodifiedSavedView.filter_rules,
|
||||
this.list.filterRules
|
||||
@ -154,10 +145,6 @@ export class DocumentListComponent
|
||||
return this.list.sortReverse
|
||||
}
|
||||
|
||||
setSortField(field: string) {
|
||||
this.list.sortField = field
|
||||
}
|
||||
|
||||
onSort(event: SortEvent) {
|
||||
this.list.setSort(event.column, event.reverse)
|
||||
}
|
||||
@ -166,25 +153,15 @@ export class DocumentListComponent
|
||||
return this.list.selected.size > 0
|
||||
}
|
||||
|
||||
saveDisplayMode() {
|
||||
localStorage.setItem('document-list:displayMode', this.displayMode)
|
||||
}
|
||||
|
||||
saveDisplayFields() {
|
||||
localStorage.setItem(
|
||||
'document-list:displayFields',
|
||||
JSON.stringify(Array.from(this.activeDisplayFields))
|
||||
toggleDisplayField(field: DocumentDisplayField) {
|
||||
if (this.activeDisplayFields.includes(field)) {
|
||||
this.activeDisplayFields = this.activeDisplayFields.filter(
|
||||
(f) => f !== field
|
||||
)
|
||||
}
|
||||
|
||||
toggleDisplayField(field: string) {
|
||||
if (this.activeDisplayFields.has(field)) {
|
||||
this.activeDisplayFields.delete(field)
|
||||
} else {
|
||||
this.activeDisplayFields.add(field)
|
||||
this.activeDisplayFields = [...this.activeDisplayFields, field]
|
||||
}
|
||||
this.updateDisplayCustomFields()
|
||||
this.saveDisplayFields()
|
||||
}
|
||||
|
||||
public getDisplayCustomFieldTitle(field: string) {
|
||||
@ -194,16 +171,6 @@ export class DocumentListComponent
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (localStorage.getItem('document-list:displayMode') != null) {
|
||||
this.displayMode = localStorage.getItem('document-list:displayMode')
|
||||
}
|
||||
|
||||
if (localStorage.getItem('document-list:displayFields') != null) {
|
||||
this.activeDisplayFields = new Set(
|
||||
JSON.parse(localStorage.getItem('document-list:displayFields'))
|
||||
)
|
||||
}
|
||||
|
||||
this.consumerStatusService
|
||||
.onDocumentConsumptionFinished()
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
@ -228,6 +195,12 @@ export class DocumentListComponent
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!view.display_mode) {
|
||||
view.display_mode = this.list.displayMode
|
||||
}
|
||||
if (!view.document_display_fields) {
|
||||
view.document_display_fields = this.list.documentDisplayFields
|
||||
}
|
||||
this.unmodifiedSavedView = view
|
||||
this.list.activateSavedViewWithQueryParams(
|
||||
view,
|
||||
|
@ -12,13 +12,19 @@ import {
|
||||
FILTER_HAS_TAGS_ALL,
|
||||
FILTER_HAS_TAGS_ANY,
|
||||
} from '../data/filter-rule-type'
|
||||
import { SavedView } from '../data/saved-view'
|
||||
import {
|
||||
DEFAULT_DOCUMENT_DISPLAY_FIELDS,
|
||||
DisplayMode,
|
||||
DocumentDisplayField,
|
||||
SavedView,
|
||||
} from '../data/saved-view'
|
||||
import { FilterRule } from '../data/filter-rule'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { PermissionsGuard } from '../guards/permissions.guard'
|
||||
import { SettingsService } from './settings.service'
|
||||
import { SETTINGS_KEYS } from '../data/ui-settings'
|
||||
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
|
||||
|
||||
const documents = [
|
||||
{
|
||||
@ -213,7 +219,7 @@ describe('DocumentListViewService', () => {
|
||||
documentListViewService.loadFromQueryParams(convertToParamMap(params))
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/?page=${page}&page_size=${
|
||||
documentListViewService.currentPageSize
|
||||
documentListViewService.pageSize
|
||||
}&ordering=${reverse ? '-' : ''}${sort}&truncate_content=true`
|
||||
)
|
||||
expect(req.request.method).toEqual('GET')
|
||||
@ -231,7 +237,7 @@ describe('DocumentListViewService', () => {
|
||||
}
|
||||
documentListViewService.loadFromQueryParams(convertToParamMap(params))
|
||||
let req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
|
||||
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
|
||||
)
|
||||
expect(req.request.method).toEqual('GET')
|
||||
expect(documentListViewService.filterRules).toEqual([
|
||||
@ -249,7 +255,7 @@ describe('DocumentListViewService', () => {
|
||||
it('should use filter rules to update query params', () => {
|
||||
documentListViewService.filterRules = filterRules
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
|
||||
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
|
||||
)
|
||||
expect(req.request.method).toEqual('GET')
|
||||
})
|
||||
@ -257,7 +263,7 @@ describe('DocumentListViewService', () => {
|
||||
it('should support quick filter', () => {
|
||||
documentListViewService.quickFilter(filterRules)
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
|
||||
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
|
||||
)
|
||||
expect(req.request.method).toEqual('GET')
|
||||
})
|
||||
@ -280,7 +286,7 @@ describe('DocumentListViewService', () => {
|
||||
convertToParamMap(params)
|
||||
)
|
||||
let req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/?page=${page}&page_size=${documentListViewService.currentPageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
|
||||
`${environment.apiBaseUrl}documents/?page=${page}&page_size=${documentListViewService.pageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
|
||||
)
|
||||
expect(req.request.method).toEqual('GET')
|
||||
// reset the list
|
||||
@ -305,8 +311,7 @@ describe('DocumentListViewService', () => {
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||
)
|
||||
expect(documentListViewService.currentPage).toEqual(1)
|
||||
documentListViewService.currentPageSize = 3
|
||||
documentListViewService.reload()
|
||||
documentListViewService.pageSize = 3
|
||||
req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
|
||||
)
|
||||
@ -362,7 +367,10 @@ describe('DocumentListViewService', () => {
|
||||
.spyOn(documentListViewService, 'documents', 'get')
|
||||
.mockReturnValue(documents)
|
||||
expect(documentListViewService.currentPage).toEqual(1)
|
||||
documentListViewService.currentPageSize = 3
|
||||
documentListViewService.pageSize = 3
|
||||
httpTestingController.match(
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
|
||||
)
|
||||
jest
|
||||
.spyOn(documentListViewService, 'getLastPage')
|
||||
.mockReturnValue(Math.ceil(documents.length / 3))
|
||||
@ -410,7 +418,13 @@ describe('DocumentListViewService', () => {
|
||||
.spyOn(documentListViewService, 'documents', 'get')
|
||||
.mockReturnValue(documents)
|
||||
documentListViewService.currentPage = 2
|
||||
documentListViewService.currentPageSize = 3
|
||||
httpTestingController.match(
|
||||
`${environment.apiBaseUrl}documents/?page=2&page_size=50&ordering=-created&truncate_content=true`
|
||||
)
|
||||
documentListViewService.pageSize = 3
|
||||
httpTestingController.match(
|
||||
`${environment.apiBaseUrl}documents/?page=2&page_size=3&ordering=-created&truncate_content=true`
|
||||
)
|
||||
const reloadSpy = jest.spyOn(documentListViewService, 'reload')
|
||||
documentListViewService.getPrevious(1).subscribe({
|
||||
next: () => {},
|
||||
@ -426,8 +440,7 @@ describe('DocumentListViewService', () => {
|
||||
|
||||
it('should update page size from settings', () => {
|
||||
settingsService.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, 10)
|
||||
documentListViewService.updatePageSize()
|
||||
expect(documentListViewService.currentPageSize).toEqual(10)
|
||||
expect(documentListViewService.pageSize).toEqual(10)
|
||||
})
|
||||
|
||||
it('should support select a document', () => {
|
||||
@ -459,8 +472,7 @@ describe('DocumentListViewService', () => {
|
||||
})
|
||||
|
||||
it('should support select page', () => {
|
||||
documentListViewService.currentPageSize = 3
|
||||
documentListViewService.reload()
|
||||
documentListViewService.pageSize = 3
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
|
||||
)
|
||||
@ -544,4 +556,38 @@ describe('DocumentListViewService', () => {
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||
)
|
||||
})
|
||||
|
||||
it('should update default view state when display mode changes', () => {
|
||||
const localStorageSpy = jest.spyOn(localStorage, 'setItem')
|
||||
expect(documentListViewService.displayMode).toEqual(DisplayMode.SMALL_CARDS)
|
||||
documentListViewService.displayMode = DisplayMode.LARGE_CARDS
|
||||
expect(documentListViewService.displayMode).toEqual(DisplayMode.LARGE_CARDS)
|
||||
documentListViewService.displayMode = 'details' as any // legacy
|
||||
expect(documentListViewService.displayMode).toEqual(DisplayMode.TABLE)
|
||||
expect(localStorageSpy).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('should update default view state when display fields change', () => {
|
||||
const localStorageSpy = jest.spyOn(localStorage, 'setItem')
|
||||
documentListViewService.documentDisplayFields = [
|
||||
DocumentDisplayField.ADDED,
|
||||
DocumentDisplayField.TITLE,
|
||||
]
|
||||
expect(documentListViewService.documentDisplayFields).toEqual([
|
||||
DocumentDisplayField.ADDED,
|
||||
DocumentDisplayField.TITLE,
|
||||
])
|
||||
expect(localStorageSpy).toHaveBeenCalled()
|
||||
// reload triggered
|
||||
httpTestingController.match(
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||
)
|
||||
documentListViewService.documentDisplayFields = null
|
||||
httpTestingController.match(
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||
)
|
||||
expect(documentListViewService.documentDisplayFields).toEqual(
|
||||
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
@ -8,7 +8,12 @@ import {
|
||||
isFullTextFilterRule,
|
||||
} from '../utils/filter-rules'
|
||||
import { Document } from '../data/document'
|
||||
import { SavedView } from '../data/saved-view'
|
||||
import {
|
||||
DEFAULT_DOCUMENT_DISPLAY_FIELDS,
|
||||
DisplayMode,
|
||||
DocumentDisplayField,
|
||||
SavedView,
|
||||
} from '../data/saved-view'
|
||||
import { SETTINGS_KEYS } from '../data/ui-settings'
|
||||
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
|
||||
import { paramsFromViewState, paramsToViewState } from '../utils/query-params'
|
||||
@ -59,6 +64,21 @@ export interface ListViewState {
|
||||
* Contains the IDs of all selected documents.
|
||||
*/
|
||||
selected?: Set<number>
|
||||
|
||||
/**
|
||||
* The page size of the list view.
|
||||
*/
|
||||
pageSize?: number
|
||||
|
||||
/**
|
||||
* Display mode of the list view.
|
||||
*/
|
||||
displayMode?: DisplayMode
|
||||
|
||||
/**
|
||||
* The fields to display in the document list.
|
||||
*/
|
||||
documentDisplayFields?: DocumentDisplayField[]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,8 +100,6 @@ export class DocumentListViewService {
|
||||
|
||||
selectionData?: SelectionData
|
||||
|
||||
currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
|
||||
|
||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
private listViewStates: Map<number, ListViewState> = new Map()
|
||||
@ -176,6 +194,10 @@ export class DocumentListViewService {
|
||||
if (this._activeSavedViewId) {
|
||||
this.activeListViewState.title = view.name
|
||||
}
|
||||
this.activeListViewState.displayMode = view.display_mode
|
||||
this.activeListViewState.pageSize = view.page_size
|
||||
this.activeListViewState.documentDisplayFields =
|
||||
view.document_display_fields
|
||||
|
||||
this.reduceSelectionToFilter()
|
||||
|
||||
@ -220,7 +242,7 @@ export class DocumentListViewService {
|
||||
this.documentService
|
||||
.listFiltered(
|
||||
activeListViewState.currentPage,
|
||||
this.currentPageSize,
|
||||
activeListViewState.pageSize ?? this.pageSize,
|
||||
activeListViewState.sortField,
|
||||
activeListViewState.sortReverse,
|
||||
activeListViewState.filterRules,
|
||||
@ -362,6 +384,45 @@ export class DocumentListViewService {
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
set displayMode(mode: DisplayMode) {
|
||||
this.activeListViewState.displayMode = mode
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get displayMode(): DisplayMode {
|
||||
const mode = this.activeListViewState.displayMode ?? DisplayMode.SMALL_CARDS
|
||||
if (mode === ('details' as any)) {
|
||||
// legacy
|
||||
return DisplayMode.TABLE
|
||||
}
|
||||
return mode
|
||||
}
|
||||
|
||||
set pageSize(size: number) {
|
||||
this.activeListViewState.pageSize = size
|
||||
this.reload()
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
get pageSize(): number {
|
||||
return (
|
||||
this.activeListViewState.pageSize ??
|
||||
this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
|
||||
)
|
||||
}
|
||||
|
||||
get documentDisplayFields(): DocumentDisplayField[] {
|
||||
return (
|
||||
this.activeListViewState.documentDisplayFields ??
|
||||
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||
)
|
||||
}
|
||||
|
||||
set documentDisplayFields(fields: DocumentDisplayField[]) {
|
||||
this.activeListViewState.documentDisplayFields = fields
|
||||
this.saveDocumentListView()
|
||||
}
|
||||
|
||||
private saveDocumentListView() {
|
||||
if (this._activeSavedViewId == null) {
|
||||
let savedState: ListViewState = {
|
||||
@ -370,6 +431,8 @@ export class DocumentListViewService {
|
||||
filterRules: this.activeListViewState.filterRules,
|
||||
sortField: this.activeListViewState.sortField,
|
||||
sortReverse: this.activeListViewState.sortReverse,
|
||||
displayMode: this.activeListViewState.displayMode,
|
||||
documentDisplayFields: this.activeListViewState.documentDisplayFields,
|
||||
}
|
||||
localStorage.setItem(
|
||||
DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG,
|
||||
@ -385,7 +448,7 @@ export class DocumentListViewService {
|
||||
}
|
||||
|
||||
getLastPage(): number {
|
||||
return Math.ceil(this.collectionSize / this.currentPageSize)
|
||||
return Math.ceil(this.collectionSize / this.pageSize)
|
||||
}
|
||||
|
||||
hasNext(doc: number) {
|
||||
@ -452,13 +515,6 @@ export class DocumentListViewService {
|
||||
})
|
||||
}
|
||||
|
||||
updatePageSize() {
|
||||
let newPageSize = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
|
||||
if (newPageSize != this.currentPageSize) {
|
||||
this.currentPageSize = newPageSize
|
||||
}
|
||||
}
|
||||
|
||||
selectNone() {
|
||||
this.selected.clear()
|
||||
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
||||
|
Loading…
x
Reference in New Issue
Block a user