diff --git a/src/app/products/loan-products/import-loan-product-dialog/import-loan-product-dialog.component.html b/src/app/products/loan-products/import-loan-product-dialog/import-loan-product-dialog.component.html new file mode 100644 index 000000000..3b19c59f6 --- /dev/null +++ b/src/app/products/loan-products/import-loan-product-dialog/import-loan-product-dialog.component.html @@ -0,0 +1,24 @@ +

Import Loan Product

+ + +
+

Select a JSON file containing the loan product definition.

+ + + +
+
+ + + + + diff --git a/src/app/products/loan-products/import-loan-product-dialog/import-loan-product-dialog.component.scss b/src/app/products/loan-products/import-loan-product-dialog/import-loan-product-dialog.component.scss new file mode 100644 index 000000000..fc71ec719 --- /dev/null +++ b/src/app/products/loan-products/import-loan-product-dialog/import-loan-product-dialog.component.scss @@ -0,0 +1,18 @@ +mat-dialog-content { + padding: 20px 24px; + min-height: 150px; + + p { + margin-bottom: 20px; + color: rgb(0 0 0 / 60%); + } +} + +mat-dialog-actions { + padding: 8px 24px 16px; + margin: 0; + + button { + margin-left: 8px; + } +} diff --git a/src/app/products/loan-products/import-loan-product-dialog/import-loan-product-dialog.component.ts b/src/app/products/loan-products/import-loan-product-dialog/import-loan-product-dialog.component.ts new file mode 100644 index 000000000..610f916dd --- /dev/null +++ b/src/app/products/loan-products/import-loan-product-dialog/import-loan-product-dialog.component.ts @@ -0,0 +1,61 @@ +import { Component, OnInit, inject } from '@angular/core'; +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialogTitle, + MatDialogActions, + MatDialogClose, + MatDialogContent +} from '@angular/material/dialog'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; +import { FileUploadComponent } from '../../../shared/file-upload/file-upload.component'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-import-loan-product-dialog', + templateUrl: './import-loan-product-dialog.component.html', + styleUrls: ['./import-loan-product-dialog.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatDialogTitle, + MatDialogContent, + FileUploadComponent, + MatDialogActions, + MatDialogClose + ] +}) +export class ImportLoanProductDialogComponent implements OnInit { + dialogRef = inject>(MatDialogRef); + private formBuilder = inject(UntypedFormBuilder); + data = inject(MAT_DIALOG_DATA); + + /** Import Loan Product form. */ + importLoanProductForm: UntypedFormGroup; + + ngOnInit() { + this.createImportLoanProductForm(); + } + + /** + * Creates the import loan product form. + */ + createImportLoanProductForm() { + this.importLoanProductForm = this.formBuilder.group({ + file: [ + '', + Validators.required + ] + }); + } + + /** + * Sets file form control value. + * @param {any} $event file change event. + */ + onFileSelect($event: any) { + if ($event.target.files.length > 0) { + const file = $event.target.files[0]; + this.importLoanProductForm.get('file').setValue(file); + } + } +} diff --git a/src/app/products/loan-products/loan-products.component.html b/src/app/products/loan-products/loan-products.component.html index 6c629c01b..2c691b361 100644 --- a/src/app/products/loan-products/loan-products.component.html +++ b/src/app/products/loan-products/loan-products.component.html @@ -5,6 +5,12 @@ {{ 'labels.buttons.Create Loan Product' | translate }} +
+ +
diff --git a/src/app/products/loan-products/loan-products.component.ts b/src/app/products/loan-products/loan-products.component.ts index b098cc098..c0f4ee595 100644 --- a/src/app/products/loan-products/loan-products.component.ts +++ b/src/app/products/loan-products/loan-products.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit, TemplateRef, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort, MatSortHeader } from '@angular/material/sort'; +import { MatDialog } from '@angular/material/dialog'; import { MatTableDataSource, MatTable, @@ -17,9 +18,17 @@ import { } from '@angular/material/table'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +/** rxjs Imports */ +import { switchMap, catchError, finalize } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; + /* Custom Services */ import { PopoverService } from '../../configuration-wizard/popover/popover.service'; import { ConfigurationWizardService } from '../../configuration-wizard/configuration-wizard.service'; +import { ProductsService } from '../products.service'; +import { SettingsService } from 'app/settings/settings.service'; +import { ErrorHandlerService } from 'app/core/error-handler/error-handler.service'; +import { ImportLoanProductDialogComponent } from './import-loan-product-dialog/import-loan-product-dialog.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MatTooltip } from '@angular/material/tooltip'; import { StatusLookupPipe } from '../../pipes/status-lookup.pipe'; @@ -56,6 +65,10 @@ export class LoanProductsComponent implements OnInit, AfterViewInit { private router = inject(Router); private configurationWizardService = inject(ConfigurationWizardService); private popoverService = inject(PopoverService); + private dialog = inject(MatDialog); + private productsService = inject(ProductsService); + private settingsService = inject(SettingsService); + private errorHandler = inject(ErrorHandlerService); loanProductsData: any; displayedColumns: string[] = [ @@ -157,4 +170,75 @@ export class LoanProductsComponent implements OnInit, AfterViewInit { this.configurationWizardService.showLoanProducts = true; this.router.navigate(['/products']); } + + /** + * Opens the import loan product dialog. + */ + openImportDialog(): void { + const importDialogRef = this.dialog.open(ImportLoanProductDialogComponent, { + width: '50rem' + }); + + importDialogRef.afterClosed().subscribe((response: any) => { + if (response && response.file) { + this.importLoanProduct(response.file); + } + }); + } + + /** + * Imports a loan product from a JSON file. + * @param {File} file The JSON file containing loan product definition. + */ + importLoanProduct(file: File): void { + const reader = new FileReader(); + + reader.onload = (e: any) => { + try { + const loanProductData = JSON.parse(e.target.result); + + // Remove fields that shouldn't be included in creation + delete loanProductData.id; + delete loanProductData.status; + + // Add required fields for API with null safety checks + const locale = this.settingsService.language?.code || 'en'; + const dateFormat = this.settingsService.dateFormat || 'dd MMMM yyyy'; + + const payload = { + ...loanProductData, + locale, + dateFormat, + // Add default required fields if not present + currencyCode: loanProductData.currencyCode || 'USD', + digitsAfterDecimal: loanProductData.digitsAfterDecimal ?? 2, + charges: loanProductData.charges || [] + }; + + // Call API to create loan product with proper error handling + this.productsService + .createLoanProduct(payload) + .pipe( + switchMap(() => this.productsService.getLoanProducts()), + catchError((error) => this.errorHandler.handleError(error, 'Loan Product Import')) + ) + .subscribe({ + next: (data: any) => { + this.loanProductsData = data; + this.dataSource.data = this.loanProductsData; + this.errorHandler.showSuccess('Loan product imported successfully!'); + }, + error: () => { + // Error already handled by ErrorHandlerService + } + }); + } catch (error) { + this.errorHandler.showInfo( + 'The selected file is not a valid JSON file. Please check the file format and try again.' + ); + } + }; + + reader.readAsText(file); + } }