Custom Fields dropdown, skeleton of posting custom fields
This commit is contained in:
parent
548ffd46b0
commit
877b9325c4
@ -103,6 +103,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop'
|
|||||||
import { FileDropComponent } from './components/file-drop/file-drop.component'
|
import { FileDropComponent } from './components/file-drop/file-drop.component'
|
||||||
import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component'
|
import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component'
|
||||||
import { CustomFieldEditDialogComponent } from './components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
import { CustomFieldEditDialogComponent } from './components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
||||||
|
import { CustomFieldsDropdownComponent } from './components/common/custom-fields-dropdown/custom-fields-dropdown.component'
|
||||||
|
|
||||||
import localeAf from '@angular/common/locales/af'
|
import localeAf from '@angular/common/locales/af'
|
||||||
import localeAr from '@angular/common/locales/ar'
|
import localeAr from '@angular/common/locales/ar'
|
||||||
@ -250,6 +251,7 @@ function initializeApp(settings: SettingsService) {
|
|||||||
FileDropComponent,
|
FileDropComponent,
|
||||||
CustomFieldsComponent,
|
CustomFieldsComponent,
|
||||||
CustomFieldEditDialogComponent,
|
CustomFieldEditDialogComponent,
|
||||||
|
CustomFieldsDropdownComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -159,21 +159,21 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||||
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Document types" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Document Types" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
|
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
|
||||||
</svg><span> <ng-container i18n>Document types</ng-container></span>
|
</svg><span> <ng-container i18n>Document Types</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||||
<a class="nav-link" routerLink="storagepaths" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Storage paths" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="storagepaths" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Storage Paths" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
|
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
|
||||||
</svg><span> <ng-container i18n>Storage paths</ng-container></span>
|
</svg><span> <ng-container i18n>Storage Paths</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }">
|
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }">
|
||||||
<a class="nav-link" routerLink="customfields" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Custom fields" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="customfields" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Custom Fields" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#ui-radios"/>
|
<use xlink:href="assets/bootstrap-icons.svg#ui-radios"/>
|
||||||
</svg><span> <ng-container i18n>Custom Fields</ng-container></span>
|
</svg><span> <ng-container i18n>Custom Fields</ng-container></span>
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
<div ngbDropdown #fieldDropdown="ngbDropdown" (openChange)="onOpenClose()">
|
||||||
|
<button class="btn btn-sm btn-outline-primary me-2" id="customFieldsDropdown" [disabled]="disabled" ngbDropdownToggle>
|
||||||
|
<svg class="toolbaricon" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#ui-radios" />
|
||||||
|
</svg>
|
||||||
|
<div class="d-none d-sm-inline"> <ng-container i18n>Custom Fields</ng-container></div>
|
||||||
|
</button>
|
||||||
|
<div ngbDropdownMenu aria-labelledby="customFieldsDropdown" class="shadow custom-fields-dropdown">
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<pngx-input-select class="mb-3"
|
||||||
|
[items]="unusedFields"
|
||||||
|
bindLabel="name"
|
||||||
|
[(ngModel)]="field"
|
||||||
|
[placeholder]="placeholderText"
|
||||||
|
bindValue="id">
|
||||||
|
</pngx-input-select>
|
||||||
|
<div class="btn-toolbar" role="toolbar">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary me-auto" type="button" (click)="createField()">
|
||||||
|
<svg fill="currentColor" class="buttonicon-sm me-1 mb-1">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#asterisk"/>
|
||||||
|
</svg><ng-container i18n>Create New Field</ng-container>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-primary me-1" type="button" (click)="addField(); fieldDropdown.close()" [disabled]="field === undefined">
|
||||||
|
<svg fill="currentColor" class="buttonicon me-1">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#plus-circle"/>
|
||||||
|
</svg><ng-container i18n>Add</ng-container>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,24 @@
|
|||||||
|
.custom-fields-dropdown {
|
||||||
|
min-width: 350px;
|
||||||
|
|
||||||
|
// correct position on mobile
|
||||||
|
@media (max-width: 575.98px) {
|
||||||
|
&.show {
|
||||||
|
margin-left: -175px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .ng-select .ng-select-container .ng-value-container .ng-placeholder,
|
||||||
|
::ng-deep .ng-select .ng-option,
|
||||||
|
::ng-deep .ng-select .ng-select-container .ng-value-container .ng-value {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .paperless-input-select .ng-select {
|
||||||
|
min-height: calc(1em + 0.75rem + 5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .paperless-input-select .ng-select .ng-select-container .ng-value-container .ng-input {
|
||||||
|
top: 4px;
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
|
|
||||||
|
import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component'
|
||||||
|
import {
|
||||||
|
HttpClientTestingModule,
|
||||||
|
HttpTestingController,
|
||||||
|
} from '@angular/common/http/testing'
|
||||||
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
import { of } from 'rxjs'
|
||||||
|
import {
|
||||||
|
PaperlessCustomField,
|
||||||
|
PaperlessCustomFieldDataType,
|
||||||
|
} from 'src/app/data/paperless-custom-field'
|
||||||
|
import { SelectComponent } from '../input/select/select.component'
|
||||||
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
|
import {
|
||||||
|
NgbModal,
|
||||||
|
NgbModalModule,
|
||||||
|
NgbModalRef,
|
||||||
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
||||||
|
import { By } from '@angular/platform-browser'
|
||||||
|
|
||||||
|
const fields: PaperlessCustomField[] = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
name: 'Field 1',
|
||||||
|
data_type: PaperlessCustomFieldDataType.Integer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Field 2',
|
||||||
|
data_type: PaperlessCustomFieldDataType.String,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('CustomFieldsDropdownComponent', () => {
|
||||||
|
let component: CustomFieldsDropdownComponent
|
||||||
|
let fixture: ComponentFixture<CustomFieldsDropdownComponent>
|
||||||
|
let customFieldService: CustomFieldsService
|
||||||
|
let toastService: ToastService
|
||||||
|
let modalService: NgbModal
|
||||||
|
let httpController: HttpTestingController
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [CustomFieldsDropdownComponent, SelectComponent],
|
||||||
|
imports: [
|
||||||
|
HttpClientTestingModule,
|
||||||
|
NgSelectModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
NgbModalModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
customFieldService = TestBed.inject(CustomFieldsService)
|
||||||
|
httpController = TestBed.inject(HttpTestingController)
|
||||||
|
toastService = TestBed.inject(ToastService)
|
||||||
|
modalService = TestBed.inject(NgbModal)
|
||||||
|
jest.spyOn(customFieldService, 'listAll').mockReturnValue(
|
||||||
|
of({
|
||||||
|
all: fields.map((f) => f.id),
|
||||||
|
count: fields.length,
|
||||||
|
results: fields.concat([]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
fixture = TestBed.createComponent(CustomFieldsDropdownComponent)
|
||||||
|
component = fixture.componentInstance
|
||||||
|
fixture.detectChanges()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support add field', () => {
|
||||||
|
let addedField
|
||||||
|
component.added.subscribe((f) => (addedField = f))
|
||||||
|
component.documentId = 11
|
||||||
|
component.field = fields[0].id
|
||||||
|
component.addField()
|
||||||
|
expect(addedField).not.toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear field on open / close, updated unused fields', () => {
|
||||||
|
component.field = fields[1].id
|
||||||
|
component.onOpenClose()
|
||||||
|
expect(component.field).toBeUndefined()
|
||||||
|
|
||||||
|
expect(component.unusedFields).toEqual(fields)
|
||||||
|
const updateSpy = jest.spyOn(
|
||||||
|
CustomFieldsDropdownComponent.prototype as any,
|
||||||
|
'updateUnusedFields'
|
||||||
|
)
|
||||||
|
component.existingFields = [fields[1]]
|
||||||
|
component.onOpenClose()
|
||||||
|
expect(updateSpy).toHaveBeenCalled()
|
||||||
|
expect(component.unusedFields).toEqual([fields[0]])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support creating field, show error if necessary', () => {
|
||||||
|
let modal: NgbModalRef
|
||||||
|
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
|
||||||
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
|
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
|
const getFieldsSpy = jest.spyOn(
|
||||||
|
CustomFieldsDropdownComponent.prototype as any,
|
||||||
|
'getFields'
|
||||||
|
)
|
||||||
|
|
||||||
|
const createButton = fixture.debugElement.queryAll(By.css('button'))[1]
|
||||||
|
createButton.triggerEventHandler('click')
|
||||||
|
|
||||||
|
expect(modal).not.toBeUndefined()
|
||||||
|
const editDialog = modal.componentInstance as CustomFieldEditDialogComponent
|
||||||
|
|
||||||
|
// fail first
|
||||||
|
editDialog.failed.emit({ error: 'error creating field' })
|
||||||
|
expect(toastErrorSpy).toHaveBeenCalled()
|
||||||
|
expect(getFieldsSpy).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
// succeed
|
||||||
|
editDialog.succeeded.emit(fields[0])
|
||||||
|
expect(toastInfoSpy).toHaveBeenCalled()
|
||||||
|
expect(getFieldsSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,100 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
Output,
|
||||||
|
SimpleChanges,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { Subject, first, takeUntil } from 'rxjs'
|
||||||
|
import { PaperlessCustomField } from 'src/app/data/paperless-custom-field'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
|
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pngx-custom-fields-dropdown',
|
||||||
|
templateUrl: './custom-fields-dropdown.component.html',
|
||||||
|
styleUrls: ['./custom-fields-dropdown.component.scss'],
|
||||||
|
})
|
||||||
|
export class CustomFieldsDropdownComponent implements OnDestroy {
|
||||||
|
@Input()
|
||||||
|
documentId: number
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
disabled: boolean = false
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
existingFields: PaperlessCustomField[] = []
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
added = new EventEmitter()
|
||||||
|
|
||||||
|
private customFields: PaperlessCustomField[] = []
|
||||||
|
public unusedFields: PaperlessCustomField[]
|
||||||
|
|
||||||
|
public name: string
|
||||||
|
|
||||||
|
public field: number
|
||||||
|
|
||||||
|
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
|
get placeholderText(): string {
|
||||||
|
return $localize`Choose field`
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private customFieldsService: CustomFieldsService,
|
||||||
|
private modalService: NgbModal,
|
||||||
|
private toastService: ToastService
|
||||||
|
) {
|
||||||
|
this.getFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.unsubscribeNotifier.next(this)
|
||||||
|
this.unsubscribeNotifier.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFields() {
|
||||||
|
this.customFieldsService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((result) => {
|
||||||
|
this.customFields = result.results
|
||||||
|
this.updateUnusedFields()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateUnusedFields() {
|
||||||
|
this.unusedFields = this.customFields.filter(
|
||||||
|
(f) => !this.existingFields.find((e) => e.id === f.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenClose() {
|
||||||
|
this.field = undefined
|
||||||
|
this.updateUnusedFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
addField() {
|
||||||
|
this.added.emit(this.customFields.find((f) => f.id === this.field))
|
||||||
|
}
|
||||||
|
|
||||||
|
createField() {
|
||||||
|
const modal = this.modalService.open(CustomFieldEditDialogComponent)
|
||||||
|
modal.componentInstance.succeeded
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((newField) => {
|
||||||
|
this.toastService.showInfo($localize`Saved field "${newField.name}".`)
|
||||||
|
this.customFieldsService.clearCache()
|
||||||
|
this.getFields()
|
||||||
|
})
|
||||||
|
modal.componentInstance.failed
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((e) => {
|
||||||
|
this.toastService.showError($localize`Error saving field.`, e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -48,6 +48,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<pngx-custom-fields-dropdown
|
||||||
|
[documentId]="documentId"
|
||||||
|
[disabled]="!userIsOwner"
|
||||||
|
[existingFields]="customFields"
|
||||||
|
(added)="addField($event)">
|
||||||
|
</pngx-custom-fields-dropdown>
|
||||||
|
|
||||||
<pngx-share-links-dropdown [documentId]="documentId" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></pngx-share-links-dropdown>
|
<pngx-share-links-dropdown [documentId]="documentId" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></pngx-share-links-dropdown>
|
||||||
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary me-2" i18n-title title="Close" (click)="close()">
|
<button type="button" class="btn btn-sm btn-outline-primary me-2" i18n-title title="Close" (click)="close()">
|
||||||
@ -93,9 +100,12 @@
|
|||||||
<pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
<pngx-input-select [items]="storagePaths" i18n-title title="Storage path" formControlName="storage_path" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
|
||||||
(createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
|
(createNew)="createStoragePath($event)" [suggestions]="suggestions?.storage_paths" i18n-placeholder placeholder="Default" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }"></pngx-input-select>
|
||||||
<pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
|
<pngx-input-tags formControlName="tags" [suggestions]="suggestions?.tags" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"></pngx-input-tags>
|
||||||
<ng-container *ngFor="let field of customFields">
|
<ng-container *ngFor="let field of customFields; let i = index">
|
||||||
<pngx-input-text [formControlName]="'custom-field-' + field.id" *ngIf="field.type === PaperlessCustomFieldDataType.String" [title]="field.name" [horizontal]="true"></pngx-input-text>
|
<div [formGroup]="customFieldFormFields.controls[i]">
|
||||||
<pngx-input-date [formControlName]="'custom-field-' + field.id" *ngIf="field.type === PaperlessCustomFieldDataType.Date" [title]="field.name" [horizontal]="true"></pngx-input-date>
|
<pngx-input-text formControlName="value" *ngIf="field.data_type === PaperlessCustomFieldDataType.String" [title]="field.name" [horizontal]="true"></pngx-input-text>
|
||||||
|
<pngx-input-date formControlName="value" *ngIf="field.data_type === PaperlessCustomFieldDataType.Date" [title]="field.name" [horizontal]="true"></pngx-input-date>
|
||||||
|
<pngx-input-number formControlName="value" *ngIf="field.data_type === PaperlessCustomFieldDataType.Integer" [title]="field.name" [horizontal]="true" [showAdd]="false"></pngx-input-number>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'
|
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'
|
||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormArray, FormControl, FormGroup } from '@angular/forms'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import {
|
import {
|
||||||
NgbDateStruct,
|
NgbDateStruct,
|
||||||
@ -124,6 +124,7 @@ export class DocumentDetailComponent
|
|||||||
archive_serial_number: new FormControl(),
|
archive_serial_number: new FormControl(),
|
||||||
tags: new FormControl([]),
|
tags: new FormControl([]),
|
||||||
permissions_form: new FormControl(null),
|
permissions_form: new FormControl(null),
|
||||||
|
custom_fields: new FormArray([]),
|
||||||
})
|
})
|
||||||
|
|
||||||
previewCurrentPage: number = 1
|
previewCurrentPage: number = 1
|
||||||
@ -140,7 +141,7 @@ export class DocumentDetailComponent
|
|||||||
ogDate: Date
|
ogDate: Date
|
||||||
|
|
||||||
public readonly PaperlessCustomFieldDataType = PaperlessCustomFieldDataType
|
public readonly PaperlessCustomFieldDataType = PaperlessCustomFieldDataType
|
||||||
customFields: PaperlessCustomField[]
|
customFields: PaperlessCustomField[] = []
|
||||||
|
|
||||||
@ViewChild('nav') nav: NgbNav
|
@ViewChild('nav') nav: NgbNav
|
||||||
@ViewChild('pdfPreview') set pdfPreview(element) {
|
@ViewChild('pdfPreview') set pdfPreview(element) {
|
||||||
@ -173,8 +174,7 @@ export class DocumentDetailComponent
|
|||||||
private storagePathService: StoragePathService,
|
private storagePathService: StoragePathService,
|
||||||
private permissionsService: PermissionsService,
|
private permissionsService: PermissionsService,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private http: HttpClient,
|
private http: HttpClient
|
||||||
private customFieldsService: CustomFieldsService
|
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
@ -332,6 +332,7 @@ export class DocumentDetailComponent
|
|||||||
owner: doc.owner,
|
owner: doc.owner,
|
||||||
set_permissions: doc.permissions,
|
set_permissions: doc.permissions,
|
||||||
},
|
},
|
||||||
|
custom_fields: doc.custom_fields,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.isDirty$ = dirtyCheck(
|
this.isDirty$ = dirtyCheck(
|
||||||
@ -393,29 +394,7 @@ export class DocumentDetailComponent
|
|||||||
updateComponent(doc: PaperlessDocument) {
|
updateComponent(doc: PaperlessDocument) {
|
||||||
this.document = doc
|
this.document = doc
|
||||||
this.requiresPassword = false
|
this.requiresPassword = false
|
||||||
// TODO: Custom fields
|
this.updateFormForCustomFields()
|
||||||
// this.customFieldsService
|
|
||||||
// .getFields(doc.id)
|
|
||||||
// .pipe(first())
|
|
||||||
// .subscribe({
|
|
||||||
// next: (fields) => {
|
|
||||||
// this.customFields = fields
|
|
||||||
// this.customFields.forEach((field) => {
|
|
||||||
// this.documentForm.addControl(
|
|
||||||
// `custom-field-${field.id}`,
|
|
||||||
// new FormControl(field.data)
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
// this.store.next(this.documentForm.value)
|
|
||||||
// console.log(fields)
|
|
||||||
// },
|
|
||||||
// error: (error) => {
|
|
||||||
// this.toastService.showError(
|
|
||||||
// $localize`Error retrieving custom fields`,
|
|
||||||
// error
|
|
||||||
// )
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.getMetadata(doc.id)
|
.getMetadata(doc.id)
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
@ -464,6 +443,22 @@ export class DocumentDetailComponent
|
|||||||
if (!this.userCanEdit) this.documentForm.disable()
|
if (!this.userCanEdit) this.documentForm.disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get customFieldFormFields(): FormArray {
|
||||||
|
return this.documentForm.get('custom_fields') as FormArray
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFormForCustomFields() {
|
||||||
|
this.customFieldFormFields.clear()
|
||||||
|
this.customFields.forEach((field) => {
|
||||||
|
this.customFieldFormFields.push(
|
||||||
|
new FormGroup({
|
||||||
|
parent: new FormControl(field.id),
|
||||||
|
value: new FormControl(null),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
createDocumentType(newName: string) {
|
createDocumentType(newName: string) {
|
||||||
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {
|
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
@ -543,11 +538,8 @@ export class DocumentDetailComponent
|
|||||||
this.title = doc.title
|
this.title = doc.title
|
||||||
this.documentForm.patchValue(doc)
|
this.documentForm.patchValue(doc)
|
||||||
// TODO: custom field reset
|
// TODO: custom field reset
|
||||||
// this.customFields.forEach((field) => {
|
this.customFields = doc.custom_fields
|
||||||
// this.documentForm
|
this.updateFormForCustomFields()
|
||||||
// .get(`custom-field-${field.id}`)
|
|
||||||
// .patchValue(field.data)
|
|
||||||
// })
|
|
||||||
this.openDocumentService.setDirty(doc, false)
|
this.openDocumentService.setDirty(doc, false)
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
@ -856,4 +848,9 @@ export class DocumentDetailComponent
|
|||||||
|
|
||||||
this.documentListViewService.quickFilter(filterRules)
|
this.documentListViewService.quickFilter(filterRules)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addField(field: PaperlessCustomField) {
|
||||||
|
this.customFields.push(field)
|
||||||
|
this.updateFormForCustomFields()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
7
src-ui/src/app/data/paperless-custom-field-instance.ts
Normal file
7
src-ui/src/app/data/paperless-custom-field-instance.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { ObjectWithId } from './object-with-id'
|
||||||
|
|
||||||
|
export interface PaperlessCustomFieldInstance extends ObjectWithId {
|
||||||
|
document: number // PaperlessDocument
|
||||||
|
field: number // PaperlessCustomField
|
||||||
|
created: Date
|
||||||
|
}
|
@ -5,6 +5,7 @@ import { Observable } from 'rxjs'
|
|||||||
import { PaperlessStoragePath } from './paperless-storage-path'
|
import { PaperlessStoragePath } from './paperless-storage-path'
|
||||||
import { ObjectWithPermissions } from './object-with-permissions'
|
import { ObjectWithPermissions } from './object-with-permissions'
|
||||||
import { PaperlessDocumentNote } from './paperless-document-note'
|
import { PaperlessDocumentNote } from './paperless-document-note'
|
||||||
|
import { PaperlessCustomField } from './paperless-custom-field'
|
||||||
|
|
||||||
export interface SearchHit {
|
export interface SearchHit {
|
||||||
score?: number
|
score?: number
|
||||||
@ -58,4 +59,6 @@ export interface PaperlessDocument extends ObjectWithPermissions {
|
|||||||
notes?: PaperlessDocumentNote[]
|
notes?: PaperlessDocumentNote[]
|
||||||
|
|
||||||
__search_hit__?: SearchHit
|
__search_hit__?: SearchHit
|
||||||
|
|
||||||
|
custom_fields?: PaperlessCustomField[]
|
||||||
}
|
}
|
||||||
|
@ -256,6 +256,10 @@ describe('PermissionsService', () => {
|
|||||||
'view_consumptiontemplate',
|
'view_consumptiontemplate',
|
||||||
'change_consumptiontemplate',
|
'change_consumptiontemplate',
|
||||||
'delete_consumptiontemplate',
|
'delete_consumptiontemplate',
|
||||||
|
'add_customfield',
|
||||||
|
'view_customfield',
|
||||||
|
'change_customfield',
|
||||||
|
'delete_customfield',
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
username: 'testuser',
|
username: 'testuser',
|
||||||
|
14
src-ui/src/app/services/rest/custom-fields.service.spec.ts
Normal file
14
src-ui/src/app/services/rest/custom-fields.service.spec.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { HttpTestingController } from '@angular/common/http/testing'
|
||||||
|
import { Subscription } from 'rxjs'
|
||||||
|
import { TestBed } from '@angular/core/testing'
|
||||||
|
import { environment } from 'src/environments/environment'
|
||||||
|
import { commonAbstractPaperlessServiceTests } from './abstract-paperless-service.spec'
|
||||||
|
import { CustomFieldsService } from './custom-fields.service'
|
||||||
|
|
||||||
|
let httpTestingController: HttpTestingController
|
||||||
|
let service: CustomFieldsService
|
||||||
|
let subscription: Subscription
|
||||||
|
const endpoint = 'custom_fields'
|
||||||
|
|
||||||
|
// run common tests
|
||||||
|
commonAbstractPaperlessServiceTests(endpoint, CustomFieldsService)
|
@ -3,6 +3,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'
|
|||||||
import { AbstractPaperlessService } from './abstract-paperless-service'
|
import { AbstractPaperlessService } from './abstract-paperless-service'
|
||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { PaperlessCustomField } from 'src/app/data/paperless-custom-field'
|
import { PaperlessCustomField } from 'src/app/data/paperless-custom-field'
|
||||||
|
import { PaperlessCustomFieldInstance } from 'src/app/data/paperless-custom-field-instance'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@ -11,30 +12,4 @@ export class CustomFieldsService extends AbstractPaperlessService<PaperlessCusto
|
|||||||
constructor(http: HttpClient) {
|
constructor(http: HttpClient) {
|
||||||
super(http, 'custom_fields')
|
super(http, 'custom_fields')
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFields(documentId: number): Observable<PaperlessCustomField[]> {
|
|
||||||
// return this.http.get<PaperlessCustomField[]>(
|
|
||||||
// this.getResourceUrl(documentId, 'custom_fields')
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// addField(
|
|
||||||
// documentId: number,
|
|
||||||
// field: PaperlessCustomField
|
|
||||||
// ): Observable<PaperlessCustomField[]> {
|
|
||||||
// return this.http.post<PaperlessCustomField[]>(
|
|
||||||
// this.getResourceUrl(documentId, 'custom_fields'),
|
|
||||||
// field
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// deleteField(
|
|
||||||
// documentId: number,
|
|
||||||
// fieldId: number
|
|
||||||
// ): Observable<PaperlessCustomField[]> {
|
|
||||||
// return this.http.delete<PaperlessCustomField[]>(
|
|
||||||
// this.getResourceUrl(documentId, 'custom_metadata'),
|
|
||||||
// { params: new HttpParams({ fromString: `id=${fieldId}` }) }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user