mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-31 10:37:12 -04:00 
			
		
		
		
	form field validation (much better error messages)
This commit is contained in:
		
							parent
							
								
									e05f365e6a
								
							
						
					
					
						commit
						a96ab9a9a4
					
				| @ -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, | ||||
|  | ||||
| @ -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 | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -30,6 +30,9 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor { | ||||
|   @Input() | ||||
|   disabled = false; | ||||
| 
 | ||||
|   @Input() | ||||
|   error: string | ||||
| 
 | ||||
|   value: T | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
|  | ||||
| @ -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> | ||||
| @ -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(); | ||||
|   }); | ||||
| }); | ||||
| @ -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() | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -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> | ||||
| @ -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> | ||||
|  | ||||
| @ -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 | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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(''), | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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(''), | ||||
|  | ||||
| @ -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"> | ||||
|  | ||||
| @ -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(''), | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user