From a96ab9a9a414fb4a367efb9322b0c73692f1ac22 Mon Sep 17 00:00:00 2001 From: jonaswinkler <jonas.winkler@jpwinkler.de> Date: Sun, 3 Jan 2021 13:09:16 +0100 Subject: [PATCH] form field validation (much better error messages) --- src-ui/src/app/app.module.ts | 4 ++- .../edit-dialog/edit-dialog.component.ts | 10 +++++++- .../components/common/input/abstract-input.ts | 3 +++ .../common/input/number/number.component.html | 8 ++++++ .../common/input/number/number.component.scss | 0 .../input/number/number.component.spec.ts | 25 +++++++++++++++++++ .../common/input/number/number.component.ts | 21 ++++++++++++++++ .../common/input/text/text.component.html | 5 +++- .../document-detail.component.html | 8 ++---- .../document-detail.component.ts | 22 ++++++++++++++-- .../correspondent-edit-dialog.component.html | 6 ++--- .../correspondent-edit-dialog.component.ts | 4 --- .../document-type-edit-dialog.component.html | 4 +-- .../document-type-edit-dialog.component.ts | 4 --- .../tag-edit-dialog.component.html | 4 +-- .../tag-edit-dialog.component.ts | 4 --- 16 files changed, 102 insertions(+), 30 deletions(-) create mode 100644 src-ui/src/app/components/common/input/number/number.component.html create mode 100644 src-ui/src/app/components/common/input/number/number.component.scss create mode 100644 src-ui/src/app/components/common/input/number/number.component.spec.ts create mode 100644 src-ui/src/app/components/common/input/number/number.component.ts diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index c78dc3cfe..8cd28b6fc 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -57,6 +57,7 @@ import { DocumentTitlePipe } from './pipes/document-title.pipe'; import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component'; import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'; import { NgSelectModule } from '@ng-select/ng-select'; +import { NumberComponent } from './components/common/input/number/number.component'; @NgModule({ declarations: [ @@ -104,7 +105,8 @@ import { NgSelectModule } from '@ng-select/ng-select'; FilterPipe, DocumentTitlePipe, MetadataCollapseComponent, - SelectDialogComponent + SelectDialogComponent, + NumberComponent ], imports: [ BrowserModule, diff --git a/src-ui/src/app/components/common/edit-dialog/edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/edit-dialog.component.ts index 8f7af1418..ff4660ca3 100644 --- a/src-ui/src/app/components/common/edit-dialog/edit-dialog.component.ts +++ b/src-ui/src/app/components/common/edit-dialog/edit-dialog.component.ts @@ -2,6 +2,7 @@ import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'; import { ObjectWithId } from 'src/app/data/object-with-id'; import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'; @@ -24,6 +25,10 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI @Output() success = new EventEmitter() + networkActive = false + + error = null + abstract getForm(): FormGroup objectForm: FormGroup = this.getForm() @@ -77,11 +82,14 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI default: break; } + this.networkActive = true serverResponse.subscribe(result => { this.activeModal.close() this.success.emit(result) + this.networkActive = false }, error => { - this.toastService.showError(this.getSaveErrorMessage(error.error.name)) + this.error = error.error + this.networkActive = false }) } diff --git a/src-ui/src/app/components/common/input/abstract-input.ts b/src-ui/src/app/components/common/input/abstract-input.ts index 78a4a1b69..f6039d2ec 100644 --- a/src-ui/src/app/components/common/input/abstract-input.ts +++ b/src-ui/src/app/components/common/input/abstract-input.ts @@ -30,6 +30,9 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor { @Input() disabled = false; + @Input() + error: string + value: T ngOnInit(): void { diff --git a/src-ui/src/app/components/common/input/number/number.component.html b/src-ui/src/app/components/common/input/number/number.component.html new file mode 100644 index 000000000..aa3a893d3 --- /dev/null +++ b/src-ui/src/app/components/common/input/number/number.component.html @@ -0,0 +1,8 @@ +<div class="form-group"> + <label [for]="inputId">{{title}}</label> + <input type="number" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)"> + <small *ngIf="hint" class="form-text text-muted">{{hint}}</small> + <div class="invalid-feedback"> + {{error}} + </div> +</div> \ No newline at end of file diff --git a/src-ui/src/app/components/common/input/number/number.component.scss b/src-ui/src/app/components/common/input/number/number.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src-ui/src/app/components/common/input/number/number.component.spec.ts b/src-ui/src/app/components/common/input/number/number.component.spec.ts new file mode 100644 index 000000000..3476cbc22 --- /dev/null +++ b/src-ui/src/app/components/common/input/number/number.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NumberComponent } from './number.component'; + +describe('NumberComponent', () => { + let component: NumberComponent; + let fixture: ComponentFixture<NumberComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NumberComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NumberComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src-ui/src/app/components/common/input/number/number.component.ts b/src-ui/src/app/components/common/input/number/number.component.ts new file mode 100644 index 000000000..987a4090b --- /dev/null +++ b/src-ui/src/app/components/common/input/number/number.component.ts @@ -0,0 +1,21 @@ +import { Component, forwardRef } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { AbstractInputComponent } from '../abstract-input'; + +@Component({ + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => NumberComponent), + multi: true + }], + selector: 'app-input-number', + templateUrl: './number.component.html', + styleUrls: ['./number.component.scss'] +}) +export class NumberComponent extends AbstractInputComponent<number> { + + constructor() { + super() + } + +} diff --git a/src-ui/src/app/components/common/input/text/text.component.html b/src-ui/src/app/components/common/input/text/text.component.html index 3a43b052f..78aa76577 100644 --- a/src-ui/src/app/components/common/input/text/text.component.html +++ b/src-ui/src/app/components/common/input/text/text.component.html @@ -1,5 +1,8 @@ <div class="form-group"> <label [for]="inputId">{{title}}</label> - <input type="text" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)"> + <input type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)"> <small *ngIf="hint" class="form-text text-muted">{{hint}}</small> + <div class="invalid-feedback"> + {{error}} + </div> </div> \ No newline at end of file diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index eae3367c1..4092b5a60 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -56,12 +56,8 @@ <a ngbNavLink i18n>Details</a> <ng-template ngbNavContent> - <app-input-text i18n-title title="Title" formControlName="title"></app-input-text> - <div class="form-group"> - <label for="archive_serial_number" i18n>Archive serial number</label> - <input type="number" class="form-control" id="archive_serial_number" - formControlName='archive_serial_number'> - </div> + <app-input-text i18n-title title="Title" formControlName="title" [error]="error?.title"></app-input-text> + <app-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" formControlName='archive_serial_number'></app-input-number> <app-input-date-time i18n-titleDate titleDate="Date created" formControlName="created"></app-input-date-time> <app-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" (createNew)="createCorrespondent()"></app-input-select> diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index 053258f34..84a03446a 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -24,8 +24,12 @@ import { PDFDocumentProxy } from 'ng2-pdf-viewer'; }) export class DocumentDetailComponent implements OnInit { - public expandOriginalMetadata = false; - public expandArchivedMetadata = false; + expandOriginalMetadata = false + expandArchivedMetadata = false + + error: any + + networkActive = false documentId: number document: PaperlessDocument @@ -131,19 +135,33 @@ export class DocumentDetailComponent implements OnInit { } save() { + this.networkActive = true this.documentsService.update(this.document).subscribe(result => { this.close() + this.networkActive = false + this.error = null + }, error => { + this.networkActive = false + this.error = error.error }) } saveEditNext() { + this.networkActive = true this.documentsService.update(this.document).subscribe(result => { + this.error = null this.documentListViewService.getNext(this.document.id).subscribe(nextDocId => { + this.networkActive = false if (nextDocId) { this.openDocumentService.closeDocument(this.document) this.router.navigate(['documents', nextDocId]) } + }, error => { + this.networkActive = false }) + }, error => { + this.networkActive = false + this.error = error.error }) } diff --git a/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.html b/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.html index 48477b229..e2b7f13e0 100644 --- a/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.html +++ b/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.html @@ -1,4 +1,4 @@ -<form [formGroup]="objectForm" class="needs-validation" novalidate (ngSubmit)="save()"> +<form [formGroup]="objectForm" (ngSubmit)="save()"> <div class="modal-header"> <h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4> <button type="button" class="close" aria-label="Close" (click)="cancel()"> @@ -7,10 +7,10 @@ </div> <div class="modal-body"> - <app-input-text i18n-title title="Name" formControlName="name"></app-input-text> + <app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text> <app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select> <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match"></app-input-text> - <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check> + <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check> </div> <div class="modal-footer"> <button type="button" class="btn btn-outline-dark" (click)="cancel()" i18n>Cancel</button> diff --git a/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.ts b/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.ts index b6c3e08d4..a94cbf909 100644 --- a/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.ts +++ b/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.ts @@ -25,10 +25,6 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl return $localize`Edit correspondent` } - getSaveErrorMessage(error: string) { - return $localize`Could not save correspondent: ${error}` - } - getForm(): FormGroup { return new FormGroup({ name: new FormControl(''), diff --git a/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.html b/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.html index ba017faf7..d672c5bd8 100644 --- a/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.html +++ b/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.html @@ -1,4 +1,4 @@ -<form [formGroup]="objectForm" class="needs-validation" novalidate (ngSubmit)="save()"> +<form [formGroup]="objectForm" (ngSubmit)="save()"> <div class="modal-header"> <h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4> <button type="button" class="close" aria-label="Close" (click)="cancel()"> @@ -7,7 +7,7 @@ </div> <div class="modal-body"> - <app-input-text i18n-title title="Name" formControlName="name"></app-input-text> + <app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text> <app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select> <app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match"></app-input-text> <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check> diff --git a/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.ts b/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.ts index df81f88c9..a0cbd25f1 100644 --- a/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.ts +++ b/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.ts @@ -25,10 +25,6 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent<Paperle return $localize`Edit document type` } - getSaveErrorMessage(error: string) { - return $localize`Could not save document type: ${error}` - } - getForm(): FormGroup { return new FormGroup({ name: new FormControl(''), diff --git a/src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.html b/src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.html index 9929b54d5..502502c5c 100644 --- a/src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.html +++ b/src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.html @@ -1,4 +1,4 @@ - <form [formGroup]="objectForm" class="needs-validation" novalidate (ngSubmit)="save()"> + <form [formGroup]="objectForm" (ngSubmit)="save()"> <div class="modal-header"> <h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4> <button type="button" class="close" aria-label="Close" (click)="cancel()"> @@ -6,7 +6,7 @@ </button> </div> <div class="modal-body"> - <app-input-text title="Name" formControlName="name"></app-input-text> + <app-input-text title="Name" formControlName="name" [error]="error?.name"></app-input-text> <div class="form-group paperless-input-select"> diff --git a/src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.ts b/src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.ts index ceca19142..d67369a8b 100644 --- a/src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.ts +++ b/src-ui/src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.ts @@ -25,10 +25,6 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> { return $localize`Edit tag` } - getSaveErrorMessage(error: string) { - return $localize`Could not save tag: ${error}` - } - getForm(): FormGroup { return new FormGroup({ name: new FormControl(''),