Update frontend for new custom_fields API spec
This commit is contained in:
parent
99d1de543c
commit
59a08ff2a2
@ -92,7 +92,7 @@ describe('CustomFieldsDropdownComponent', () => {
|
|||||||
CustomFieldsDropdownComponent.prototype as any,
|
CustomFieldsDropdownComponent.prototype as any,
|
||||||
'updateUnusedFields'
|
'updateUnusedFields'
|
||||||
)
|
)
|
||||||
component.existingFields = [{ field: fields[1] } as any]
|
component.existingFields = [{ field: fields[1].id } as any]
|
||||||
component.onOpenClose()
|
component.onOpenClose()
|
||||||
expect(updateSpy).toHaveBeenCalled()
|
expect(updateSpy).toHaveBeenCalled()
|
||||||
expect(component.unusedFields).toEqual([fields[0]])
|
expect(component.unusedFields).toEqual([fields[0]])
|
||||||
|
@ -34,7 +34,10 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
|
|||||||
existingFields: PaperlessCustomFieldInstance[] = []
|
existingFields: PaperlessCustomFieldInstance[] = []
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
added = new EventEmitter()
|
added: EventEmitter<PaperlessCustomField> = new EventEmitter()
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
created: EventEmitter<PaperlessCustomField> = new EventEmitter()
|
||||||
|
|
||||||
private customFields: PaperlessCustomField[] = []
|
private customFields: PaperlessCustomField[] = []
|
||||||
public unusedFields: PaperlessCustomField[]
|
public unusedFields: PaperlessCustomField[]
|
||||||
@ -84,9 +87,18 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCustomFieldFromInstance(
|
||||||
|
instance: PaperlessCustomFieldInstance
|
||||||
|
): PaperlessCustomField {
|
||||||
|
return this.customFields.find((f) => f.id === instance.field)
|
||||||
|
}
|
||||||
|
|
||||||
private updateUnusedFields() {
|
private updateUnusedFields() {
|
||||||
this.unusedFields = this.customFields.filter(
|
this.unusedFields = this.customFields.filter(
|
||||||
(f) => !this.existingFields?.find((e) => e.field.id === f.id)
|
(f) =>
|
||||||
|
!this.existingFields?.find(
|
||||||
|
(e) => this.getCustomFieldFromInstance(e)?.id === f.id
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +120,7 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
|
|||||||
this.toastService.showInfo($localize`Saved field "${newField.name}".`)
|
this.toastService.showInfo($localize`Saved field "${newField.name}".`)
|
||||||
this.customFieldsService.clearCache()
|
this.customFieldsService.clearCache()
|
||||||
this.getFields()
|
this.getFields()
|
||||||
|
this.created.emit(newField)
|
||||||
})
|
})
|
||||||
modal.componentInstance.failed
|
modal.componentInstance.failed
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
[documentId]="documentId"
|
[documentId]="documentId"
|
||||||
[disabled]="!userIsOwner"
|
[disabled]="!userIsOwner"
|
||||||
[existingFields]="document?.custom_fields"
|
[existingFields]="document?.custom_fields"
|
||||||
|
(created)="refreshCustomFields()"
|
||||||
(added)="addField($event)">
|
(added)="addField($event)">
|
||||||
</pngx-custom-fields-dropdown>
|
</pngx-custom-fields-dropdown>
|
||||||
|
|
||||||
@ -94,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-x-scroll" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
|
<ul ngbNav #nav="ngbNav" class="nav-underline flex-nowrap flex-md-wrap overflow-auto" (navChange)="onNavChange($event)" [(activeId)]="activeNavID">
|
||||||
<li [ngbNavItem]="DocumentDetailNavIDs.Details">
|
<li [ngbNavItem]="DocumentDetailNavIDs.Details">
|
||||||
<a ngbNavLink i18n>Details</a>
|
<a ngbNavLink i18n>Details</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
@ -113,11 +114,11 @@
|
|||||||
<ng-container *ngFor="let fieldInstance of document?.custom_fields; let i = index">
|
<ng-container *ngFor="let fieldInstance of document?.custom_fields; let i = index">
|
||||||
<div [formGroup]="customFieldFormFields.controls[i]">
|
<div [formGroup]="customFieldFormFields.controls[i]">
|
||||||
<!-- TODO: boolean + URL -->
|
<!-- TODO: boolean + URL -->
|
||||||
<pngx-input-text formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.String" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-text>
|
<pngx-input-text formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.String" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-text>
|
||||||
<pngx-input-date formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Date" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-date>
|
<pngx-input-date formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Date" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-date>
|
||||||
<pngx-input-number formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Integer" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false"></pngx-input-number>
|
<pngx-input-number formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Integer" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true" [showAdd]="false"></pngx-input-number>
|
||||||
<pngx-input-check formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Boolean" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-check>
|
<pngx-input-check formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Boolean" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-check>
|
||||||
<pngx-input-url formControlName="value" *ngIf="fieldInstance.field.data_type === PaperlessCustomFieldDataType.Url" [title]="fieldInstance.field.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-url>
|
<pngx-input-url formControlName="value" *ngIf="getCustomFieldFromInstance(fieldInstance)?.data_type === PaperlessCustomFieldDataType.Url" [title]="getCustomFieldFromInstance(fieldInstance)?.name" [removable]="true" (removed)="removeField(fieldInstance)" [horizontal]="true"></pngx-input-url>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,6 +69,7 @@ import { DocumentDetailComponent } from './document-detail.component'
|
|||||||
import { ShareLinksDropdownComponent } from '../common/share-links-dropdown/share-links-dropdown.component'
|
import { ShareLinksDropdownComponent } from '../common/share-links-dropdown/share-links-dropdown.component'
|
||||||
import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component'
|
import { CustomFieldsDropdownComponent } from '../common/custom-fields-dropdown/custom-fields-dropdown.component'
|
||||||
import { PaperlessCustomFieldDataType } from 'src/app/data/paperless-custom-field'
|
import { PaperlessCustomFieldDataType } from 'src/app/data/paperless-custom-field'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
|
||||||
const doc: PaperlessDocument = {
|
const doc: PaperlessDocument = {
|
||||||
id: 3,
|
id: 3,
|
||||||
@ -98,12 +99,7 @@ const doc: PaperlessDocument = {
|
|||||||
],
|
],
|
||||||
custom_fields: [
|
custom_fields: [
|
||||||
{
|
{
|
||||||
field: {
|
field: 0,
|
||||||
id: 0,
|
|
||||||
name: 'Field 1',
|
|
||||||
data_type: PaperlessCustomFieldDataType.String,
|
|
||||||
created: new Date(),
|
|
||||||
},
|
|
||||||
document: 3,
|
document: 3,
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
value: 'custom foo bar',
|
value: 'custom foo bar',
|
||||||
@ -111,6 +107,21 @@ const doc: PaperlessDocument = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const customFields = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: 'Field 1',
|
||||||
|
data_type: PaperlessCustomFieldDataType.String,
|
||||||
|
created: new Date(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Custom Field 2',
|
||||||
|
data_type: PaperlessCustomFieldDataType.Integer,
|
||||||
|
created: new Date(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
describe('DocumentDetailComponent', () => {
|
describe('DocumentDetailComponent', () => {
|
||||||
let component: DocumentDetailComponent
|
let component: DocumentDetailComponent
|
||||||
let fixture: ComponentFixture<DocumentDetailComponent>
|
let fixture: ComponentFixture<DocumentDetailComponent>
|
||||||
@ -122,6 +133,7 @@ describe('DocumentDetailComponent', () => {
|
|||||||
let toastService: ToastService
|
let toastService: ToastService
|
||||||
let documentListViewService: DocumentListViewService
|
let documentListViewService: DocumentListViewService
|
||||||
let settingsService: SettingsService
|
let settingsService: SettingsService
|
||||||
|
let customFieldsService: CustomFieldsService
|
||||||
|
|
||||||
let currentUserCan = true
|
let currentUserCan = true
|
||||||
let currentUserHasObjectPermissions = true
|
let currentUserHasObjectPermissions = true
|
||||||
@ -215,6 +227,7 @@ describe('DocumentDetailComponent', () => {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
CustomFieldsService,
|
||||||
{
|
{
|
||||||
provide: PermissionsService,
|
provide: PermissionsService,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -250,6 +263,7 @@ describe('DocumentDetailComponent', () => {
|
|||||||
toastService = TestBed.inject(ToastService)
|
toastService = TestBed.inject(ToastService)
|
||||||
documentListViewService = TestBed.inject(DocumentListViewService)
|
documentListViewService = TestBed.inject(DocumentListViewService)
|
||||||
settingsService = TestBed.inject(SettingsService)
|
settingsService = TestBed.inject(SettingsService)
|
||||||
|
customFieldsService = TestBed.inject(CustomFieldsService)
|
||||||
fixture = TestBed.createComponent(DocumentDetailComponent)
|
fixture = TestBed.createComponent(DocumentDetailComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
})
|
})
|
||||||
@ -306,6 +320,13 @@ describe('DocumentDetailComponent', () => {
|
|||||||
it('should load already-opened document via param', () => {
|
it('should load already-opened document via param', () => {
|
||||||
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
jest.spyOn(documentService, 'get').mockReturnValueOnce(of(doc))
|
||||||
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(doc)
|
jest.spyOn(openDocumentsService, 'getOpenDocument').mockReturnValue(doc)
|
||||||
|
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
||||||
|
of({
|
||||||
|
count: customFields.length,
|
||||||
|
all: customFields.map((f) => f.id),
|
||||||
|
results: customFields,
|
||||||
|
})
|
||||||
|
)
|
||||||
fixture.detectChanges() // calls ngOnInit
|
fixture.detectChanges() // calls ngOnInit
|
||||||
expect(component.document).toEqual(doc)
|
expect(component.document).toEqual(doc)
|
||||||
})
|
})
|
||||||
@ -816,29 +837,26 @@ describe('DocumentDetailComponent', () => {
|
|||||||
it('should display custom fields', () => {
|
it('should display custom fields', () => {
|
||||||
initNormally()
|
initNormally()
|
||||||
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||||
doc.custom_fields[0].field.name
|
customFields[0].name
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support add custom field, correctly send via post', () => {
|
it('should support add custom field, correctly send via post', () => {
|
||||||
const field = {
|
|
||||||
id: 1,
|
|
||||||
name: 'Custom Field 2',
|
|
||||||
data_type: PaperlessCustomFieldDataType.Integer,
|
|
||||||
}
|
|
||||||
initNormally()
|
initNormally()
|
||||||
const initialLength = doc.custom_fields.length
|
const initialLength = doc.custom_fields.length
|
||||||
expect(component.customFieldFormFields).toHaveLength(initialLength)
|
expect(component.customFieldFormFields).toHaveLength(initialLength)
|
||||||
component.addField(field)
|
component.addField(customFields[1])
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.document.custom_fields).toHaveLength(initialLength + 1)
|
expect(component.document.custom_fields).toHaveLength(initialLength + 1)
|
||||||
expect(component.customFieldFormFields).toHaveLength(initialLength + 1)
|
expect(component.customFieldFormFields).toHaveLength(initialLength + 1)
|
||||||
expect(fixture.debugElement.nativeElement.textContent).toContain(field.name)
|
expect(fixture.debugElement.nativeElement.textContent).toContain(
|
||||||
|
customFields[1].name
|
||||||
|
)
|
||||||
const updateSpy = jest.spyOn(documentService, 'update')
|
const updateSpy = jest.spyOn(documentService, 'update')
|
||||||
component.save(true)
|
component.save(true)
|
||||||
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength(2)
|
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength(2)
|
||||||
expect(updateSpy.mock.lastCall[0].custom_fields[1]).toEqual({
|
expect(updateSpy.mock.lastCall[0].custom_fields[1]).toEqual({
|
||||||
field,
|
field: customFields[1].id,
|
||||||
value: null,
|
value: null,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -869,6 +887,13 @@ describe('DocumentDetailComponent', () => {
|
|||||||
jest
|
jest
|
||||||
.spyOn(openDocumentsService, 'openDocument')
|
.spyOn(openDocumentsService, 'openDocument')
|
||||||
.mockReturnValueOnce(of(true))
|
.mockReturnValueOnce(of(true))
|
||||||
|
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
||||||
|
of({
|
||||||
|
count: customFields.length,
|
||||||
|
all: customFields.map((f) => f.id),
|
||||||
|
results: customFields,
|
||||||
|
})
|
||||||
|
)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -68,6 +68,7 @@ import {
|
|||||||
PaperlessCustomFieldDataType,
|
PaperlessCustomFieldDataType,
|
||||||
} from 'src/app/data/paperless-custom-field'
|
} from 'src/app/data/paperless-custom-field'
|
||||||
import { PaperlessCustomFieldInstance } from 'src/app/data/paperless-custom-field-instance'
|
import { PaperlessCustomFieldInstance } from 'src/app/data/paperless-custom-field-instance'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
|
||||||
enum DocumentDetailNavIDs {
|
enum DocumentDetailNavIDs {
|
||||||
Details = 1,
|
Details = 1,
|
||||||
@ -140,6 +141,7 @@ export class DocumentDetailComponent
|
|||||||
|
|
||||||
ogDate: Date
|
ogDate: Date
|
||||||
|
|
||||||
|
customFields: PaperlessCustomField[]
|
||||||
public readonly PaperlessCustomFieldDataType = PaperlessCustomFieldDataType
|
public readonly PaperlessCustomFieldDataType = PaperlessCustomFieldDataType
|
||||||
|
|
||||||
@ViewChild('nav') nav: NgbNav
|
@ViewChild('nav') nav: NgbNav
|
||||||
@ -173,6 +175,7 @@ export class DocumentDetailComponent
|
|||||||
private storagePathService: StoragePathService,
|
private storagePathService: StoragePathService,
|
||||||
private permissionsService: PermissionsService,
|
private permissionsService: PermissionsService,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
|
private customFieldsService: CustomFieldsService,
|
||||||
private http: HttpClient
|
private http: HttpClient
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@ -239,6 +242,8 @@ export class DocumentDetailComponent
|
|||||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe((result) => (this.users = result.results))
|
.subscribe((result) => (this.users = result.results))
|
||||||
|
|
||||||
|
this.getCustomFields()
|
||||||
|
|
||||||
this.route.paramMap
|
this.route.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(this.unsubscribeNotifier),
|
takeUntil(this.unsubscribeNotifier),
|
||||||
@ -836,16 +841,32 @@ export class DocumentDetailComponent
|
|||||||
this.documentListViewService.quickFilter(filterRules)
|
this.documentListViewService.quickFilter(filterRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFormForCustomFields(emitEvent: boolean = false) {
|
private getCustomFields() {
|
||||||
|
this.customFieldsService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((result) => (this.customFields = result.results))
|
||||||
|
}
|
||||||
|
|
||||||
|
public refreshCustomFields() {
|
||||||
|
this.customFieldsService.clearCache()
|
||||||
|
this.getCustomFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCustomFieldFromInstance(
|
||||||
|
instance: PaperlessCustomFieldInstance
|
||||||
|
): PaperlessCustomField {
|
||||||
|
return this.customFields.find((f) => f.id === instance.field)
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFormForCustomFields(emitEvent: boolean = false) {
|
||||||
this.customFieldFormFields.clear({ emitEvent: false })
|
this.customFieldFormFields.clear({ emitEvent: false })
|
||||||
this.document.custom_fields?.forEach((fieldInstance) => {
|
this.document.custom_fields?.forEach((fieldInstance) => {
|
||||||
this.customFieldFormFields.push(
|
this.customFieldFormFields.push(
|
||||||
new FormGroup({
|
new FormGroup({
|
||||||
field: new FormGroup({
|
field: new FormControl(
|
||||||
id: new FormControl(fieldInstance.field.id),
|
this.getCustomFieldFromInstance(fieldInstance)?.id
|
||||||
name: new FormControl(fieldInstance.field.name),
|
),
|
||||||
data_type: new FormControl(fieldInstance.field.data_type),
|
|
||||||
}),
|
|
||||||
value: new FormControl(fieldInstance.value),
|
value: new FormControl(fieldInstance.value),
|
||||||
}),
|
}),
|
||||||
{ emitEvent }
|
{ emitEvent }
|
||||||
@ -853,9 +874,9 @@ export class DocumentDetailComponent
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addField(field: PaperlessCustomField) {
|
public addField(field: PaperlessCustomField) {
|
||||||
this.document.custom_fields.push({
|
this.document.custom_fields.push({
|
||||||
field,
|
field: field.id,
|
||||||
value: null,
|
value: null,
|
||||||
document: this.documentId,
|
document: this.documentId,
|
||||||
created: new Date(),
|
created: new Date(),
|
||||||
@ -863,11 +884,12 @@ export class DocumentDetailComponent
|
|||||||
this.updateFormForCustomFields(true)
|
this.updateFormForCustomFields(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeField(fieldInstance: PaperlessCustomFieldInstance) {
|
public removeField(fieldInstance: PaperlessCustomFieldInstance) {
|
||||||
this.document.custom_fields.splice(
|
this.document.custom_fields.splice(
|
||||||
this.document.custom_fields.indexOf(fieldInstance),
|
this.document.custom_fields.indexOf(fieldInstance),
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
this.updateFormForCustomFields(true)
|
this.updateFormForCustomFields(true)
|
||||||
|
this.documentForm.updateValueAndValidity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { PaperlessCustomField } from './paperless-custom-field'
|
|||||||
|
|
||||||
export interface PaperlessCustomFieldInstance extends ObjectWithId {
|
export interface PaperlessCustomFieldInstance extends ObjectWithId {
|
||||||
document: number // PaperlessDocument
|
document: number // PaperlessDocument
|
||||||
field: PaperlessCustomField
|
field: number // PaperlessCustomField
|
||||||
created: Date
|
created: Date
|
||||||
value?: any
|
value?: any
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user