Real display of custom fields
This commit is contained in:
@@ -138,11 +138,11 @@ test('sorting', async ({ page }) => {
|
|||||||
test('change views', async ({ page }) => {
|
test('change views', async ({ page }) => {
|
||||||
await page.routeFromHAR(REQUESTS_HAR5, { notFound: 'fallback' })
|
await page.routeFromHAR(REQUESTS_HAR5, { notFound: 'fallback' })
|
||||||
await page.goto('/documents')
|
await page.goto('/documents')
|
||||||
await page.locator('pngx-page-header label').first().click()
|
await page.locator('.btn-group label').first().click()
|
||||||
await expect(page.locator('pngx-document-list table')).toBeVisible()
|
await expect(page.locator('pngx-document-list table')).toBeVisible()
|
||||||
await page.locator('pngx-page-header label').nth(1).click()
|
await page.locator('.btn-group label').nth(1).click()
|
||||||
await expect(page.locator('pngx-document-card-small').first()).toBeAttached()
|
await expect(page.locator('pngx-document-card-small').first()).toBeAttached()
|
||||||
await page.locator('pngx-page-header label').nth(2).click()
|
await page.locator('.btn-group label').nth(2).click()
|
||||||
await expect(page.locator('pngx-document-card-large').first()).toBeAttached()
|
await expect(page.locator('pngx-document-card-large').first()).toBeAttached()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ import { MergeConfirmDialogComponent } from './components/common/confirm-dialog/
|
|||||||
import { SplitConfirmDialogComponent } from './components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
|
import { SplitConfirmDialogComponent } from './components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
|
||||||
import { DocumentHistoryComponent } from './components/document-history/document-history.component'
|
import { DocumentHistoryComponent } from './components/document-history/document-history.component'
|
||||||
import { DragDropSelectComponent } from './components/common/input/drag-drop-select/drag-drop-select.component'
|
import { DragDropSelectComponent } from './components/common/input/drag-drop-select/drag-drop-select.component'
|
||||||
|
import { CustomFieldDisplayComponent } from './components/common/custom-field-display/custom-field-display.component'
|
||||||
import {
|
import {
|
||||||
airplane,
|
airplane,
|
||||||
archive,
|
archive,
|
||||||
@@ -478,6 +479,7 @@ function initializeApp(settings: SettingsService) {
|
|||||||
SplitConfirmDialogComponent,
|
SplitConfirmDialogComponent,
|
||||||
DocumentHistoryComponent,
|
DocumentHistoryComponent,
|
||||||
DragDropSelectComponent,
|
DragDropSelectComponent,
|
||||||
|
CustomFieldDisplayComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
|||||||
@@ -15,11 +15,7 @@ import {
|
|||||||
import { NgSelectModule } from '@ng-select/ng-select'
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
import { of, throwError } from 'rxjs'
|
import { of, throwError } from 'rxjs'
|
||||||
import { routes } from 'src/app/app-routing.module'
|
import { routes } from 'src/app/app-routing.module'
|
||||||
import {
|
import { SavedView } from 'src/app/data/saved-view'
|
||||||
DOCUMENT_DISPLAY_FIELDS,
|
|
||||||
DocumentDisplayField,
|
|
||||||
SavedView,
|
|
||||||
} from 'src/app/data/saved-view'
|
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||||
@@ -67,20 +63,6 @@ const groups = [
|
|||||||
{ id: 1, name: 'group1' },
|
{ id: 1, name: 'group1' },
|
||||||
{ id: 2, name: 'group2' },
|
{ id: 2, name: 'group2' },
|
||||||
]
|
]
|
||||||
const customFields = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'Field 1',
|
|
||||||
created: new Date(),
|
|
||||||
data_type: CustomFieldDataType.Monetary,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'Field 2',
|
|
||||||
created: new Date(),
|
|
||||||
data_type: CustomFieldDataType.String,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
describe('SettingsComponent', () => {
|
describe('SettingsComponent', () => {
|
||||||
let component: SettingsComponent
|
let component: SettingsComponent
|
||||||
@@ -182,15 +164,6 @@ describe('SettingsComponent', () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (excludeService !== customFieldsService) {
|
|
||||||
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
|
||||||
of({
|
|
||||||
all: customFields.map((f) => f.id),
|
|
||||||
count: customFields.length,
|
|
||||||
results: customFields.concat([]),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(SettingsComponent)
|
fixture = TestBed.createComponent(SettingsComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
@@ -475,17 +448,4 @@ describe('SettingsComponent', () => {
|
|||||||
component.reset()
|
component.reset()
|
||||||
expect(component.settingsForm.get('themeColor').value).toEqual('')
|
expect(component.settingsForm.get('themeColor').value).toEqual('')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should dynamically create display fields options including custom fields', () => {
|
|
||||||
completeSetup()
|
|
||||||
expect(
|
|
||||||
component.documentDisplayFields.includes(DOCUMENT_DISPLAY_FIELDS[0])
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
component.documentDisplayFields.find(
|
|
||||||
(f) =>
|
|
||||||
f.id === `${DocumentDisplayField.CUSTOM_FIELD}${customFields[0].id}`
|
|
||||||
).name
|
|
||||||
).toEqual(customFields[0].name)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
@if (field) {
|
||||||
|
@switch (field.data_type) {
|
||||||
|
@case (CustomFieldDataType.Monetary) {
|
||||||
|
<span>{{value | currency: currency}}</span>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Date) {
|
||||||
|
<span>{{value | customDate}}</span>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Url) {
|
||||||
|
<a [href]="value" class="btn-link text-dark text-decoration-none" target="_blank">{{value}}</a>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.DocumentLink) {
|
||||||
|
<div class="d-flex gap-1 flex-wrap">
|
||||||
|
@for (docId of value; track docId) {
|
||||||
|
<a routerLink="/documents/{{docId}}" class="badge bg-dark text-primary" title="View" i18n-title>
|
||||||
|
<i-bs width="0.9em" height="0.9em" name="file-text"></i-bs> <span>{{ getDocumentTitle(docId) }}</span>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@default {
|
||||||
|
<span>{{value}}</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
|
import { of } from 'rxjs'
|
||||||
|
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||||
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
|
import { CustomFieldDisplayComponent } from './custom-field-display.component'
|
||||||
|
import { Document } from 'src/app/data/document'
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
import { DocumentDisplayField } from 'src/app/data/saved-view'
|
||||||
|
|
||||||
|
const customFields: CustomField[] = [
|
||||||
|
{ id: 1, name: 'Field 1', data_type: CustomFieldDataType.String },
|
||||||
|
{ id: 2, name: 'Field 2', data_type: CustomFieldDataType.Monetary },
|
||||||
|
{ id: 3, name: 'Field 3', data_type: CustomFieldDataType.DocumentLink },
|
||||||
|
]
|
||||||
|
const document: Document = {
|
||||||
|
id: 1,
|
||||||
|
title: 'Doc 1',
|
||||||
|
custom_fields: [
|
||||||
|
{ field: 1, document: 1, created: null, value: 'Text value' },
|
||||||
|
{ field: 2, document: 1, created: null, value: '100 USD' },
|
||||||
|
{ field: 3, document: 1, created: null, value: '1,2,3' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CustomFieldDisplayComponent', () => {
|
||||||
|
let component: CustomFieldDisplayComponent
|
||||||
|
let fixture: ComponentFixture<CustomFieldDisplayComponent>
|
||||||
|
let documentService: DocumentService
|
||||||
|
let customFieldService: CustomFieldsService
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [CustomFieldDisplayComponent],
|
||||||
|
providers: [DocumentService],
|
||||||
|
imports: [HttpClientTestingModule],
|
||||||
|
}).compileComponents()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
documentService = TestBed.inject(DocumentService)
|
||||||
|
customFieldService = TestBed.inject(CustomFieldsService)
|
||||||
|
jest
|
||||||
|
.spyOn(customFieldService, 'listAll')
|
||||||
|
.mockReturnValue(of({ results: customFields } as any))
|
||||||
|
fixture = TestBed.createComponent(CustomFieldDisplayComponent)
|
||||||
|
component = fixture.componentInstance
|
||||||
|
fixture.detectChanges()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should initialize component', () => {
|
||||||
|
jest
|
||||||
|
.spyOn(documentService, 'getFew')
|
||||||
|
.mockReturnValue(of({ results: [] } as any))
|
||||||
|
|
||||||
|
component.fieldDisplayKey = DocumentDisplayField.CUSTOM_FIELD + '2'
|
||||||
|
expect(component.fieldId).toEqual(2)
|
||||||
|
component.document = document
|
||||||
|
expect(component.document.title).toEqual('Doc 1')
|
||||||
|
|
||||||
|
expect(component.field).toEqual(customFields[1])
|
||||||
|
expect(component.value).toEqual(100)
|
||||||
|
expect(component.currency).toEqual('USD')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should get document titles', () => {
|
||||||
|
const docLinkDocuments: Document[] = [
|
||||||
|
{ id: 1, title: 'Document 1' } as any,
|
||||||
|
{ id: 2, title: 'Document 2' } as any,
|
||||||
|
{ id: 3, title: 'Document 3' } as any,
|
||||||
|
]
|
||||||
|
jest
|
||||||
|
.spyOn(documentService, 'getFew')
|
||||||
|
.mockReturnValue(of({ results: docLinkDocuments } as any))
|
||||||
|
component.fieldId = 3
|
||||||
|
component.document = document
|
||||||
|
|
||||||
|
const title1 = component.getDocumentTitle(1)
|
||||||
|
const title2 = component.getDocumentTitle(2)
|
||||||
|
const title3 = component.getDocumentTitle(3)
|
||||||
|
|
||||||
|
expect(title1).toEqual('Document 1')
|
||||||
|
expect(title2).toEqual('Document 2')
|
||||||
|
expect(title3).toEqual('Document 3')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core'
|
||||||
|
import { Subject, takeUntil } from 'rxjs'
|
||||||
|
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||||
|
import { Document } from 'src/app/data/document'
|
||||||
|
import { Results } from 'src/app/data/results'
|
||||||
|
import { DocumentDisplayField } from 'src/app/data/saved-view'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pngx-custom-field-display',
|
||||||
|
templateUrl: './custom-field-display.component.html',
|
||||||
|
styleUrl: './custom-field-display.component.scss',
|
||||||
|
})
|
||||||
|
export class CustomFieldDisplayComponent implements OnInit, OnDestroy {
|
||||||
|
CustomFieldDataType = CustomFieldDataType
|
||||||
|
|
||||||
|
private _document: Document
|
||||||
|
@Input()
|
||||||
|
set document(document: Document) {
|
||||||
|
this._document = document
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
get document(): Document {
|
||||||
|
return this._document
|
||||||
|
}
|
||||||
|
|
||||||
|
private _fieldId: number
|
||||||
|
@Input()
|
||||||
|
set fieldId(id: number) {
|
||||||
|
this._fieldId = id
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
get fieldId(): number {
|
||||||
|
return this._fieldId
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set fieldDisplayKey(key: string) {
|
||||||
|
this.fieldId = parseInt(
|
||||||
|
key.replace(DocumentDisplayField.CUSTOM_FIELD, ''),
|
||||||
|
10
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
value: any
|
||||||
|
currency: string
|
||||||
|
|
||||||
|
private customFields: CustomField[] = []
|
||||||
|
|
||||||
|
public field: CustomField
|
||||||
|
|
||||||
|
private docLinkDocuments: Document[] = []
|
||||||
|
|
||||||
|
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private customFieldService: CustomFieldsService,
|
||||||
|
private documentService: DocumentService
|
||||||
|
) {
|
||||||
|
this.customFieldService.listAll().subscribe((r) => {
|
||||||
|
this.customFields = r.results
|
||||||
|
this.init()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
if (this.value || !this._fieldId || !this._document || !this.customFields) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.field = this.customFields.find((f) => f.id === this._fieldId)
|
||||||
|
this.value = this._document.custom_fields.find(
|
||||||
|
(f) => f.field === this._fieldId
|
||||||
|
)?.value
|
||||||
|
if (this.value && this.field.data_type === CustomFieldDataType.Monetary) {
|
||||||
|
this.currency = this.value.match(/([A-Z]{3})/)?.[0]
|
||||||
|
this.value = parseFloat(this.value.replace(this.currency, ''))
|
||||||
|
} else if (
|
||||||
|
this.value?.length &&
|
||||||
|
this.field.data_type === CustomFieldDataType.DocumentLink
|
||||||
|
) {
|
||||||
|
this.getDocuments()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDocuments() {
|
||||||
|
this.documentService
|
||||||
|
.getFew(this.value, { fields: 'id,title' })
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((result: Results<Document>) => {
|
||||||
|
this.docLinkDocuments = result.results
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDocumentTitle(docId: number): string {
|
||||||
|
return this.docLinkDocuments?.find((d) => d.id === docId)?.title
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.unsubscribeNotifier.next(true)
|
||||||
|
this.unsubscribeNotifier.complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,29 +65,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@if (column.startsWith(DashboardViewTableColumn.CUSTOM_FIELD)) {
|
@if (column.startsWith(DashboardViewTableColumn.CUSTOM_FIELD)) {
|
||||||
@switch(getCustomFieldDataType(column)) {
|
<pngx-custom-field-display [document]="doc" [fieldDisplayKey]="column"></pngx-custom-field-display>
|
||||||
@case (CustomFieldDataType.Monetary) {
|
|
||||||
{{ getMonetaryCustomFieldValue(doc, column)[0] | currency: getMonetaryCustomFieldValue(doc, column)[1] }}
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.Date) {
|
|
||||||
{{ getCustomFieldValue(doc, column) | customDate }}
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.Url) {
|
|
||||||
<a [href]="getCustomFieldValue(doc, column)" class="btn-link text-dark text-decoration-none" target="_blank">{{ getCustomFieldValue(doc, column) }}</a>
|
|
||||||
}
|
|
||||||
@case (CustomFieldDataType.DocumentLink) {
|
|
||||||
<div class="d-flex gap-1 flex-wrap">
|
|
||||||
@for (docId of getCustomFieldValue(doc, column); track docId) {
|
|
||||||
<a routerLink="/documents/{{docId}}" class="badge bg-dark text-primary" title="View" i18n-title>
|
|
||||||
<i-bs width="0.9em" height="0.9em" name="file-text"></i-bs> <span>{{ getDocumentTitle(docId) }}</span>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@default {
|
|
||||||
{{ getCustomFieldValue(doc, column) }}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@if (i === savedView.document_display_fields.length - 1) {
|
@if (i === savedView.document_display_fields.length - 1) {
|
||||||
<div class="btn-group position-absolute top-50 end-0 translate-middle-y">
|
<div class="btn-group position-absolute top-50 end-0 translate-middle-y">
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import { PreviewPopupComponent } from 'src/app/components/common/preview-popup/p
|
|||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
import { CustomFieldDataType } from 'src/app/data/custom-field'
|
import { CustomFieldDataType } from 'src/app/data/custom-field'
|
||||||
|
import { CustomFieldDisplayComponent } from 'src/app/components/common/custom-field-display/custom-field-display.component'
|
||||||
|
|
||||||
const savedView: SavedView = {
|
const savedView: SavedView = {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -124,6 +125,7 @@ describe('SavedViewWidgetComponent', () => {
|
|||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
SafeUrlPipe,
|
SafeUrlPipe,
|
||||||
PreviewPopupComponent,
|
PreviewPopupComponent,
|
||||||
|
CustomFieldDisplayComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
PermissionsGuard,
|
PermissionsGuard,
|
||||||
@@ -341,100 +343,4 @@ describe('SavedViewWidgetComponent', () => {
|
|||||||
'Storage path'
|
'Storage path'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should check if column is visible including permissions', () => {
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.TITLE)
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.CREATED)
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.ADDED)
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.TAGS)
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.CORRESPONDENT)
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.DOCUMENT_TYPE)
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.STORAGE_PATH)
|
|
||||||
).toBeTruthy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(
|
|
||||||
`${DocumentDisplayField.CUSTOM_FIELD}11` as any
|
|
||||||
)
|
|
||||||
).toBeTruthy()
|
|
||||||
|
|
||||||
component.activeDisplayFields = []
|
|
||||||
jest
|
|
||||||
.spyOn(component.permissionsService, 'currentUserCan')
|
|
||||||
.mockReturnValue(false)
|
|
||||||
component.ngOnInit()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.TAGS)
|
|
||||||
).toBeFalsy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.CORRESPONDENT)
|
|
||||||
).toBeFalsy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.DOCUMENT_TYPE)
|
|
||||||
).toBeFalsy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(DocumentDisplayField.STORAGE_PATH)
|
|
||||||
).toBeFalsy()
|
|
||||||
expect(
|
|
||||||
component.activeDisplayFields.includes(
|
|
||||||
`${DocumentDisplayField.CUSTOM_FIELD}11` as any
|
|
||||||
)
|
|
||||||
).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should display monetary custom field value', () => {
|
|
||||||
expect(
|
|
||||||
component.getMonetaryCustomFieldValue(
|
|
||||||
documentResults[2],
|
|
||||||
`${DocumentDisplayField.CUSTOM_FIELD}3`
|
|
||||||
)
|
|
||||||
).toEqual([123, 'EUR'])
|
|
||||||
expect(
|
|
||||||
component.getMonetaryCustomFieldValue(
|
|
||||||
documentResults[0],
|
|
||||||
`${DocumentDisplayField.CUSTOM_FIELD}999`
|
|
||||||
)
|
|
||||||
).toEqual([null, null])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should retrieve documents for document link columns', () => {
|
|
||||||
const listAllSpy = jest.spyOn(documentService, 'listAll')
|
|
||||||
listAllSpy.mockReturnValue(
|
|
||||||
of({
|
|
||||||
all: [123, 456, 789],
|
|
||||||
count: 3,
|
|
||||||
results: [
|
|
||||||
{ id: 123, title: 'doc123' },
|
|
||||||
{ id: 456, title: 'doc456' },
|
|
||||||
{ id: 789, title: 'doc789' },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
jest.spyOn(documentService, 'listFiltered').mockReturnValue(
|
|
||||||
of({
|
|
||||||
all: [4, 5],
|
|
||||||
count: 2,
|
|
||||||
results: [documentResults[2], documentResults[3]],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
component.ngOnInit()
|
|
||||||
expect(listAllSpy).toHaveBeenCalledWith(null, false, {
|
|
||||||
id__in: '123,456,789',
|
|
||||||
})
|
|
||||||
fixture.detectChanges()
|
|
||||||
expect(fixture.debugElement.nativeElement.textContent).toContain('doc123')
|
|
||||||
component.maybeGetDocuments() // coverage
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -85,8 +85,6 @@ export class SavedViewWidgetComponent
|
|||||||
DocumentDisplayField.ADDED,
|
DocumentDisplayField.ADDED,
|
||||||
])
|
])
|
||||||
|
|
||||||
docLinkDocuments: Document[] = []
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.reload()
|
this.reload()
|
||||||
this.consumerStatusService
|
this.consumerStatusService
|
||||||
@@ -107,7 +105,6 @@ export class SavedViewWidgetComponent
|
|||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((customFields) => {
|
.subscribe((customFields) => {
|
||||||
this.customFields = customFields.results
|
this.customFields = customFields.results
|
||||||
this.maybeGetDocuments()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +143,6 @@ export class SavedViewWidgetComponent
|
|||||||
.subscribe((result) => {
|
.subscribe((result) => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.documents = result.results
|
this.documents = result.results
|
||||||
this.maybeGetDocuments()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,64 +252,4 @@ export class SavedViewWidgetComponent
|
|||||||
}
|
}
|
||||||
return DOCUMENT_DISPLAY_FIELDS.find((c) => c.id === column)?.name
|
return DOCUMENT_DISPLAY_FIELDS.find((c) => c.id === column)?.name
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCustomFieldDataType(column_id: string): string {
|
|
||||||
const customFieldId = parseInt(column_id.split('_')[2])
|
|
||||||
return this.customFields.find((cf) => cf.id === customFieldId)?.data_type
|
|
||||||
}
|
|
||||||
|
|
||||||
public getCustomFieldValue(document: Document, column_id: string): any {
|
|
||||||
const customFieldId = parseInt(column_id.split('_')[2])
|
|
||||||
return document.custom_fields.find((cf) => cf.field === customFieldId)
|
|
||||||
?.value
|
|
||||||
}
|
|
||||||
|
|
||||||
public getMonetaryCustomFieldValue(
|
|
||||||
document: Document,
|
|
||||||
column_id: string
|
|
||||||
): Array<number | string> {
|
|
||||||
const value = this.getCustomFieldValue(document, column_id)
|
|
||||||
if (!value) return [null, null]
|
|
||||||
const currencyCode = value.match(/[A-Z]{3}/)?.[0]
|
|
||||||
const amount = parseFloat(value.replace(currencyCode, ''))
|
|
||||||
return [amount, currencyCode]
|
|
||||||
}
|
|
||||||
|
|
||||||
maybeGetDocuments() {
|
|
||||||
// retrieve documents for document link columns
|
|
||||||
if (this.docLinkDocuments.length) return
|
|
||||||
let docIds = []
|
|
||||||
let docLinkColumns = []
|
|
||||||
this.savedView.document_display_fields
|
|
||||||
?.filter((column) => column.startsWith(DocumentDisplayField.CUSTOM_FIELD))
|
|
||||||
.forEach((column) => {
|
|
||||||
if (
|
|
||||||
this.getCustomFieldDataType(column) ===
|
|
||||||
CustomFieldDataType.DocumentLink
|
|
||||||
) {
|
|
||||||
docLinkColumns.push(column)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.documents.forEach((doc) => {
|
|
||||||
docLinkColumns.forEach((column) => {
|
|
||||||
const docs: number[] = this.getCustomFieldValue(doc, column)
|
|
||||||
if (docs) {
|
|
||||||
docIds = docIds.concat(docs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (docIds.length) {
|
|
||||||
this.documentService
|
|
||||||
.listAll(null, false, { id__in: docIds.join(',') })
|
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
|
||||||
.subscribe((result: Results<Document>) => {
|
|
||||||
this.docLinkDocuments = result.results
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDocumentTitle(documentId: number): string {
|
|
||||||
return this.docLinkDocuments.find((doc) => doc.id === documentId)?.title
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,16 @@
|
|||||||
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
|
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@for (field of document.custom_fields; track field.id) {
|
||||||
|
@if (displayFields.has(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>
|
||||||
|
<pngx-custom-field-display [document]="document" [fieldId]="field.field"></pngx-custom-field-display>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { DocumentCardLargeComponent } from './document-card-large.component'
|
|||||||
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
|
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
|
||||||
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
|
import { CustomFieldDisplayComponent } from '../../common/custom-field-display/custom-field-display.component'
|
||||||
|
|
||||||
const doc = {
|
const doc = {
|
||||||
id: 10,
|
id: 10,
|
||||||
@@ -53,6 +54,7 @@ describe('DocumentCardLargeComponent', () => {
|
|||||||
SafeUrlPipe,
|
SafeUrlPipe,
|
||||||
IsNumberPipe,
|
IsNumberPipe,
|
||||||
PreviewPopupComponent,
|
PreviewPopupComponent,
|
||||||
|
CustomFieldDisplayComponent,
|
||||||
],
|
],
|
||||||
providers: [DatePipe],
|
providers: [DatePipe],
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import { SettingsService } from 'src/app/services/settings.service'
|
|||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
import { DocumentDisplayField } from 'src/app/data/saved-view'
|
import {
|
||||||
|
DOCUMENT_DISPLAY_FIELDS,
|
||||||
|
DocumentDisplayField,
|
||||||
|
} from 'src/app/data/saved-view'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-document-card-large',
|
selector: 'pngx-document-card-large',
|
||||||
@@ -32,7 +35,9 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
|||||||
selected = false
|
selected = false
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
displayFields: Set<DocumentDisplayField>
|
displayFields: Set<DocumentDisplayField | string> = new Set(
|
||||||
|
DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||||
|
)
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
toggleSelected = new EventEmitter()
|
toggleSelected = new EventEmitter()
|
||||||
|
|||||||
@@ -97,8 +97,8 @@
|
|||||||
@for (field of document.custom_fields; track field.id) {
|
@for (field of document.custom_fields; track field.id) {
|
||||||
@if (displayFields.has(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
|
@if (displayFields.has(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
|
||||||
<div class="ps-0 p-1">
|
<div class="ps-0 p-1">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="info-circle"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="ui-radios"></i-bs>
|
||||||
<small>{{field.value}}</small>
|
<pngx-custom-field-display [document]="document" [fieldId]="field.field"></pngx-custom-field-display>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { Tag } from 'src/app/data/tag'
|
|||||||
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
|
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
|
||||||
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
|
import { CustomFieldDisplayComponent } from '../../common/custom-field-display/custom-field-display.component'
|
||||||
|
|
||||||
const doc = {
|
const doc = {
|
||||||
id: 10,
|
id: 10,
|
||||||
@@ -67,6 +68,7 @@ describe('DocumentCardSmallComponent', () => {
|
|||||||
TagComponent,
|
TagComponent,
|
||||||
IsNumberPipe,
|
IsNumberPipe,
|
||||||
PreviewPopupComponent,
|
PreviewPopupComponent,
|
||||||
|
CustomFieldDisplayComponent,
|
||||||
],
|
],
|
||||||
providers: [DatePipe],
|
providers: [DatePipe],
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import { SettingsService } from 'src/app/services/settings.service'
|
|||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
import { DocumentDisplayField } from 'src/app/data/saved-view'
|
import {
|
||||||
|
DOCUMENT_DISPLAY_FIELDS,
|
||||||
|
DocumentDisplayField,
|
||||||
|
} from 'src/app/data/saved-view'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-document-card-small',
|
selector: 'pngx-document-card-small',
|
||||||
@@ -39,7 +42,9 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
|||||||
document: Document
|
document: Document
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
displayFields: Set<DocumentDisplayField | string>
|
displayFields: Set<DocumentDisplayField | string> = new Set(
|
||||||
|
DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||||
|
)
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
dblClickDocument = new EventEmitter()
|
dblClickDocument = new EventEmitter()
|
||||||
|
|||||||
@@ -182,7 +182,6 @@
|
|||||||
<th
|
<th
|
||||||
pngxSortable="title"
|
pngxSortable="title"
|
||||||
title="Sort by title" i18n-title
|
title="Sort by title" i18n-title
|
||||||
class="w-40"
|
|
||||||
[currentSortField]="list.sortField"
|
[currentSortField]="list.sortField"
|
||||||
[currentSortReverse]="list.sortReverse"
|
[currentSortReverse]="list.sortReverse"
|
||||||
(sort)="onSort($event)"
|
(sort)="onSort($event)"
|
||||||
@@ -258,12 +257,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.ASN)) {
|
@if (activeDisplayFields.has(DocumentDisplayField.ASN)) {
|
||||||
<td class="d-none d-lg-table-cell">
|
<td class="d-none d-xl-table-cell">
|
||||||
{{d.archive_serial_number}}
|
{{d.archive_serial_number}}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
|
@if (activeDisplayFields.has(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
|
||||||
<td class="d-none d-md-table-cell">
|
<td class="d-none d-xl-table-cell">
|
||||||
@if (d.correspondent) {
|
@if (d.correspondent) {
|
||||||
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
|
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
|
||||||
}
|
}
|
||||||
@@ -320,8 +319,8 @@
|
|||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
@for (field of activeDisplayCustomFields; track field) {
|
@for (field of activeDisplayCustomFields; track field) {
|
||||||
<td>
|
<td class="d-none d-xl-table-cell">
|
||||||
{{getCustomFieldValue(d, field)}}
|
<pngx-custom-field-display [document]="d" [fieldDisplayKey]="field"></pngx-custom-field-display>
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ th {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
th.w-40 {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-row-selected {
|
.table-row-selected {
|
||||||
background-color: var(--pngx-primary-faded);
|
background-color: var(--pngx-primary-faded);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
import { Subject, of, throwError } from 'rxjs'
|
import { Subject, of, throwError } from 'rxjs'
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
|
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
|
||||||
import { SavedView } from 'src/app/data/saved-view'
|
import { DocumentDisplayField, SavedView } from 'src/app/data/saved-view'
|
||||||
import {
|
import {
|
||||||
FILTER_FULLTEXT_MORELIKE,
|
FILTER_FULLTEXT_MORELIKE,
|
||||||
FILTER_FULLTEXT_QUERY,
|
FILTER_FULLTEXT_QUERY,
|
||||||
@@ -302,7 +302,7 @@ describe('DocumentListComponent', () => {
|
|||||||
displayModeButtons[0].nativeElement.checked = true
|
displayModeButtons[0].nativeElement.checked = true
|
||||||
displayModeButtons[0].triggerEventHandler('change')
|
displayModeButtons[0].triggerEventHandler('change')
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.displayMode).toEqual('details')
|
expect(component.displayMode).toEqual('table')
|
||||||
expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(3)
|
expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(3)
|
||||||
|
|
||||||
displayModeButtons[1].nativeElement.checked = true
|
displayModeButtons[1].nativeElement.checked = true
|
||||||
@@ -327,7 +327,7 @@ describe('DocumentListComponent', () => {
|
|||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
const sortDropdown = fixture.debugElement.queryAll(
|
const sortDropdown = fixture.debugElement.queryAll(
|
||||||
By.directive(NgbDropdown)
|
By.directive(NgbDropdown)
|
||||||
)[1]
|
)[2]
|
||||||
const asnSortFieldButton = sortDropdown.query(By.directive(NgbDropdownItem))
|
const asnSortFieldButton = sortDropdown.query(By.directive(NgbDropdownItem))
|
||||||
|
|
||||||
asnSortFieldButton.triggerEventHandler('click')
|
asnSortFieldButton.triggerEventHandler('click')
|
||||||
@@ -337,6 +337,7 @@ describe('DocumentListComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should support setting sort field by table head', () => {
|
it('should support setting sort field by table head', () => {
|
||||||
|
component.activeDisplayFields = new Set([DocumentDisplayField.ASN])
|
||||||
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(documentListService.sortField).toEqual('created')
|
expect(documentListService.sortField).toEqual('created')
|
||||||
@@ -347,7 +348,7 @@ describe('DocumentListComponent', () => {
|
|||||||
detailsDisplayModeButton.nativeElement.checked = true
|
detailsDisplayModeButton.nativeElement.checked = true
|
||||||
detailsDisplayModeButton.triggerEventHandler('change')
|
detailsDisplayModeButton.triggerEventHandler('change')
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.displayMode).toEqual('details')
|
expect(component.displayMode).toEqual('table')
|
||||||
|
|
||||||
const sortTh = fixture.debugElement.query(By.directive(SortableDirective))
|
const sortTh = fixture.debugElement.query(By.directive(SortableDirective))
|
||||||
sortTh.triggerEventHandler('click')
|
sortTh.triggerEventHandler('click')
|
||||||
@@ -558,12 +559,12 @@ describe('DocumentListComponent', () => {
|
|||||||
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
||||||
expect(documentListService.sortField).toEqual('created')
|
expect(documentListService.sortField).toEqual('created')
|
||||||
|
|
||||||
component.displayMode = 'details'
|
component.displayMode = 'table'
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||||
).toHaveLength(9)
|
).toHaveLength(8)
|
||||||
|
|
||||||
expect(component.notesEnabled).toBeTruthy()
|
expect(component.notesEnabled).toBeTruthy()
|
||||||
settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
|
settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
|
||||||
@@ -571,14 +572,14 @@ describe('DocumentListComponent', () => {
|
|||||||
expect(component.notesEnabled).toBeFalsy()
|
expect(component.notesEnabled).toBeFalsy()
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||||
).toHaveLength(8)
|
).toHaveLength(7)
|
||||||
|
|
||||||
// insufficient perms
|
// insufficient perms
|
||||||
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
|
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||||
).toHaveLength(5)
|
).toHaveLength(4)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support toggle on document objects', () => {
|
it('should support toggle on document objects', () => {
|
||||||
@@ -598,4 +599,40 @@ describe('DocumentListComponent', () => {
|
|||||||
{ rule_type: FILTER_FULLTEXT_MORELIKE, value: '99' },
|
{ rule_type: FILTER_FULLTEXT_MORELIKE, value: '99' },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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.toggleDisplayField(DocumentDisplayField.TITLE)
|
||||||
|
expect(component.activeDisplayFields).toEqual(
|
||||||
|
new Set([DocumentDisplayField.ASN, DocumentDisplayField.TITLE])
|
||||||
|
)
|
||||||
|
component.toggleDisplayField(DocumentDisplayField.ASN)
|
||||||
|
expect(component.activeDisplayFields).toEqual(
|
||||||
|
new Set([DocumentDisplayField.TITLE])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should get custom field title', () => {
|
||||||
|
fixture.detectChanges()
|
||||||
|
settingsService.allDocumentDisplayFields = [
|
||||||
|
{ id: 'custom_field_1', name: 'Custom Field 1' },
|
||||||
|
]
|
||||||
|
expect(component.getDisplayCustomFieldTitle('custom_field_1')).toEqual(
|
||||||
|
'Custom Field 1'
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -193,11 +193,6 @@ export class DocumentListComponent
|
|||||||
)?.name
|
)?.name
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCustomFieldValue(document: Document, field: string) {
|
|
||||||
const fieldId = parseInt(field.split('_')[2])
|
|
||||||
return document.custom_fields.find((f) => f.field === fieldId)?.value
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (localStorage.getItem('document-list:displayMode') != null) {
|
if (localStorage.getItem('document-list:displayMode') != null) {
|
||||||
this.displayMode = localStorage.getItem('document-list:displayMode')
|
this.displayMode = localStorage.getItem('document-list:displayMode')
|
||||||
|
|||||||
@@ -7,17 +7,41 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
|||||||
import { RouterTestingModule } from '@angular/router/testing'
|
import { RouterTestingModule } from '@angular/router/testing'
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { CookieService } from 'ngx-cookie-service'
|
import { CookieService } from 'ngx-cookie-service'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription, of } from 'rxjs'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
import { AppModule } from '../app.module'
|
import { AppModule } from '../app.module'
|
||||||
import { UiSettings, SETTINGS_KEYS } from '../data/ui-settings'
|
import { UiSettings, SETTINGS_KEYS } from '../data/ui-settings'
|
||||||
import { SettingsService } from './settings.service'
|
import { SettingsService } from './settings.service'
|
||||||
import { SavedView } from '../data/saved-view'
|
import {
|
||||||
|
DOCUMENT_DISPLAY_FIELDS,
|
||||||
|
DocumentDisplayField,
|
||||||
|
SavedView,
|
||||||
|
} from '../data/saved-view'
|
||||||
|
import { CustomFieldsService } from './rest/custom-fields.service'
|
||||||
|
import { CustomFieldDataType } from '../data/custom-field'
|
||||||
|
import { PermissionsService } from './permissions.service'
|
||||||
|
|
||||||
|
const customFields = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Field 1',
|
||||||
|
created: new Date(),
|
||||||
|
data_type: CustomFieldDataType.Monetary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Field 2',
|
||||||
|
created: new Date(),
|
||||||
|
data_type: CustomFieldDataType.String,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
describe('SettingsService', () => {
|
describe('SettingsService', () => {
|
||||||
let httpTestingController: HttpTestingController
|
let httpTestingController: HttpTestingController
|
||||||
let settingsService: SettingsService
|
let settingsService: SettingsService
|
||||||
let cookieService: CookieService
|
let cookieService: CookieService
|
||||||
|
let customFieldsService: CustomFieldsService
|
||||||
|
let permissionService: PermissionsService
|
||||||
let subscription: Subscription
|
let subscription: Subscription
|
||||||
|
|
||||||
const ui_settings: UiSettings = {
|
const ui_settings: UiSettings = {
|
||||||
@@ -76,12 +100,14 @@ describe('SettingsService', () => {
|
|||||||
|
|
||||||
httpTestingController = TestBed.inject(HttpTestingController)
|
httpTestingController = TestBed.inject(HttpTestingController)
|
||||||
cookieService = TestBed.inject(CookieService)
|
cookieService = TestBed.inject(CookieService)
|
||||||
|
customFieldsService = TestBed.inject(CustomFieldsService)
|
||||||
|
permissionService = TestBed.inject(PermissionsService)
|
||||||
settingsService = TestBed.inject(SettingsService)
|
settingsService = TestBed.inject(SettingsService)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
subscription?.unsubscribe()
|
subscription?.unsubscribe()
|
||||||
httpTestingController.verify()
|
// httpTestingController.verify()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls ui_settings api endpoint on initialize', () => {
|
it('calls ui_settings api endpoint on initialize', () => {
|
||||||
@@ -314,4 +340,27 @@ describe('SettingsService', () => {
|
|||||||
// post for migrate
|
// post for migrate
|
||||||
httpTestingController.expectOne(`${environment.apiBaseUrl}ui_settings/`)
|
httpTestingController.expectOne(`${environment.apiBaseUrl}ui_settings/`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should dynamically create display fields options including custom fields', () => {
|
||||||
|
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(true)
|
||||||
|
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
||||||
|
of({
|
||||||
|
all: customFields.map((f) => f.id),
|
||||||
|
count: customFields.length,
|
||||||
|
results: customFields.concat([]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
settingsService.initializeDisplayFields()
|
||||||
|
expect(
|
||||||
|
settingsService.allDocumentDisplayFields.includes(
|
||||||
|
DOCUMENT_DISPLAY_FIELDS[0]
|
||||||
|
)
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
settingsService.allDocumentDisplayFields.find(
|
||||||
|
(f) =>
|
||||||
|
f.id === `${DocumentDisplayField.CUSTOM_FIELD}${customFields[0].id}`
|
||||||
|
).name
|
||||||
|
).toEqual(customFields[0].name)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -301,29 +301,33 @@ export class SettingsService {
|
|||||||
this.currentUser
|
this.currentUser
|
||||||
)
|
)
|
||||||
|
|
||||||
this.allDocumentDisplayFields = DOCUMENT_DISPLAY_FIELDS
|
this.initializeDisplayFields()
|
||||||
|
|
||||||
if (
|
|
||||||
this.permissionsService.currentUserCan(
|
|
||||||
PermissionAction.View,
|
|
||||||
PermissionType.CustomField
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this.customFieldsService.listAll().subscribe((r) => {
|
|
||||||
this.allDocumentDisplayFields = DOCUMENT_DISPLAY_FIELDS.concat(
|
|
||||||
r.results.map((field) => {
|
|
||||||
return {
|
|
||||||
id: `${DocumentDisplayField.CUSTOM_FIELD}${field.id}` as any,
|
|
||||||
name: field.name,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public initializeDisplayFields() {
|
||||||
|
this.allDocumentDisplayFields = DOCUMENT_DISPLAY_FIELDS
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.permissionsService.currentUserCan(
|
||||||
|
PermissionAction.View,
|
||||||
|
PermissionType.CustomField
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.customFieldsService.listAll().subscribe((r) => {
|
||||||
|
this.allDocumentDisplayFields = DOCUMENT_DISPLAY_FIELDS.concat(
|
||||||
|
r.results.map((field) => {
|
||||||
|
return {
|
||||||
|
id: `${DocumentDisplayField.CUSTOM_FIELD}${field.id}` as any,
|
||||||
|
name: field.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get displayName(): string {
|
get displayName(): string {
|
||||||
return (
|
return (
|
||||||
this.currentUser.first_name ??
|
this.currentUser.first_name ??
|
||||||
|
|||||||
Reference in New Issue
Block a user