Enhancement: refactor monetary field (#6370)

This commit is contained in:
shamoon 2024-04-19 01:08:46 -07:00 committed by GitHub
parent 78f338484f
commit 95fd1ae879
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 43 additions and 59 deletions

View File

@ -12,9 +12,9 @@
</div> </div>
<div class="position-relative" [class.col-md-9]="horizontal"> <div class="position-relative" [class.col-md-9]="horizontal">
<div class="input-group" [class.is-invalid]="error"> <div class="input-group" [class.is-invalid]="error">
<span class="input-group-text fw-bold bg-light">{{monetaryValue | currency: currencyCode }}</span> <span class="input-group-text fw-bold bg-light">{{ monetaryValue | currency: currency }}</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 #currencyField class="form-control text-muted mw-60" [(ngModel)]="currency" (input)="currencyChange()" maxlength="3" [class.is-invalid]="error" [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"> <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>
<div class="invalid-feedback position-absolute top-100"> <div class="invalid-feedback position-absolute top-100">
{{error}} {{error}}

View File

@ -11,7 +11,6 @@ import { MonetaryComponent } from './monetary.component'
describe('MonetaryComponent', () => { describe('MonetaryComponent', () => {
let component: MonetaryComponent let component: MonetaryComponent
let fixture: ComponentFixture<MonetaryComponent> let fixture: ComponentFixture<MonetaryComponent>
let input: HTMLInputElement
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
@ -24,37 +23,22 @@ describe('MonetaryComponent', () => {
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR) fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
component = fixture.componentInstance component = fixture.componentInstance
fixture.detectChanges() fixture.detectChanges()
input = component.inputField.nativeElement
}) })
it('should set the currency code correctly', () => { it('should set the currency code and monetary value correctly', () => {
expect(component.currencyCode).toEqual('USD') // default expect(component.currency).toEqual('USD') // default
component.currencyCode = 'EUR' component.writeValue('G123.4')
expect(component.currencyCode).toEqual('EUR') expect(component.currency).toEqual('G')
component.value = 'G123.4' component.writeValue('EUR123.4')
jest expect(component.currency).toEqual('EUR')
.spyOn(document, 'activeElement', 'get') expect(component.monetaryValue).toEqual('123.40')
.mockReturnValue(component.currencyField.nativeElement)
expect(component.currencyCode).toEqual('G')
}) })
it('should parse monetary value only when out of focus', () => { it('should set monetary value to fixed decimals', () => {
component.monetaryValue = 10.5 component.monetaryValue = '10.5'
jest.spyOn(document, 'activeElement', 'get').mockReturnValue(null) component.monetaryValueChange(true)
expect(component.monetaryValue).toEqual('10.50') 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', () => { it('should set the default currency code based on LOCALE_ID', () => {

View File

@ -1,11 +1,4 @@
import { import { Component, forwardRef, Inject, LOCALE_ID } from '@angular/core'
Component,
ElementRef,
forwardRef,
Inject,
LOCALE_ID,
ViewChild,
} from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms' import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { AbstractInputComponent } from '../abstract-input' import { AbstractInputComponent } from '../abstract-input'
import { getLocaleCurrencyCode } from '@angular/common' import { getLocaleCurrencyCode } from '@angular/common'
@ -23,40 +16,47 @@ import { getLocaleCurrencyCode } from '@angular/common'
styleUrls: ['./monetary.component.scss'], styleUrls: ['./monetary.component.scss'],
}) })
export class MonetaryComponent extends AbstractInputComponent<string> { export class MonetaryComponent extends AbstractInputComponent<string> {
@ViewChild('currencyField') public currency: string = ''
currencyField: ElementRef public monetaryValue: string = ''
defaultCurrencyCode: string defaultCurrencyCode: string
constructor(@Inject(LOCALE_ID) currentLocale: string) { constructor(@Inject(LOCALE_ID) currentLocale: string) {
super() super()
this.defaultCurrencyCode = getLocaleCurrencyCode(currentLocale) this.currency = this.defaultCurrencyCode =
getLocaleCurrencyCode(currentLocale)
} }
get currencyCode(): string { writeValue(newValue: any): void {
const focused = document.activeElement === this.currencyField?.nativeElement this.currency = this.parseCurrencyCode(newValue)
if (focused && this.value) this.monetaryValue = this.parseMonetaryValue(newValue, true)
return this.value.toUpperCase().match(/^([A-Z]{0,3})/)?.[0]
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 ( return (
this.value value
?.toString() ?.toString()
.toUpperCase() .toUpperCase()
.match(/^([A-Z]{1,3})/)?.[0] ?? this.defaultCurrencyCode .match(/^([A-Z]{1,3})/)?.[0] ?? this.defaultCurrencyCode
) )
} }
set currencyCode(value: string) { private parseMonetaryValue(value: string, fixed: boolean = false): string {
this.value = value.toUpperCase() + this.monetaryValue?.toString() const val: number = parseFloat(value.toString().replace(/[^0-9.,-]+/g, ''))
} return fixed ? val.toFixed(2) : val.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)
} }
} }

View File

@ -112,7 +112,7 @@
<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>
@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]"> <div [formGroup]="customFieldFormFields.controls[i]">
@switch (getCustomFieldFromInstance(fieldInstance)?.data_type) { @switch (getCustomFieldFromInstance(fieldInstance)?.data_type) {
@case (CustomFieldDataType.String) { @case (CustomFieldDataType.String) {