/** * Copyright since 2025 Mifos Initiative * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Angular Imports */ import { Component, OnInit, TemplateRef, ElementRef, ViewChild, AfterViewInit, OnDestroy, OnChanges, SimpleChanges, inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { UntypedFormBuilder, UntypedFormControl, Validators, ReactiveFormsModule } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; /** Custom Dialogs */ import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dialog.component'; /** Custom Services */ import { OrganizationService } from '../../organization.service'; import { PopoverService } from '../../../configuration-wizard/popover/popover.service'; import { ConfigurationWizardService } from '../../../configuration-wizard/configuration-wizard.service'; /** Custom Dialog Component */ import { ContinueSetupDialogComponent } from '../../../configuration-wizard/continue-setup-dialog/continue-setup-dialog.component'; import { takeUntil } from 'rxjs/operators'; import { ReplaySubject, Subject } from 'rxjs'; import { Currency } from 'app/shared/models/general.model'; import { NgxMatSelectSearchModule } from 'ngx-mat-select-search'; import { AsyncPipe } from '@angular/common'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MatGridList, MatGridTile } from '@angular/material/grid-list'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { AlertService } from 'app/core/alert/alert.service'; /** * Manage Currencies component. */ @Component({ selector: 'mifosx-manage-currencies', templateUrl: './manage-currencies.component.html', styleUrls: ['./manage-currencies.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, NgxMatSelectSearchModule, FaIconComponent, MatGridList, MatGridTile, AsyncPipe ] }) export class ManageCurrenciesComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges { private route = inject(ActivatedRoute); private formBuilder = inject(UntypedFormBuilder); private organizationservice = inject(OrganizationService); dialog = inject(MatDialog); private router = inject(Router); private translateService = inject(TranslateService); private configurationWizardService = inject(ConfigurationWizardService); private popoverService = inject(PopoverService); private alertService = inject(AlertService); //** Defining PlaceHolders for the search bar */ placeHolderLabel = ''; noEntriesFoundLabel = ''; /** Selected Currencies data. */ selectedCurrencies: any[]; /** Currency options data */ currencyList: Currency[] = []; /** Currency form */ currencyForm: any; /** Currency form reference */ @ViewChild('formRef', { static: true }) formRef: any; @ViewChild('currencyFormRef') currencyFormRef: ElementRef; @ViewChild('templateCurrencyFormRef') templateCurrencyFormRef: TemplateRef; /** Currency data. */ protected currencyData: ReplaySubject = new ReplaySubject(1); /** control for the filter select */ protected filterFormCtrl: UntypedFormControl = new UntypedFormControl(''); /** Subject that emits when the component has been destroyed. */ protected _onDestroy = new Subject(); /** * Retrieves the currency data from `resolve`. * @param {ActivatedRoute} route Activated Route * @param {FormBuilder} formBuilder Form Builder * @param {OrganizationService} organizationservice Organization Service * @param {MatDialog} dialog Mat Dialog */ constructor() { this.route.parent.data.subscribe((data: { currencies: any }) => { this.selectedCurrencies = data.currencies.selectedCurrencyOptions; this.currencyList = data.currencies.currencyOptions; }); } ngOnInit() { this.placeHolderLabel = this.translateService.instant('labels.text.Search'); this.noEntriesFoundLabel = this.translateService.instant('labels.text.No data found'); this.filterFormCtrl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => { this.searchItem(); }); this.createCurrencyForm(); } ngOnDestroy(): void { this._onDestroy.next(); this._onDestroy.complete(); } ngOnChanges(changes: SimpleChanges): void { if (this.currencyList) { this.currencyData.next(this.currencyList.slice()); } } /** * Creates the currency form. */ createCurrencyForm() { this.currencyForm = this.formBuilder.group({ currency: [ '', Validators.required ] }); } searchItem(): void { if (this.currencyList) { const search: string = this.filterFormCtrl.value.toLowerCase(); if (!search) { this.currencyData.next(this.currencyList.slice()); } else { this.currencyData.next( this.currencyList.filter((option: Currency) => { return option.name.toLowerCase().indexOf(search) >= 0 || option.code.toLowerCase().indexOf(search) >= 0; }) ); } } } /** * Adds a new currency to the list. */ addCurrency() { const newCurrency = this.currencyForm.value.currency; const selectedCurrencyCodes: any[] = this.selectedCurrencies.map((currency) => currency.code); if (!selectedCurrencyCodes.includes(newCurrency.code)) { selectedCurrencyCodes.push(newCurrency.code); this.organizationservice.updateCurrencies(selectedCurrencyCodes).subscribe((response: any) => { this.formRef.resetForm(); if (response?.rollbackTransaction) { // Parked for checker approval - the currency has not actually been added yet, so the // local list must not be mutated as if it had. this.alertService.alert({ type: 'Success', message: 'Currency configuration update has been submitted for approval.' }); return; } this.selectedCurrencies.push(newCurrency); if (this.configurationWizardService.showCurrencyForm) { this.configurationWizardService.showCurrencyForm = false; this.openDialog(); } }); } } /** * Deletes Currency * @param {string} currencyCode Currency Code * @param {number} index Index */ deleteCurrency(currencyCode: string, index: number) { const selectedCurrencyCodes: any[] = this.selectedCurrencies.map((currency) => currency.code); selectedCurrencyCodes.splice(index, 1); const deleteCurrencyDialogRef = this.dialog.open(DeleteDialogComponent, { data: { deleteContext: `currency: ${currencyCode}` } }); deleteCurrencyDialogRef.afterClosed().subscribe((response: any) => { if (response.delete) { this.organizationservice.updateCurrencies(selectedCurrencyCodes).subscribe((updateResponse: any) => { this.formRef.resetForm(); if (updateResponse?.rollbackTransaction) { // Parked for checker approval - the currency has not actually been removed yet, so the // local list must not be mutated as if it had. this.alertService.alert({ type: 'Success', message: 'Currency configuration update has been submitted for approval.' }); return; } this.selectedCurrencies.splice(index, 1); }); } }); } showPopover( template: TemplateRef, target: HTMLElement | ElementRef, position: string, backdrop: boolean ): void { setTimeout(() => this.popoverService.open(template, target, position, backdrop, {}), 200); } ngAfterViewInit() { if (this.configurationWizardService.showCurrencyForm) { setTimeout(() => { this.showPopover(this.templateCurrencyFormRef, this.currencyFormRef.nativeElement, 'bottom', true); }); } } nextStep() { this.configurationWizardService.showCurrencyForm = false; this.configurationWizardService.showCreateHoliday = true; this.router.navigate(['/organization']); } previousStep() { this.configurationWizardService.showCurrencyForm = false; this.configurationWizardService.showCurrencyList = true; this.router.navigate(['/organization/currencies']); } openDialog() { const continueSetupDialogRef = this.dialog.open(ContinueSetupDialogComponent, { data: { stepName: 'currency' } }); continueSetupDialogRef.afterClosed().subscribe((response: { step: number }) => { if (response.step === 1) { this.configurationWizardService.showCurrencyForm = false; this.router.navigate(['../'], { relativeTo: this.route }); } else if (response.step === 2) { this.configurationWizardService.showCurrencyForm = true; this.router.routeReuseStrategy.shouldReuseRoute = () => false; this.router.onSameUrlNavigation = 'reload'; this.router.navigate(['/organization/currencies/manage']); } else if (response.step === 3) { this.configurationWizardService.showCurrencyForm = false; this.configurationWizardService.showCreateHoliday = true; this.router.navigate(['/organization']); } }); } }