Upgrading to Angular 20:

Replaced checked in package-lock.json by regenerated one.
Ran ng update @angular/core@20 @angular/cli@20
Ran npm install @angular-builders/jest@^20.0.0 @fortawesome/angular-fontawesome@^3.0.0 @swimlane/ngx-graph@^11
Ran npm install ngx-mat-select-search@^8.0.2.
Ran ng update @angular/material@20.
Updated all eslint and typescript to the latest versions.
Removed @angular-devkit/schematics.
Updated fontawesome.
Updated rxjs and tslib.
Moved required node version from 16 to 20.
Updated jest-preset-angular.
Set preserveWhitespaces to false.
Corrected two type declarations in two typescript files to make ng serve run without errors.
Removed unused angular-devkit/schematics from view-user.component.ts .
Updated list of supported browsers.
Upgraded prettier.
Ran prettier . --write the first time.
Ran prettier . --write the second time.
Ran ng generate @angular/core:inject with defaults, excepted a handful of files.
Fixed remaining eslint problems.
Added dist directory to ignores in eslint.config.js.
Let prettier fix formatting, almost everything is just line breaks.
Made htmlhint ignore @if and @for Angular constructs.
Updated node version in Github workflow to 22.12.0 (build.yml).
Updated .env.ts.
Updated Angular datetime format strings from YYYY-MM-DD to yyyy-MM-dd.
Merged changes from dev into angular20 branch.
This commit is contained in:
Felix van Hove
2025-12-08 11:01:01 +01:00
parent f1b4ec2cf2
commit 7fd1486c08
1516 changed files with 39367 additions and 36037 deletions
@@ -1,12 +1,20 @@
<span (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()">
<span class="m-l-5" *ngIf="displayL && iconVisible" (click)="copyValue()"
><b><fa-icon icon="copy" size="sm" title="{{ 'labels.text.Copy Account Number' | translate }}"></fa-icon></b
></span>
<span class="m-l-5" *ngIf="accountType">
<a href="{{ getAccountLink() }}">{{ accountNo }}</a>
</span>
<span class="m-l-5" *ngIf="!accountType">{{ accountNo }}</span>
<span class="m-l-3" *ngIf="displayR && iconVisible" (click)="copyValue()"
><b><fa-icon icon="copy" size="sm" title="{{ 'labels.text.Copy Account Number' | translate }}"></fa-icon></b
></span>
@if (displayL && iconVisible) {
<span class="m-l-5" (click)="copyValue()"
><b><fa-icon icon="copy" size="sm" title="{{ 'labels.text.Copy Account Number' | translate }}"></fa-icon></b
></span>
}
@if (accountType) {
<span class="m-l-5">
<a href="{{ getAccountLink() }}">{{ accountNo }}</a>
</span>
}
@if (!accountType) {
<span class="m-l-5">{{ accountNo }}</span>
}
@if (displayR && iconVisible) {
<span class="m-l-3" (click)="copyValue()"
><b><fa-icon icon="copy" size="sm" title="{{ 'labels.text.Copy Account Number' | translate }}"></fa-icon></b
></span>
}
</span>
@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnInit, inject } from '@angular/core';
import { Clipboard } from '@angular/cdk/clipboard';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -13,6 +13,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class AccountNumberComponent implements OnInit {
private clipboard = inject(Clipboard);
@Input() accountNo: string;
@Input() display = 'right';
@Input() clientId: number | null = null;
@@ -23,8 +25,6 @@ export class AccountNumberComponent implements OnInit {
displayL = false;
displayR = true;
constructor(private clipboard: Clipboard) {}
ngOnInit(): void {
this.displayL = this.display === 'left';
this.displayR = this.display === 'right';
@@ -1,13 +1,16 @@
<span class="layout-row" *ngIf="accountTitle">
<div [ngClass]="getWithClass(withTitle)" class="header">
{{ 'labels.inputs.' + accountTitle | translate }}
</div>
@if (accountTitle) {
<span class="layout-row">
<div [ngClass]="getWithClass(withTitle)" class="header">
{{ 'labels.inputs.' + accountTitle | translate }}
</div>
<div [ngClass]="getWithClass(withAccount)">
{{ glAccountValue() }}
</div>
</span>
}
<div [ngClass]="getWithClass(withAccount)">
@if (!accountTitle) {
<div class="flex-100">
{{ glAccountValue() }}
</div>
</span>
<div class="flex-100" *ngIf="!accountTitle">
{{ glAccountValue() }}
</div>
}
@@ -1,6 +1,6 @@
import { Component, Input } from '@angular/core';
import { GLAccount } from 'app/shared/models/general.model';
import { NgIf, NgClass } from '@angular/common';
import { NgClass } from '@angular/common';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -1,40 +1,48 @@
<div class="layout-column m-r-10">
<mat-form-field class="flex-100" *ngIf="required">
<mat-label>{{ 'labels.inputs.' + inputLabel | translate }}</mat-label>
@if (required) {
<mat-form-field class="flex-100">
<mat-label>{{ 'labels.inputs.' + inputLabel | translate }}</mat-label>
<mat-select required [formControl]="inputFormControl">
<mat-option>
<ngx-mat-select-search
placeholderLabel="{{ placeHolderLabel }}"
noEntriesFoundLabel="{{ noEntriesFoundLabel }}"
[formControl]="filterFormCtrl"
></ngx-mat-select-search>
</mat-option>
@for (glAccount of glAccountData | async; track glAccount) {
<mat-option [value]="glAccount.id">
{{ '(' + glAccount.glCode + ') ' + glAccount.name }}
</mat-option>
}
</mat-select>
@if (inputFormControl.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Account' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<mat-select required [formControl]="inputFormControl">
<mat-option>
<ngx-mat-select-search
placeholderLabel="{{ placeHolderLabel }}"
noEntriesFoundLabel="{{ noEntriesFoundLabel }}"
[formControl]="filterFormCtrl"
></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let glAccount of glAccountData | async" [value]="glAccount.id">
{{ '(' + glAccount.glCode + ') ' + glAccount.name }}
</mat-option>
</mat-select>
<mat-error *ngIf="inputFormControl.hasError('required')">
{{ 'labels.inputs.Account' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-100" *ngIf="!required">
<mat-label>{{ 'labels.inputs.' + inputLabel | translate }}</mat-label>
<mat-select [formControl]="inputFormControl">
<mat-option>
<ngx-mat-select-search
[formControl]="filterFormCtrl"
placeholderLabel="{{ placeHolderLabel }}"
noEntriesFoundLabel="{{ noEntriesFoundLabel }}"
>
</ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let glAccount of glAccountData | async" [value]="glAccount.id">
{{ '(' + glAccount.glCode + ') ' + glAccount.name }}
</mat-option>
</mat-select>
</mat-form-field>
@if (!required) {
<mat-form-field class="flex-100">
<mat-label>{{ 'labels.inputs.' + inputLabel | translate }}</mat-label>
<mat-select [formControl]="inputFormControl">
<mat-option>
<ngx-mat-select-search
[formControl]="filterFormCtrl"
placeholderLabel="{{ placeHolderLabel }}"
noEntriesFoundLabel="{{ noEntriesFoundLabel }}"
>
</ngx-mat-select-search>
</mat-option>
@for (glAccount of glAccountData | async; track glAccount) {
<mat-option [value]="glAccount.id">
{{ '(' + glAccount.glCode + ') ' + glAccount.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
</div>
@@ -1,10 +1,10 @@
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, inject } from '@angular/core';
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { GLAccount } from 'app/shared/models/general.model';
import { ReplaySubject, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { NgIf, NgFor, AsyncPipe } from '@angular/common';
import { AsyncPipe } from '@angular/common';
import { NgxMatSelectSearchModule } from 'ngx-mat-select-search';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -19,6 +19,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class GlAccountSelectorComponent implements OnInit, OnChanges, OnDestroy {
private translateService = inject(TranslateService);
@Input() inputFormControl: UntypedFormControl;
@Input() glAccountList: GLAccount[] = [];
@Input() required = false;
@@ -36,8 +38,6 @@ export class GlAccountSelectorComponent implements OnInit, OnChanges, OnDestroy
placeHolderLabel = '';
noEntriesFoundLabel = '';
constructor(private translateService: TranslateService) {}
ngOnInit(): void {
// listen for search field value changes
this.filterFormCtrl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => {
@@ -1,155 +1,158 @@
<div *ngIf="isJournalEntryLoaded">
<div *ngIf="dataSource?.data[0].manualEntry" class="layout-row layout-align-end container m-b-20">
<button
mat-raised-button
color="primary"
(click)="revertTransaction()"
*mifosxHasPermission="'REVERSE_JOURNALENTRY'"
[disabled]="dataSource?.data[0].reversed"
>
<span *ngIf="!dataSource.data[0].reversed">
<fa-icon icon="undo" class="m-r-10"></fa-icon>{{ 'labels.buttons.Revert Transaction' | translate }}
</span>
<span *ngIf="dataSource.data[0].reversed">{{ 'labels.buttons.Transaction Reverted' | translate }}</span>
</button>
@if (isJournalEntryLoaded) {
<div>
@if (dataSource?.data[0].manualEntry) {
<div class="layout-row layout-align-end container m-b-20">
<button
mat-raised-button
color="primary"
(click)="revertTransaction()"
*mifosxHasPermission="'REVERSE_JOURNALENTRY'"
[disabled]="dataSource?.data[0].reversed"
>
@if (!dataSource.data[0].reversed) {
<span>
<fa-icon icon="undo" class="m-r-10"></fa-icon>{{ 'labels.buttons.Revert Transaction' | translate }}
</span>
}
@if (dataSource.data[0].reversed) {
<span>{{ 'labels.buttons.Transaction Reverted' | translate }}</span>
}
</button>
</div>
}
@if (isViewTransaction()) {
<div class="container m-b-20">
@if (dataSource) {
<mat-card class="mat-elevation-z8" ngClass="{{ journalEntryColor() }}">
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-25 header">
{{ 'labels.inputs.Office' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].officeName }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Transaction Date' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].transactionDate | dateFormat }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Created By' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].createdByUserName }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Submitted on' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].submittedOnDate | datetimeFormat }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Manual Journal Entry' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].manualEntry | yesNo }}
</div>
@if (dataSource.data[0].externalAssetOwner) {
<div class="flex-25 header">
{{ 'labels.inputs.External Asset Owner' | translate }}
</div>
}
@if (dataSource.data[0].externalAssetOwner) {
<div class="flex-25">
{{ dataSource.data[0].externalAssetOwner }}
</div>
}
</div>
</mat-card-content>
</mat-card>
}
</div>
}
<div class="mat-elevation-z8 container layout-align-center layout-row-wrap">
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="id">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Entry ID' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.id }}</td>
</ng-container>
<ng-container matColumnDef="glAccountType">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Type' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.glAccountType.value }}</td>
</ng-container>
<ng-container matColumnDef="glAccountCode">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Account Code' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.glAccountCode }}</td>
</ng-container>
<ng-container matColumnDef="glAccountName">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Account Name' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.glAccountName }}</td>
</ng-container>
<ng-container matColumnDef="debit">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Debit' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">
@if (transaction.entryType.value === 'DEBIT') {
<span>
{{ transaction.currency.displaySymbol || transaction.currency.code }}
{{ transaction.amount | formatNumber }}
</span>
}
</td>
</ng-container>
<ng-container matColumnDef="credit">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Credit' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">
@if (transaction.entryType.value === 'CREDIT') {
<span>
{{ transaction.currency.displaySymbol || transaction.currency.code }}
{{ transaction.amount | formatNumber }}
</span>
}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="viewJournalEntry(row)"
class="select-row"
></tr>
</table>
<div class="layout-row layout-align-center m-t-15 m-b-15 gap-5px layout-xs-column">
<button type="button" color="primary" mat-raised-button (click)="goBack()">
{{ 'labels.buttons.Back' | translate }}
</button>
</div>
</div>
</div>
}
<div class="container m-b-20" *ngIf="isViewTransaction()">
<mat-card class="mat-elevation-z8" ngClass="{{ journalEntryColor() }}" *ngIf="dataSource">
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-25 header">
{{ 'labels.inputs.Office' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].officeName }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Transaction Date' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].transactionDate | dateFormat }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Created By' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].createdByUserName }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Submitted on' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].submittedOnDate | datetimeFormat }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Manual Journal Entry' | translate }}
</div>
<div class="flex-25">
{{ dataSource.data[0].manualEntry | yesNo }}
</div>
<div class="flex-25 header" *ngIf="dataSource.data[0].externalAssetOwner">
{{ 'labels.inputs.External Asset Owner' | translate }}
</div>
<div class="flex-25" *ngIf="dataSource.data[0].externalAssetOwner">
{{ dataSource.data[0].externalAssetOwner }}
</div>
</div>
</mat-card-content>
</mat-card>
</div>
<div class="mat-elevation-z8 container layout-align-center layout-row-wrap">
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="id">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Entry ID' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.id }}</td>
</ng-container>
<ng-container matColumnDef="glAccountType">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Type' | translate }}</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.glAccountType.value }}</td>
</ng-container>
<ng-container matColumnDef="glAccountCode">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Account Code' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.glAccountCode }}</td>
</ng-container>
<ng-container matColumnDef="glAccountName">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Account Name' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.glAccountName }}</td>
</ng-container>
<ng-container matColumnDef="debit">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Debit' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">
<span *ngIf="transaction.entryType.value === 'DEBIT'">
{{ transaction.currency.displaySymbol || transaction.currency.code }}
{{ transaction.amount | formatNumber }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="credit">
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Credit' | translate }}
</th>
<td mat-cell *matCellDef="let transaction">
<span *ngIf="transaction.entryType.value === 'CREDIT'">
{{ transaction.currency.displaySymbol || transaction.currency.code }}
{{ transaction.amount | formatNumber }}
</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="viewJournalEntry(row)"
class="select-row"
></tr>
</table>
@if (!isJournalEntryLoaded) {
<div class="container m-b-20">
<div class="alert">
<div class="message">
<i class="fa fa-exclamation-circle alert-check"></i>
{{ 'labels.text.No data found' | translate }}
</div>
</div>
<div class="layout-row layout-align-center m-t-15 m-b-15 gap-5px layout-xs-column">
<button type="button" color="primary" mat-raised-button (click)="goBack()">
{{ 'labels.buttons.Back' | translate }}
</button>
</div>
</div>
</div>
<div class="container m-b-20" *ngIf="!isJournalEntryLoaded">
<div class="alert">
<div class="message">
<i class="fa fa-exclamation-circle alert-check"></i>
{{ 'labels.text.No data found' | translate }}
</div>
</div>
<div class="layout-row layout-align-center m-t-15 m-b-15 gap-5px layout-xs-column">
<button type="button" color="primary" mat-raised-button (click)="goBack()">
{{ 'labels.buttons.Back' | translate }}
</button>
</div>
</div>
}
@@ -1,4 +1,4 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Component, OnInit, ViewChild, inject } from '@angular/core';
import { ViewJournalEntryComponent } from '../view-journal-entry/view-journal-entry.component';
import { RevertTransactionComponent } from 'app/accounting/revert-transaction/revert-transaction.component';
import { AccountingService } from 'app/accounting/accounting.service';
@@ -19,7 +19,7 @@ import {
MatRowDef,
MatRow
} from '@angular/material/table';
import { Location, NgIf } from '@angular/common';
import { Location } from '@angular/common';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { DatetimeFormatPipe } from '../../../pipes/datetime-format.pipe';
@@ -53,6 +53,12 @@ import { YesnoPipe } from '@pipes/yesno.pipe';
]
})
export class ViewJournalEntryTransactionComponent implements OnInit {
private accountingService = inject(AccountingService);
private route = inject(ActivatedRoute);
private router = inject(Router);
dialog = inject(MatDialog);
private location = inject(Location);
title: string;
journalEntriesData: any[];
/** Transaction data. */
@@ -80,20 +86,6 @@ export class ViewJournalEntryTransactionComponent implements OnInit {
isManualJournalEntry = false;
/**
* @param {AccountingService} accountingService Accounting Service.
* @param {ActivatedRoute} route Activated Route.
* @param {Router} router Router for navigation.
* @param {MatDialog} dialog Dialog reference.
*/
constructor(
private accountingService: AccountingService,
private route: ActivatedRoute,
private router: Router,
public dialog: MatDialog,
private location: Location
) {}
/**
* Retrieves the transaction data from `resolve` and sets the transaction table.
*/
@@ -39,8 +39,12 @@
<tr>
<th>
<span *ngIf="data.journalEntry.entryType.value === 'CREDIT'">{{ 'labels.inputs.Credit' | translate }}</span>
<span *ngIf="data.journalEntry.entryType.value === 'DEBIT'">{{ 'labels.inputs.Debit' | translate }}</span>
@if (data.journalEntry.entryType.value === 'CREDIT') {
<span>{{ 'labels.inputs.Credit' | translate }}</span>
}
@if (data.journalEntry.entryType.value === 'DEBIT') {
<span>{{ 'labels.inputs.Debit' | translate }}</span>
}
</th>
<td>{{ data.journalEntry.currency.displaySymbol }} {{ data.journalEntry.amount | formatNumber }}</td>
</tr>
@@ -50,54 +54,74 @@
<td>({{ data.journalEntry.currency.code }}) {{ data.journalEntry.currency.name }}</td>
</tr>
<tr *ngIf="data.journalEntry.referenceNumber">
<th>{{ 'labels.inputs.Reference Number' | translate }}</th>
<td>{{ data.journalEntry.referenceNumber }}</td>
</tr>
@if (data.journalEntry.referenceNumber) {
<tr>
<th>{{ 'labels.inputs.Reference Number' | translate }}</th>
<td>{{ data.journalEntry.referenceNumber }}</td>
</tr>
}
<tr *ngIf="data.journalEntry.comments">
<th>{{ 'labels.inputs.Comments' | translate }}</th>
<td>{{ data.journalEntry.comments }}</td>
</tr>
@if (data.journalEntry.comments) {
<tr>
<th>{{ 'labels.inputs.Comments' | translate }}</th>
<td>{{ data.journalEntry.comments }}</td>
</tr>
}
<tr *ngIf="existsPaymentDetails">
<th colspan="2" class="header">{{ 'labels.inputs.Payment Details' | translate }}</th>
</tr>
@if (existsPaymentDetails) {
<tr>
<th colspan="2" class="header">{{ 'labels.inputs.Payment Details' | translate }}</th>
</tr>
}
<tr *ngIf="existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.paymentType">
<th>{{ 'labels.inputs.Payment Type' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.paymentType.name }}</td>
</tr>
@if (existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.paymentType) {
<tr>
<th>{{ 'labels.inputs.Payment Type' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.paymentType.name }}</td>
</tr>
}
<tr *ngIf="existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.accountNumber">
<th>{{ 'labels.inputs.Account Number' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.accountNumber }}</td>
</tr>
@if (existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.accountNumber) {
<tr>
<th>{{ 'labels.inputs.Account Number' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.accountNumber }}</td>
</tr>
}
<tr *ngIf="existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.checkNumber">
<th>{{ 'labels.inputs.Cheque Number' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.checkNumber }}</td>
</tr>
@if (existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.checkNumber) {
<tr>
<th>{{ 'labels.inputs.Cheque Number' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.checkNumber }}</td>
</tr>
}
<tr *ngIf="existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.routingCode">
<th>{{ 'labels.inputs.Routing Code' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.routingCode }}</td>
</tr>
@if (existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.routingCode) {
<tr>
<th>{{ 'labels.inputs.Routing Code' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.routingCode }}</td>
</tr>
}
<tr *ngIf="existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.receiptNumber">
<th>{{ 'labels.inputs.Receipt Number' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.receiptNumber }}</td>
</tr>
@if (existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.receiptNumber) {
<tr>
<th>{{ 'labels.inputs.Receipt Number' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.receiptNumber }}</td>
</tr>
}
<tr *ngIf="existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.bankNumber">
<th>{{ 'labels.inputs.Bank Number' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.bankNumber }}</td>
</tr>
@if (existsPaymentDetails && data.journalEntry.transactionDetails.paymentDetails?.bankNumber) {
<tr>
<th>{{ 'labels.inputs.Bank Number' | translate }}</th>
<td>{{ data.journalEntry.transactionDetails.paymentDetails.bankNumber }}</td>
</tr>
}
<tr *ngIf="existsPaymentDetails">
<th>{{ 'labels.inputs.Created by' | translate }}</th>
<td>{{ data.journalEntry.createdByUserName }}</td>
</tr>
@if (existsPaymentDetails) {
<tr>
<th>{{ 'labels.inputs.Created by' | translate }}</th>
<td>{{ data.journalEntry.createdByUserName }}</td>
</tr>
}
<tr>
<th>{{ 'labels.inputs.Submitted on' | translate }}</th>
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, Inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -34,15 +34,17 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ViewJournalEntryComponent {
dialogRef = inject<MatDialogRef<ViewJournalEntryComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
existsPaymentDetails = false;
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {any} data Provides journal entry.
*/
constructor(
public dialogRef: MatDialogRef<ViewJournalEntryComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {
constructor() {
const data = this.data;
this.existsPaymentDetails =
data.journalEntry.transactionDetails != null && data.journalEntry.transactionDetails.paymentDetails != null;
}
@@ -1,150 +1,159 @@
<div class="layout-row-wrap responsive-column">
<div *ngIf="accountingRule" class="layout-row responsive-column flex-100">
<span class="flex-40">{{ 'labels.inputs.Type' | translate }}:</span>
<span class="flex-60">{{ 'labels.accounting.' + getAccountingRuleName(accountingRule.value) | translate }}</span>
</div>
@if (accountingRule) {
<div class="layout-row responsive-column flex-100">
<span class="flex-40">{{ 'labels.inputs.Type' | translate }}:</span>
<span class="flex-60">{{ 'labels.accounting.' + getAccountingRuleName(accountingRule.value) | translate }}</span>
</div>
}
<div class="layout-row-wrap responsive-column flex-100">
<div *ngIf="isCashOrAccrualAccounting()" class="layout-row-wrap responsive-column flex-100">
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Assets' | translate }}</h4>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Saving reference'"
[glAccount]="accountingMappings.savingsReferenceAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Overdraft portfolio'"
*ngIf="accountingMappings.overdraftPortfolioControl"
[glAccount]="accountingMappings.overdraftPortfolioControl"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Fees Receivable'"
*ngIf="isAccrualAccounting()"
[glAccount]="accountingMappings.feeReceivableAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Penalties Receivable'"
*ngIf="isAccrualAccounting()"
[glAccount]="accountingMappings.penaltyReceivableAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Interest Receivable'"
*ngIf="isAccrualAccounting()"
[glAccount]="accountingMappings.interestReceivableAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Liabilities' | translate }}</h4>
@if (isCashOrAccrualAccounting()) {
<div class="layout-row-wrap responsive-column flex-100">
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Assets' | translate }}</h4>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Saving control'"
[glAccount]="accountingMappings.savingsControlAccount"
[accountTitle]="'Saving reference'"
[glAccount]="accountingMappings.savingsReferenceAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Savings transfers in suspense'"
[glAccount]="accountingMappings.transfersInSuspenseAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
@if (accountingMappings.overdraftPortfolioControl) {
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Overdraft portfolio'"
[glAccount]="accountingMappings.overdraftPortfolioControl"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
}
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Interest Payable'"
*ngIf="isAccrualAccounting()"
[glAccount]="accountingMappings.interestPayableAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
@if (isAccrualAccounting()) {
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Fees Receivable'"
[glAccount]="accountingMappings.feeReceivableAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
}
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Escheat liability'"
*ngIf="accountingMappings.escheatLiabilityAccount"
[glAccount]="accountingMappings.escheatLiabilityAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Income' | translate }}</h4>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Income from fees'"
[glAccount]="accountingMappings.incomeFromFeeAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Income from penalties'"
[glAccount]="accountingMappings.incomeFromPenaltyAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Income from Interest'"
*ngIf="accountingMappings.incomeFromInterest"
[glAccount]="accountingMappings.incomeFromInterest"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Expenses' | translate }}</h4>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Interest on savings'"
[glAccount]="accountingMappings.interestOnSavingsAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Write-off'"
*ngIf="accountingMappings.writeOffAccount"
[glAccount]="accountingMappings.writeOffAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
@if (isAccrualAccounting()) {
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Penalties Receivable'"
[glAccount]="accountingMappings.penaltyReceivableAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
}
@if (isAccrualAccounting()) {
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Interest Receivable'"
[glAccount]="accountingMappings.interestReceivableAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
}
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Liabilities' | translate }}</h4>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Saving control'"
[glAccount]="accountingMappings.savingsControlAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Savings transfers in suspense'"
[glAccount]="accountingMappings.transfersInSuspenseAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
@if (isAccrualAccounting()) {
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Interest Payable'"
[glAccount]="accountingMappings.interestPayableAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
}
</div>
<div class="flex-100 layout-row-wrap responsive-column">
@if (accountingMappings.escheatLiabilityAccount) {
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Escheat liability'"
[glAccount]="accountingMappings.escheatLiabilityAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
}
</div>
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Income' | translate }}</h4>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Income from fees'"
[glAccount]="accountingMappings.incomeFromFeeAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Income from penalties'"
[glAccount]="accountingMappings.incomeFromPenaltyAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
@if (accountingMappings.incomeFromInterest) {
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Income from Interest'"
[glAccount]="accountingMappings.incomeFromInterest"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
}
</div>
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Expenses' | translate }}</h4>
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Interest on savings'"
[glAccount]="accountingMappings.interestOnSavingsAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
</div>
<div class="flex-100 layout-row-wrap responsive-column">
@if (accountingMappings.writeOffAccount) {
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Write-off'"
[glAccount]="accountingMappings.writeOffAccount"
[withTitle]="'40%'"
>
</mifosx-gl-account-display>
}
</div>
</div>
</div>
</div>
}
</div>
</div>
@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component, Input, inject } from '@angular/core';
import { Accounting } from 'app/core/utils/accounting';
import { OptionData } from 'app/shared/models/option-data.model';
import { GlAccountDisplayComponent } from '../gl-account-display/gl-account-display.component';
@@ -14,11 +14,11 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ViewSavingsAccountingDetailsComponent {
private accounting = inject(Accounting);
@Input() accountingRule: OptionData;
@Input() accountingMappings: any[] = [];
constructor(private accounting: Accounting) {}
isCashOrAccrualAccounting(): boolean {
if (this.accountingRule) {
return this.accounting.isCashOrAccrualAccounting(this.accountingRule);
@@ -1,11 +1,11 @@
<h1 mat-dialog-title>{{ 'labels.buttons.Cancel' | translate }}</h1>
<div mat-dialog-content>
<p *ngIf="!data.cancelContext">
{{ 'labels.text.Are you sure you want to leave this page and lose all inputted data' | translate }}?
</p>
<p *ngIf="data.cancelContext">
{{ 'labels.text.Are you sure you want to cancel' | translate }} {{ data.cancelContext }} ?
</p>
@if (!data.cancelContext) {
<p>{{ 'labels.text.Are you sure you want to leave this page and lose all inputted data' | translate }}?</p>
}
@if (data.cancelContext) {
<p>{{ 'labels.text.Are you sure you want to cancel' | translate }} {{ data.cancelContext }} ?</p>
}
</div>
<mat-dialog-actions align="end">
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.No' | translate }}</button>
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, Inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import {
MAT_DIALOG_DATA,
MatDialogRef,
@@ -28,11 +28,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class CancelDialogComponent {
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
*/
constructor(
public dialogRef: MatDialogRef<CancelDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
dialogRef = inject<MatDialogRef<CancelDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
}
@@ -9,17 +9,17 @@
label="{{ 'Password' | translateKey: 'inputs' }}"
></mifosx-input-password>
<mat-error>
<p
*ngIf="
changePasswordForm.get('password').errors?.['uppercase'] ||
changePasswordForm.get('password').errors?.['number'] ||
changePasswordForm.get('password').errors?.['repeated'] ||
changePasswordForm.get('password').errors?.['specialChar'] ||
changePasswordForm.get('password').errors?.['minlength']
"
>
{{ 'labels.commons.Password validation' | translate: { minchar: minPasswordLength } }}
</p>
@if (
changePasswordForm.get('password').errors?.['uppercase'] ||
changePasswordForm.get('password').errors?.['number'] ||
changePasswordForm.get('password').errors?.['repeated'] ||
changePasswordForm.get('password').errors?.['specialChar'] ||
changePasswordForm.get('password').errors?.['minlength']
) {
<p>
{{ 'labels.commons.Password validation' | translate: { minchar: minPasswordLength } }}
</p>
}
</mat-error>
<mifosx-input-password
@@ -30,9 +30,11 @@
</div>
<div class="error">
<p *ngIf="changePasswordForm.get('repeatPassword').errors?.['notequal']">
{{ 'Passwords do not match' | translateKey: 'inputs' }}
</p>
@if (changePasswordForm.get('repeatPassword').errors?.['notequal']) {
<p>
{{ 'Passwords do not match' | translateKey: 'inputs' }}
</p>
}
</div>
</form>
</div>
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, OnInit, Inject } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -41,6 +41,11 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ChangePasswordDialogComponent implements OnInit {
dialogRef = inject<MatDialogRef<ChangePasswordDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
private formBuilder = inject(UntypedFormBuilder);
private passwordsUtility = inject(PasswordsUtility);
minPasswordLength: number = environment.minPasswordLength | 12;
/** Change Password Form */
@@ -51,17 +56,6 @@ export class ChangePasswordDialogComponent implements OnInit {
'password'
];
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {any} data Provides any data.
*/
constructor(
public dialogRef: MatDialogRef<ChangePasswordDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private formBuilder: UntypedFormBuilder,
private passwordsUtility: PasswordsUtility
) {}
ngOnInit() {
this.createChangePasswordForm();
}
@@ -78,7 +72,8 @@ export class ChangePasswordDialogComponent implements OnInit {
'',
[
Validators.required,
this.confirmPassword('password')]
this.confirmPassword('password')
]
]
},
{ updateOn: 'blur' }
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, OnInit, Inject } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -30,16 +30,11 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ConfirmationDialogComponent implements OnInit {
dialogRef = inject<MatDialogRef<ConfirmationDialogComponent>>(MatDialogRef);
private dialogs = inject(Dialogs);
data = inject<DialogData>(MAT_DIALOG_DATA);
color: string;
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {any} data Provides a deleteContext.
*/
constructor(
public dialogRef: MatDialogRef<ConfirmationDialogComponent>,
private dialogs: Dialogs,
@Inject(MAT_DIALOG_DATA) public data: DialogData
) {}
ngOnInit() {
this.setColor();
@@ -2,7 +2,6 @@ import { Component, Input } from '@angular/core';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/* eslint-disable @angular-eslint/component-selector */
/* eslint-disable @angular-eslint/prefer-standalone */
@Component({
selector: 'ngx-mat-datetime-picker',
template: `
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, OnInit, Inject } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -28,12 +28,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class DeleteDialogComponent {
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {any} data Provides a deleteContext.
*/
constructor(
public dialogRef: MatDialogRef<DeleteDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
dialogRef = inject<MatDialogRef<DeleteDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
}
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, Inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -28,12 +28,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class DisableDialogComponent {
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {any} data Provides a disableContext.
*/
constructor(
public dialogRef: MatDialogRef<DisableDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
dialogRef = inject<MatDialogRef<DisableDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
}
@@ -1,11 +1,15 @@
<mat-form-field class="flex-28">
<mat-label>{{ getLabelText() }}</mat-label>
<mat-select placeholder="{{ getPlaceHolderText() }}" formControlName="controlSelect">
<mat-option *ngFor="let option of selectOptions" [value]="option.id">
{{ option.value | translateKey: 'catalogs' }}
</mat-option>
@for (option of selectOptions; track option) {
<mat-option [value]="option.id">
{{ option.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
<button *ngIf="controlSelect" matSuffix mat-icon-button aria-label="Clear" (click)="clearProperty($event)">
<mat-icon>{{ 'labels.buttons.Close' | translate }}</mat-icon>
</button>
@if (controlSelect) {
<button matSuffix mat-icon-button aria-label="Clear" (click)="clearProperty($event)">
<mat-icon>{{ 'labels.buttons.Close' | translate }}</mat-icon>
</button>
}
</mat-form-field>
@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component, Input, inject } from '@angular/core';
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { MatIconButton } from '@angular/material/button';
@@ -16,14 +16,14 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class DropdownComponent {
private translateService = inject(TranslateService);
@Input() placeHolderText: string;
@Input() labelText: string;
@Input() selectOptions: any[] = [];
@Input() controlSelect: UntypedFormControl;
@Input() required: boolean;
constructor(private translateService: TranslateService) {}
getPlaceHolderText(): string {
return this.placeHolderText ? this.translateService.instant('labels.inputs.' + this.placeHolderText) : '';
}
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, Inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -28,12 +28,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class EnableDialogComponent {
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {any} data Provides a enableContext.
*/
constructor(
public dialogRef: MatDialogRef<EnableDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any
) {}
dialogRef = inject<MatDialogRef<EnableDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
}
@@ -1,11 +1,15 @@
<span (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()">
<span class="m-l-5" *ngIf="displayL && iconVisible" (click)="copyValue()"
><b><fa-icon icon="copy" size="sm" title="{{ 'labels.text.Copy Account Number' | translate }}"></fa-icon></b
></span>
@if (displayL && iconVisible) {
<span class="m-l-5" (click)="copyValue()"
><b><fa-icon icon="copy" size="sm" title="{{ 'labels.text.Copy Account Number' | translate }}"></fa-icon></b
></span>
}
<span class="m-l-5"
><b>{{ entityName }}</b></span
>
<span class="m-l-3" *ngIf="displayR && iconVisible" (click)="copyValue()"
><b><fa-icon icon="copy" size="sm" title="{{ 'labels.text.Copy Account Number' | translate }}"></fa-icon></b
></span>
@if (displayR && iconVisible) {
<span class="m-l-3" (click)="copyValue()"
><b><fa-icon icon="copy" size="sm" title="{{ 'labels.text.Copy Account Number' | translate }}"></fa-icon></b
></span>
}
</span>
@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnInit, inject } from '@angular/core';
import { Clipboard } from '@angular/cdk/clipboard';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -13,6 +13,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class EntityNameComponent implements OnInit {
private clipboard = inject(Clipboard);
@Input() entityName: string;
@Input() display = 'right';
@@ -20,8 +22,6 @@ export class EntityNameComponent implements OnInit {
displayL = false;
displayR = true;
constructor(private clipboard: Clipboard) {}
ngOnInit(): void {
this.displayL = this.display === 'left';
this.displayR = this.display === 'right';
@@ -1,8 +1,12 @@
<h1 mat-dialog-title>{{ 'Error Log' | translate }}</h1>
<div mat-dialog-content>
<p *ngIf="!showAsCode">{{ displayData }}</p>
<span *ngIf="showAsCode" [innerHTML]="sanitizedData"></span>
@if (!showAsCode) {
<p>{{ displayData }}</p>
}
@if (showAsCode) {
<span [innerHTML]="sanitizedData"></span>
}
</div>
<mat-dialog-actions align="left">
@@ -1,5 +1,5 @@
/** Angular Imports. */
import { Component, OnInit, Inject, SecurityContext } from '@angular/core';
import { Component, OnInit, SecurityContext, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -26,17 +26,19 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ErrorDialogComponent {
dialogRef = inject<MatDialogRef<ErrorDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
private sanitizer = inject(DomSanitizer);
showAsCode = false;
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {unknown} data Provides any data.
* @param {DomSanitizer} sanitizer Service to sanitize HTML content.
*/
constructor(
public dialogRef: MatDialogRef<ErrorDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: unknown,
private sanitizer: DomSanitizer
) {
constructor() {
const data = this.data;
// Guard for non-string data to avoid runtime error
this.showAsCode = typeof data === 'string' && data.startsWith('<pre><code>');
}
@@ -1,16 +1,24 @@
<span (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()" *ngIf="!emptyValue">
<span (click)="copyValue()" *ngIf="displayL && iconVisible"
><fa-icon icon="copy" size="sm" title="Copy External Id"></fa-icon
></span>
<span class="m-l-3" *ngIf="displayL && !completed && iconVisible && isLongValue()" (click)="showValue()"
><fa-icon icon="eye" size="sm" [title]="externalId"></fa-icon
></span>
<span class="m-l-5" *ngIf="!completed" (click)="showValue()">{{ externalId | externalIdentifier }}</span>
<span *ngIf="completed" (click)="showValue()">{{ externalId }}</span>
<span class="m-l-5" *ngIf="displayR && !completed && iconVisible && isLongValue()" (click)="showValue()"
><fa-icon icon="eye" size="sm" [title]="externalId"></fa-icon
></span>
<span class="m-l-3" (click)="copyValue()" *ngIf="displayR && iconVisible"
><fa-icon icon="copy" size="sm" title="Copy External Id"></fa-icon
></span>
</span>
@if (!emptyValue) {
<span (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()">
@if (displayL && iconVisible) {
<span (click)="copyValue()"><fa-icon icon="copy" size="sm" title="Copy External Id"></fa-icon></span>
}
@if (displayL && !completed && iconVisible && isLongValue()) {
<span class="m-l-3" (click)="showValue()"><fa-icon icon="eye" size="sm" [title]="externalId"></fa-icon></span>
}
@if (!completed) {
<span class="m-l-5" (click)="showValue()">{{ externalId | externalIdentifier }}</span>
}
@if (completed) {
<span (click)="showValue()">{{ externalId }}</span>
}
@if (displayR && !completed && iconVisible && isLongValue()) {
<span class="m-l-5" (click)="showValue()"><fa-icon icon="eye" size="sm" [title]="externalId"></fa-icon></span>
}
@if (displayR && iconVisible) {
<span class="m-l-3" (click)="copyValue()"
><fa-icon icon="copy" size="sm" title="Copy External Id"></fa-icon
></span>
}
</span>
}
@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnInit, inject } from '@angular/core';
import { Clipboard } from '@angular/cdk/clipboard';
import { AlertService } from 'app/core/alert/alert.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
@@ -16,6 +16,9 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ExternalIdentifierComponent implements OnInit {
private clipboard = inject(Clipboard);
private alertService = inject(AlertService);
@Input() externalId: string;
@Input() completed = false;
@Input() display = 'right';
@@ -25,11 +28,6 @@ export class ExternalIdentifierComponent implements OnInit {
displayR = true;
emptyValue = false;
constructor(
private clipboard: Clipboard,
private alertService: AlertService
) {}
ngOnInit(): void {
this.emptyValue = !this.externalId || this.externalId === '';
this.displayL = this.display === 'left';
+37 -31
View File
@@ -1,35 +1,41 @@
<div class="layout-column m-b-20 f12" ngClass="{{ styleClass }}" id="footer" *ngIf="displayBackEndInfo">
<mat-divider class="divider align-center"></mat-divider>
<div class="layout-column m-b-20 content-wrapper footer-center">
<table class="versions">
<tr>
<td class="footer-content">Mifos</td>
<td class="right footer-content">
{{ versions.mifos }} - <b>{{ hash }}</b>
</td>
</tr>
<tr>
<td class="footer-content">{{ 'labels.text.Fineract' | translate }}</td>
<td class="right footer-content">
{{ versions.fineract.version }} - <b>{{ versions.fineract.hash }}</b>
</td>
</tr>
<tr *ngIf="displayBackEndInfo">
<td class="footer-content">{{ 'labels.inputs.Server' | translate }}</td>
<td class="right footer-content">
{{ server }}
</td>
</tr>
<tr *ngIf="isBusinessDateDefined">
<td class="footer-content">{{ 'labels.text.Current Business Date' | translate }}:</td>
<td class="center footer-content business-date">
<b>{{ businessDate | date: 'EEEE, MMMM dd, y' }}</b>
</td>
</tr>
</table>
@if (displayBackEndInfo) {
<div class="layout-column m-b-20 f12" ngClass="{{ styleClass }}" id="footer">
<mat-divider class="divider align-center"></mat-divider>
<div class="layout-column m-b-20 content-wrapper footer-center">
<table class="versions">
<tr>
<td class="footer-content">Mifos</td>
<td class="right footer-content">
{{ versions.mifos }} - <b>{{ hash }}</b>
</td>
</tr>
<tr>
<td class="footer-content">{{ 'labels.text.Fineract' | translate }}</td>
<td class="right footer-content">
{{ versions.fineract.version }} - <b>{{ versions.fineract.hash }}</b>
</td>
</tr>
@if (displayBackEndInfo) {
<tr>
<td class="footer-content">{{ 'labels.inputs.Server' | translate }}</td>
<td class="right footer-content">
{{ server }}
</td>
</tr>
}
@if (isBusinessDateDefined) {
<tr>
<td class="footer-content">{{ 'labels.text.Current Business Date' | translate }}:</td>
<td class="center footer-content business-date">
<b>{{ businessDate | date: 'EEEE, MMMM dd, y' }}</b>
</td>
</tr>
}
</table>
</div>
<mat-divider class="divider align-center"></mat-divider>
</div>
<mat-divider class="divider align-center"></mat-divider>
</div>
}
<div class="layout-column m-b-20 copy-label">
<p class="footer-content align-center">{{ 'APP_NAME' | translate }} {{ 'labels.text.by' | translate }} Mifos</p>
</div>
+10 -10
View File
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Component, Input, OnDestroy, OnInit, inject } from '@angular/core';
import { Alert } from 'app/core/alert/alert.model';
import { AlertService } from 'app/core/alert/alert.service';
import { AuthenticationService } from 'app/core/authentication/authentication.service';
@@ -11,7 +11,7 @@ import { VersionService } from 'app/system/version.service';
/** Environment Configuration */
import { environment } from '../../../environments/environment';
import { Subscription } from 'rxjs';
import { NgIf, NgClass, DatePipe } from '@angular/common';
import { NgClass, DatePipe } from '@angular/common';
import { MatDivider } from '@angular/material/divider';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -30,6 +30,13 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class FooterComponent implements OnInit, OnDestroy {
private systemService = inject(SystemService);
private settingsService = inject(SettingsService);
private authenticationService = inject(AuthenticationService);
private alertService = inject(AlertService);
private dateUtils = inject(Dates);
private versionService = inject(VersionService);
@Input() styleClass: string = '';
/** Mifos X version. */
@@ -54,14 +61,7 @@ export class FooterComponent implements OnInit, OnDestroy {
displayBackEndInfo = true;
constructor(
private systemService: SystemService,
private settingsService: SettingsService,
private authenticationService: AuthenticationService,
private alertService: AlertService,
private dateUtils: Dates,
private versionService: VersionService
) {
constructor() {
this.displayBackEndInfo = environment.displayBackEndInfo === 'true';
}
@@ -8,9 +8,11 @@
'responsive-column'
]"
>
<div *ngFor="let formfield of formfields" [ngClass]="'flex-' + layout.flex + ' responsive-flex-full'">
<mifosx-formfield [formfield]="formfield" [form]="form" class="flex-fill"></mifosx-formfield>
</div>
@for (formfield of formfields; track formfield) {
<div [ngClass]="'flex-' + layout.flex + ' responsive-flex-full'">
<mifosx-formfield [formfield]="formfield" [form]="form" class="flex-fill"></mifosx-formfield>
</div>
}
</div>
<mat-dialog-actions class="layout-row layout-xs-column layout-align-center gap-2percent" align="end">
@@ -1,4 +1,4 @@
import { Component, OnInit, Inject } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
@@ -13,7 +13,7 @@ import { FormfieldBase } from './formfield/model/formfield-base';
import { FormGroupService } from './form-group.service';
import { CdkScrollable } from '@angular/cdk/scrolling';
import { NgClass, NgFor } from '@angular/common';
import { NgClass } from '@angular/common';
import { FormfieldComponent } from './formfield/formfield.component';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -35,6 +35,10 @@ const layoutGap = 2;
]
})
export class FormDialogComponent implements OnInit {
dialogRef = inject<MatDialogRef<FormDialogComponent>>(MatDialogRef);
data = inject(MAT_DIALOG_DATA);
private formGroupService = inject(FormGroupService);
layout: {
columns: number;
columnWidth?: number;
@@ -54,11 +58,9 @@ export class FormDialogComponent implements OnInit {
formfields: FormfieldBase[];
pristine: boolean;
constructor(
public dialogRef: MatDialogRef<FormDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private formGroupService: FormGroupService
) {
constructor() {
const data = this.data;
this.dialogRef.disableClose = data.disableClose !== undefined ? data.disableClose : true;
this.formfields = data.formfields.sort(
(formfieldA: FormfieldBase, formfieldB: FormfieldBase) => formfieldA.order - formfieldB.order
@@ -1,80 +1,95 @@
<div [formGroup]="form" class="flex-fill">
<div *ngIf="formfield.controlType === 'input' || formfield.controlType === 'select'" class="flex-fill">
<mat-form-field class="flex-fill">
<mat-label>{{ formfield.label }}</mat-label>
@if (formfield.controlType === 'input' || formfield.controlType === 'select') {
<div class="flex-fill">
<mat-form-field class="flex-fill">
<mat-label>{{ formfield.label }}</mat-label>
@if (formfield.controlType === 'input') {
<input
[type]="formfield.type"
matInput
[formControlName]="formfield.controlName"
[required]="formfield.required"
/>
}
@if (form.controls[formfield.controlName].hasError('max')) {
<mat-error>
<strong>{{ 'labels.commons.Maximum Value must be' | translate }} {{ formfield.max }}</strong>
</mat-error>
}
@if (form.controls[formfield.controlName].hasError('min')) {
<mat-error>
<strong>{{ 'labels.commons.Minimum Value must be' | translate }} {{ formfield.min }}</strong>
</mat-error>
}
@if (formfield.controlType === 'select') {
<mat-select [formControlName]="formfield.controlName" [required]="formfield.required">
@for (option of formfield.options.data; track option) {
<mat-option [value]="option[formfield.options.value]">
{{ option[formfield.options.label] }}
</mat-option>
}
</mat-select>
}
@if (form.controls[formfield.controlName].hasError('required')) {
<mat-error>
{{ formfield.label }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
}
@if (formfield.controlType === 'datepicker') {
<mat-form-field class="flex-fill" Fill (click)="datePicker.open()">
<mat-label>{{ formfield.label }}</mat-label>
<input
*ngIf="formfield.controlType === 'input'"
[type]="formfield.type"
matInput
[matDatepicker]="datePicker"
[min]="formfield.minDate"
[max]="formfield.maxDate"
[formControlName]="formfield.controlName"
[required]="formfield.required"
/>
<mat-error *ngIf="form.controls[formfield.controlName].hasError('max')">
<strong>{{ 'labels.commons.Maximum Value must be' | translate }} {{ formfield.max }}</strong>
</mat-error>
<mat-error *ngIf="form.controls[formfield.controlName].hasError('min')">
<strong>{{ 'labels.commons.Minimum Value must be' | translate }} {{ formfield.min }}</strong>
</mat-error>
<mat-datepicker-toggle matSuffix [for]="datePicker"></mat-datepicker-toggle>
<mat-datepicker #datePicker></mat-datepicker>
@if (form.controls[formfield.controlName].hasError('required')) {
<mat-error>
{{ formfield.label }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<mat-select
*ngIf="formfield.controlType === 'select'"
@if (formfield.controlType === 'datetimepicker') {
<mat-form-field class="flex-fill" Fill (click)="datePicker.open()">
<mat-label>{{ formfield.label }}</mat-label>
<input
matInput
type="datetime-local"
[min]="formfield.minDate"
[max]="formfield.maxDate"
[formControlName]="formfield.controlName"
[required]="formfield.required"
>
<mat-option *ngFor="let option of formfield.options.data" [value]="option[formfield.options.value]">
{{ option[formfield.options.label] }}
</mat-option>
</mat-select>
<mat-error *ngIf="form.controls[formfield.controlName].hasError('required')">
{{ formfield.label }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
/>
@if (form.controls[formfield.controlName].hasError('required')) {
<mat-error>
{{ formfield.label }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
}
<mat-form-field class="flex-fill" Fill *ngIf="formfield.controlType === 'datepicker'" (click)="datePicker.open()">
<mat-label>{{ formfield.label }}</mat-label>
<input
matInput
[matDatepicker]="datePicker"
[min]="formfield.minDate"
[max]="formfield.maxDate"
@if (formfield.controlType === 'checkbox') {
<mat-checkbox
labelPosition="before"
[checked]="formfield.value"
[formControlName]="formfield.controlName"
[required]="formfield.required"
/>
<mat-datepicker-toggle matSuffix [for]="datePicker"></mat-datepicker-toggle>
<mat-datepicker #datePicker></mat-datepicker>
<mat-error *ngIf="form.controls[formfield.controlName].hasError('required')">
{{ formfield.label }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-fill" Fill *ngIf="formfield.controlType === 'datetimepicker'" (click)="datePicker.open()">
<mat-label>{{ formfield.label }}</mat-label>
<input
matInput
type="datetime-local"
[min]="formfield.minDate"
[max]="formfield.maxDate"
[formControlName]="formfield.controlName"
[required]="formfield.required"
/>
<mat-error *ngIf="form.controls[formfield.controlName].hasError('required')">
{{ formfield.label }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-checkbox
*ngIf="formfield.controlType === 'checkbox'"
labelPosition="before"
[checked]="formfield.value"
[formControlName]="formfield.controlName"
[required]="formfield.required"
>
{{ formfield.label }}
</mat-checkbox>
>
{{ formfield.label }}
</mat-checkbox>
}
</div>
+4 -2
View File
@@ -1,5 +1,5 @@
/** Angular Imports */
import { NgModule } from '@angular/core';
import { NgModule, inject } from '@angular/core';
/** Angular Font Awesome Imports */
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
@@ -125,7 +125,9 @@ import {
exports: [FontAwesomeModule]
})
export class IconsModule {
constructor(library: FaIconLibrary) {
constructor() {
const library = inject(FaIconLibrary);
library.addIcons(
faAnchor,
faArchive,
@@ -1,52 +1,72 @@
<mat-form-field appearance="fill" class="flex-100 input-container" *ngIf="isRequired">
<mat-label>{{ 'labels.inputs.' + inputLabel | translate }}</mat-label>
<div class="input-group">
<span>{{ currency.code }}</span>
@if (isRequired) {
<mat-form-field appearance="fill" class="flex-100 input-container">
<mat-label>{{ 'labels.inputs.' + inputLabel | translate }}</mat-label>
<div class="input-group">
<span>{{ currency.code }}</span>
<input
mifosxFormatAmount="{{ currency.code }}:{{ currency.displaySymbol }}"
class="right-input"
[formControl]="inputFormControl"
(keypress)="numberOnly($event)"
required
matInput
(blur)="displayHint = false"
(focus)="displayHint = true"
/>
</div>
@if (inputFormControl.hasError('required')) {
<mat-error>
{{ 'labels.inputs.' + inputLabel | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (displayHint) {
<mat-hint class="right-hint">{{
inputFormControl.value | currency: currency.code : currency.displaySymbol : '1.2-2'
}}</mat-hint>
}
@if (minVal && inputFormControl.hasError('min')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.Must be at least' | translate }} {{ minVal }}
</mat-error>
}
@if (maxVal && inputFormControl.hasError('max')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.Must be less or equal to' | translate }}
{{ maxVal }}
</mat-error>
}
</mat-form-field>
}
@if (!isRequired) {
<mat-form-field appearance="fill" class="flex-100">
<mat-label>{{ 'labels.inputs.' + inputLabel | translate }}</mat-label>
<span matTextSuffix class="m-l-10">{{ currency.code }}</span>
<span class="flex-auto"></span>
<input
mifosxFormatAmount="{{ currency.code }}:{{ currency.displaySymbol }}"
class="right-input"
[formControl]="inputFormControl"
(keypress)="numberOnly($event)"
required
matInput
(blur)="displayHint = false"
(focus)="displayHint = true"
/>
</div>
<mat-error *ngIf="inputFormControl.hasError('required')">
{{ 'labels.inputs.' + inputLabel | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-hint class="right-hint" *ngIf="displayHint">{{
inputFormControl.value | currency: currency.code : currency.displaySymbol : '1.2-2'
}}</mat-hint>
<mat-error *ngIf="minVal && inputFormControl.hasError('min')">
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.Must be at least' | translate }} {{ minVal }}
</mat-error>
<mat-error *ngIf="maxVal && inputFormControl.hasError('max')">
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.Must be less or equal to' | translate }} {{ maxVal }}
</mat-error>
</mat-form-field>
<mat-form-field appearance="fill" class="flex-100" *ngIf="!isRequired">
<mat-label>{{ 'labels.inputs.' + inputLabel | translate }}</mat-label>
<span matTextSuffix class="m-l-10">{{ currency.code }}</span>
<span class="flex-auto"></span>
<input
mifosxFormatAmount="{{ currency.code }}:{{ currency.displaySymbol }}"
class="right-input"
[formControl]="inputFormControl"
(keypress)="numberOnly($event)"
matInput
(blur)="displayHint = false"
(focus)="displayHint = true"
/>
<mat-hint class="right-hint" *ngIf="displayHint">{{
inputFormControl.value | currency: currency.code : currency.displaySymbol : '1.2-2'
}}</mat-hint>
<mat-error *ngIf="minVal && inputFormControl.hasError('min')">
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.Must be at least' | translate }} {{ minVal }}
</mat-error>
<mat-error *ngIf="maxVal && inputFormControl.hasError('max')">
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.Must be less or equal to' | translate }} {{ maxVal }}
</mat-error>
</mat-form-field>
@if (displayHint) {
<mat-hint class="right-hint">{{
inputFormControl.value | currency: currency.code : currency.displaySymbol : '1.2-2'
}}</mat-hint>
}
@if (minVal && inputFormControl.hasError('min')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.Must be at least' | translate }} {{ minVal }}
</mat-error>
}
@if (maxVal && inputFormControl.hasError('max')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.Must be less or equal to' | translate }}
{{ maxVal }}
</mat-error>
}
</mat-form-field>
}
@@ -11,6 +11,10 @@
[value]="value"
/>
<mat-icon matSuffix (click)="onVisibilityClick($event)">{{ icon }}</mat-icon>
<mat-error *ngIf="ngControl.hasError('required')">{{ label }} is a required field.</mat-error>
<mat-error *ngIf="ngControl.hasError(customErrorName)">{{ customErrorMessage }}</mat-error>
@if (ngControl.hasError('required')) {
<mat-error>{{ label }} is a required field.</mat-error>
}
@if (ngControl.hasError(customErrorName)) {
<mat-error>{{ customErrorMessage }}</mat-error>
}
</mat-form-field>
@@ -1,4 +1,4 @@
import { Component, Input, Optional, Self, ViewChild } from '@angular/core';
import { Component, Input, ViewChild, inject } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
@@ -22,6 +22,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class InputPasswordComponent implements ControlValueAccessor, ErrorStateMatcher {
ngControl = inject(NgControl, { optional: true, self: true });
disabled = false;
icon = 'visibility_off';
@@ -61,7 +63,9 @@ export class InputPasswordComponent implements ControlValueAccessor, ErrorStateM
private _visible = false;
constructor(@Optional() @Self() public ngControl: NgControl) {
constructor() {
const ngControl = this.ngControl;
if (ngControl !== null) {
ngControl.valueAccessor = this;
}
@@ -2,25 +2,33 @@
<div mat-dialog-content>
<div class="layout-column gap-2px">
<ng-container *ngFor="let item of buttonConfig.buttonCombinations">
@for (item of buttonConfig.buttonCombinations; track item) {
<mat-divider [inset]="true"></mat-divider>
<div class="flex-fill flex-50">
<span class="flex-50">
<span *ngIf="item.ctrlKey">
<span class="modifier-key">{{ 'labels.inputs.Ctrl' | translate }}</span> +
</span>
<span *ngIf="item.shiftKey">
<span class="modifier-key">{{ 'labels.inputs.Shift' | translate }}</span> +
</span>
<span *ngIf="item.altKey">
<span class="modifier-key">{{ 'labels.inputs.Alt' | translate }}</span> +
</span>
<span *ngIf="item.key">
<span class="modifier-key">{{ item.key }}</span></span
>
@if (item.ctrlKey) {
<span>
<span class="modifier-key">{{ 'labels.inputs.Ctrl' | translate }}</span> +
</span>
}
@if (item.shiftKey) {
<span>
<span class="modifier-key">{{ 'labels.inputs.Shift' | translate }}</span> +
</span>
}
@if (item.altKey) {
<span>
<span class="modifier-key">{{ 'labels.inputs.Alt' | translate }}</span> +
</span>
}
@if (item.key) {
<span>
<span class="modifier-key">{{ item.key }}</span></span
>
}
</span>
<span class="flex-50">{{ item.title }}</span>
</div>
</ng-container>
}
</div>
</div>
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, OnInit, Inject } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogTitle, MatDialogContent } from '@angular/material/dialog';
import { KeyboardShortcutsConfiguration } from '../../keyboards-shortcut-config';
import { CdkScrollable } from '@angular/cdk/scrolling';
@@ -21,13 +21,9 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class KeyboardShortcutsDialogComponent implements OnInit {
buttonConfig: KeyboardShortcutsConfiguration;
dialogRef = inject<MatDialogRef<KeyboardShortcutsDialogComponent>>(MatDialogRef);
/**
* @param {MatDialogRef} dialogRef Component reference to dialog.
* @param {any} data Provides a deleteContext.
*/
constructor(public dialogRef: MatDialogRef<KeyboardShortcutsDialogComponent>) {}
buttonConfig: KeyboardShortcutsConfiguration;
ngOnInit() {
this.dialogRef.updateSize(`800px`);
@@ -1,8 +1,10 @@
<mat-form-field id="language-selector" class="m-l-10">
<mat-label>{{ 'labels.inputs.Language' | translate }}</mat-label>
<mat-select [formControl]="languageSelector" (selectionChange)="setLanguage()" class="languageselector">
<mat-option *ngFor="let language of languages" [value]="language">
{{ 'languages.' + language | translate }}
</mat-option>
@for (language of languages; track language) {
<mat-option [value]="language">
{{ 'languages.' + language | translate }}
</mat-option>
}
</mat-select>
</mat-form-field>
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
/** Custom Services */
@@ -22,6 +22,9 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class LanguageSelectorComponent {
private translateService = inject(TranslateService);
private settingsService = inject(SettingsService);
/** Language selector form control. */
languageSelector = new UntypedFormControl();
@@ -29,10 +32,7 @@ export class LanguageSelectorComponent {
* Sets the language of the application in the selector on initial setup.
* @param {TranslateService} translateService Translate Service.
*/
constructor(
private translateService: TranslateService,
private settingsService: SettingsService
) {
constructor() {
this.languageSelector.setValue(this.currentLanguage);
}
@@ -1,7 +1,13 @@
<span (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()" *ngIf="!emptyValue">
<span class="m-l-3" *ngIf="isLongValue()" (click)="showValue()"
><fa-icon icon="eye" size="sm" [title]="textValue"></fa-icon
></span>
<span class="m-l-5" *ngIf="isLongValue()" (click)="showValue()">{{ textValue | truncateText: printChars }}...</span>
<span class="m-l-5" *ngIf="!isLongValue()" (click)="showValue()">{{ textValue }}</span>
</span>
@if (!emptyValue) {
<span (mouseenter)="mouseEnter()" (mouseleave)="mouseLeave()">
@if (isLongValue()) {
<span class="m-l-3" (click)="showValue()"><fa-icon icon="eye" size="sm" [title]="textValue"></fa-icon></span>
}
@if (isLongValue()) {
<span class="m-l-5" (click)="showValue()">{{ textValue | truncateText: printChars }}...</span>
}
@if (!isLongValue()) {
<span class="m-l-5" (click)="showValue()">{{ textValue }}</span>
}
</span>
}
@@ -13,29 +13,26 @@
</button>
<mat-menu class="mifosx-notifications-menu" #notificationsMenu="matMenu" [overlapTrigger]="false">
<div *ngIf="unreadNotifications.length === 0" class="layout-column align-center-center no-notifications">
<mat-icon class="no-notifications-icon">
<span class="material-icons">{{ 'labels.menus.Notifications' | translate }}</span>
</mat-icon>
<p>{{ 'labels.text.No notifications' | translate }}</p>
</div>
@if (unreadNotifications.length === 0) {
<div class="layout-column align-center-center no-notifications">
<mat-icon class="no-notifications-icon">
<span class="material-icons">{{ 'labels.menus.Notifications' | translate }}</span>
</mat-icon>
<p>{{ 'labels.text.No notifications' | translate }}</p>
</div>
}
<button
mat-menu-item
class="unread"
*ngFor="let notification of unreadNotifications"
[routerLink]="[routeMap[notification.objectType], notification.objectId]"
>
{{ notification.content }}*<br />
<span class="time">[{{ notification.createdAt }}]</span>
</button>
@for (notification of unreadNotifications; track notification) {
<button mat-menu-item class="unread" [routerLink]="[routeMap[notification.objectType], notification.objectId]">
{{ notification.content }}*<br />
<span class="time">[{{ notification.createdAt }}]</span>
</button>
}
<button
mat-menu-item
*ngFor="let notification of displayedReadNotifications"
[routerLink]="[routeMap[notification.objectType], notification.objectId]"
>
{{ notification.content }}<br />
<span class="time">[{{ notification.createdAt }}]</span>
</button>
@for (notification of displayedReadNotifications; track notification) {
<button mat-menu-item [routerLink]="[routeMap[notification.objectType], notification.objectId]">
{{ notification.content }}<br />
<span class="time">[{{ notification.createdAt }}]</span>
</button>
}
</mat-menu>
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
import { Component, OnInit, OnDestroy, ViewEncapsulation, inject } from '@angular/core';
/** RxJS Imports */
import { forkJoin } from 'rxjs';
@@ -36,6 +36,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class NotificationsTrayComponent implements OnInit, OnDestroy {
notificationsService = inject(NotificationsService);
/** Wait time between API status calls 60 seg */
waitTime = environment.waitTimeForNotifications || 60;
/** Read Notifications */
@@ -67,10 +69,11 @@ export class NotificationsTrayComponent implements OnInit, OnDestroy {
/**
* @param {NotificationsService} notificationsService Notifications Service
*/
constructor(public notificationsService: NotificationsService) {
constructor() {
forkJoin([
this.notificationsService.getNotifications(true, 9),
this.notificationsService.getNotifications(false, 9)]).subscribe((response: any[]) => {
this.notificationsService.getNotifications(false, 9)
]).subscribe((response: any[]) => {
this.readNotifications = response[0].pageItems;
this.unreadNotifications = response[1].pageItems;
this.setNotifications();
@@ -1,19 +1,22 @@
<div class="flex-100 layout-row">
<div *ngIf="searchVisible" class="search-bar flex-90 layout-row hide-lt-sm" [@fadeInOut]>
<mat-form-field class="search flex-45">
<mat-label>{{ 'labels.text.Search' | translate }}</mat-label>
<input matInput type="text" [formControl]="query" (keydown.enter)="search()" />
</mat-form-field>
<mat-form-field class="resource flex-50">
<mat-label>{{ 'labels.inputs.Resource' | translate }}</mat-label>
<mat-select [formControl]="resource">
<mat-option *ngFor="let option of resourceOptions" [value]="option.value">
{{ 'labels.text.' + option.name | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
@if (searchVisible) {
<div class="search-bar flex-90 layout-row hide-lt-sm" [@fadeInOut]>
<mat-form-field class="search flex-45">
<mat-label>{{ 'labels.text.Search' | translate }}</mat-label>
<input matInput type="text" [formControl]="query" (keydown.enter)="search()" />
</mat-form-field>
<mat-form-field class="resource flex-50">
<mat-label>{{ 'labels.inputs.Resource' | translate }}</mat-label>
<mat-select [formControl]="resource">
@for (option of resourceOptions; track option) {
<mat-option [value]="option.value">
{{ 'labels.text.' + option.name | translate }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
}
<div class="search-icon flex-10">
<button mat-icon-button (click)="toggleSearchVisibility()" class="hide-lt-sm">
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import { style, animate, transition, trigger } from '@angular/animations';
import { Router } from '@angular/router';
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
@@ -19,12 +19,12 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
trigger('fadeInOut', [
transition(':enter', [
style({ opacity: 0 }),
animate(500, style({ opacity: 1 }))]),
animate(500, style({ opacity: 1 }))
]),
transition(':leave', [
animate(500, style({ opacity: 0 }))])
animate(500, style({ opacity: 0 }))
])
])
],
imports: [
...STANDALONE_SHARED_IMPORTS,
@@ -34,6 +34,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class SearchToolComponent {
private router = inject(Router);
/** Query Form Control */
query = new UntypedFormControl('');
/** Resource Form Control */
@@ -72,7 +74,7 @@ export class SearchToolComponent {
/**
* @param {Router} router Router
*/
constructor(private router: Router) {
constructor() {
this.resource.patchValue('clients,clientIdentifiers,groups,savings,shares,loans');
}
@@ -1,31 +1,36 @@
<mat-form-field id="server-selector" *ngIf="existMoreThanOneServer">
<mat-label>{{ 'labels.inputs.Server' | translate }}</mat-label>
<mat-select
[formControl]="serverSelector"
(selectionChange)="setServer()"
panelClass="server-panel"
panelWidth="null"
>
<div class="sticky">
<form [formGroup]="form" (ngSubmit)="addNewServer()">
<div class="actions">
<mat-form-field class="ml align-start">
<span matPrefix><mat-icon>language</mat-icon></span>
<mat-label>{{ 'labels.placeholders.Add new server' | translate }}</mat-label>
<input matInput type="text" autocomplete="off" formControlName="url" />
<mat-error *ngIf="form.controls.url.hasError('required')">
{{ 'labels.inputs.Url' | translate }} <strong>{{ 'labels.commons.is required' | translate }}</strong>
</mat-error>
</mat-form-field>
<button type="submit" [disabled]="form.invalid" class="ml" mat-raised-button color="primary">
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
</form>
</div>
<mat-option *ngFor="let server of servers" [value]="server">
{{ server }}
</mat-option>
</mat-select>
</mat-form-field>
@if (existMoreThanOneServer) {
<mat-form-field id="server-selector">
<mat-label>{{ 'labels.inputs.Server' | translate }}</mat-label>
<mat-select
[formControl]="serverSelector"
(selectionChange)="setServer()"
panelClass="server-panel"
panelWidth="null"
>
<div class="sticky">
<form [formGroup]="form" (ngSubmit)="addNewServer()">
<div class="actions">
<mat-form-field class="ml align-start">
<span matPrefix><mat-icon>language</mat-icon></span>
<mat-label>{{ 'labels.placeholders.Add new server' | translate }}</mat-label>
<input matInput type="text" autocomplete="off" formControlName="url" />
@if (form.controls.url.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Url' | translate }} <strong>{{ 'labels.commons.is required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<button type="submit" [disabled]="form.invalid" class="ml" mat-raised-button color="primary">
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
</form>
</div>
@for (server of servers; track server) {
<mat-option [value]="server">
{{ server }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { UntypedFormBuilder, Validators } from '@angular/forms';
@@ -23,6 +23,10 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ServerSelectorComponent implements OnInit {
private settingsService = inject(SettingsService);
dialog = inject(MatDialog);
private formBuilder = inject(UntypedFormBuilder);
/** Input server. */
form: any;
@@ -35,15 +39,6 @@ export class ServerSelectorComponent implements OnInit {
/** Server list to show */
existMoreThanOneServer = false;
/**
* @param {SettingsService} settingsService Settings Service
*/
constructor(
private settingsService: SettingsService,
public dialog: MatDialog,
private formBuilder: UntypedFormBuilder
) {}
ngOnInit(): void {
this.servers = this.settingsService.servers;
this.existMoreThanOneServer = this.servers && this.servers.length > 1;
-1
View File
@@ -145,7 +145,6 @@ import { DatetimePickerStubComponent } from './datetime-picker-stub/datetime-pic
DropdownComponent,
InputAmountComponent,
DatetimePickerStubComponent // Export stub for datetime picker
]
})
export class SharedModule {}
@@ -10,47 +10,52 @@
</button>
</span>
<ng-container *mifosxHasPermission="'DELETE_' + datatableName">
<span *ngIf="isSelected">
<button class="delete-button" mat-raised-button color="warn" (click)="deleteSelected()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Delete Selected' | translate }}
</button>
</span>
@if (isSelected) {
<span>
<button class="delete-button" mat-raised-button color="warn" (click)="deleteSelected()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Delete Selected' | translate }}
</button>
</span>
}
</ng-container>
<ng-container *mifosxHasPermission="'DELETE_' + datatableName">
<span *ngIf="datatableData.length > 0">
<button class="delete-button" mat-raised-button color="warn" (click)="delete()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Delete All' | translate }}
</button>
</span>
@if (datatableData.length > 0) {
<span>
<button class="delete-button" mat-raised-button color="warn" (click)="delete()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Delete All' | translate }}
</button>
</span>
}
</ng-container>
</div>
</div>
<div class="m-t-10">
<table #dataTable mat-table [dataSource]="datatableData" class="mat-elevation-z1 m-b-25">
<ng-container *ngFor="let datatableColumn of datatableColumns; let i = index" [matColumnDef]="datatableColumn">
<ng-container *ngIf="i === 0">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let data">
<mat-checkbox
class="center"
(click)="$event.stopPropagation()"
(change)="$event ? itemToggle(data) : null"
[checked]="selection.isSelected(data)"
>
</mat-checkbox>
</td>
@for (datatableColumn of datatableColumns; track datatableColumn; let i = $index) {
<ng-container [matColumnDef]="datatableColumn">
@if (i === 0) {
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let data">
<mat-checkbox
class="center"
(click)="$event.stopPropagation()"
(change)="$event ? itemToggle(data) : null"
[checked]="selection.isSelected(data)"
>
</mat-checkbox>
</td>
}
@if (i > 0) {
<th mat-header-cell class="right" *matHeaderCellDef>{{ getInputName(datatableColumn) }}</th>
<td mat-cell class="right" *matCellDef="let data" [ngClass]="isToDelete(data)">
{{ formatValue(data, datatableColumn) }}
</td>
}
</ng-container>
<ng-container *ngIf="i > 0">
<th mat-header-cell class="right" *matHeaderCellDef>{{ getInputName(datatableColumn) }}</th>
<td mat-cell class="right" *matCellDef="let data" [ngClass]="isToDelete(data)">
{{ formatValue(data, datatableColumn) }}
</td>
</ng-container>
</ng-container>
}
<tr mat-header-row *matHeaderRowDef="datatableColumns"></tr>
<tr mat-row *matRowDef="let row; columns: datatableColumns"></tr>
@@ -1,6 +1,6 @@
import { SelectionModel } from '@angular/cdk/collections';
import { DecimalPipe, NgIf, NgFor, NgClass } from '@angular/common';
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core';
import { DecimalPipe, NgClass } from '@angular/common';
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, inject } from '@angular/core';
import { MatCheckboxChange as MatCheckboxChange, MatCheckbox } from '@angular/material/checkbox';
import { MatDialog } from '@angular/material/dialog';
import {
@@ -51,6 +51,16 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class DatatableMultiRowComponent implements OnInit, OnDestroy, OnChanges {
private route = inject(ActivatedRoute);
private dateUtils = inject(Dates);
private systemService = inject(SystemService);
private settingsService = inject(SettingsService);
private dialog = inject(MatDialog);
private datatables = inject(Datatables);
private dateFormat = inject(DateFormatPipe);
private dateTimeFormat = inject(DatetimeFormatPipe);
private numberFormat = inject(DecimalPipe);
SELECT_NAME_FIELD = 'select';
/** Data Object */
@Input() dataObject: any;
@@ -74,27 +84,6 @@ export class DatatableMultiRowComponent implements OnInit, OnDestroy, OnChanges
/** Data Table Reference */
@ViewChild('dataTable') dataTableRef: MatTable<Element>;
/**
* Fetches center Id from parent route params.
* @param {ActivatedRoute} route Activated Route.
* @param {Dates} dateUtils Date Utils.
* @param {SystemService} systemService system Service.
* @param {SettingsService} settingsService Settings Service.
* @param {MatDialog} dialog Mat Dialog.
* @param {Datatables} datatables Datatable utils
*/
constructor(
private route: ActivatedRoute,
private dateUtils: Dates,
private systemService: SystemService,
private settingsService: SettingsService,
private dialog: MatDialog,
private datatables: Datatables,
private dateFormat: DateFormatPipe,
private dateTimeFormat: DatetimeFormatPipe,
private numberFormat: DecimalPipe
) {}
/**
* Fetches data table name from route params.
* subscription is required due to asynchronicity.
@@ -1,70 +1,92 @@
<div class="tab-container mat-typography">
<h3>{{ datatableName }}</h3>
<div class="layout-row align-end m-b-20">
<button mat-raised-button color="primary" (click)="add()" *ngIf="!dataObject.data[0]">
<fa-icon icon="plus" class="m-r-10"></fa-icon>{{ 'labels.buttons.Add' | translate }}
</button>
<button mat-raised-button color="primary" (click)="edit()" *ngIf="dataObject.data[0]">
<fa-icon icon="edit" class="m-r-10"></fa-icon>{{ 'labels.buttons.Edit' | translate }}
</button>
<button class="delete-button" mat-raised-button color="warn" (click)="delete()" *ngIf="dataObject.data[0]">
<fa-icon icon="trash" class="m-r-10"></fa-icon>{{ 'labels.buttons.Delete' | translate }}
</button>
@if (!dataObject.data[0]) {
<button mat-raised-button color="primary" (click)="add()">
<fa-icon icon="plus" class="m-r-10"></fa-icon>{{ 'labels.buttons.Add' | translate }}
</button>
}
@if (dataObject.data[0]) {
<button mat-raised-button color="primary" (click)="edit()">
<fa-icon icon="edit" class="m-r-10"></fa-icon>{{ 'labels.buttons.Edit' | translate }}
</button>
}
@if (dataObject.data[0]) {
<button class="delete-button" mat-raised-button color="warn" (click)="delete()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>{{ 'labels.buttons.Delete' | translate }}
</button>
}
</div>
<mat-divider></mat-divider>
<div class="m-t-10" *ngIf="dataObject.data.length > 0">
<div
class="flex-fill"
*ngFor="let columnHeader of dataObject.columnHeaders; let i = index"
[ngClass]="setAttributeClass(columnHeader.columnName)"
>
<div class="mat-body-strong left flex-40">
{{ columnHeader.columnDisplayName }}
{{ getInputName(columnHeader.columnName) }}
</div>
<div [ngSwitch]="getColumnType(columnHeader.columnDisplayType, columnHeader.columnType)" class="right flex-60">
<span *ngSwitchCase="'DATE'">
{{ dataObject.data[0].row[i] | dateFormat }}
</span>
<span *ngSwitchCase="'DATETIME'">
{{ dataObject.data[0].row[i] | datetimeFormat }}
</span>
<span *ngSwitchCase="'INTEGER'">
{{ dataObject.data[0].row[i] }}
</span>
<span *ngSwitchCase="'DECIMAL'">
{{ dataObject.data[0].row[i] | formatNumber }}
</span>
<span *ngSwitchCase="'TEXT'" class="long-text">
{{ dataObject.data[0].row[i] }}
</span>
<textarea
*ngSwitchCase="'JSON'"
cdkTextareaAutosize="true"
cdkAutosizeMaxRows="20"
cdkAutosizeMinRows="1"
class="json-textarea"
[innerHTML]="dataObject.data[0].row[i].value | prettyPrint"
>
</textarea>
<span *ngSwitchDefault>
<span class="m-r-5" *ngIf="isValidUrl(dataObject.data[0].row[i])">
<button
mat-icon-button
matTooltip="{{ 'tooltips.View Link' | translate }}"
(click)="openSite(dataObject.data[0].row[i])"
matTooltipPosition="right"
class="small-icon"
>
<fa-icon icon="eye" size="lg"></fa-icon>
</button>
</span>
{{ dataObject.data[0].row[i] }}
</span>
</div>
@if (dataObject.data.length > 0) {
<div class="m-t-10">
@for (columnHeader of dataObject.columnHeaders; track columnHeader; let i = $index) {
<div class="flex-fill" [ngClass]="setAttributeClass(columnHeader.columnName)">
<div class="mat-body-strong left flex-40">
{{ columnHeader.columnDisplayName }}
{{ getInputName(columnHeader.columnName) }}
</div>
<div class="right flex-60">
@switch (getColumnType(columnHeader.columnDisplayType, columnHeader.columnType)) {
@case ('DATE') {
<span>
{{ dataObject.data[0].row[i] | dateFormat }}
</span>
}
@case ('DATETIME') {
<span>
{{ dataObject.data[0].row[i] | datetimeFormat }}
</span>
}
@case ('INTEGER') {
<span>
{{ dataObject.data[0].row[i] }}
</span>
}
@case ('DECIMAL') {
<span>
{{ dataObject.data[0].row[i] | formatNumber }}
</span>
}
@case ('TEXT') {
<span class="long-text">
{{ dataObject.data[0].row[i] }}
</span>
}
@case ('JSON') {
<textarea
cdkTextareaAutosize="true"
cdkAutosizeMaxRows="20"
cdkAutosizeMinRows="1"
class="json-textarea"
[innerHTML]="dataObject.data[0].row[i].value | prettyPrint"
>
</textarea>
}
@default {
<span>
@if (isValidUrl(dataObject.data[0].row[i])) {
<span class="m-r-5">
<button
mat-icon-button
matTooltip="{{ 'tooltips.View Link' | translate }}"
(click)="openSite(dataObject.data[0].row[i])"
matTooltipPosition="right"
class="small-icon"
>
<fa-icon icon="eye" size="lg"></fa-icon>
</button>
</span>
}
{{ dataObject.data[0].row[i] }}
</span>
}
}
</div>
</div>
}
</div>
</div>
}
</div>
@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Input, OnInit, inject } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute } from '@angular/router';
import { Datatables } from 'app/core/utils/datatables';
@@ -8,7 +8,7 @@ import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.co
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
import { SystemService } from 'app/system/system.service';
import { NgIf, NgFor, NgClass, NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';
import { NgClass } from '@angular/common';
import { MatButton, MatIconButton } from '@angular/material/button';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatDivider } from '@angular/material/divider';
@@ -29,10 +29,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
FaIconComponent,
MatDivider,
NgClass,
NgSwitch,
NgSwitchCase,
CdkTextareaAutosize,
NgSwitchDefault,
MatIconButton,
MatTooltip,
DateFormatPipe,
@@ -42,27 +39,18 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class DatatableSingleRowComponent implements OnInit {
private route = inject(ActivatedRoute);
private dateUtils = inject(Dates);
private dialog = inject(MatDialog);
private settingsService = inject(SettingsService);
private datatables = inject(Datatables);
private systemService = inject(SystemService);
@Input() dataObject: any;
@Input() entityId: string;
@Input() entityType: string;
datatableName: string;
/**
* @param {ActivatedRoute} route Activated Route.
* @param {Dates} dateUtils Date Utils.
* @param {SystemService} systemService System Service.
* @param {SettingsService} settingsService Settings Service
* @param {Datatables} datatables Datatable utils
*/
constructor(
private route: ActivatedRoute,
private dateUtils: Dates,
private dialog: MatDialog,
private settingsService: SettingsService,
private datatables: Datatables,
private systemService: SystemService
) {}
ngOnInit() {
this.route.params.subscribe((routeParams: any) => {
this.datatableName = routeParams.datatableName;
@@ -1,14 +1,16 @@
<div>
<mifosx-datatable-multi-row
*ngIf="multiRowDatatableFlag"
[entityType]="entityType"
[entityId]="entityId"
[dataObject]="entityDatatable"
></mifosx-datatable-multi-row>
<mifosx-datatable-single-row
*ngIf="!multiRowDatatableFlag"
[entityType]="entityType"
[entityId]="entityId"
[dataObject]="entityDatatable"
></mifosx-datatable-single-row>
@if (multiRowDatatableFlag) {
<mifosx-datatable-multi-row
[entityType]="entityType"
[entityId]="entityId"
[dataObject]="entityDatatable"
></mifosx-datatable-multi-row>
}
@if (!multiRowDatatableFlag) {
<mifosx-datatable-single-row
[entityType]="entityType"
[entityId]="entityId"
[dataObject]="entityDatatable"
></mifosx-datatable-single-row>
}
</div>
@@ -9,59 +9,71 @@
</div>
</div>
<div *ngIf="entityDocuments?.length; else empty">
<div class="document-grid">
<div class="document-card" *ngFor="let document of entityDocuments; trackBy: trackByDocumentId">
<div
class="thumb"
[class.clickable]="isPreviewable(document)"
role="button"
tabindex="0"
[attr.aria-label]="('labels.buttons.Preview' | translate) + ': ' + document.name"
(keydown.enter)="isPreviewable(document) && openPreview(document)"
(keydown.space)="isPreviewable(document) && openPreview(document)"
(click)="isPreviewable(document) && openPreview(document)"
>
<ng-container *ngIf="previewThumbnails[document.id]; else placeholder">
<img [src]="previewThumbnails[document.id]" [alt]="document.name" [title]="document.name" loading="lazy" />
</ng-container>
<ng-template #placeholder>
<div class="placeholder">
<fa-icon icon="file"></fa-icon>
<span>{{ document.fileName || document.name }}</span>
</div>
</ng-template>
<div class="preview-overlay" *ngIf="isPreviewable(document)">
<fa-icon icon="eye"></fa-icon>
</div>
</div>
<div class="card-body">
<div class="title">{{ document.name }}</div>
<div class="meta" *ngIf="document.fileName">{{ document.fileName }}</div>
<div class="description" *ngIf="document.description">{{ document.description }}</div>
<div class="actions">
<button
mat-icon-button
color="warn"
(click)="deleteDocument(document.id, document.name)"
title="Delete"
[attr.aria-label]="'labels.buttons.Delete' | translate"
*mifosxHasPermission="'DELETE_DOCUMENT'"
@if (entityDocuments?.length) {
<div>
<div class="document-grid">
@for (document of entityDocuments; track trackByDocumentId($index, document)) {
<div class="document-card">
<div
class="thumb"
[class.clickable]="isPreviewable(document)"
role="button"
tabindex="0"
[attr.aria-label]="('labels.buttons.Preview' | translate) + ': ' + document.name"
(keydown.enter)="isPreviewable(document) && openPreview(document)"
(keydown.space)="isPreviewable(document) && openPreview(document)"
(click)="isPreviewable(document) && openPreview(document)"
>
<fa-icon icon="times"></fa-icon>
</button>
@if (previewThumbnails[document.id]) {
<img
[src]="previewThumbnails[document.id]"
[alt]="document.name"
[title]="document.name"
loading="lazy"
/>
} @else {
<div class="placeholder">
<fa-icon icon="file"></fa-icon>
<span>{{ document.fileName || document.name }}</span>
</div>
}
@if (isPreviewable(document)) {
<div class="preview-overlay">
<fa-icon icon="eye"></fa-icon>
</div>
}
</div>
<div class="card-body">
<div class="title">{{ document.name }}</div>
@if (document.fileName) {
<div class="meta">{{ document.fileName }}</div>
}
@if (document.description) {
<div class="description">{{ document.description }}</div>
}
<div class="actions">
<button
mat-icon-button
color="warn"
(click)="deleteDocument(document.id, document.name)"
title="Delete"
[attr.aria-label]="'labels.buttons.Delete' | translate"
*mifosxHasPermission="'DELETE_DOCUMENT'"
>
<fa-icon icon="times"></fa-icon>
</button>
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>
<ng-template #empty>
} @else {
<div class="empty-state">
<p>{{ 'labels.text.NoDocuments' | translate }}</p>
<p class="muted">{{ 'labels.text.UploadDocumentHint' | translate }}</p>
</div>
</ng-template>
}
</div>
<div #lightboxRoot class="document-lightbox-host"></div>
@@ -1,4 +1,4 @@
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import lightGallery from 'lightgallery';
import lgFullscreen from 'lightgallery/plugins/fullscreen';
@@ -27,6 +27,12 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class EntityDocumentsTabComponent implements OnInit, OnDestroy {
dialog = inject(MatDialog);
private savingsService = inject(SavingsService);
private loansService = inject(LoansService);
private clientsService = inject(ClientsService);
private documentPreviewService = inject(DocumentPreviewService);
@ViewChild('lightboxRoot', { static: true }) lightboxRoot: ElementRef<HTMLElement>;
@Input() entityId: string;
@@ -44,14 +50,6 @@ export class EntityDocumentsTabComponent implements OnInit, OnDestroy {
lgFullscreen
];
constructor(
public dialog: MatDialog,
private savingsService: SavingsService,
private loansService: LoansService,
private clientsService: ClientsService,
private documentPreviewService: DocumentPreviewService
) {}
ngOnInit(): void {
this.prefetchThumbnails();
}
@@ -25,32 +25,32 @@
</div>
<div class="notes-container">
<div *ngIf="entityNotes && entityNotes.length > 0; else emptyState">
<div class="note-card" *ngFor="let entityNote of entityNotes; let i = index">
<div class="note-content">
{{ entityNote.note }}
</div>
<div class="note-footer">
<div class="note-meta">
<div class="created-by">Created by {{ entityNote.createdByUsername }}</div>
<div class="created-date">
{{ entityNote.createdOn | dateFormat }}
@if (entityNotes && entityNotes.length > 0) {
<div>
@for (entityNote of entityNotes; track entityNote; let i = $index) {
<div class="note-card">
<div class="note-content">
{{ entityNote.note }}
</div>
<div class="note-footer">
<div class="note-meta">
<div class="created-by">Created by {{ entityNote.createdByUsername }}</div>
<div class="created-date">
{{ entityNote.createdOn | dateFormat }}
</div>
</div>
<div class="note-actions">
<button class="edit-btn" (click)="editNote(entityNote.id, entityNote.note, i)" title="Edit note">
Edit
</button>
<button class="delete-btn" (click)="deleteNote(entityNote.id, i)" title="Delete note">Delete</button>
</div>
</div>
</div>
<div class="note-actions">
<button class="edit-btn" (click)="editNote(entityNote.id, entityNote.note, i)" title="Edit note">
Edit
</button>
<button class="delete-btn" (click)="deleteNote(entityNote.id, i)" title="Delete note">Delete</button>
</div>
</div>
}
</div>
</div>
<ng-template #emptyState>
} @else {
<div class="empty-state">No notes available</div>
</ng-template>
}
</div>
</div>
@@ -1,4 +1,4 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { Component, Input, OnInit, ViewChild, inject } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ClientsService } from 'app/clients/clients.service';
@@ -24,6 +24,13 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class EntityNotesTabComponent implements OnInit {
private formBuilder = inject(UntypedFormBuilder);
private savingsService = inject(SavingsService);
private loansService = inject(LoansService);
private clientsService = inject(ClientsService);
private groupsService = inject(GroupsService);
private dialog = inject(MatDialog);
@ViewChild('formRef', { static: true }) formRef: any;
@Input() entityId: string;
@@ -35,15 +42,6 @@ export class EntityNotesTabComponent implements OnInit {
noteForm: UntypedFormGroup;
constructor(
private formBuilder: UntypedFormBuilder,
private savingsService: SavingsService,
private loansService: LoansService,
private clientsService: ClientsService,
private groupsService: GroupsService,
private dialog: MatDialog
) {}
ngOnInit() {
this.createNoteForm();
}
@@ -4,8 +4,10 @@
</span>
<mat-label>{{ 'labels.inputs.Tenant' | translate }}</mat-label>
<mat-select [formControl]="tenantSelector" (selectionChange)="setTenantIdentifier()" class="tenantselector">
<mat-option *ngFor="let tenant of tenants" [value]="tenant">
{{ tenant }}
</mat-option>
@for (tenant of tenants; track tenant) {
<mat-option [value]="tenant">
{{ tenant }}
</mat-option>
}
</mat-select>
</mat-form-field>
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
import { SettingsService } from 'app/settings/settings.service';
import { MatFormField, MatPrefix, MatLabel } from '@angular/material/form-field';
@@ -16,15 +16,11 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class TenantSelectorComponent implements OnInit {
private settingsService = inject(SettingsService);
/** Tenant selector form control. */
tenantSelector = new UntypedFormControl();
/**
* Sets the Tenant Identifier of the application in the selector on initial setup.
* @param {SettingsService} settingsService Settings Service.
*/
constructor(private settingsService: SettingsService) {}
ngOnInit(): void {
this.tenantSelector.setValue(this.settingsService.tenantIdentifier);
if (this.tenants.length > 1) {
@@ -4,19 +4,18 @@
<mat-menu class="mifosx-theme-picker-menu" #themeMenu="matMenu" x-position="before">
<mat-grid-list cols="2">
<mat-grid-tile *ngFor="let theme of themes">
<div mat-menu-item (click)="installTheme(theme)">
<div class="mifosx-theme-picker-swatch">
<fa-icon
class="mifosx-theme-chosen-icon"
*ngIf="currentTheme.href === theme.href"
icon="check-circle"
size="lg"
></fa-icon>
<div class="mifosx-theme-picker-primary" [style.background]="theme.primary"></div>
<div class="mifosx-theme-picker-accent" [style.background]="theme.accent"></div>
@for (theme of themes; track theme) {
<mat-grid-tile>
<div mat-menu-item (click)="installTheme(theme)">
<div class="mifosx-theme-picker-swatch">
@if (currentTheme.href === theme.href) {
<fa-icon class="mifosx-theme-chosen-icon" icon="check-circle" size="lg"></fa-icon>
}
<div class="mifosx-theme-picker-primary" [style.background]="theme.primary"></div>
<div class="mifosx-theme-picker-accent" [style.background]="theme.accent"></div>
</div>
</div>
</div>
</mat-grid-tile>
</mat-grid-tile>
}
</mat-grid-list>
</mat-menu>
@@ -1,5 +1,5 @@
/** Angular Imports */
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Component, OnInit, ViewEncapsulation, inject } from '@angular/core';
/** Custom Model */
import { Theme } from './theme.model';
@@ -36,6 +36,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ThemePickerComponent implements OnInit {
themeStorageService = inject(ThemeStorageService);
/** Default theme for the application. */
currentTheme: Theme = {
href: 'denim-yellowgreen.css',
@@ -79,11 +81,6 @@ export class ThemePickerComponent implements OnInit {
}
];
/**
* @param {ThemeStorageService} themeStorageService Theme Storage Service.
*/
constructor(public themeStorageService: ThemeStorageService) {}
/**
* Initializes the theme for the application.
*/
@@ -1,4 +1,4 @@
import { Injectable, EventEmitter } from '@angular/core';
import { Injectable, EventEmitter, inject } from '@angular/core';
import { Theme } from './theme.model';
import { ThemeManagerService } from './theme-manager.service';
@@ -6,10 +6,12 @@ import { ThemeManagerService } from './theme-manager.service';
providedIn: 'root'
})
export class ThemeStorageService {
themeManagerService = inject(ThemeManagerService);
private themeStorageKey = 'mifosXTheme';
onThemeUpdate: EventEmitter<Theme>;
constructor(public themeManagerService: ThemeManagerService) {
constructor() {
this.onThemeUpdate = new EventEmitter<Theme>();
}
@@ -1,4 +1,4 @@
import { Component, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { Component, OnChanges, OnInit, SimpleChanges, inject } from '@angular/core';
import { ThemingService } from './theming.service';
import { SettingsService } from 'app/settings/settings.service';
import { MatIconButton } from '@angular/material/button';
@@ -16,12 +16,10 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
]
})
export class ThemeToggleComponent implements OnInit, OnChanges {
darkModeOn: boolean;
private themingService = inject(ThemingService);
private settingsService = inject(SettingsService);
constructor(
private themingService: ThemingService,
private settingsService: SettingsService
) {}
darkModeOn: boolean;
ngOnInit(): void {
this.darkModeOn = !!this.settingsService.themeDarkEnabled;
@@ -1,10 +1,12 @@
import { ApplicationRef, Injectable } from '@angular/core';
import { ApplicationRef, Injectable, inject } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ThemingService {
private ref = inject(ApplicationRef);
private darkModeOn = false;
themes = [
@@ -13,7 +15,7 @@ export class ThemingService {
]; // <- list all themes in this array
theme = new BehaviorSubject('light-theme'); // <- initial theme
constructor(private ref: ApplicationRef) {
constructor() {
// Initially check if dark mode is enabled on system
this.darkModeOn = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -7,30 +7,42 @@
{{ paymentDetailData.paymentType.name }}
</div>
</div>
<table *ngIf="hasSomeValue()">
<tbody>
<tr>
<td class="mat-body-strong center" colspan="2">{{ 'labels.inputs.Payment Details' | translate }}</td>
</tr>
<tr *ngIf="isNotNullOrEmpty(paymentDetailData.accountNumber)">
<td class="mat-body-strong">{{ 'labels.inputs.Account No' | translate }}</td>
<td>{{ paymentDetailData.accountNumber }}</td>
</tr>
<tr *ngIf="isNotNullOrEmpty(paymentDetailData.checkNumber)">
<td class="mat-body-strong">{{ 'labels.inputs.Cheque Number' | translate }}</td>
<td>{{ paymentDetailData.checkNumber }}</td>
</tr>
<tr *ngIf="isNotNullOrEmpty(paymentDetailData.routingCode)">
<td class="mat-body-strong">{{ 'labels.inputs.Routing Code' | translate }}</td>
<td class="mat-body-strong">{{ paymentDetailData.routingCode }}</td>
</tr>
<tr *ngIf="isNotNullOrEmpty(paymentDetailData.receiptNumber)">
<td class="mat-body-strong">{{ 'labels.inputs.Receipt No' | translate }}</td>
<td>{{ paymentDetailData.receiptNumber }}</td>
</tr>
<tr *ngIf="isNotNullOrEmpty(paymentDetailData.bankNumber)">
<td class="mat-body-strong">{{ 'labels.inputs.Bank No' | translate }}</td>
<td>{{ paymentDetailData.bankNumber }}</td>
</tr>
</tbody>
</table>
@if (hasSomeValue()) {
<table>
<tbody>
<tr>
<td class="mat-body-strong center" colspan="2">{{ 'labels.inputs.Payment Details' | translate }}</td>
</tr>
@if (isNotNullOrEmpty(paymentDetailData.accountNumber)) {
<tr>
<td class="mat-body-strong">{{ 'labels.inputs.Account No' | translate }}</td>
<td>{{ paymentDetailData.accountNumber }}</td>
</tr>
}
@if (isNotNullOrEmpty(paymentDetailData.checkNumber)) {
<tr>
<td class="mat-body-strong">{{ 'labels.inputs.Cheque Number' | translate }}</td>
<td>{{ paymentDetailData.checkNumber }}</td>
</tr>
}
@if (isNotNullOrEmpty(paymentDetailData.routingCode)) {
<tr>
<td class="mat-body-strong">{{ 'labels.inputs.Routing Code' | translate }}</td>
<td class="mat-body-strong">{{ paymentDetailData.routingCode }}</td>
</tr>
}
@if (isNotNullOrEmpty(paymentDetailData.receiptNumber)) {
<tr>
<td class="mat-body-strong">{{ 'labels.inputs.Receipt No' | translate }}</td>
<td>{{ paymentDetailData.receiptNumber }}</td>
</tr>
}
@if (isNotNullOrEmpty(paymentDetailData.bankNumber)) {
<tr>
<td class="mat-body-strong">{{ 'labels.inputs.Bank No' | translate }}</td>
<td>{{ paymentDetailData.bankNumber }}</td>
</tr>
}
</tbody>
</table>
}