]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Enhancement: refactor monetary field (#6370)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Fri, 19 Apr 2024 08:08:46 +0000 (01:08 -0700)
committerGitHub <noreply@github.com>
Fri, 19 Apr 2024 08:08:46 +0000 (08:08 +0000)
src-ui/src/app/components/common/input/monetary/monetary.component.html
src-ui/src/app/components/common/input/monetary/monetary.component.spec.ts
src-ui/src/app/components/common/input/monetary/monetary.component.ts
src-ui/src/app/components/document-detail/document-detail.component.html

index ff3d7b9b5deb1ba509983cd20bb1997a01728374..a21932c59b87cd30166da5af01ccbf6fed78b9e6 100644 (file)
@@ -12,9 +12,9 @@
     </div>
     <div class="position-relative" [class.col-md-9]="horizontal">
       <div class="input-group" [class.is-invalid]="error">
-        <span class="input-group-text fw-bold bg-light">{{monetaryValue | currency: currencyCode }}</span>
-        <input #currencyField class="form-control text-muted mw-60" tabindex="0" [(ngModel)]="currencyCode" maxlength="3" [class.is-invalid]="error" (change)="onChange(value)" [disabled]="disabled">
-        <input #inputField type="number" tabindex="0" class="form-control text-muted" step=".01" [id]="inputId" [(ngModel)]="monetaryValue" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
+        <span class="input-group-text fw-bold bg-light">{{ monetaryValue | currency: currency }}</span>
+        <input #currencyField class="form-control text-muted mw-60" [(ngModel)]="currency" (input)="currencyChange()" maxlength="3" [class.is-invalid]="error" [disabled]="disabled">
+        <input #monetaryValueField type="number" class="form-control text-muted" step=".01" [(ngModel)]="monetaryValue" (input)="monetaryValueChange()" (change)="monetaryValueChange(true)" [class.is-invalid]="error" [disabled]="disabled">
       </div>
       <div class="invalid-feedback position-absolute top-100">
         {{error}}
index 4106c01eca55f0c05c6f4685925fc42940452d18..60df71742452dbe3df0ade18cd4db5583005d235 100644 (file)
@@ -11,7 +11,6 @@ import { MonetaryComponent } from './monetary.component'
 describe('MonetaryComponent', () => {
   let component: MonetaryComponent
   let fixture: ComponentFixture<MonetaryComponent>
-  let input: HTMLInputElement
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
@@ -24,37 +23,22 @@ describe('MonetaryComponent', () => {
     fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
     component = fixture.componentInstance
     fixture.detectChanges()
-    input = component.inputField.nativeElement
   })
 
-  it('should set the currency code correctly', () => {
-    expect(component.currencyCode).toEqual('USD') // default
-    component.currencyCode = 'EUR'
-    expect(component.currencyCode).toEqual('EUR')
+  it('should set the currency code and monetary value correctly', () => {
+    expect(component.currency).toEqual('USD') // default
+    component.writeValue('G123.4')
+    expect(component.currency).toEqual('G')
 
-    component.value = 'G123.4'
-    jest
-      .spyOn(document, 'activeElement', 'get')
-      .mockReturnValue(component.currencyField.nativeElement)
-    expect(component.currencyCode).toEqual('G')
+    component.writeValue('EUR123.4')
+    expect(component.currency).toEqual('EUR')
+    expect(component.monetaryValue).toEqual('123.40')
   })
 
-  it('should parse monetary value only when out of focus', () => {
-    component.monetaryValue = 10.5
-    jest.spyOn(document, 'activeElement', 'get').mockReturnValue(null)
+  it('should set monetary value to fixed decimals', () => {
+    component.monetaryValue = '10.5'
+    component.monetaryValueChange(true)
     expect(component.monetaryValue).toEqual('10.50')
-
-    component.value = 'GBP123.4'
-    jest
-      .spyOn(document, 'activeElement', 'get')
-      .mockReturnValue(component.inputField.nativeElement)
-    expect(component.monetaryValue).toEqual('123.4')
-  })
-
-  it('should report value including currency code and monetary value', () => {
-    component.currencyCode = 'EUR'
-    component.monetaryValue = 10.5
-    expect(component.value).toEqual('EUR10.50')
   })
 
   it('should set the default currency code based on LOCALE_ID', () => {
index db190c59d86d767092ffe74ff3a9ba3d17e3766b..edaad3859e33312431293adc3496fbf2e6bdccab 100644 (file)
@@ -1,11 +1,4 @@
-import {
-  Component,
-  ElementRef,
-  forwardRef,
-  Inject,
-  LOCALE_ID,
-  ViewChild,
-} from '@angular/core'
+import { Component, forwardRef, Inject, LOCALE_ID } from '@angular/core'
 import { NG_VALUE_ACCESSOR } from '@angular/forms'
 import { AbstractInputComponent } from '../abstract-input'
 import { getLocaleCurrencyCode } from '@angular/common'
@@ -23,40 +16,47 @@ import { getLocaleCurrencyCode } from '@angular/common'
   styleUrls: ['./monetary.component.scss'],
 })
 export class MonetaryComponent extends AbstractInputComponent<string> {
-  @ViewChild('currencyField')
-  currencyField: ElementRef
+  public currency: string = ''
+  public monetaryValue: string = ''
   defaultCurrencyCode: string
 
   constructor(@Inject(LOCALE_ID) currentLocale: string) {
     super()
 
-    this.defaultCurrencyCode = getLocaleCurrencyCode(currentLocale)
+    this.currency = this.defaultCurrencyCode =
+      getLocaleCurrencyCode(currentLocale)
   }
 
-  get currencyCode(): string {
-    const focused = document.activeElement === this.currencyField?.nativeElement
-    if (focused && this.value)
-      return this.value.toUpperCase().match(/^([A-Z]{0,3})/)?.[0]
+  writeValue(newValue: any): void {
+    this.currency = this.parseCurrencyCode(newValue)
+    this.monetaryValue = this.parseMonetaryValue(newValue, true)
+
+    this.value = this.currency + this.monetaryValue
+  }
+
+  public monetaryValueChange(fixed: boolean = false): void {
+    this.monetaryValue = this.parseMonetaryValue(this.monetaryValue, fixed)
+    this.onChange(this.currency + this.monetaryValue)
+  }
+
+  public currencyChange(): void {
+    if (this.currency.length) {
+      this.currency = this.parseCurrencyCode(this.currency)
+      this.onChange(this.currency + this.monetaryValue?.toString())
+    }
+  }
+
+  private parseCurrencyCode(value: string): string {
     return (
-      this.value
+      value
         ?.toString()
         .toUpperCase()
         .match(/^([A-Z]{1,3})/)?.[0] ?? this.defaultCurrencyCode
     )
   }
 
-  set currencyCode(value: string) {
-    this.value = value.toUpperCase() + this.monetaryValue?.toString()
-  }
-
-  get monetaryValue(): string {
-    if (!this.value) return null
-    const focused = document.activeElement === this.inputField?.nativeElement
-    const val = parseFloat(this.value.toString().replace(/[^0-9.,-]+/g, ''))
-    return focused ? val.toString() : val.toFixed(2)
-  }
-
-  set monetaryValue(value: number) {
-    this.value = this.currencyCode + value.toFixed(2)
+  private parseMonetaryValue(value: string, fixed: boolean = false): string {
+    const val: number = parseFloat(value.toString().replace(/[^0-9.,-]+/g, ''))
+    return fixed ? val.toFixed(2) : val.toString()
   }
 }
index aed4d67da67a19b7de3b0d74eca62dc57dc46a0b..64646c0b113707e9239d09e00940722ccb4292fe 100644 (file)
               <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>
               <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>
-              @for (fieldInstance of document?.custom_fields; track fieldInstance; let i = $index) {
+              @for (fieldInstance of document?.custom_fields; track fieldInstance.field; let i = $index) {
                 <div [formGroup]="customFieldFormFields.controls[i]">
                   @switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
                     @case (CustomFieldDataType.String) {