feat: non-individual entity UI improvements — hide individual-only sections, fix legal form display, send extended fields to API

- Hide Family Members tab/section for non-individual entities in client view, create flow, and preview
- Hide Financial & Classification section for non-individual entities in personal data and preview
- Fix legal form column in clients list — fetch legalForm from client API for correct display
- Add person/business icon before client name in list rows
- Remove unsupportedNpd filter — send authorizedSignatory, additionalInfo, dateOfRegistration to API
- Add registration number format hint (mat-hint) based on constitution type
- Add spacing between verification row and constitution type dropdowns
- Reduce table cell padding and prevent action buttons from being clipped
- Add missing MPL-2.0 license headers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-04 15:10:51 +05:30
co-authored by Claude Opus 4.6
parent ab01111424
commit b890d24bb7
522 changed files with 51991 additions and 21885 deletions
+36 -29
View File
@@ -6,37 +6,44 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<div #buttonCreateCharge class="in-block">
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_CHARGE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.text.Create Charge' | translate }}
</button>
</div>
</div>
<div class="container">
<div #filter class="layout-row gap-20px">
<mat-form-field class="flex-fill flex-68">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<mat-form-field class="flex-28">
<mat-select
placeholder="{{ 'labels.inputs.Filter' | translate }}"
(selectionChange)="filterByAppliesTo($event.value)"
>
@for (chargeAppliesTo of chargeAppliesToOptions; track chargeAppliesTo) {
<mat-option [value]="chargeAppliesTo.id">
{{ chargeAppliesTo.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-file-invoice-dollar title-icon"></i>
{{ 'labels.heading.Charges' | translate }}
</h2>
</div>
<div class="table-actions">
<mat-form-field appearance="outline" class="table-search">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<mat-form-field appearance="outline" class="table-search" #filter>
<mat-label>{{ 'labels.inputs.Applies To' | translate }}</mat-label>
<mat-select (selectionChange)="filterByAppliesTo($event.value)">
@for (chargeAppliesTo of chargeAppliesToOptions; track chargeAppliesTo) {
<mat-option [value]="chargeAppliesTo.id">
{{ chargeAppliesTo.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="table-buttons" #buttonCreateCharge>
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_CHARGE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.text.Create Charge' | translate }}
</button>
</div>
</div>
</div>
<div #chargesTable class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<div class="table-container" #chargesTable>
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let charge">{{ charge.name }}</td>
@@ -107,7 +114,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
@@ -6,13 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
width: 100%;
.select-row:hover {
cursor: pointer;
}
}
@use '../../shared/styles/list-layout' as *;
.true {
color: #32cd32;
@@ -6,284 +6,302 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="chargeForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Applies To' | translate }}</mat-label>
<mat-select required formControlName="chargeAppliesTo">
@for (chargeAppliesTo of chargesTemplateData.chargeAppliesToOptions; track chargeAppliesTo) {
<mat-option [value]="chargeAppliesTo.id">
{{ chargeAppliesTo.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeAppliesTo.hasError('required')) {
<div class="cc-container">
<div class="cc-header">
<div class="cc-header__icon"><i class="fas fa-file-invoice-dollar"></i></div>
<h1 class="cc-header__title">{{ 'labels.text.Create Charge' | translate }}</h1>
</div>
<form [formGroup]="chargeForm" (ngSubmit)="submit()">
<!-- Section 1: Charge Type -->
<div class="cc-section">
<div class="cc-section__header">
<i class="fas fa-tag"></i>
<h3>{{ 'labels.inputs.Charge Applies To' | translate }}</h3>
</div>
<div class="cc-section__body">
<mat-form-field appearance="outline" class="full-width">
<mat-label>{{ 'labels.inputs.Charge Applies To' | translate }}</mat-label>
<mat-select required formControlName="chargeAppliesTo">
@for (chargeAppliesTo of chargesTemplateData.chargeAppliesToOptions; track chargeAppliesTo) {
<mat-option [value]="chargeAppliesTo.id">
{{ chargeAppliesTo.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeAppliesTo.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Applies To' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</div>
<!-- Section 2: Charge Details (conditional) -->
@if (chargeForm.controls.chargeAppliesTo.value) {
<div class="cc-section">
<div class="cc-section__header">
<i class="fas fa-list-alt"></i>
<h3>{{ 'labels.heading.Charge Details' | translate }}</h3>
</div>
<div class="cc-section__body cc-grid">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Name' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (chargeForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Applies To' | translate }} {{ 'labels.commons.is' | translate }}
{{ 'labels.inputs.Charge Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-divider [inset]="true"></mat-divider>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select required formControlName="currencyCode">
@for (currency of chargesTemplateData.currencyOptions; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.currencyCode.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
@if (chargeForm.controls.chargeAppliesTo.value) {
<div class="layout-row-wrap gap-2percent layout-lt-md-column form-section">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Name' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (chargeForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Time Type' | translate }}</mat-label>
<mat-select required formControlName="chargeTimeType">
@for (chargeTimeType of chargeTimeTypeData; track chargeTimeType) {
<mat-option [value]="chargeTimeType.id">
{{ chargeTimeType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeTimeType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Time Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Calculation Type' | translate }}</mat-label>
<mat-select required formControlName="chargeCalculationType">
@for (chargeCalculationType of filteredChargeCalculationType(); track chargeCalculationType) {
<mat-option [value]="chargeCalculationType.id">
{{ chargeCalculationType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeCalculationType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Calculation Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
@if (chargeForm.controls.chargeAppliesTo.value === 1) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Payment Mode' | translate }}</mat-label>
<mat-select required formControlName="chargePaymentMode">
@for (chargePaymentMode of chargesTemplateData.chargePaymetModeOptions; track chargePaymentMode) {
<mat-option [value]="chargePaymentMode.id">
{{ chargePaymentMode.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select required formControlName="currencyCode">
@for (currency of chargesTemplateData.currencyOptions; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.currencyCode.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Time Type' | translate }}</mat-label>
<mat-select required formControlName="chargeTimeType">
@for (chargeTimeType of chargeTimeTypeData; track chargeTimeType) {
<mat-option [value]="chargeTimeType.id">
{{ chargeTimeType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeTimeType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Time Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Calculation Type' | translate }}</mat-label>
<mat-select required formControlName="chargeCalculationType">
@for (chargeCalculationType of filteredChargeCalculationType(); track chargeCalculationType) {
<mat-option [value]="chargeCalculationType.id">
{{ chargeCalculationType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeCalculationType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Calculation Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
@if (chargeForm.controls.chargeAppliesTo.value === 1) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Payment Mode' | translate }}</mat-label>
<mat-select required formControlName="chargePaymentMode">
@for (chargePaymentMode of chargesTemplateData.chargePaymetModeOptions; track chargePaymentMode) {
<mat-option [value]="chargePaymentMode.id">
{{ chargePaymentMode.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargePaymentMode.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Payment Mode' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</mat-select>
@if (chargeForm.controls.chargePaymentMode.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Payment Mode' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (chargeForm.controls.chargeTimeType.value === 9) {
<div class="flex-48 add-fee-frequency-wrapper">
<mat-checkbox labelPosition="before" formControlName="addFeeFrequency">
{{ 'labels.inputs.Add Fee Frequency' | translate }}
</mat-checkbox>
</div>
}
@if (chargeForm.controls.chargeTimeType.value === 9 && chargeForm.controls.addFeeFrequency.value) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency Interval' | translate }}</mat-label>
<input matInput required formControlName="feeInterval" />
@if (chargeForm.controls.feeInterval.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Frequency Interval' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (chargeForm.controls.feeInterval.hasError('pattern')) {
<mat-error>
{{ 'labels.inputs.Frequency Interval' | translate }}
<strong>{{ 'labels.commons.must be a positive integer' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (chargeForm.controls.chargeTimeType.value === 9 && chargeForm.controls.addFeeFrequency.value) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Frequency' | translate }}</mat-label>
<mat-select required formControlName="feeFrequency">
@for (feeFrequency of chargesTemplateData.feeFrequencyOptions; track feeFrequency) {
<mat-option [value]="feeFrequency.id">
{{ feeFrequency.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.feeFrequency.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Frequency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (chargeForm.controls.chargeTimeType.value === 6 || chargeForm.controls.chargeTimeType.value === 7) {
<mat-form-field (click)="dueDatePicker.open()" class="flex-48">
<mat-label>{{ 'labels.inputs.Due Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="dueDatePicker"
required
formControlName="feeOnMonthDay"
/>
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
<mat-datepicker #dueDatePicker></mat-datepicker>
@if (chargeForm.controls.feeOnMonthDay.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Due Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (chargeForm.controls.chargeTimeType.value === 7 || chargeForm.controls.chargeTimeType.value === 11) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Repeat Every' | translate }} ({{ repeatEveryLabel }})</mat-label>
<input matInput required formControlName="feeInterval" />
@if (chargeForm.controls.feeInterval.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Repeat Every' | translate }} ({{ repeatEveryLabel }})
{{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (
(chargeForm.controls.feeInterval.hasError('min') ||
chargeForm.controls.feeInterval.hasError('max')) &&
!chargeForm.controls.feeInterval.hasError('pattern')
) {
<mat-error>
{{ 'labels.inputs.Repeat Every' | translate }} ({{ repeatEveryLabel }})
<strong>{{ 'labels.commons.must be between 1 and 12' | translate }}</strong>
</mat-error>
}
@if (chargeForm.controls.feeInterval.hasError('pattern')) {
<mat-error>
{{ 'labels.inputs.Repeat Every' | translate }} ({{ repeatEveryLabel }})
<strong>{{ 'labels.commons.must be a positive integer' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input matInput required formControlName="amount" />
@if (chargeForm.controls.amount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<!--
* minCap and maxCap only allowed for loan ,shares and savings entitites
* In Loan case: Only for the "charge calculation type" set as "%amount", "% loan amount+interest", "%interest",and"%disbursement amount".
* In saving case: Only for 1. charge time type is "withdrawlfee" or "savings no activity fee" with charge calculation type as "% amount"
* In shares case: Only for charge time type: SHARE_PURCHASE and SHARE_REDEEM and with charge calculation type as % amount only
-->
@if (showMinMaxCap()) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Minimum Charge Cap' | translate }}</mat-label>
<input matInput autofocus formControlName="minCap" mifosxValidateOnFocus />
@if (chargeForm.controls.minCap.hasError('maxValue')) {
<mat-error>
{{ 'errors.validation.msg.loanproduct.minimumGap.not.greater.than.specified.number' | translate }}
({{ chargeForm.controls.maxCap.value }})
</mat-error>
}
</mat-form-field>
}
@if (showMinMaxCap()) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Maximum Charge Cap' | translate }}</mat-label>
<input matInput autofocus formControlName="maxCap" mifosxValidateOnFocus />
@if (chargeForm.controls.maxCap.hasError('minValue')) {
<mat-error>
{{ 'errors.validation.msg.loanproduct.maximumGap.not.greater.than.specified.number' | translate }}
({{ chargeForm.controls.minCap.value }})
</mat-error>
}
</mat-form-field>
}
@if (chargeForm.controls.chargeAppliesTo.value === 3) {
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="chargeForm.controls.incomeAccountId"
[glAccountList]="incomeAndLiabilityAccountData"
[inputLabel]="'Income from Charge'"
>
</mifosx-gl-account-selector>
}
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
<mat-select formControlName="taxGroupId">
@for (taxGroup of chargesTemplateData.taxGroupOptions; track taxGroup) {
<mat-option [value]="taxGroup.id">
{{ taxGroup.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="flex-48 layout-row gap-2percent layout-lt-md-column">
<div class="flex-50 active-wrapper">
<mat-checkbox labelPosition="before" formControlName="active">
{{ 'labels.status.Active' | translate }}
</mat-checkbox>
</div>
<div class="flex-50 penalty-wrapper">
<mat-checkbox labelPosition="before" formControlName="penalty">
{{ 'labels.commons.Is' | translate }} {{ 'labels.inputs.Penalty' | translate }}
</mat-checkbox>
</div>
</div>
</mat-form-field>
}
@if (chargeForm.controls.chargeTimeType.value === 9) {
<div class="cc-checkbox-row">
<mat-checkbox labelPosition="before" formControlName="addFeeFrequency">
{{ 'labels.inputs.Add Fee Frequency' | translate }}
</mat-checkbox>
</div>
}
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!chargeForm.valid" *mifosxHasPermission="'CREATE_CHARGE'">
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
@if (chargeForm.controls.chargeTimeType.value === 9 && chargeForm.controls.addFeeFrequency.value) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Frequency Interval' | translate }}</mat-label>
<input matInput required formControlName="feeInterval" />
@if (chargeForm.controls.feeInterval.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Frequency Interval' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (chargeForm.controls.feeInterval.hasError('pattern')) {
<mat-error>
{{ 'labels.inputs.Frequency Interval' | translate }}
<strong>{{ 'labels.commons.must be a positive integer' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (chargeForm.controls.chargeTimeType.value === 9 && chargeForm.controls.addFeeFrequency.value) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Frequency' | translate }}</mat-label>
<mat-select required formControlName="feeFrequency">
@for (feeFrequency of chargesTemplateData.feeFrequencyOptions; track feeFrequency) {
<mat-option [value]="feeFrequency.id">
{{ feeFrequency.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.feeFrequency.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Frequency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (chargeForm.controls.chargeTimeType.value === 6 || chargeForm.controls.chargeTimeType.value === 7) {
<mat-form-field appearance="outline" (click)="dueDatePicker.open()">
<mat-label>{{ 'labels.inputs.Due Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="dueDatePicker"
required
formControlName="feeOnMonthDay"
/>
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
<mat-datepicker #dueDatePicker></mat-datepicker>
@if (chargeForm.controls.feeOnMonthDay.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Due Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (chargeForm.controls.chargeTimeType.value === 7 || chargeForm.controls.chargeTimeType.value === 11) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Repeat Every' | translate }} ({{ repeatEveryLabel }})</mat-label>
<input matInput required formControlName="feeInterval" />
@if (chargeForm.controls.feeInterval.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Repeat Every' | translate }} ({{ repeatEveryLabel }})
{{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (
(chargeForm.controls.feeInterval.hasError('min') ||
chargeForm.controls.feeInterval.hasError('max')) &&
!chargeForm.controls.feeInterval.hasError('pattern')
) {
<mat-error>
{{ 'labels.inputs.Repeat Every' | translate }} ({{ repeatEveryLabel }})
<strong>{{ 'labels.commons.must be between 1 and 12' | translate }}</strong>
</mat-error>
}
@if (chargeForm.controls.feeInterval.hasError('pattern')) {
<mat-error>
{{ 'labels.inputs.Repeat Every' | translate }} ({{ repeatEveryLabel }})
<strong>{{ 'labels.commons.must be a positive integer' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input matInput required formControlName="amount" />
@if (chargeForm.controls.amount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
@if (showMinMaxCap()) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Minimum Charge Cap' | translate }}</mat-label>
<input matInput autofocus formControlName="minCap" mifosxValidateOnFocus />
@if (chargeForm.controls.minCap.hasError('maxValue')) {
<mat-error>
{{ 'errors.validation.msg.loanproduct.minimumGap.not.greater.than.specified.number' | translate }}
({{ chargeForm.controls.maxCap.value }})
</mat-error>
}
</mat-form-field>
}
@if (showMinMaxCap()) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Maximum Charge Cap' | translate }}</mat-label>
<input matInput autofocus formControlName="maxCap" mifosxValidateOnFocus />
@if (chargeForm.controls.maxCap.hasError('minValue')) {
<mat-error>
{{ 'errors.validation.msg.loanproduct.maximumGap.not.greater.than.specified.number' | translate }}
({{ chargeForm.controls.minCap.value }})
</mat-error>
}
</mat-form-field>
}
@if (chargeForm.controls.chargeAppliesTo.value === 3) {
<mifosx-gl-account-selector
[inputFormControl]="chargeForm.controls.incomeAccountId"
[glAccountList]="incomeAndLiabilityAccountData"
[inputLabel]="'Income from Charge'"
>
</mifosx-gl-account-selector>
}
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
<mat-select formControlName="taxGroupId">
@for (taxGroup of chargesTemplateData.taxGroupOptions; track taxGroup) {
<mat-option [value]="taxGroup.id">
{{ taxGroup.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="cc-checkbox-row cc-checkbox-pair">
<mat-checkbox labelPosition="before" formControlName="active">
{{ 'labels.status.Active' | translate }}
</mat-checkbox>
<mat-checkbox labelPosition="before" formControlName="penalty">
{{ 'labels.commons.Is' | translate }} {{ 'labels.inputs.Penalty' | translate }}
</mat-checkbox>
</div>
</div>
</div>
}
<!-- Action Bar -->
<div class="cc-actions">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-flat-button color="primary" [disabled]="!chargeForm.valid" *mifosxHasPermission="'CREATE_CHARGE'">
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</form>
</div>
@@ -6,14 +6,103 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.penalty-wrapper {
padding: 17.5px 0;
.cc-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
.active-wrapper {
padding: 17.5px 0;
.cc-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
&__icon {
width: 40px;
height: 40px;
border-radius: 10px;
background: linear-gradient(135deg, #0ea5e9 0%, #1353a0 100%);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 18px;
}
&__title {
font-size: 20px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
.add-fee-frequency-wrapper {
padding: 17.5px 0;
.cc-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
margin-bottom: 16px;
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
&__body {
padding: 16px 24px;
.full-width {
width: 100%;
}
}
}
.cc-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
}
.cc-checkbox-row {
display: flex;
align-items: center;
gap: 16px;
padding: 4px 0;
}
.cc-checkbox-pair {
grid-column: 1 / -1;
}
.cc-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
padding: 16px 0;
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
@media (max-width: 600px) {
.cc-grid {
grid-template-columns: 1fr;
}
}
@@ -23,7 +23,6 @@ import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates';
import { minNumberValueValidator } from 'app/shared/validators/min-number-value.validator';
import { maxNumberValueValidator } from 'app/shared/validators/max-number-value.validator';
import { MatDivider } from '@angular/material/divider';
import { MatCheckbox } from '@angular/material/checkbox';
import { ValidateOnFocusDirective } from '../../../directives/validate-on-focus.directive';
import { GlAccountSelectorComponent } from '../../../shared/accounting/gl-account-selector/gl-account-selector.component';
@@ -38,7 +37,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./create-charge.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatDivider,
MatCheckbox,
ValidateOnFocusDirective,
GlAccountSelectorComponent
@@ -6,276 +6,288 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="chargeForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Applies To' | translate }}</mat-label>
<mat-select required formControlName="chargeAppliesTo">
@for (chargeAppliesTo of chargeData.chargeAppliesToOptions; track chargeAppliesTo) {
<mat-option [value]="chargeAppliesTo.id">
{{ chargeAppliesTo.value | translateKey: 'catalogs' }}
<div class="ec-container">
<div class="ec-header">
<div class="ec-header__icon"><i class="fas fa-edit"></i></div>
<h1 class="ec-header__title">{{ 'labels.heading.Edit' | translate }} {{ 'labels.inputs.Charge' | translate }}</h1>
</div>
<form [formGroup]="chargeForm" (ngSubmit)="submit()">
<!-- Section 1: Charge Type -->
<div class="ec-section">
<div class="ec-section__header">
<i class="fas fa-tag"></i>
<h3>{{ 'labels.inputs.Charge Applies To' | translate }}</h3>
</div>
<div class="ec-section__body">
<mat-form-field appearance="outline" class="full-width">
<mat-label>{{ 'labels.inputs.Charge Applies To' | translate }}</mat-label>
<mat-select required formControlName="chargeAppliesTo">
@for (chargeAppliesTo of chargeData.chargeAppliesToOptions; track chargeAppliesTo) {
<mat-option [value]="chargeAppliesTo.id">
{{ chargeAppliesTo.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
<!-- Section 2: Charge Details -->
<div class="ec-section">
<div class="ec-section__header">
<i class="fas fa-list-alt"></i>
<h3>{{ 'labels.heading.Charge Details' | translate }}</h3>
</div>
<div class="ec-section__body ec-grid">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required autofocus formControlName="name" />
@if (chargeForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select required formControlName="currencyCode">
@for (currency of chargeData.currencyOptions; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.currencyCode.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.inputs.Type' | translate }}
{{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Time Type' | translate }}</mat-label>
<mat-select required formControlName="chargeTimeType">
@for (chargeTime of chargeTimeTypeOptions; track chargeTime) {
<mat-option [value]="chargeTime.id">
{{ chargeTime.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeTimeType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Time Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Calculation' | translate }}</mat-label>
<mat-select required formControlName="chargeCalculationType">
@for (chargeCalculation of chargeCalculationTypeOptions; track chargeCalculation) {
<mat-option [value]="chargeCalculation.id">
{{ chargeCalculation.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeCalculationType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Calculation Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
@if (chargePaymentMode) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Payment By' | translate }}</mat-label>
<mat-select required formControlName="chargePaymentMode">
@for (chargePaymentMode of chargeData.chargePaymetModeOptions; track chargePaymentMode) {
<mat-option [value]="chargePaymentMode.id">
{{ chargePaymentMode.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="layout-row-wrap gap-2percent layout-lt-md-column form-section">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required autofocus formControlName="name" />
@if (chargeForm.controls.name.hasError('required')) {
@if (chargeForm.controls.chargePaymentMode.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
{{ 'labels.inputs.Charge Payment Mode' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select required formControlName="currencyCode">
@for (currency of chargeData.currencyOptions; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.currencyCode.hasError('required')) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input matInput required autofocus type="text" formControlName="amount" />
@if (chargeForm.controls.amount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<!--
* minCap and maxCap only allowed for loan ,shares and savings entitites
* In Loan case: Only for the "charge calculation type" set as "%amount", "% loan amount+interest", "%interest",and "%disbursement amount".
* In saving case: Only for 1. charge time type is "withdrawlfee" or "savings no activity fee" with charge calculation type as "% amount"
* In shares case: Only for charge time type: SHARE_PURCHASE and SHARE_REDEEM and with charge calculation type as % amount only
-->
@if (
(chargeForm.controls.chargeAppliesTo.value === 1 &&
(chargeForm.controls.chargeCalculationType.value === 2 ||
chargeForm.controls.chargeCalculationType.value === 3 ||
chargeForm.controls.chargeCalculationType.value === 4 ||
chargeForm.controls.chargeCalculationType.value === 5)) ||
(chargeForm.controls.chargeAppliesTo.value === 2 &&
(chargeForm.controls.chargeTimeType.value === 16 || chargeForm.controls.chargeTimeType.value === 5) &&
chargeForm.controls.chargeCalculationType.value === 2) ||
(chargeForm.controls.chargeAppliesTo.value === 4 &&
(chargeForm.controls.chargeTimeType.value === 14 || chargeForm.controls.chargeTimeType.value === 15) &&
chargeForm.controls.chargeCalculationType.value === 2)
) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Minimum Charge Cap' | translate }}</mat-label>
<input matInput autofocus formControlName="minCap" mifosxValidateOnFocus />
@if (chargeForm.controls.minCap.hasError('maxValue')) {
<mat-error>
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.inputs.Type' | translate }}
{{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong>
{{ 'errors.validation.msg.loanproduct.minimumGap.not.greater.than.specified.number' | translate }} ({{
chargeForm.controls.maxCap.value
}})
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Time Type' | translate }}</mat-label>
<mat-select required formControlName="chargeTimeType">
@for (chargeTime of chargeTimeTypeOptions; track chargeTime) {
<mat-option [value]="chargeTime.id">
{{ chargeTime.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeTimeType.hasError('required')) {
}
@if (
(chargeForm.controls.chargeAppliesTo.value === 1 &&
(chargeForm.controls.chargeCalculationType.value === 2 ||
chargeForm.controls.chargeCalculationType.value === 3 ||
chargeForm.controls.chargeCalculationType.value === 4 ||
chargeForm.controls.chargeCalculationType.value === 5)) ||
(chargeForm.controls.chargeAppliesTo.value === 2 &&
(chargeForm.controls.chargeTimeType.value === 16 || chargeForm.controls.chargeTimeType.value === 5) &&
chargeForm.controls.chargeCalculationType.value === 2) ||
(chargeForm.controls.chargeAppliesTo.value === 4 &&
(chargeForm.controls.chargeTimeType.value === 14 || chargeForm.controls.chargeTimeType.value === 15) &&
chargeForm.controls.chargeCalculationType.value === 2)
) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Maximum Charge Cap' | translate }}</mat-label>
<input matInput autofocus formControlName="maxCap" mifosxValidateOnFocus />
@if (chargeForm.controls.maxCap.hasError('minValue')) {
<mat-error>
{{ 'labels.inputs.Charge Time Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
{{ 'errors.validation.msg.loanproduct.maximumGap.not.greater.than.specified.number' | translate }} ({{
chargeForm.controls.minCap.value
}})
</mat-error>
}
</mat-form-field>
}
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Calculation' | translate }}</mat-label>
<mat-select required formControlName="chargeCalculationType">
@for (chargeCalculation of chargeCalculationTypeOptions; track chargeCalculation) {
<mat-option [value]="chargeCalculation.id">
{{ chargeCalculation.value | translateKey: 'catalogs' }}
@if (showGLAccount) {
<mifosx-gl-account-selector
[inputFormControl]="chargeForm.controls.incomeAccountId"
[glAccountList]="chargeData.incomeOrLiabilityAccountOptions.incomeAccountOptions"
[required]="true"
[inputLabel]="'Income from Charge'"
>
</mifosx-gl-account-selector>
}
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
@if (chargeData.taxGroup) {
<mat-select formControlName="taxGroupId">
@for (taxGroup of chargeData.taxGroupOptions; track taxGroup) {
<mat-option [value]="taxGroup.id">
{{ taxGroup.name }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargeCalculationType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Calculation Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
@if (chargePaymentMode) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Payment By' | translate }}</mat-label>
<mat-select required formControlName="chargePaymentMode">
@for (chargePaymentMode of chargeData.chargePaymetModeOptions; track chargePaymentMode) {
<mat-option [value]="chargePaymentMode.id">
{{ chargePaymentMode.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.chargePaymentMode.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge Payment Mode' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input matInput required autofocus type="text" formControlName="amount" />
@if (chargeForm.controls.amount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<!--
* minCap and maxCap only allowed for loan ,shares and savings entitites
* In Loan case: Only for the "charge calculation type" set as "%amount", "% loan amount+interest", "%interest",and "%disbursement amount".
* In saving case: Only for 1. charge time type is "withdrawlfee" or "savings no activity fee" with charge calculation type as "% amount"
* In shares case: Only for charge time type: SHARE_PURCHASE and SHARE_REDEEM and with charge calculation type as % amount only
-->
@if (
(chargeForm.controls.chargeAppliesTo.value === 1 &&
(chargeForm.controls.chargeCalculationType.value === 2 ||
chargeForm.controls.chargeCalculationType.value === 3 ||
chargeForm.controls.chargeCalculationType.value === 4 ||
chargeForm.controls.chargeCalculationType.value === 5)) ||
(chargeForm.controls.chargeAppliesTo.value === 2 &&
(chargeForm.controls.chargeTimeType.value === 16 || chargeForm.controls.chargeTimeType.value === 5) &&
chargeForm.controls.chargeCalculationType.value === 2) ||
(chargeForm.controls.chargeAppliesTo.value === 4 &&
(chargeForm.controls.chargeTimeType.value === 14 || chargeForm.controls.chargeTimeType.value === 15) &&
chargeForm.controls.chargeCalculationType.value === 2)
) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Minimum Charge Cap' | translate }}</mat-label>
<input matInput autofocus formControlName="minCap" mifosxValidateOnFocus />
@if (chargeForm.controls.minCap.hasError('maxValue')) {
<mat-error>
{{ 'errors.validation.msg.loanproduct.minimumGap.not.greater.than.specified.number' | translate }} ({{
chargeForm.controls.maxCap.value
}})
</mat-error>
@if (!chargeData.taxGroup) {
<mat-select formControlName="taxGroupId">
@for (taxGroup of chargeData.taxGroupOptions; track taxGroup) {
<mat-option [value]="taxGroup.id">
{{ taxGroup.name }}
</mat-option>
}
</mat-form-field>
}
@if (
(chargeForm.controls.chargeAppliesTo.value === 1 &&
(chargeForm.controls.chargeCalculationType.value === 2 ||
chargeForm.controls.chargeCalculationType.value === 3 ||
chargeForm.controls.chargeCalculationType.value === 4 ||
chargeForm.controls.chargeCalculationType.value === 5)) ||
(chargeForm.controls.chargeAppliesTo.value === 2 &&
(chargeForm.controls.chargeTimeType.value === 16 || chargeForm.controls.chargeTimeType.value === 5) &&
chargeForm.controls.chargeCalculationType.value === 2) ||
(chargeForm.controls.chargeAppliesTo.value === 4 &&
(chargeForm.controls.chargeTimeType.value === 14 || chargeForm.controls.chargeTimeType.value === 15) &&
chargeForm.controls.chargeCalculationType.value === 2)
) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Maximum Charge Cap' | translate }}</mat-label>
<input matInput autofocus formControlName="maxCap" mifosxValidateOnFocus />
@if (chargeForm.controls.maxCap.hasError('minValue')) {
<mat-error>
{{ 'errors.validation.msg.loanproduct.maximumGap.not.greater.than.specified.number' | translate }} ({{
chargeForm.controls.minCap.value
}})
</mat-error>
}
</mat-form-field>
</mat-select>
}
</mat-form-field>
@if (showGLAccount) {
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="chargeForm.controls.incomeAccountId"
[glAccountList]="chargeData.incomeOrLiabilityAccountOptions.incomeAccountOptions"
[required]="true"
[inputLabel]="'Income from Charge'"
@if (addFeeFrequency) {
<div class="ec-checkbox-row">
<mat-checkbox
labelPosition="before"
[checked]="addFeeFrequency && showFeeOptions"
(change)="getFeeFrequency($event.checked)"
>
</mifosx-gl-account-selector>
}
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
@if (chargeData.taxGroup) {
<mat-select formControlName="taxGroupId">
@for (taxGroup of chargeData.taxGroupOptions; track taxGroup) {
<mat-option [value]="taxGroup.id">
{{ taxGroup.name }}
</mat-option>
}
</mat-select>
}
@if (!chargeData.taxGroup) {
<mat-select formControlName="taxGroupId">
@for (taxGroup of chargeData.taxGroupOptions; track taxGroup) {
<mat-option [value]="taxGroup.id">
{{ taxGroup.name }}
</mat-option>
}
</mat-select>
}
</mat-form-field>
@if (addFeeFrequency) {
<div class="password-never-expires-wrapper checkbox flex-48">
<mat-checkbox
labelPosition="before"
[checked]="addFeeFrequency && showFeeOptions"
(change)="getFeeFrequency($event.checked)"
>
{{ 'labels.inputs.Add Fee Frequency' | translate }}
</mat-checkbox>
</div>
}
@if (addFeeFrequency && showFeeOptions) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency Interval' | translate }}</mat-label>
<input matInput required autofocus type="text" formControlName="feeInterval" />
@if (chargeForm.controls.feeInterval.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Frequency Interval' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (addFeeFrequency && showFeeOptions) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge Frequency' | translate }}</mat-label>
<mat-select required formControlName="feeFrequency">
@for (chargeFrequency of chargeData.feeFrequencyOptions; track chargeFrequency) {
<mat-option [value]="chargeFrequency.id">
{{ chargeFrequency.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.feeFrequency.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Add Fee Frequency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
</div>
<div class="layout-row-wrap gap-2percent layout-lt-md-column form-section">
<div class="password-never-expires-wrapper flex-48">
<mat-checkbox labelPosition="before" formControlName="active">
{{ 'labels.status.Active' | translate }}
{{ 'labels.inputs.Add Fee Frequency' | translate }}
</mat-checkbox>
</div>
}
@if (addFeeFrequency && showFeeOptions) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Frequency Interval' | translate }}</mat-label>
<input matInput required autofocus type="text" formControlName="feeInterval" />
@if (chargeForm.controls.feeInterval.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Frequency Interval' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (addFeeFrequency && showFeeOptions) {
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Frequency' | translate }}</mat-label>
<mat-select required formControlName="feeFrequency">
@for (chargeFrequency of chargeData.feeFrequencyOptions; track chargeFrequency) {
<mat-option [value]="chargeFrequency.id">
{{ chargeFrequency.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
@if (chargeForm.controls.feeFrequency.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Add Fee Frequency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<div class="ec-checkbox-row ec-checkbox-pair">
<mat-checkbox labelPosition="before" formControlName="active">
{{ 'labels.status.Active' | translate }}
</mat-checkbox>
@if (showPenalty) {
<div class="send-password-to-email-wrapper flex-48">
<mat-checkbox labelPosition="before" formControlName="penalty">
{{ 'labels.commons.Is' | translate }} {{ 'labels.inputs.Penalty' | translate }}
</mat-checkbox>
</div>
<mat-checkbox labelPosition="before" formControlName="penalty">
{{ 'labels.commons.Is' | translate }} {{ 'labels.inputs.Penalty' | translate }}
</mat-checkbox>
}
</div>
</mat-card-content>
</div>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!chargeForm.valid" *mifosxHasPermission="'UPDATE_CHARGE'">
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<!-- Action Bar -->
<div class="ec-actions">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-flat-button color="primary" [disabled]="!chargeForm.valid" *mifosxHasPermission="'UPDATE_CHARGE'">
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</form>
</div>
@@ -6,10 +6,103 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.checkbox {
padding-top: 10px;
.ec-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
.form-section {
margin-top: 10px;
.ec-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
&__icon {
width: 40px;
height: 40px;
border-radius: 10px;
background: linear-gradient(135deg, #0ea5e9 0%, #1353a0 100%);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 18px;
}
&__title {
font-size: 20px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
.ec-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
margin-bottom: 16px;
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
&__body {
padding: 16px 24px;
.full-width {
width: 100%;
}
}
}
.ec-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
}
.ec-checkbox-row {
display: flex;
align-items: center;
gap: 16px;
padding: 4px 0;
}
.ec-checkbox-pair {
grid-column: 1 / -1;
}
.ec-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
padding: 16px 0;
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
@media (max-width: 600px) {
.ec-grid {
grid-template-columns: 1fr;
}
}
@@ -6,169 +6,125 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="layout-row align-end gap-2px responsive-column container m-b-20">
<button mat-raised-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_CHARGE'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.heading.Edit' | translate }}
</button>
<button mat-raised-button color="warn" (click)="deleteCharge()" *mifosxHasPermission="'DELETE_CHARGE'">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.heading.Delete' | translate }}
</button>
</div>
<div class="vc-container">
<div class="vc-top-bar">
<h1 class="vc-top-bar__title">
<i class="fas fa-file-invoice-dollar"></i>
{{ chargeData.name }}
</h1>
<div class="vc-top-bar__actions">
<button mat-flat-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_CHARGE'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.heading.Edit' | translate }}
</button>
<button mat-flat-button color="warn" (click)="deleteCharge()" *mifosxHasPermission="'DELETE_CHARGE'">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.heading.Delete' | translate }}
</button>
</div>
</div>
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Charge Name' | translate }}
</div>
<div class="flex-50">
{{ chargeData.name }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Charge Applies To' | translate }}
</div>
<div class="flex-50">
{{ chargeData.chargeAppliesTo.value | translateKey: 'catalogs' }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Penalty' | translate }}
</div>
<div class="flex-50">
{{ chargeData.penalty === true | yesNo }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Currency' | translate }}
</div>
<div class="flex-50">
{{ chargeData.currency.name }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Amount' | translate }}
</div>
<div class="flex-50">
{{ chargeData.amount }}
</div>
@if (minCap) {
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Minimum Charge Cap' | translate }}
</div>
}
@if (minCap) {
<div class="flex-50">
{{ chargeData.minCap }}
</div>
}
@if (maxCap) {
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Maximum Charge Cap' | translate }}
</div>
}
@if (maxCap) {
<div class="flex-50">
{{ chargeData.maxCap }}
</div>
}
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Charge Time Type' | translate }}
</div>
<div class="flex-50">
{{ chargeData.chargeTimeType.value | translateKey: 'catalogs' }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Charge Calculation Type' | translate }}
</div>
<div class="flex-50">
{{ chargeData.chargeCalculationType.value | translateKey: 'catalogs' }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Charge Payment Mode' | translate }}
</div>
<div class="flex-50">
{{ chargeData.chargePaymentMode.value | translateKey: 'catalogs' }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.status.Active' | translate }}
</div>
<div class="flex-50">
{{ chargeData.active === true | yesNo }}
</div>
@if (chargeData.chargeTimeType.id === 9 && chargeData.feeFrequency) {
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Add Fee Frequency' | translate }}
</div>
}
@if (chargeData.chargeTimeType.id === 9 && chargeData.feeFrequency && chargeData.feeFrequency) {
<div class="flex-50">
{{ chargeData.feeFrequency.value | translateKey: 'catalogs' }}
</div>
}
@if (chargeData.chargeTimeType.id === 9 && chargeData.feeInterval) {
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Frequency Interval' | translate }}
</div>
}
@if (chargeData.chargeTimeType.id === 9 && chargeData.feeInterval) {
<div class="flex-50">
{{ chargeData.feeInterval }}
</div>
}
@if (chargeData.incomeOrLiabilityAccount) {
<mifosx-gl-account-display
class="flex-100"
[accountTitle]="'Income From Charges'"
[glAccount]="chargeData.incomeOrLiabilityAccount"
[withTitle]="'50%'"
>
</mifosx-gl-account-display>
}
@if (chargeData.taxGroup) {
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Tax Group' | translate }}
</div>
}
@if (chargeData.taxGroup) {
<div class="flex-50">
{{ chargeData.taxGroup.name }}
</div>
}
<div class="vc-section">
<div class="vc-section__header">
<i class="fas fa-list-alt"></i>
<h3>{{ 'labels.heading.Charge Details' | translate }}</h3>
</div>
<div class="vc-section__body">
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Charge Name' | translate }}</span>
<span class="vc-row__value">{{ chargeData.name }}</span>
</div>
<div class="layout-row layout-align-center gap-2percent column-on-mobile">
<button type="button" mat-raised-button color="primary" [routerLink]="['../']">
{{ 'labels.buttons.Back' | translate }}
</button>
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Charge Applies To' | translate }}</span>
<span class="vc-row__value">{{ chargeData.chargeAppliesTo.value | translateKey: 'catalogs' }}</span>
</div>
</mat-card-content>
</mat-card>
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Penalty' | translate }}</span>
<span class="vc-row__value">{{ chargeData.penalty === true | yesNo }}</span>
</div>
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Currency' | translate }}</span>
<span class="vc-row__value">{{ chargeData.currency.name }}</span>
</div>
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Amount' | translate }}</span>
<span class="vc-row__value">{{ chargeData.amount }}</span>
</div>
@if (minCap) {
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Minimum Charge Cap' | translate }}</span>
<span class="vc-row__value">{{ chargeData.minCap }}</span>
</div>
}
@if (maxCap) {
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Maximum Charge Cap' | translate }}</span>
<span class="vc-row__value">{{ chargeData.maxCap }}</span>
</div>
}
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Charge Time Type' | translate }}</span>
<span class="vc-row__value">{{ chargeData.chargeTimeType.value | translateKey: 'catalogs' }}</span>
</div>
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Charge Calculation Type' | translate }}</span>
<span class="vc-row__value">{{ chargeData.chargeCalculationType.value | translateKey: 'catalogs' }}</span>
</div>
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Charge Payment Mode' | translate }}</span>
<span class="vc-row__value">{{ chargeData.chargePaymentMode.value | translateKey: 'catalogs' }}</span>
</div>
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.status.Active' | translate }}</span>
<span class="vc-row__value">{{ chargeData.active === true | yesNo }}</span>
</div>
@if (chargeData.chargeTimeType.id === 9 && chargeData.feeFrequency) {
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Add Fee Frequency' | translate }}</span>
<span class="vc-row__value">{{ chargeData.feeFrequency.value | translateKey: 'catalogs' }}</span>
</div>
}
@if (chargeData.chargeTimeType.id === 9 && chargeData.feeInterval) {
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Frequency Interval' | translate }}</span>
<span class="vc-row__value">{{ chargeData.feeInterval }}</span>
</div>
}
@if (chargeData.incomeOrLiabilityAccount) {
<mifosx-gl-account-display
class="vc-gl-account"
[accountTitle]="'Income From Charges'"
[glAccount]="chargeData.incomeOrLiabilityAccount"
[withTitle]="'50%'"
>
</mifosx-gl-account-display>
}
@if (chargeData.taxGroup) {
<div class="vc-row">
<span class="vc-row__label">{{ 'labels.inputs.Tax Group' | translate }}</span>
<span class="vc-row__value">{{ chargeData.taxGroup.name }}</span>
</div>
}
</div>
</div>
<div class="vc-actions">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Back' | translate }}
</button>
</div>
</div>
@@ -6,13 +6,124 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
.vc-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
.vc-top-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 12px;
&__title {
font-size: 20px;
font-weight: 600;
color: #202124;
margin: 0;
display: flex;
align-items: center;
gap: 10px;
i {
color: #1353a0;
font-size: 18px;
}
}
&__actions {
display: flex;
gap: 8px;
}
}
.vc-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
margin-bottom: 16px;
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
&__body {
padding: 4px 24px;
}
}
.vc-row {
display: flex;
padding: 6px 0;
border-bottom: 1px solid #f1f3f4;
&:last-child {
border-bottom: none;
}
&__label {
flex: 0 0 40%;
font-size: 13px;
font-weight: 500;
color: #5f6368;
}
&__value {
flex: 1;
font-size: 13px;
color: #202124;
}
}
.vc-gl-account {
display: block;
padding: 6px 0;
border-bottom: 1px solid #f1f3f4;
}
.vc-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
padding: 16px 0;
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
@media (max-width: 600px) {
.vc-top-bar {
flex-direction: column;
align-items: flex-start;
}
.vc-row {
flex-direction: column;
gap: 2px;
&__label {
flex: none;
}
}
}
@@ -6,22 +6,30 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_CHARGE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.text.Create Collateral' | translate }}
</button>
</div>
<div class="container">
<div class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-shield-alt title-icon"></i>
{{ 'labels.text.Collateral' | translate }}
</h2>
</div>
<div class="table-actions">
<mat-form-field appearance="outline" class="table-search">
<i matPrefix class="fas fa-search"></i>
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="{{ 'labels.inputs.Filter' | translate }}" />
</mat-form-field>
<div class="table-buttons">
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_CHARGE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.text.Create Collateral' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let collateral">{{ collateral.name }}</td>
@@ -48,7 +56,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
@@ -6,26 +6,4 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
width: 100%;
.select-row:hover {
cursor: pointer;
}
}
.true {
color: #32cd32;
}
.false {
color: #f44366;
}
.ispenalty {
color: #32cd32;
}
.nopenalty {
color: #f44366;
}
@use '../../shared/styles/list-layout' as *;
@@ -6,93 +6,105 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="collateralForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (collateralForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type/Quality' | translate }}</mat-label>
<input matInput required formControlName="quality" />
@if (collateralForm.controls.quality.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Type/Quality' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Unit Type' | translate }}</mat-label>
<input matInput required formControlName="unitType" />
@if (collateralForm.controls.unitType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Unit Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Base Price' | translate }}</mat-label>
<input matInput required formControlName="basePrice" />
@if (collateralForm.controls.basePrice.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Base Price' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Percentage to Base' | translate }}</mat-label>
<input matInput required formControlName="pctToBase" />
@if (collateralForm.controls.pctToBase.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Percentage to Base' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.heading.Currency' | translate }}</mat-label>
<mat-select required formControlName="currency">
@for (currency of collateralTemplateData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
@if (collateralForm.controls.currency.hasError('required')) {
<mat-error>
{{ 'labels.heading.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<div class="ccl-container">
<div class="ccl-page-header">
<i class="fas fa-shield-alt ccl-page-header__icon"></i>
<h2 class="ccl-page-header__title">{{ 'labels.text.Create Collateral' | translate }}</h2>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
color="primary"
[disabled]="!collateralForm.valid"
*mifosxHasPermission="'CREATE_CHARGE'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<form [formGroup]="collateralForm" (ngSubmit)="submit()">
<div class="ccl-section">
<div class="ccl-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.heading.Collateral Details' | translate }}</h3>
</div>
<div class="ccl-section__body">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (collateralForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Type/Quality' | translate }}</mat-label>
<input matInput required formControlName="quality" />
@if (collateralForm.controls.quality.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Type/Quality' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Unit Type' | translate }}</mat-label>
<input matInput required formControlName="unitType" />
@if (collateralForm.controls.unitType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Unit Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Base Price' | translate }}</mat-label>
<input matInput required formControlName="basePrice" />
@if (collateralForm.controls.basePrice.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Base Price' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Percentage to Base' | translate }}</mat-label>
<input matInput required formControlName="pctToBase" />
@if (collateralForm.controls.pctToBase.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Percentage to Base' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.heading.Currency' | translate }}</mat-label>
<mat-select required formControlName="currency">
@for (currency of collateralTemplateData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
@if (collateralForm.controls.currency.hasError('required')) {
<mat-error>
{{ 'labels.heading.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</div>
<div class="ccl-actions">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-flat-button
color="primary"
[disabled]="!collateralForm.valid"
*mifosxHasPermission="'CREATE_CHARGE'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</form>
</div>
@@ -5,3 +5,78 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.ccl-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
.ccl-page-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 24px;
&__icon {
font-size: 20px;
color: #1353a0;
}
&__title {
font-size: 20px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
.ccl-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
&__body {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
}
}
.ccl-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
padding: 16px 0;
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
@media (max-width: 600px) {
.ccl-section__body {
grid-template-columns: 1fr;
}
}
@@ -6,98 +6,105 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="collateralForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required autoFocus formControlName="name" />
@if (collateralForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="ecl-container">
<div class="ecl-page-header">
<i class="fas fa-shield-alt ecl-page-header__icon"></i>
<h2 class="ecl-page-header__title">{{ 'labels.heading.Edit Collateral' | translate }}</h2>
</div>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type/Quality' | translate }}</mat-label>
<input matInput required autoFocus formControlName="quality" />
@if (collateralForm.controls.quality.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Type/Quality' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<form [formGroup]="collateralForm" (ngSubmit)="submit()">
<div class="ecl-section">
<div class="ecl-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.heading.Collateral Details' | translate }}</h3>
</div>
<div class="ecl-section__body">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required autoFocus formControlName="name" />
@if (collateralForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Unit Type' | translate }}</mat-label>
<input matInput required autoFocus formControlName="unitType" />
@if (collateralForm.controls.unitType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Unit Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Type/Quality' | translate }}</mat-label>
<input matInput required autoFocus formControlName="quality" />
@if (collateralForm.controls.quality.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Type/Quality' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Base Price' | translate }}</mat-label>
<input matInput required autoFocus formControlName="basePrice" />
@if (collateralForm.controls.basePrice.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Base Price' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Unit Type' | translate }}</mat-label>
<input matInput required autoFocus formControlName="unitType" />
@if (collateralForm.controls.unitType.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Unit Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Percentage to Base' | translate }}</mat-label>
<input matInput required autoFocus formControlName="pctToBase" />
@if (collateralForm.controls.pctToBase.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Percentage to Base' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Base Price' | translate }}</mat-label>
<input matInput required autoFocus formControlName="basePrice" />
@if (collateralForm.controls.basePrice.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Base Price' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.heading.Currency' | translate }}</mat-label>
<mat-select required formControlName="currency">
@for (currency of collateralTemplateData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
@if (collateralForm.controls.currency.hasError('required')) {
<mat-error>
{{ 'labels.heading.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Percentage to Base' | translate }}</mat-label>
<input matInput required autoFocus formControlName="pctToBase" />
@if (collateralForm.controls.pctToBase.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Percentage to Base' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
color="primary"
[disabled]="!collateralForm.valid"
*mifosxHasPermission="'UPDATE_CHARGE'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.heading.Currency' | translate }}</mat-label>
<mat-select required formControlName="currency">
@for (currency of collateralTemplateData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
@if (collateralForm.controls.currency.hasError('required')) {
<mat-error>
{{ 'labels.heading.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</div>
<div class="ecl-actions">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-flat-button
color="primary"
[disabled]="!collateralForm.valid"
*mifosxHasPermission="'UPDATE_CHARGE'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</form>
</div>
@@ -5,3 +5,78 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.ecl-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
.ecl-page-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 24px;
&__icon {
font-size: 20px;
color: #1353a0;
}
&__title {
font-size: 20px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
.ecl-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
&__body {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
}
}
.ecl-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
padding: 16px 0;
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
@media (max-width: 600px) {
.ecl-section__body {
grid-template-columns: 1fr;
}
}
@@ -6,69 +6,54 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="layout-row align-end gap-2px responsive-column container m-b-20">
<button mat-raised-button color="primary" [routerLink]="['edit']">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.heading.Edit' | translate }}
</button>
<button mat-raised-button color="warn" (click)="deleteCollateral()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.heading.Delete' | translate }}
</button>
</div>
<div class="vcl-container">
<div class="vcl-top-bar">
<div class="vcl-top-bar__title">
<i class="fas fa-shield-alt vcl-top-bar__icon"></i>
<h2>{{ collateralData.name }}</h2>
</div>
<div class="vcl-top-bar__actions">
<button mat-flat-button color="primary" [routerLink]="['edit']">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.heading.Edit' | translate }}
</button>
<button mat-flat-button color="warn" (click)="deleteCollateral()">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.heading.Delete' | translate }}
</button>
</div>
</div>
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Collateral Name' | translate }}
</div>
<div class="flex-50">
{{ collateralData.name }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Type/Quality' | translate }}
</div>
<div class="flex-50">
{{ collateralData.quality }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Base Price' | translate }}
</div>
<div class="flex-50">
{{ collateralData.basePrice }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Base Percentage' | translate }}
</div>
<div class="flex-50">
{{ collateralData.pctToBase }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Unit Type' | translate }}
</div>
<div class="flex-50">
{{ collateralData.unitType }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.heading.Currency' | translate }}
</div>
<div class="flex-50">
{{ collateralData.currency }}
</div>
<div class="vcl-section">
<div class="vcl-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.heading.Collateral Details' | translate }}</h3>
</div>
<div class="vcl-section__body">
<div class="vcl-row">
<span class="vcl-row__label">{{ 'labels.inputs.Collateral Name' | translate }}</span>
<span class="vcl-row__value">{{ collateralData.name }}</span>
</div>
</mat-card-content>
</mat-card>
<div class="vcl-row">
<span class="vcl-row__label">{{ 'labels.inputs.Type/Quality' | translate }}</span>
<span class="vcl-row__value">{{ collateralData.quality }}</span>
</div>
<div class="vcl-row">
<span class="vcl-row__label">{{ 'labels.inputs.Base Price' | translate }}</span>
<span class="vcl-row__value">{{ collateralData.basePrice }}</span>
</div>
<div class="vcl-row">
<span class="vcl-row__label">{{ 'labels.inputs.Base Percentage' | translate }}</span>
<span class="vcl-row__value">{{ collateralData.pctToBase }}</span>
</div>
<div class="vcl-row">
<span class="vcl-row__label">{{ 'labels.inputs.Unit Type' | translate }}</span>
<span class="vcl-row__value">{{ collateralData.unitType }}</span>
</div>
<div class="vcl-row">
<span class="vcl-row__label">{{ 'labels.heading.Currency' | translate }}</span>
<span class="vcl-row__value">{{ collateralData.currency }}</span>
</div>
</div>
</div>
</div>
@@ -6,13 +6,111 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
.vcl-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
.vcl-top-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 12px;
&__title {
display: flex;
align-items: center;
gap: 10px;
h2 {
font-size: 20px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
&__icon {
font-size: 20px;
color: #1353a0;
}
&__actions {
display: flex;
gap: 10px;
}
}
.vcl-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
&__body {
padding: 8px 24px;
}
}
.vcl-row {
display: flex;
padding: 6px 0;
border-bottom: 1px solid #f1f3f4;
&:last-child {
border-bottom: none;
}
&__label {
flex: 0 0 40%;
font-size: 13px;
font-weight: 500;
color: #5f6368;
}
&__value {
flex: 1;
font-size: 13px;
color: #202124;
}
}
@media (max-width: 600px) {
.vcl-top-bar {
flex-direction: column;
align-items: flex-start;
}
.vcl-row {
flex-direction: column;
gap: 2px;
&__label {
flex: none;
}
}
}
@@ -1,67 +1,135 @@
<div class="container">
<mat-stepper orientation="vertical" [linear]="false" class="mat-elevation-z8" labelPosition="bottom">
<ng-template matStepperIcon="number"><fa-icon icon="pencil-alt" size="sm"></fa-icon></ng-template>
<ng-template matStepperIcon="edit"><fa-icon icon="pencil-alt" size="sm"></fa-icon></ng-template>
<ng-template matStepperIcon="done"><fa-icon icon="check" size="sm"></fa-icon></ng-template>
<ng-template matStepperIcon="error"><fa-icon icon="exclamation-triangle" size="lg"></fa-icon></ng-template>
<ng-template matStepperIcon="preview"><fa-icon icon="eye" size="sm"></fa-icon></ng-template>
<!--
Copyright since 2025 Mifos Initiative
<mat-step [stepControl]="detailsForm">
<ng-template matStepLabel>{{ 'labels.inputs.DETAILS' | translate }}</ng-template>
<mifosx-saving-product-details-step></mifosx-saving-product-details-step>
</mat-step>
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<mat-step [stepControl]="currencyForm">
<ng-template matStepLabel>{{ 'labels.inputs.CURRENCY' | translate }}</ng-template>
<mifosx-saving-product-currency-step
[savingProductsTemplate]="currentProductsTemplate"
></mifosx-saving-product-currency-step>
</mat-step>
<div class="cp-container">
<div class="cp-header">
<i class="fas fa-credit-card cp-header__icon"></i>
<h2 class="cp-header__title">{{ 'labels.heading.Create Current Product' | translate }}</h2>
</div>
<mat-step [stepControl]="termsForm">
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
<mifosx-saving-product-terms-step
[savingProductsTemplate]="currentProductsTemplate"
></mifosx-saving-product-terms-step>
</mat-step>
<mat-tab-group class="cp-tabs" animationDuration="200ms" [preserveContent]="true" [(selectedIndex)]="selectedTabIndex">
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-info-circle cp-tab-icon"></i>
{{ 'labels.inputs.DETAILS' | translate }}
</ng-template>
<div class="cp-tab-content">
<mifosx-saving-product-details-step></mifosx-saving-product-details-step>
</div>
</mat-tab>
<mat-step [stepControl]="settingsForm">
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
<mifosx-saving-product-settings-step
[savingProductsTemplate]="currentProductsTemplate"
></mifosx-saving-product-settings-step>
</mat-step>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-coins cp-tab-icon"></i>
{{ 'labels.inputs.CURRENCY' | translate }}
</ng-template>
<div class="cp-tab-content">
<mifosx-saving-product-currency-step
[savingProductsTemplate]="currentProductsTemplate"
></mifosx-saving-product-currency-step>
</div>
</mat-tab>
<mat-step>
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
<mifosx-saving-product-charges-step
[savingProductsTemplate]="currentProductsTemplate"
[currencyCode]="currencyForm.get('currencyCode')"
></mifosx-saving-product-charges-step>
</mat-step>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-sliders-h cp-tab-icon"></i>
{{ 'labels.inputs.TERMS' | translate }}
</ng-template>
<div class="cp-tab-content">
<mifosx-saving-product-terms-step
[savingProductsTemplate]="currentProductsTemplate"
></mifosx-saving-product-terms-step>
</div>
</mat-tab>
<mat-step [stepControl]="accountingForm">
<ng-template matStepLabel>{{ 'labels.inputs.ACCOUNTING' | translate }}</ng-template>
<mifosx-saving-product-accounting-step
[savingProductsTemplate]="currentProductsTemplate"
[accountingRuleData]="accountingRuleData"
[isDormancyTrackingActive]="settingsForm.get('isDormancyTrackingActive')"
[savingProductFormValid]="formValid"
[allowOverdraft]="settingsForm.get('allowOverdraft')"
></mifosx-saving-product-accounting-step>
</mat-step>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-cog cp-tab-icon"></i>
{{ 'labels.inputs.SETTINGS' | translate }}
</ng-template>
<div class="cp-tab-content">
<mifosx-saving-product-settings-step
[savingProductsTemplate]="currentProductsTemplate"
></mifosx-saving-product-settings-step>
</div>
</mat-tab>
@if (formValid) {
<mat-step state="preview" completed>
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
<mifosx-saving-product-preview-step
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-receipt cp-tab-icon"></i>
{{ 'labels.inputs.CHARGES' | translate }}
</ng-template>
<div class="cp-tab-content">
<mifosx-saving-product-charges-step
[savingProductsTemplate]="currentProductsTemplate"
[currencyCode]="currencyForm.get('currencyCode')"
></mifosx-saving-product-charges-step>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-book cp-tab-icon"></i>
{{ 'labels.inputs.ACCOUNTING' | translate }}
</ng-template>
<div class="cp-tab-content">
<mifosx-saving-product-accounting-step
[savingProductsTemplate]="currentProductsTemplate"
[accountingRuleData]="accountingRuleData"
[savingProduct]="currentProduct"
[taskPermission]="'CREATE_CURRENTPRODUCT'"
(submitEvent)="submit()"
></mifosx-saving-product-preview-step>
</mat-step>
[isDormancyTrackingActive]="settingsForm.get('isDormancyTrackingActive')"
[savingProductFormValid]="formValid"
[allowOverdraft]="settingsForm.get('allowOverdraft')"
></mifosx-saving-product-accounting-step>
</div>
</mat-tab>
@if (formValid) {
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-eye cp-tab-icon"></i>
{{ 'labels.inputs.PREVIEW' | translate }}
</ng-template>
<div class="cp-tab-content">
<mifosx-saving-product-preview-step
[savingProductsTemplate]="currentProductsTemplate"
[accountingRuleData]="accountingRuleData"
[savingProduct]="currentProduct"
[taskPermission]="'CREATE_CURRENTPRODUCT'"
(submitEvent)="submit()"
></mifosx-saving-product-preview-step>
</div>
</mat-tab>
}
</mat-stepper>
</mat-tab-group>
<div class="cp-action-bar">
<button mat-raised-button [routerLink]="['../']" class="cp-action-bar__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<div class="cp-action-bar__right">
@if (selectedTabIndex > 0) {
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
<i class="fas fa-arrow-left" style="margin-right: 8px;"></i>
{{ 'labels.buttons.Previous' | translate }}
</button>
}
@if (selectedTabIndex < totalTabs - 1) {
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
{{ 'labels.buttons.Next' | translate }}
<i class="fas fa-arrow-right" style="margin-left: 8px;"></i>
</button>
}
@if (formValid && selectedTabIndex === totalTabs - 1) {
<button mat-raised-button color="primary" (click)="submit()">
<i class="fas fa-check" style="margin-right: 8px;"></i>
{{ 'labels.buttons.Submit' | translate }}
</button>
}
</div>
</div>
</div>
@@ -1,4 +1,90 @@
fa-icon {
position: relative;
left: 5%;
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
$primary: #1353a0;
.cp-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
.cp-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
&__icon {
font-size: 20px;
color: $primary;
}
&__title {
margin: 0;
font-size: 22px;
font-weight: 500;
}
}
.cp-tabs {
background: var(--md-sys-color-surface, #fff);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
::ng-deep {
.mat-mdc-tab-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.mat-mdc-tab {
min-width: 120px;
padding: 0 16px;
height: 52px;
}
.mat-mdc-tab-body-wrapper {
min-height: 200px;
}
.mat-mdc-tab-body {
overflow: visible !important;
}
.mat-mdc-tab-body-content {
overflow: visible !important;
}
}
}
.cp-tab-icon {
margin-right: 8px;
font-size: 14px;
}
.cp-tab-content {
padding: 24px 16px;
}
.cp-action-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding: 16px 0;
border-top: 1px solid rgba(0, 0, 0, 0.08);
&__cancel {
color: rgba(0, 0, 0, 0.6);
}
&__right {
display: flex;
gap: 12px;
}
}
@@ -1,3 +1,11 @@
/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import { Component, ViewChild, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { SavingProductDetailsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component';
@@ -10,8 +18,7 @@ import { SavingProductPreviewStepComponent } from '../../saving-products/saving-
import { ProductsService } from 'app/products/products.service';
import { SettingsService } from 'app/settings/settings.service';
import { Accounting } from 'app/core/utils/accounting';
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatTabsModule } from '@angular/material/tabs';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -20,11 +27,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./create-current-product.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatStepper,
MatStepperIcon,
FaIconComponent,
MatStep,
MatStepLabel,
MatTabsModule,
SavingProductDetailsStepComponent,
SavingProductCurrencyStepComponent,
SavingProductTermsStepComponent,
@@ -51,6 +54,7 @@ export class CreateCurrentProductComponent {
currentProductsTemplate: any;
accountingRuleData: string[] = [];
selectedTabIndex = 0;
constructor() {
this.route.data.subscribe((data: { currentProductsTemplate: any }) => {
@@ -75,6 +79,10 @@ export class CreateCurrentProductComponent {
return this.accountingStep.savingProductAccountingForm;
}
get totalTabs(): number {
return this.formValid ? 7 : 6;
}
get formValid() {
return (
this.detailsForm.valid &&
@@ -9,8 +9,9 @@
<div class="container">
<div class="layout-row">
<mat-form-field class="flex-fill">
<mat-form-field appearance="outline" class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<i matPrefix class="fas fa-search"></i>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
</div>
@@ -6,105 +6,140 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-stepper
orientation="vertical"
[linear]="false"
class="mat-elevation-z8"
labelPosition="bottom"
#fixedDepositProductStepper
>
<ng-template matStepperIcon="number">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<div class="fdp-container">
<div class="fdp-header">
<i class="fas fa-piggy-bank fdp-header__icon"></i>
<h2 class="fdp-header__title">{{ 'labels.heading.Create Fixed Deposit Product' | translate }}</h2>
</div>
<ng-template matStepperIcon="edit">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<mat-tab-group class="fdp-tabs" animationDuration="200ms" [preserveContent]="true" [(selectedIndex)]="selectedTabIndex">
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-info-circle fdp-tab-icon"></i>
{{ 'labels.inputs.DETAILS' | translate }}
</ng-template>
<div class="fdp-tab-content">
<mifosx-fixed-deposit-product-details-step></mifosx-fixed-deposit-product-details-step>
</div>
</mat-tab>
<ng-template matStepperIcon="done">
<fa-icon icon="check" size="sm"></fa-icon>
</ng-template>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-coins fdp-tab-icon"></i>
{{ 'labels.inputs.CURRENCY' | translate }}
</ng-template>
<div class="fdp-tab-content">
<mifosx-fixed-deposit-product-currency-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
></mifosx-fixed-deposit-product-currency-step>
</div>
</mat-tab>
<ng-template matStepperIcon="error">
<fa-icon icon="exclamation-triangle" size="lg"></fa-icon>
</ng-template>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-sliders-h fdp-tab-icon"></i>
{{ 'labels.inputs.TERMS' | translate }}
</ng-template>
<div class="fdp-tab-content">
<mifosx-fixed-deposit-product-terms-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
></mifosx-fixed-deposit-product-terms-step>
</div>
</mat-tab>
<ng-template matStepperIcon="preview">
<fa-icon icon="eye" size="sm"></fa-icon>
</ng-template>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-cog fdp-tab-icon"></i>
{{ 'labels.inputs.SETTINGS' | translate }}
</ng-template>
<div class="fdp-tab-content">
<mifosx-fixed-deposit-product-settings-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
></mifosx-fixed-deposit-product-settings-step>
</div>
</mat-tab>
<mat-step [stepControl]="fixedDepositProductDetailsForm">
<ng-template matStepLabel>{{ 'labels.inputs.DETAILS' | translate }}</ng-template>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-chart-area fdp-tab-icon"></i>
{{ 'labels.inputs.INTEREST RATE CHART' | translate }}
</ng-template>
<div class="fdp-tab-content">
<mifosx-fixed-deposit-product-interest-rate-chart-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
></mifosx-fixed-deposit-product-interest-rate-chart-step>
</div>
</mat-tab>
<mifosx-fixed-deposit-product-details-step></mifosx-fixed-deposit-product-details-step>
</mat-step>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-receipt fdp-tab-icon"></i>
{{ 'labels.inputs.CHARGES' | translate }}
</ng-template>
<div class="fdp-tab-content">
<mifosx-fixed-deposit-product-charges-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
[currencyCode]="fixedDepositProductCurrencyForm.get('currencyCode')"
></mifosx-fixed-deposit-product-charges-step>
</div>
</mat-tab>
<mat-step [stepControl]="fixedDepositProductCurrencyForm">
<ng-template matStepLabel>{{ 'labels.inputs.CURRENCY' | translate }}</ng-template>
<mifosx-fixed-deposit-product-currency-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
></mifosx-fixed-deposit-product-currency-step>
</mat-step>
<mat-step [stepControl]="fixedDepositProductTermsForm">
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
<mifosx-fixed-deposit-product-terms-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
></mifosx-fixed-deposit-product-terms-step>
</mat-step>
<mat-step [stepControl]="fixedDepositProductSettingsForm">
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
<mifosx-fixed-deposit-product-settings-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
></mifosx-fixed-deposit-product-settings-step>
</mat-step>
<mat-step [stepControl]="fixedDepositProductInterestRateChartForm">
<ng-template matStepLabel>{{ 'labels.inputs.INTEREST RATE CHART' | translate }}</ng-template>
<mifosx-fixed-deposit-product-interest-rate-chart-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
></mifosx-fixed-deposit-product-interest-rate-chart-step>
</mat-step>
<mat-step>
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
<mifosx-fixed-deposit-product-charges-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
[currencyCode]="fixedDepositProductCurrencyForm.get('currencyCode')"
>
</mifosx-fixed-deposit-product-charges-step>
</mat-step>
<mat-step [stepControl]="fixedDepositProductAccountingForm">
<ng-template matStepLabel>{{ 'labels.inputs.ACCOUNTING' | translate }}</ng-template>
<mifosx-fixed-deposit-product-accounting-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
[accountingRuleData]="accountingRuleData"
[fixedDepositProductFormValid]="fixedDepositProductFormValid"
>
</mifosx-fixed-deposit-product-accounting-step>
</mat-step>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-book fdp-tab-icon"></i>
{{ 'labels.inputs.ACCOUNTING' | translate }}
</ng-template>
<div class="fdp-tab-content">
<mifosx-fixed-deposit-product-accounting-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
[accountingRuleData]="accountingRuleData"
[fixedDepositProductFormValid]="fixedDepositProductFormValid"
></mifosx-fixed-deposit-product-accounting-step>
</div>
</mat-tab>
@if (fixedDepositProductFormValid) {
<mat-step state="preview" completed>
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
<mifosx-fixed-deposit-product-preview-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
[chartSlabsDisplayedColumns]="fixedDepositProductInterestRateChartStep.chartSlabsDisplayedColumns"
[accountingRuleData]="accountingRuleData"
[fixedDepositProduct]="fixedDepositProduct"
(submitEvent)="submit()"
>
</mifosx-fixed-deposit-product-preview-step>
</mat-step>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-eye fdp-tab-icon"></i>
{{ 'labels.inputs.PREVIEW' | translate }}
</ng-template>
<div class="fdp-tab-content">
<mifosx-fixed-deposit-product-preview-step
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
[chartSlabsDisplayedColumns]="fixedDepositProductInterestRateChartStep.chartSlabsDisplayedColumns"
[accountingRuleData]="accountingRuleData"
[fixedDepositProduct]="fixedDepositProduct"
(submitEvent)="submit()"
></mifosx-fixed-deposit-product-preview-step>
</div>
</mat-tab>
}
</mat-stepper>
</mat-tab-group>
<div class="fdp-action-bar">
<button mat-raised-button [routerLink]="['../']" class="fdp-action-bar__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<div class="fdp-action-bar__right">
@if (selectedTabIndex > 0) {
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
<i class="fas fa-arrow-left" style="margin-right: 8px;"></i>
{{ 'labels.buttons.Previous' | translate }}
</button>
}
@if (selectedTabIndex < totalTabs - 1) {
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
{{ 'labels.buttons.Next' | translate }}
<i class="fas fa-arrow-right" style="margin-left: 8px;"></i>
</button>
}
@if (fixedDepositProductFormValid && selectedTabIndex === totalTabs - 1) {
<button mat-raised-button color="primary" (click)="submit()">
<i class="fas fa-check" style="margin-right: 8px;"></i>
{{ 'labels.buttons.Submit' | translate }}
</button>
}
</div>
</div>
</div>
@@ -5,3 +5,35 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
$primary: #1353a0;
.fdp-container { max-width: 960px; margin: 0 auto; padding: 24px 16px; }
.fdp-header {
display: flex; align-items: center; gap: 10px; margin-bottom: 20px;
&__icon { font-size: 20px; color: $primary; }
&__title { margin: 0; font-size: 22px; font-weight: 500; }
}
.fdp-tabs {
background: var(--md-sys-color-surface, #fff); border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
::ng-deep {
.mat-mdc-tab-header { border-bottom: 1px solid rgba(0, 0, 0, 0.08); }
.mat-mdc-tab { min-width: 100px; padding: 0 12px; height: 52px; }
.mat-mdc-tab-body-wrapper { min-height: 200px; }
.mat-mdc-tab-body { overflow: visible !important; }
.mat-mdc-tab-body-content { overflow: visible !important; }
}
}
.fdp-tab-icon { margin-right: 8px; font-size: 14px; }
.fdp-tab-content { padding: 24px 16px; }
.fdp-action-bar {
display: flex; justify-content: space-between; align-items: center;
margin-top: 20px; padding: 16px 0; border-top: 1px solid rgba(0, 0, 0, 0.08);
&__cancel { color: rgba(0, 0, 0, 0.6); }
&__right { display: flex; gap: 12px; }
}
@@ -23,9 +23,9 @@ import { FixedDepositProductAccountingStepComponent } from '../fixed-deposit-pro
import { ProductsService } from 'app/products/products.service';
import { SettingsService } from 'app/settings/settings.service';
import { Accounting } from 'app/core/utils/accounting';
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { FixedDepositProductPreviewStepComponent } from '../fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component';
import { MatTabsModule } from '@angular/material/tabs';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -34,11 +34,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./create-fixed-deposit-product.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatStepper,
MatStepperIcon,
MatTabsModule,
FaIconComponent,
MatStep,
MatStepLabel,
FixedDepositProductDetailsStepComponent,
FixedDepositProductCurrencyStepComponent,
FixedDepositProductTermsStepComponent,
@@ -73,6 +70,7 @@ export class CreateFixedDepositProductComponent {
fixedDepositProductsTemplate: any;
accountingRuleData: string[] = [];
selectedTabIndex = 0;
/**
* @param {ActivatedRoute} route Activated Route.
@@ -112,6 +110,10 @@ export class CreateFixedDepositProductComponent {
return this.fixedDepositProductAccountingStep.fixedDepositProductAccountingForm;
}
get totalTabs(): number {
return this.fixedDepositProductFormValid ? 8 : 7;
}
get fixedDepositProductFormValid() {
return (
this.fixedDepositProductDetailsForm.valid &&
@@ -7,25 +7,33 @@
-->
<form [formGroup]="fixedDepositProductAccountingForm">
<div class="layout-row-wrap gap-2px responsive-column">
<mat-radio-group
class="flex-98 layout-row gap-5percent layout-lt-md-column radio-group-spacing"
formControlName="accountingRule"
>
@for (accountingRule of accountingRuleData; track accountingRule; let i = $index) {
<mat-radio-button [value]="i + 1">
{{ 'labels.accounting.' + accountingRule | translate }}
</mat-radio-button>
}
</mat-radio-group>
<div class="fdpa-section">
<div class="fdpa-section__header">
<i class="fas fa-book"></i>
<h3>{{ 'labels.heading.Accounting' | translate }}</h3>
</div>
<div class="fdpa-grid">
<div class="fdpa-field fdpa-field--full">
<mat-radio-group class="fdpa-radio-group" formControlName="accountingRule">
@for (accountingRule of accountingRuleData; track accountingRule; let i = $index) {
<mat-radio-button [value]="i + 1">
{{ 'labels.accounting.' + accountingRule | translate }}
</mat-radio-button>
}
</mat-radio-group>
</div>
</div>
</div>
<mat-divider class="flex-98"></mat-divider>
@if (isCashOrAccrualAccounting()) {
<div class="flex-fill layout-row-wrap gap-2percent layout-xs-column">
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Assets' | translate }}</h4>
@if (isCashOrAccrualAccounting()) {
<div class="fdpa-section">
<div class="fdpa-section__header">
<i class="fas fa-landmark"></i>
<h3>{{ 'labels.heading.Assets' | translate }}</h3>
</div>
<div class="fdpa-grid">
<mifosx-gl-account-selector
class="flex-48"
class="fdpa-field"
[inputFormControl]="fixedDepositProductAccountingForm.controls.savingsReferenceAccountId"
[glAccountList]="assetAccountData"
[required]="true"
@@ -34,7 +42,7 @@
</mifosx-gl-account-selector>
@if (isAccrualAccounting()) {
<mifosx-gl-account-selector
class="flex-48"
class="fdpa-field"
[inputFormControl]="fixedDepositProductAccountingForm.controls.feesReceivableAccountId"
[glAccountList]="assetAccountData"
[required]="true"
@@ -44,7 +52,7 @@
}
@if (isAccrualAccounting()) {
<mifosx-gl-account-selector
class="flex-48"
class="fdpa-field"
[inputFormControl]="fixedDepositProductAccountingForm.controls.penaltiesReceivableAccountId"
[glAccountList]="assetAccountData"
[required]="true"
@@ -52,10 +60,17 @@
>
</mifosx-gl-account-selector>
}
<mat-divider class="flex-98"></mat-divider>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Liabilities' | translate }}</h4>
</div>
</div>
<div class="fdpa-section">
<div class="fdpa-section__header">
<i class="fas fa-balance-scale"></i>
<h3>{{ 'labels.heading.Liabilities' | translate }}</h3>
</div>
<div class="fdpa-grid">
<mifosx-gl-account-selector
class="flex-48"
class="fdpa-field"
[inputFormControl]="fixedDepositProductAccountingForm.controls.savingsControlAccountId"
[glAccountList]="liabilityAccountData"
[required]="true"
@@ -63,7 +78,7 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
class="fdpa-field"
[inputFormControl]="fixedDepositProductAccountingForm.controls.transfersInSuspenseAccountId"
[glAccountList]="liabilityAccountData"
[required]="true"
@@ -72,7 +87,7 @@
</mifosx-gl-account-selector>
@if (isAccrualAccounting()) {
<mifosx-gl-account-selector
class="flex-48"
class="fdpa-field"
[inputFormControl]="fixedDepositProductAccountingForm.controls.interestPayableAccountId"
[glAccountList]="liabilityAccountData"
[required]="true"
@@ -80,20 +95,34 @@
>
</mifosx-gl-account-selector>
}
<mat-divider class="flex-98"></mat-divider>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Expenses' | translate }}</h4>
</div>
</div>
<div class="fdpa-section">
<div class="fdpa-section__header">
<i class="fas fa-arrow-circle-down"></i>
<h3>{{ 'labels.heading.Expenses' | translate }}</h3>
</div>
<div class="fdpa-grid">
<mifosx-gl-account-selector
class="flex-48"
class="fdpa-field"
[inputFormControl]="fixedDepositProductAccountingForm.controls.interestOnSavingsAccountId"
[glAccountList]="expenseAccountData"
[required]="true"
[inputLabel]="'Interest on savings'"
>
</mifosx-gl-account-selector>
<mat-divider class="flex-98"></mat-divider>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Income' | translate }}</h4>
</div>
</div>
<div class="fdpa-section">
<div class="fdpa-section__header">
<i class="fas fa-arrow-circle-up"></i>
<h3>{{ 'labels.heading.Income' | translate }}</h3>
</div>
<div class="fdpa-grid">
<mifosx-gl-account-selector
class="flex-48"
class="fdpa-field"
[inputFormControl]="fixedDepositProductAccountingForm.controls.incomeFromFeeAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -101,186 +130,192 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
class="fdpa-field"
[inputFormControl]="fixedDepositProductAccountingForm.controls.incomeFromPenaltyAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
[inputLabel]="'Income from penalties'"
>
</mifosx-gl-account-selector>
<mat-divider class="flex-98"></mat-divider>
<h3 class="mat-h3 flex-23">{{ 'labels.heading.Advanced Accounting Rules' | translate }}</h3>
<mat-checkbox class="flex-73" formControlName="advancedAccountingRules"></mat-checkbox>
@if (fixedDepositProductAccountingForm.value.advancedAccountingRules) {
<div class="flex-fill layout-row-wrap gap-2percent responsive-column">
<div class="flex-100 layout-row-wrap gap-2percent layout-lt-md-column">
<h4 class="mat-h4 flex-63">
{{ 'labels.heading.Configure Fund Sources for Payment Channels' | translate }}
</h4>
<div class="flex-33">
<button
type="button"
mat-raised-button
color="primary"
(click)="add('PaymentFundSource', paymentChannelToFundSourceMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (paymentChannelToFundSourceMappings.value.length !== 0) {
<table
class="flex-98 mat-elevation-z1"
mat-table
[dataSource]="paymentChannelToFundSourceMappings.value"
>
<ng-container matColumnDef="paymentTypeId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.heading.Payment Type' | translate }}</th>
<td mat-cell *matCellDef="let paymentFundSource">
{{ paymentFundSource.paymentTypeId | find: paymentTypeData : 'id' : 'name' }}
</div>
</div>
<div class="fdpa-section">
<div class="fdpa-section__header">
<i class="fas fa-cogs"></i>
<h3>{{ 'labels.heading.Advanced Accounting Rules' | translate }}</h3>
</div>
<div class="fdpa-grid">
<div class="fdpa-field fdpa-field--full fdpa-checkbox-row">
<mat-checkbox formControlName="advancedAccountingRules"></mat-checkbox>
</div>
</div>
@if (fixedDepositProductAccountingForm.value.advancedAccountingRules) {
<!-- Payment Fund Source -->
<div class="fdpa-subsection">
<div class="fdpa-subsection__header">
<h4>{{ 'labels.heading.Configure Fund Sources for Payment Channels' | translate }}</h4>
<button
type="button"
mat-raised-button
color="primary"
class="fdpa-add-btn"
(click)="add('PaymentFundSource', paymentChannelToFundSourceMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (paymentChannelToFundSourceMappings.value.length !== 0) {
<div class="fdpa-table-wrapper">
<table
mat-table
[dataSource]="paymentChannelToFundSourceMappings.value"
>
<ng-container matColumnDef="paymentTypeId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.heading.Payment Type' | translate }}</th>
<td mat-cell *matCellDef="let paymentFundSource">
{{ paymentFundSource.paymentTypeId | find: paymentTypeData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="fundSourceAccountId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fund Source' | translate }}</th>
<td mat-cell *matCellDef="let paymentFundSource">
{{ paymentFundSource.fundSourceAccountId | find: assetAccountData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.buttons.Actions' | translate }}</th>
<td mat-cell *matCellDef="let paymentFundSource; let i = index">
<button
mat-icon-button
color="primary"
(click)="edit('PaymentFundSource', paymentChannelToFundSourceMappings, i)"
>
<fa-icon icon="edit"></fa-icon>
</button>
<button mat-icon-button color="warn" (click)="delete(paymentChannelToFundSourceMappings, i)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
</table>
</div>
}
</div>
@if (existCharges()) {
<!-- Fees Income -->
<div class="fdpa-subsection">
<div class="fdpa-subsection__header">
<h4>{{ 'labels.heading.Map Fees to Specific Income Accounts' | translate }}</h4>
<button
type="button"
mat-raised-button
color="primary"
class="fdpa-add-btn"
(click)="add('FeesIncome', feeToIncomeAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (feeToIncomeAccountMappings.value.length !== 0) {
<div class="fdpa-table-wrapper">
<table mat-table [dataSource]="feeToIncomeAccountMappings.value">
<ng-container matColumnDef="chargeId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.catalogs.Fees' | translate }}</th>
<td mat-cell *matCellDef="let feesIncome">
{{ feesIncome.chargeId | find: chargeData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="fundSourceAccountId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fund Source' | translate }}</th>
<td mat-cell *matCellDef="let paymentFundSource">
{{ paymentFundSource.fundSourceAccountId | find: assetAccountData : 'id' : 'name' }}
<ng-container matColumnDef="incomeAccountId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
<td mat-cell *matCellDef="let feesIncome">
{{ feesIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.buttons.Actions' | translate }}</th>
<td mat-cell *matCellDef="let paymentFundSource; let i = index">
<td mat-cell *matCellDef="let feesIncome; let i = index">
<button
mat-icon-button
color="primary"
(click)="edit('PaymentFundSource', paymentChannelToFundSourceMappings, i)"
(click)="edit('FeesIncome', feeToIncomeAccountMappings, i)"
>
<fa-icon icon="edit"></fa-icon>
</button>
<button mat-icon-button color="warn" (click)="delete(paymentChannelToFundSourceMappings, i)">
<button mat-icon-button color="warn" (click)="delete(feeToIncomeAccountMappings, i)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
</table>
}
</div>
@if (existCharges()) {
<div class="flex-100">
<h4 class="mat-h4 flex-63">{{ 'labels.heading.Map Fees to Specific Income Accounts' | translate }}</h4>
<div class="flex-33">
<button
type="button"
mat-raised-button
color="primary"
(click)="add('FeesIncome', feeToIncomeAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (feeToIncomeAccountMappings.value.length !== 0) {
<table class="flex-98 mat-elevation-z1" mat-table [dataSource]="feeToIncomeAccountMappings.value">
<ng-container matColumnDef="chargeId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.catalogs.Fees' | translate }}</th>
<td mat-cell *matCellDef="let feesIncome">
{{ feesIncome.chargeId | find: chargeData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="incomeAccountId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
<td mat-cell *matCellDef="let feesIncome">
{{ feesIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.buttons.Actions' | translate }}</th>
<td mat-cell *matCellDef="let feesIncome; let i = index">
<button
mat-icon-button
color="primary"
(click)="edit('FeesIncome', feeToIncomeAccountMappings, i)"
>
<fa-icon icon="edit"></fa-icon>
</button>
<button mat-icon-button color="warn" (click)="delete(feeToIncomeAccountMappings, i)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
</table>
}
</div>
}
@if (existCharges()) {
<div class="flex-100">
<h4 class="mat-h4 flex-63">
{{ 'labels.catalogs.Penalties' | translate }}
{{ 'labels.heading.Map Penalties to Specific Income Accounts' | translate }}
</h4>
<div class="flex-33">
<button
type="button"
mat-raised-button
color="primary"
(click)="add('PenaltyIncome', penaltyToIncomeAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (penaltyToIncomeAccountMappings.value.length !== 0) {
<table class="flex-98 mat-elevation-z1" mat-table [dataSource]="penaltyToIncomeAccountMappings.value">
<ng-container matColumnDef="chargeId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
<td mat-cell *matCellDef="let penaltyIncome">
{{ penaltyIncome.chargeId | find: penaltyData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="incomeAccountId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
<td mat-cell *matCellDef="let penaltyIncome">
{{ penaltyIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.buttons.Actions' | translate }}</th>
<td mat-cell *matCellDef="let penaltyIncome; let i = index">
<button
mat-icon-button
color="primary"
(click)="edit('PenaltyIncome', penaltyToIncomeAccountMappings, i)"
>
<fa-icon icon="edit"></fa-icon>
</button>
<button mat-icon-button color="warn" (click)="delete(penaltyToIncomeAccountMappings, i)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
</table>
}
</div>
<!-- Penalty Income -->
<div class="fdpa-subsection">
<div class="fdpa-subsection__header">
<h4>
{{ 'labels.catalogs.Penalties' | translate }}
{{ 'labels.heading.Map Penalties to Specific Income Accounts' | translate }}
</h4>
<button
type="button"
mat-raised-button
color="primary"
class="fdpa-add-btn"
(click)="add('PenaltyIncome', penaltyToIncomeAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (penaltyToIncomeAccountMappings.value.length !== 0) {
<div class="fdpa-table-wrapper">
<table mat-table [dataSource]="penaltyToIncomeAccountMappings.value">
<ng-container matColumnDef="chargeId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
<td mat-cell *matCellDef="let penaltyIncome">
{{ penaltyIncome.chargeId | find: penaltyData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="incomeAccountId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
<td mat-cell *matCellDef="let penaltyIncome">
{{ penaltyIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.buttons.Actions' | translate }}</th>
<td mat-cell *matCellDef="let penaltyIncome; let i = index">
<button
mat-icon-button
color="primary"
(click)="edit('PenaltyIncome', penaltyToIncomeAccountMappings, i)"
>
<fa-icon icon="edit"></fa-icon>
</button>
<button mat-icon-button color="warn" (click)="delete(penaltyToIncomeAccountMappings, i)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
</table>
</div>
}
</div>
}
</div>
}
</div>
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext [disabled]="!fixedDepositProductFormValid">
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
</div>
}
</div>
}
</form>
@@ -6,40 +6,106 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
.fdpa-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
}
.fdpa-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.fdpa-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
.mat-elevation-z1 {
margin: 1em 0 1.5em;
}
h3,
h4 {
font-weight: 500;
}
mat-radio-group,
h4 {
margin: 1em 0;
}
mat-divider {
margin: 1em 0 2em;
}
.margin-t {
margin-top: 1em;
}
.radio-group-spacing {
.fdpa-radio-group {
display: flex;
gap: 2rem;
flex-direction: row;
}
gap: 24px;
flex-wrap: wrap;
@media (width <= 768px) {
.radio-group-spacing {
@media (max-width: 600px) {
flex-direction: column;
}
}
.fdpa-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
.fdpa-add-btn {
height: 36px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
}
.fdpa-subsection {
padding: 0 24px 16px;
&__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
flex-wrap: wrap;
gap: 12px;
h4 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
}
.fdpa-table-wrapper {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
table {
width: 100%;
}
}
@@ -25,7 +25,6 @@ import { Accounting } from 'app/core/utils/accounting';
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base';
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
import { MatDivider } from '@angular/material/divider';
import { GlAccountSelectorComponent } from '../../../../shared/accounting/gl-account-selector/gl-account-selector.component';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatButton, MatIconButton } from '@angular/material/button';
@@ -42,7 +41,6 @@ import {
MatRowDef,
MatRow
} from '@angular/material/table';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FindPipe } from '../../../../pipes/find.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -54,7 +52,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
MatRadioGroup,
MatRadioButton,
MatDivider,
GlAccountSelectorComponent,
MatCheckbox,
FaIconComponent,
@@ -69,8 +66,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatHeaderRow,
MatRowDef,
MatRow,
MatStepperPrevious,
MatStepperNext,
FindPipe
]
})
@@ -6,80 +6,77 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
<mat-select #charge>
@for (charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value; track charge) {
<mat-option [value]="charge">
{{ charge.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="fdpch-section">
<div class="fdpch-section__header">
<i class="fas fa-money-check-alt"></i>
<h3>{{ 'labels.inputs.Charge' | translate }}</h3>
</div>
<div class="fdpch-grid">
<mat-form-field appearance="outline" class="fdpch-field">
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
<mat-select #charge>
@for (charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value; track charge) {
<mat-option [value]="charge">
{{ charge.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="flex-48 align-center">
<button type="button" mat-raised-button color="primary" (click)="addCharge(charge)" [disabled]="!charge.value">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
<div class="fdpch-field fdpch-btn-wrap">
<button type="button" mat-raised-button color="primary" class="fdpch-add-btn" (click)="addCharge(charge)" [disabled]="!charge.value">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
</div>
<table
class="flex-98 mat-elevation-z1"
mat-table
[dataSource]="chargesDataSource"
[hidden]="chargesDataSource.length === 0"
>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.name + ', ' + charge.currency.displaySymbol }}
</td>
</ng-container>
@if (chargesDataSource.length > 0) {
<div class="fdpch-table-wrapper">
<table
mat-table
[dataSource]="chargesDataSource"
>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.name + ', ' + charge.currency.displaySymbol }}
</td>
</ng-container>
<ng-container matColumnDef="chargeCalculationType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.chargeCalculationType.value }}
</td>
</ng-container>
<ng-container matColumnDef="chargeCalculationType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.chargeCalculationType.value }}
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.amount | formatNumber }}
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.amount | formatNumber }}
</td>
</ng-container>
<ng-container matColumnDef="chargeTimeType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.chargeTimeType.value }}
</td>
</ng-container>
<ng-container matColumnDef="chargeTimeType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.chargeTimeType.value }}
</td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let charge">
<button mat-icon-button color="warn" (click)="deleteCharge(charge)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let charge">
<button mat-icon-button color="warn" (click)="deleteCharge(charge)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
<div class="layout-row responsive-column align-center gap-2px margin-t">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
}
</div>
@@ -6,14 +6,76 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
.fdpch-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
}
.fdpch-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.fdpch-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
.mat-elevation-z1 {
margin: 1em 0 1.5em;
.fdpch-btn-wrap {
display: flex;
align-items: center;
}
.margin-t {
margin-top: 1em;
.fdpch-add-btn {
height: 36px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
}
.fdpch-table-wrapper {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
margin: 0 24px 16px;
table {
width: 100%;
}
}
@@ -26,7 +26,6 @@ import {
MatRowDef,
MatRow
} from '@angular/material/table';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { ChargesFilterPipe } from '../../../../pipes/charges-filter.pipe';
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -49,8 +48,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatHeaderRow,
MatRowDef,
MatRow,
MatStepperPrevious,
MatStepperNext,
ChargesFilterPipe,
FormatNumberPipe
]
@@ -7,66 +7,63 @@
-->
<form [formGroup]="fixedDepositProductCurrencyForm">
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select formControlName="currencyCode" required>
@for (currency of currencyData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
matTooltip="{{ 'tooltips.Decimal places to be used to track and report' | translate }}"
formControlName="digitsAfterDecimal"
required
/>
<mat-error *ngIf="fixedDepositProductCurrencyForm.get('digitsAfterDecimal')?.hasError('required')">
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error *ngIf="fixedDepositProductCurrencyForm.get('digitsAfterDecimal')?.hasError('min')">
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.Minimum Value must be' | translate }}
<strong>0</strong>
</mat-error>
</mat-form-field>
<mat-checkbox labelPosition="before" formControlName="setMultiples" class="flex-98 margin-t checkbox-align-center">
{{ 'labels.inputs.Set the fixed deposit installment in multiples of' | translate }}
</mat-checkbox>
@if (fixedDepositProductCurrencyForm.value.setMultiples) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Currency in multiples of' | translate }}</mat-label>
<input type="number" min="1" matInput formControlName="inMultiplesOf" />
<div class="fdpc-section">
<div class="fdpc-section__header">
<i class="fas fa-coins"></i>
<h3>{{ 'labels.inputs.Currency' | translate }}</h3>
</div>
<div class="fdpc-grid">
<mat-form-field appearance="outline" class="fdpc-field">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select formControlName="currencyCode" required>
@for (currency of currencyData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Currency in multiples of' | translate }} {{ 'labels.commons.is' | translate }}
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
}
</div>
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<mat-form-field appearance="outline" class="fdpc-field">
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
matTooltip="{{ 'tooltips.Decimal places to be used to track and report' | translate }}"
formControlName="digitsAfterDecimal"
required
/>
<mat-error *ngIf="fixedDepositProductCurrencyForm.get('digitsAfterDecimal')?.hasError('required')">
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error *ngIf="fixedDepositProductCurrencyForm.get('digitsAfterDecimal')?.hasError('min')">
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.Minimum Value must be' | translate }}
<strong>0</strong>
</mat-error>
</mat-form-field>
<div class="fdpc-field fdpc-field--full fdpc-checkbox-row">
<mat-checkbox labelPosition="before" formControlName="setMultiples">
{{ 'labels.inputs.Set the fixed deposit installment in multiples of' | translate }}
</mat-checkbox>
</div>
@if (fixedDepositProductCurrencyForm.value.setMultiples) {
<mat-form-field appearance="outline" class="fdpc-field">
<mat-label>{{ 'labels.inputs.Currency in multiples of' | translate }}</mat-label>
<input type="number" min="1" matInput formControlName="inMultiplesOf" />
<mat-error>
{{ 'labels.inputs.Currency in multiples of' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
}
</div>
</div>
</form>
@@ -6,10 +6,59 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.margin-t {
margin-top: 1em;
.fdpc-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
}
.checkbox-align-center {
.fdpc-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.fdpc-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
.fdpc-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
@@ -10,10 +10,8 @@ import { Component, OnInit, Input, inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { startWith } from 'rxjs/operators';
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatTooltip } from '@angular/material/tooltip';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -23,9 +21,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
MatStepperPrevious,
FaIconComponent,
MatStepperNext,
MatCheckbox
]
})
@@ -7,49 +7,44 @@
-->
<form [formGroup]="fixedDepositProductDetailsForm">
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
<input matInput formControlName="name" required />
@if (fixedDepositProductDetailsForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="fdpd-section">
<div class="fdpd-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.inputs.Product Name' | translate }} &amp; {{ 'labels.inputs.Description' | translate }}</h3>
</div>
<div class="fdpd-grid">
<mat-form-field appearance="outline" class="fdpd-field">
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
<input matInput formControlName="name" required />
@if (fixedDepositProductDetailsForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Short Name' | translate }}</mat-label>
<input matInput formControlName="shortName" maxlength="4" required />
@if (fixedDepositProductDetailsForm.controls.shortName.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Short Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="fdpd-field">
<mat-label>{{ 'labels.inputs.Short Name' | translate }}</mat-label>
<input matInput formControlName="shortName" maxlength="4" required />
@if (fixedDepositProductDetailsForm.controls.shortName.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Short Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-98">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea matInput formControlName="description" required></textarea>
@if (fixedDepositProductDetailsForm.controls.description.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Description' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious disabled>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<mat-form-field appearance="outline" class="fdpd-field fdpd-field--full">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea matInput formControlName="description" required></textarea>
@if (fixedDepositProductDetailsForm.controls.description.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Description' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</div>
</form>
@@ -6,6 +6,53 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.margin-t {
margin-top: 1em;
.fdpd-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
}
.fdpd-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.fdpd-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
@@ -8,8 +8,6 @@
import { Component, OnInit, Input, inject } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -17,10 +15,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './fixed-deposit-product-details-step.component.html',
styleUrls: ['./fixed-deposit-product-details-step.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatStepperPrevious,
FaIconComponent,
MatStepperNext
...STANDALONE_SHARED_IMPORTS
]
})
export class FixedDepositProductDetailsStepComponent implements OnInit {
@@ -7,298 +7,298 @@
-->
<form [formGroup]="fixedDepositProductInterestRateChartForm">
<div class="layout-row-wrap gap-2px responsive-column">
<h4 class="mat-h3 flex-83">
{{ 'labels.inputs.Interest Rate Chart' | translate }}
<i class="fas fa-question" matTooltip="{{ 'tooltips.The interest rate chart' | translate }}"></i>
</h4>
<div class="flex-13 layout-row align-start-center">
<button type="button" mat-raised-button color="primary" (click)="addChart()">
<div class="fdpi-section">
<div class="fdpi-section__header">
<i class="fas fa-chart-line"></i>
<h3>{{ 'labels.inputs.Interest Rate Chart' | translate }}</h3>
<span class="fdpi-spacer"></span>
<button type="button" mat-raised-button color="primary" class="fdpi-add-btn" (click)="addChart()">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@for (chart of charts.controls; track chart; let chartIndex = $index) {
<div class="flex-fill layout-row-wrap responsive-column" formArrayName="charts">
<mat-divider class="flex-98"></mat-divider>
<div class="flex-fill layout-row-wrap responsive-column" [formGroupName]="chartIndex">
<div class="flex-100" align="end">
<button
type="button"
mat-icon-button
color="warn"
(click)="delete(charts, chartIndex)"
matTooltip="{{ 'tooltips.Delete Interest Rate Chart' | translate }}"
matTooltipPosition="left"
>
<fa-icon icon="trash"></fa-icon>
</button>
</div>
<mat-form-field class="flex-28 m-r-5">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput formControlName="name" required />
@if (chart.get('name')?.touched && chart.get('name')?.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-70">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea matInput formControlName="description" required></textarea>
@if (chart.get('description')?.touched && chart.get('description')?.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Description' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48 m-r-5" (click)="validFromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="validFromDatePicker"
formControlName="fromDate"
required
/>
<mat-datepicker-toggle matSuffix [for]="validFromDatePicker"></mat-datepicker-toggle>
<mat-datepicker #validFromDatePicker></mat-datepicker>
<mat-error>
{{ 'labels.inputs.Valid from Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48 m-r-5" (click)="endDatePicker.open()">
<mat-label>{{ 'labels.inputs.End Date' | translate }}</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="endDatePicker" formControlName="endDate" />
<mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
<mat-datepicker #endDatePicker></mat-datepicker>
</mat-form-field>
<div class="flex-100 layout-row align-between align-items-center">
<mat-checkbox labelPosition="before" formControlName="isPrimaryGroupingByAmount">
{{ 'labels.inputs.Is primary grouping by amount?' | translate }}
</mat-checkbox>
</div>
<div class="center flex-100">
<button type="button" mat-raised-button color="primary" (click)="addChartSlab(chart.controls.chartSlabs)">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add Slab' | translate }}
</button>
</div>
@if (chart.value.chartSlabs.length === 0) {
<div class="flex-100">
<h3 class="mat-h3">{{ 'labels.heading.It is required to add at least one Slab' | translate }}</h3>
<div class="fdpi-body">
@for (chart of charts.controls; track chart; let chartIndex = $index) {
<div class="fdpi-chart-card" formArrayName="charts">
<div class="fdpi-chart-card__top" [formGroupName]="chartIndex">
<div class="fdpi-chart-card__actions">
<button
type="button"
mat-icon-button
color="warn"
(click)="delete(charts, chartIndex)"
matTooltip="{{ 'tooltips.Delete Interest Rate Chart' | translate }}"
matTooltipPosition="left"
>
<fa-icon icon="trash"></fa-icon>
</button>
</div>
}
@if (chart.value.chartSlabs.length !== 0) {
<table
class="flex-98 mat-elevation-z1"
mat-table
[dataSource]="chart.value.chartSlabs"
multiTemplateDataRows
>
<ng-container matColumnDef="period">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab">
{{
chartSlab.fromPeriod +
' - ' +
chartSlab.toPeriod +
' ' +
(chartSlab.periodType | find: periodTypeData : 'id' : 'value')
}}
</td>
</ng-container>
<ng-container matColumnDef="amountRange">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab">
{{ chartSlab.amountRangeFrom + ' - ' + chartSlab.amountRangeTo }}
</td>
</ng-container>
<ng-container matColumnDef="annualInterestRate">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate }} %</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab">
{{ chartSlab.description }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
<button
mat-icon-button
color="primary"
(click)="editChartSlab(chart.controls.chartSlabs, chartSlabIndex)"
>
<fa-icon icon="edit"></fa-icon>
</button>
<button mat-icon-button color="warn" (click)="delete(chart.controls.chartSlabs, chartSlabIndex)">
<fa-icon icon="trash"></fa-icon>
</button>
<button
mat-button
color="primary"
(click)="
expandChartSlabIndex[chartIndex] =
expandChartSlabIndex[chartIndex] === chartSlabIndex ? null : chartSlabIndex
"
>
@if (expandChartSlabIndex[chartIndex] !== chartSlabIndex) {
<span>
<fa-icon icon="eye" class="m-r-10"></fa-icon>
{{ 'labels.buttons.View Incentives' | translate }}
</span>
}
@if (expandChartSlabIndex[chartIndex] === chartSlabIndex) {
<span>
<fa-icon icon="eye-slash" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Hide Incentives' | translate }}
</span>
}
</button>
</td>
</ng-container>
<ng-container matColumnDef="incentives">
<td
mat-cell
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
[attr.colspan]="chartSlabsDisplayedColumns[chartIndex].length"
>
<div
class="layout-row-wrap flex-fill incentives"
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex[chartIndex] ? 'expanded' : 'collapsed'"
>
<mat-card class="layout-row-wrap flex-fill">
<h4 class="mat-h4 flex-63">
{{ 'labels.heading.Incentives' | translate }}
</h4>
<div class="flex-13">
<button
mat-raised-button
color="primary"
(click)="addIncentive(getIncentives(chart.controls.chartSlabs, chartSlabIndex))"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (chartSlab.incentives.length) {
<table class="mat-elevation-z1 flex-fill" mat-table [dataSource]="chartSlab.incentives">
<ng-container matColumnDef="entityType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.entityType | find: entityTypeData : 'id' : 'value' }}
</td>
</ng-container>
<ng-container matColumnDef="attributeName">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.attributeName | find: attributeNameData : 'id' : 'value' }}
</td>
</ng-container>
<ng-container matColumnDef="conditionType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.conditionType | find: conditionTypeData : 'id' : 'value' }}
</td>
</ng-container>
<ng-container matColumnDef="attributeValue">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
@switch (incentive.attributeName) {
@case (2) {
<span>{{ incentive.attributeValue | find: genderData : 'id' : 'name' }}</span>
}
@case (3) {
<span>{{ incentive.attributeValue }}</span>
}
@case (4) {
<span>{{ incentive.attributeValue | find: clientTypeData : 'id' : 'name' }}</span>
}
@case (5) {
<span>{{
incentive.attributeValue | find: clientClassificationData : 'id' : 'name'
}}</span>
}
}
</td>
</ng-container>
<ng-container matColumnDef="incentiveType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.incentiveType | find: incentiveTypeData : 'id' : 'value' }}
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.amount }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let incentive; let incentiveIndex = index">
<button
mat-icon-button
color="primary"
(click)="
editIncentive(
getIncentives(chart.controls.chartSlabs, chartSlabIndex),
incentiveIndex
)
"
>
<fa-icon icon="edit"></fa-icon>
</button>
<button
mat-icon-button
color="warn"
(click)="
delete(getIncentives(chart.controls.chartSlabs, chartSlabIndex), incentiveIndex)
"
>
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
</table>
}
</mat-card>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns[chartIndex]"></tr>
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns[chartIndex]"></tr>
<tr
mat-row
*matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns"
class="incentives-row"
></tr>
</table>
}
</div>
</div>
}
</div>
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<div class="fdpi-grid">
<mat-form-field appearance="outline" class="fdpi-field">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput formControlName="name" required />
@if (chart.get('name')?.touched && chart.get('name')?.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="fdpi-field fdpi-field--full">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea matInput formControlName="description" required></textarea>
@if (chart.get('description')?.touched && chart.get('description')?.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Description' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="fdpi-field" (click)="validFromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="validFromDatePicker"
formControlName="fromDate"
required
/>
<mat-datepicker-toggle matSuffix [for]="validFromDatePicker"></mat-datepicker-toggle>
<mat-datepicker #validFromDatePicker></mat-datepicker>
<mat-error>
{{ 'labels.inputs.Valid from Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="fdpi-field" (click)="endDatePicker.open()">
<mat-label>{{ 'labels.inputs.End Date' | translate }}</mat-label>
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="endDatePicker" formControlName="endDate" />
<mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
<mat-datepicker #endDatePicker></mat-datepicker>
</mat-form-field>
<div class="fdpi-field fdpi-field--full fdpi-checkbox-row">
<mat-checkbox labelPosition="before" formControlName="isPrimaryGroupingByAmount">
{{ 'labels.inputs.Is primary grouping by amount?' | translate }}
</mat-checkbox>
</div>
</div>
<div class="fdpi-slab-actions">
<button type="button" mat-raised-button color="primary" class="fdpi-add-btn" (click)="addChartSlab(chart.controls.chartSlabs)">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add Slab' | translate }}
</button>
</div>
@if (chart.value.chartSlabs.length === 0) {
<div class="fdpi-empty-message">
<h3>{{ 'labels.heading.It is required to add at least one Slab' | translate }}</h3>
</div>
}
@if (chart.value.chartSlabs.length !== 0) {
<div class="fdpi-table-wrapper">
<table
mat-table
[dataSource]="chart.value.chartSlabs"
multiTemplateDataRows
>
<ng-container matColumnDef="period">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab">
{{
chartSlab.fromPeriod +
' - ' +
chartSlab.toPeriod +
' ' +
(chartSlab.periodType | find: periodTypeData : 'id' : 'value')
}}
</td>
</ng-container>
<ng-container matColumnDef="amountRange">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab">
{{ chartSlab.amountRangeFrom + ' - ' + chartSlab.amountRangeTo }}
</td>
</ng-container>
<ng-container matColumnDef="annualInterestRate">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate }} %</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab">
{{ chartSlab.description }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
<button
mat-icon-button
color="primary"
(click)="editChartSlab(chart.controls.chartSlabs, chartSlabIndex)"
>
<fa-icon icon="edit"></fa-icon>
</button>
<button mat-icon-button color="warn" (click)="delete(chart.controls.chartSlabs, chartSlabIndex)">
<fa-icon icon="trash"></fa-icon>
</button>
<button
mat-button
color="primary"
(click)="
expandChartSlabIndex[chartIndex] =
expandChartSlabIndex[chartIndex] === chartSlabIndex ? null : chartSlabIndex
"
>
@if (expandChartSlabIndex[chartIndex] !== chartSlabIndex) {
<span>
<fa-icon icon="eye" class="m-r-10"></fa-icon>
{{ 'labels.buttons.View Incentives' | translate }}
</span>
}
@if (expandChartSlabIndex[chartIndex] === chartSlabIndex) {
<span>
<fa-icon icon="eye-slash" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Hide Incentives' | translate }}
</span>
}
</button>
</td>
</ng-container>
<ng-container matColumnDef="incentives">
<td
mat-cell
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
[attr.colspan]="chartSlabsDisplayedColumns[chartIndex].length"
>
<div
class="fdpi-incentives-wrap"
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex[chartIndex] ? 'expanded' : 'collapsed'"
>
<mat-card class="fdpi-incentive-card">
<h4>{{ 'labels.heading.Incentives' | translate }}</h4>
<div class="fdpi-incentive-actions">
<button
mat-raised-button
color="primary"
class="fdpi-add-btn"
(click)="addIncentive(getIncentives(chart.controls.chartSlabs, chartSlabIndex))"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (chartSlab.incentives.length) {
<div class="fdpi-table-wrapper">
<table mat-table [dataSource]="chartSlab.incentives">
<ng-container matColumnDef="entityType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.entityType | find: entityTypeData : 'id' : 'value' }}
</td>
</ng-container>
<ng-container matColumnDef="attributeName">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.attributeName | find: attributeNameData : 'id' : 'value' }}
</td>
</ng-container>
<ng-container matColumnDef="conditionType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.conditionType | find: conditionTypeData : 'id' : 'value' }}
</td>
</ng-container>
<ng-container matColumnDef="attributeValue">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
@switch (incentive.attributeName) {
@case (2) {
<span>{{ incentive.attributeValue | find: genderData : 'id' : 'name' }}</span>
}
@case (3) {
<span>{{ incentive.attributeValue }}</span>
}
@case (4) {
<span>{{ incentive.attributeValue | find: clientTypeData : 'id' : 'name' }}</span>
}
@case (5) {
<span>{{
incentive.attributeValue | find: clientClassificationData : 'id' : 'name'
}}</span>
}
}
</td>
</ng-container>
<ng-container matColumnDef="incentiveType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.incentiveType | find: incentiveTypeData : 'id' : 'value' }}
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
<td mat-cell *matCellDef="let incentive">
{{ incentive.amount }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let incentive; let incentiveIndex = index">
<button
mat-icon-button
color="primary"
(click)="
editIncentive(
getIncentives(chart.controls.chartSlabs, chartSlabIndex),
incentiveIndex
)
"
>
<fa-icon icon="edit"></fa-icon>
</button>
<button
mat-icon-button
color="warn"
(click)="
delete(getIncentives(chart.controls.chartSlabs, chartSlabIndex), incentiveIndex)
"
>
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
</table>
</div>
}
</mat-card>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns[chartIndex]"></tr>
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns[chartIndex]"></tr>
<tr
mat-row
*matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns"
class="incentives-row"
></tr>
</table>
</div>
}
</div>
</div>
}
</div>
</div>
</form>
@@ -6,48 +6,146 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
h3,
h4 {
font-weight: 500;
margin: 1em 0;
.fdpi-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
}
table {
.fdpi-spacer {
flex: 1;
}
.fdpi-body {
padding: 16px 24px;
}
.fdpi-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.fdpi-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
.fdpi-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
.fdpi-add-btn {
height: 36px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
}
.fdpi-chart-card {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
margin-bottom: 16px;
&__top {
padding: 16px;
}
&__actions {
display: flex;
justify-content: flex-end;
}
}
.fdpi-slab-actions {
display: flex;
justify-content: center;
padding: 8px 0;
}
.fdpi-empty-message {
text-align: center;
padding: 16px;
h3 {
margin: 0;
font-size: 14px;
font-weight: 500;
color: #5f6368;
}
}
.fdpi-table-wrapper {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
margin-top: 12px;
table {
width: 100%;
}
tr.incentives-row {
height: 0;
> td {
padding: 0;
}
.incentives {
overflow: hidden;
}
.mat-card {
border-radius: 0;
> h4 {
margin: 0.5em 0 0;
}
}
}
}
mat-divider {
margin: 1em 0;
.fdpi-incentives-wrap {
overflow: hidden;
}
.mat-elevation-z1 {
margin: 1em 0 1.5em;
.fdpi-incentive-card {
border-radius: 0;
padding: 16px;
h4 {
margin: 0 0 12px;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
.margin-t {
margin-top: 1em;
}
.margin-v {
margin: 1em 0;
.fdpi-incentive-actions {
margin-bottom: 12px;
}
@@ -36,7 +36,6 @@ import { MatTooltip } from '@angular/material/tooltip';
import { MatButton, MatIconButton } from '@angular/material/button';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatDivider } from '@angular/material/divider';
import { MatCheckbox } from '@angular/material/checkbox';
import {
MatTable,
@@ -50,7 +49,6 @@ import {
MatRowDef,
MatRow
} from '@angular/material/table';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FindPipe } from '../../../../pipes/find.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -69,7 +67,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
FaIconComponent,
MatDivider,
MatIconButton,
MatCheckbox,
MatTable,
@@ -82,8 +79,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatHeaderRow,
MatRowDef,
MatRow,
MatStepperPrevious,
MatStepperNext,
FindPipe
]
})
@@ -6,18 +6,106 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use 'assets/styles/helper';
@use 'assets/styles/colours' as *;
h2,
h3,
h4 {
margin: 0;
font-weight: 500;
.fdpp-container {
display: flex;
flex-direction: column;
gap: 16px;
}
table {
width: 100%;
.fdpp-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: #202124;
}
.fdpp-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
}
.fdpp-section-body {
padding: 16px 24px;
}
.fdpp-preview-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.fdpp-row {
display: flex;
flex-direction: column;
padding: 6px 0;
&--full {
grid-column: 1 / -1;
}
}
.fdpp-label {
font-size: 12px;
font-weight: 500;
color: #5f6368;
margin-bottom: 2px;
}
.fdpp-value {
font-size: 14px;
font-weight: 500;
color: #202124;
}
.fdpp-chart-block {
padding-bottom: 16px;
border-bottom: 1px solid #e2e8f0;
margin-bottom: 16px;
&:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
}
.fdpp-table-wrapper {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
margin-top: 8px;
table {
width: 100%;
}
tr.incentives-row {
height: 0;
@@ -25,40 +113,39 @@ table {
> td {
padding: 0;
}
.incentives {
overflow: hidden;
}
.mat-card {
border-radius: 0;
> h4 {
font-weight: 500;
margin: 0.5em 0 0;
}
}
}
}
.mat-h3 {
margin-top: 10px !important;
font-weight: 300;
color: $status-approved;
.fdpp-incentives-wrap {
overflow: hidden;
}
.mat-elevation-z1 {
margin: 1em 0 1.5em;
.fdpp-incentive-card {
border-radius: 0;
padding: 16px;
h4 {
margin: 0 0 12px;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
span {
margin: 0.5em 0;
.fdpp-subsection-title {
margin: 16px 0 8px;
font-size: 14px;
font-weight: 600;
color: #202124;
&:first-child {
margin-top: 0;
}
}
mat-divider {
margin: 0 0 0.5em;
}
.margin-t {
margin-top: 1em;
.fdpp-actions {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
}
@@ -10,7 +10,6 @@ import { Component, OnInit, Input, Output, EventEmitter, SimpleChanges, OnChange
import { trigger, state, transition, animate, style } from '@angular/animations';
import { Accounting } from 'app/core/utils/accounting';
import { OptionData } from 'app/shared/models/option-data.model';
import { MatDivider } from '@angular/material/divider';
import {
MatTable,
@@ -26,7 +25,6 @@ import {
} from '@angular/material/table';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { ViewSavingsAccountingDetailsComponent } from '../../../../shared/accounting/view-savings-accounting-details/view-savings-accounting-details.component';
import { MatStepperPrevious } from '@angular/material/stepper';
import { FindPipe } from '../../../../pipes/find.pipe';
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
@@ -46,7 +44,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatDivider,
MatTable,
MatColumnDef,
MatHeaderCellDef,
@@ -59,7 +56,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatRowDef,
MatRow,
ViewSavingsAccountingDetailsComponent,
MatStepperPrevious,
FindPipe,
DateFormatPipe,
FormatNumberPipe,
@@ -7,172 +7,201 @@
-->
<form [formGroup]="fixedDepositProductSettingsForm">
<div class="layout-row-wrap gap-2px responsive-column">
<h4 class="mat-h4 flex-98">
{{ 'labels.inputs.Lock-in Period' | translate }}
<i
class="fas fa-question"
matTooltip="{{ 'tooltips.A period of time during which a fixed deposit account' | translate }}"
></i>
</h4>
<div class="fdps-section">
<div class="fdps-section__header">
<i class="fas fa-lock"></i>
<h3>{{ 'labels.inputs.Lock-in Period' | translate }}</h3>
</div>
<div class="fdps-grid">
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="lockinPeriodFrequency"
matTooltip="{{ 'tooltips.A period of time during which a fixed deposit account' | translate }}"
/>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input type="number" min="0" matInput formControlName="lockinPeriodFrequency" />
</mat-form-field>
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="lockinPeriodFrequencyType">
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
<mat-option [value]="lockinPeriodFrequencyType.id">
{{ lockinPeriodFrequencyType.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="lockinPeriodFrequencyType">
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
<mat-option [value]="lockinPeriodFrequencyType.id">
{{ lockinPeriodFrequencyType.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="fdps-section">
<div class="fdps-section__header">
<i class="fas fa-calendar-check"></i>
<h3>{{ 'labels.inputs.Minimum Deposit Term' | translate }}</h3>
</div>
<div class="fdps-grid">
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="minDepositTerm"
required
matTooltip="{{ 'tooltips.The minimum length of time' | translate }}"
/>
<mat-error>
{{ 'labels.inputs.Minimum Deposit Term Frequency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<h4 class="mat-h4 flex-98">
{{ 'labels.inputs.Minimum Deposit Term' | translate }}
<i class="fas fa-question" matTooltip="{{ 'tooltips.The minimum length of time' | translate }}"></i>
</h4>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input type="number" min="0" matInput formControlName="minDepositTerm" required />
<mat-error>
{{ 'labels.inputs.Minimum Deposit Term Frequency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency Type' | translate }}</mat-label>
<mat-select formControlName="minDepositTermTypeId" required>
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Minimum Deposit Term Frequency Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<h4 class="mat-h4 flex-98">
{{ 'labels.heading.And thereafter, in Multiples of' | translate }}
<i
class="fas fa-question-circle"
matTooltip="{{ 'tooltips.After the minimum deposit term has passed' | translate }}"
></i>
</h4>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input type="number" min="0" matInput formControlName="inMultiplesOfDepositTerm" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="inMultiplesOfDepositTermTypeId">
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
<h4 class="mat-h4 flex-98">
{{ 'labels.inputs.Maximum Deposit Term' | translate }}
<i class="fas fa-question-circle" matTooltip="{{ 'tooltips.The maximum length of time funds' | translate }}"></i>
</h4>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input type="number" min="0" matInput formControlName="maxDepositTerm" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="maxDepositTermTypeId">
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-divider class="flex-98"></mat-divider>
<h4 class="mat-h4 flex-23">
{{ 'labels.heading.For Pre-mature closure' | translate }}
<i
class="fas fa-question-circle"
matTooltip="{{ 'tooltips.This sections defines an interest penalty' | translate }}"
></i>
</h4>
<mat-checkbox class="flex-73 margin-v" labelPosition="before" formControlName="preClosurePenalApplicable">
{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}
</mat-checkbox>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Penal Interest' | translate }} (%)</mat-label>
<input type="number" min="0" matInput formControlName="preClosurePenalInterest" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Period' | translate }}</mat-label>
<mat-select formControlName="preClosurePenalInterestOnTypeId">
@for (preClosurePenalInterestOnType of preClosurePenalInterestOnTypeData; track preClosurePenalInterestOnType) {
<mat-option [value]="preClosurePenalInterestOnType.id">
{{ preClosurePenalInterestOnType.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-divider class="flex-98"></mat-divider>
<mat-checkbox
class="flex-48 margin-v"
labelPosition="before"
formControlName="withHoldTax"
matTooltip="{{ 'tooltips.An boolean flag to attach' | translate }}"
>
{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}
</mat-checkbox>
@if (fixedDepositProductSettingsForm.value.withHoldTax) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
<mat-select formControlName="taxGroupId" required>
@for (taxGroup of taxGroupData; track taxGroup) {
<mat-option [value]="taxGroup.id">
{{ taxGroup.name }}
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Frequency Type' | translate }}</mat-label>
<mat-select formControlName="minDepositTermTypeId" required>
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Tax Group' | translate }} {{ 'labels.commons.is' | translate }}
{{ 'labels.inputs.Minimum Deposit Term Frequency Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
}
</div>
</div>
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<div class="fdps-section">
<div class="fdps-section__header">
<i class="fas fa-layer-group"></i>
<h3>{{ 'labels.heading.And thereafter, in Multiples of' | translate }}</h3>
</div>
<div class="fdps-grid">
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="inMultiplesOfDepositTerm"
matTooltip="{{ 'tooltips.After the minimum deposit term has passed' | translate }}"
/>
</mat-form-field>
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="inMultiplesOfDepositTermTypeId">
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
<div class="fdps-section">
<div class="fdps-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>{{ 'labels.inputs.Maximum Deposit Term' | translate }}</h3>
</div>
<div class="fdps-grid">
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="maxDepositTerm"
matTooltip="{{ 'tooltips.The maximum length of time funds' | translate }}"
/>
</mat-form-field>
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
<mat-select formControlName="maxDepositTermTypeId">
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
<mat-option [value]="periodFrequencyType.id">
{{ periodFrequencyType.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
<div class="fdps-section">
<div class="fdps-section__header">
<i class="fas fa-exclamation-triangle"></i>
<h3>{{ 'labels.heading.For Pre-mature closure' | translate }}</h3>
</div>
<div class="fdps-grid">
<div class="fdps-field fdps-field--full fdps-checkbox-row">
<mat-checkbox
labelPosition="before"
formControlName="preClosurePenalApplicable"
matTooltip="{{ 'tooltips.This sections defines an interest penalty' | translate }}"
>
{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}
</mat-checkbox>
</div>
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Penal Interest' | translate }} (%)</mat-label>
<input type="number" min="0" matInput formControlName="preClosurePenalInterest" />
</mat-form-field>
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Period' | translate }}</mat-label>
<mat-select formControlName="preClosurePenalInterestOnTypeId">
@for (preClosurePenalInterestOnType of preClosurePenalInterestOnTypeData; track preClosurePenalInterestOnType) {
<mat-option [value]="preClosurePenalInterestOnType.id">
{{ preClosurePenalInterestOnType.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
<div class="fdps-section">
<div class="fdps-section__header">
<i class="fas fa-file-invoice-dollar"></i>
<h3>{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}</h3>
</div>
<div class="fdps-grid">
<div class="fdps-field fdps-field--full fdps-checkbox-row">
<mat-checkbox
labelPosition="before"
formControlName="withHoldTax"
matTooltip="{{ 'tooltips.An boolean flag to attach' | translate }}"
>
{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}
</mat-checkbox>
</div>
@if (fixedDepositProductSettingsForm.value.withHoldTax) {
<mat-form-field appearance="outline" class="fdps-field">
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
<mat-select formControlName="taxGroupId" required>
@for (taxGroup of taxGroupData; track taxGroup) {
<mat-option [value]="taxGroup.id">
{{ taxGroup.name }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Tax Group' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
}
</div>
</div>
</form>
@@ -6,23 +6,59 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
h4 {
font-weight: 500;
margin: 1em 0;
.fdps-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
}
mat-divider {
margin: 1em 0 2em;
.fdps-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.margin-v {
margin: 1em 0;
.fdps-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
.margin-b {
margin: 0 0 1em;
}
.margin-t {
margin-top: 1em;
.fdps-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
@@ -15,10 +15,7 @@ import {
ReactiveFormsModule
} from '@angular/forms';
import { MatTooltip } from '@angular/material/tooltip';
import { MatDivider } from '@angular/material/divider';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -28,11 +25,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
MatDivider,
MatCheckbox,
MatStepperPrevious,
FaIconComponent,
MatStepperNext
MatCheckbox
]
})
export class FixedDepositProductSettingsStepComponent implements OnInit {
@@ -7,138 +7,136 @@
-->
<form [formGroup]="fixedDepositProductTermsForm">
<div class="layout-row-wrap gap-2px responsive-column">
<h4 class="mat-h4 flex-98">{{ 'labels.inputs.Deposit Amount' | translate }}</h4>
<div class="fdpt-section">
<div class="fdpt-section__header">
<i class="fas fa-wallet"></i>
<h3>{{ 'labels.inputs.Deposit Amount' | translate }}</h3>
</div>
<div class="fdpt-grid">
<mat-form-field appearance="outline" class="fdpt-field">
<mat-label>{{ 'labels.inputs.Minimum' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="minDepositAmount"
matTooltip="{{ 'tooltips.The minimum deposit amount required to open a fixed deposit' | translate }}"
/>
</mat-form-field>
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Minimum' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="minDepositAmount"
matTooltip="{{ 'tooltips.The minimum deposit amount required to open a fixed deposit' | translate }}"
/>
</mat-form-field>
<mat-form-field appearance="outline" class="fdpt-field">
<mat-label>{{ 'labels.inputs.Default' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="depositAmount"
matTooltip="{{ 'tooltips.The default deposit amount expected' | translate }}"
required
/>
<mat-error>
{{ 'labels.inputs.Default' | translate }} {{ 'labels.inputs.Deposit Amount' | translate }}
{{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Default' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="depositAmount"
matTooltip="{{ 'tooltips.The default deposit amount expected' | translate }}"
required
/>
<mat-error>
{{ 'labels.inputs.Default' | translate }} {{ 'labels.inputs.Deposit Amount' | translate }}
{{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Maximum' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="maxDepositAmount"
matTooltip="{{ 'tooltips.The maximum deposit amount allowed when a fixed deposit' | translate }}"
/>
</mat-form-field>
<mat-divider class="flex-98"></mat-divider>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Interest Compounding Period' | translate }}</mat-label>
<mat-select
formControlName="interestCompoundingPeriodType"
matTooltip="{{ 'tooltips.The period at which interest rate' | translate }}"
required
>
@for (interestCompoundingPeriodType of interestCompoundingPeriodTypeData; track interestCompoundingPeriodType) {
<mat-option [value]="interestCompoundingPeriodType.id">
{{ interestCompoundingPeriodType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Compounding Period' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
<mat-select
formControlName="interestPostingPeriodType"
matTooltip="{{ 'tooltips.The period at which interest rate is posted or credited' | translate }}"
required
>
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
<mat-option [value]="interestPostingPeriodType.id">
{{ interestPostingPeriodType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Interest Calculated using' | translate }}</mat-label>
<mat-select
formControlName="interestCalculationType"
matTooltip="{{ 'tooltips.The method used to calculate interest' | translate }}"
required
>
@for (interestCalculationType of interestCalculationTypeData; track interestCalculationType) {
<mat-option [value]="interestCalculationType.id">
{{ interestCalculationType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Calculated using' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Days in Year' | translate }}</mat-label>
<mat-select
formControlName="interestCalculationDaysInYearType"
matTooltip="{{ 'tooltips.The setting for number of days' | translate }}"
required
>
@for (
interestCalculationDaysInYearType of interestCalculationDaysInYearTypeData;
track interestCalculationDaysInYearType
) {
<mat-option [value]="interestCalculationDaysInYearType.id">
{{ interestCalculationDaysInYearType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Days in Year' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="fdpt-field">
<mat-label>{{ 'labels.inputs.Maximum' | translate }}</mat-label>
<input
type="number"
min="0"
matInput
formControlName="maxDepositAmount"
matTooltip="{{ 'tooltips.The maximum deposit amount allowed when a fixed deposit' | translate }}"
/>
</mat-form-field>
</div>
</div>
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<div class="fdpt-section">
<div class="fdpt-section__header">
<i class="fas fa-percentage"></i>
<h3>{{ 'labels.inputs.Interest Compounding Period' | translate }}</h3>
</div>
<div class="fdpt-grid">
<mat-form-field appearance="outline" class="fdpt-field">
<mat-label>{{ 'labels.inputs.Interest Compounding Period' | translate }}</mat-label>
<mat-select
formControlName="interestCompoundingPeriodType"
matTooltip="{{ 'tooltips.The period at which interest rate' | translate }}"
required
>
@for (interestCompoundingPeriodType of interestCompoundingPeriodTypeData; track interestCompoundingPeriodType) {
<mat-option [value]="interestCompoundingPeriodType.id">
{{ interestCompoundingPeriodType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Compounding Period' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="fdpt-field">
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
<mat-select
formControlName="interestPostingPeriodType"
matTooltip="{{ 'tooltips.The period at which interest rate is posted or credited' | translate }}"
required
>
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
<mat-option [value]="interestPostingPeriodType.id">
{{ interestPostingPeriodType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="fdpt-field">
<mat-label>{{ 'labels.inputs.Interest Calculated using' | translate }}</mat-label>
<mat-select
formControlName="interestCalculationType"
matTooltip="{{ 'tooltips.The method used to calculate interest' | translate }}"
required
>
@for (interestCalculationType of interestCalculationTypeData; track interestCalculationType) {
<mat-option [value]="interestCalculationType.id">
{{ interestCalculationType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Interest Calculated using' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="fdpt-field">
<mat-label>{{ 'labels.inputs.Days in Year' | translate }}</mat-label>
<mat-select
formControlName="interestCalculationDaysInYearType"
matTooltip="{{ 'tooltips.The setting for number of days' | translate }}"
required
>
@for (
interestCalculationDaysInYearType of interestCalculationDaysInYearTypeData;
track interestCalculationDaysInYearType
) {
<mat-option [value]="interestCalculationDaysInYearType.id">
{{ interestCalculationDaysInYearType.value }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Days in Year' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
</div>
</div>
</form>
@@ -6,15 +6,53 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
h4 {
font-weight: 500;
margin: 1em 0;
.fdpt-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #202124;
}
}
}
mat-divider {
margin: 1em 0 2em;
.fdpt-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.margin-t {
margin-top: 1em;
.fdpt-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
@@ -9,9 +9,6 @@
import { Component, OnInit, Input, inject } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { MatTooltip } from '@angular/material/tooltip';
import { MatDivider } from '@angular/material/divider';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -20,11 +17,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./fixed-deposit-product-terms-step.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
MatDivider,
MatStepperPrevious,
FaIconComponent,
MatStepperNext
MatTooltip
]
})
export class FixedDepositProductTermsStepComponent implements OnInit {
@@ -22,8 +22,9 @@
<div class="container">
<div #filter class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-form-field appearance="outline" class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<i matPrefix class="fas fa-search"></i>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
</div>
@@ -6,173 +6,171 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="floatingRateForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap responsive-column gap-4percent">
<mat-form-field class="flex-30">
<mat-label>{{ 'labels.inputs.Floating Rate Name' | translate }}</mat-label>
<input
matInput
required
matTooltip="{{ 'tooltips.Unique name for the floating rate scheme' | translate }}"
formControlName="name"
/>
@if (floatingRateForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Floating Rate Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="cfr-container">
<div class="cfr-page-header">
<i class="fas fa-percentage cfr-page-header__icon"></i>
<h2 class="cfr-page-header__title">{{ 'labels.buttons.Create Floating Rate' | translate }}</h2>
</div>
<div class="checkbox-wrapper flex-30">
<mat-checkbox
formControlName="isBaseLendingRate"
matTooltip="{{ 'tooltips.Base lending rate for the organisation' | translate }}"
class="checkbox"
labelPosition="before"
>{{ 'labels.inputs.Is Base Lending Rate' | translate }}?</mat-checkbox
>
</div>
<form [formGroup]="floatingRateForm" (ngSubmit)="submit()">
<!-- Rate Details Section -->
<div class="cfr-section">
<div class="cfr-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.heading.Rate Details' | translate }}</h3>
</div>
<div class="cfr-section__body">
<mat-form-field appearance="outline" class="cfr-field-full">
<mat-label>{{ 'labels.inputs.Floating Rate Name' | translate }}</mat-label>
<input
matInput
required
matTooltip="{{ 'tooltips.Unique name for the floating rate scheme' | translate }}"
formControlName="name"
/>
@if (floatingRateForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Floating Rate Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="checkbox-wrapper flex-30">
<mat-checkbox
formControlName="isActive"
matTooltip="{{ 'tooltips.Floating rate scheme to be activated' | translate }}"
class="checkbox"
labelPosition="before"
>{{ 'labels.inputs.Active' | translate }}</mat-checkbox
>
</div>
<div class="cfr-checkbox-row">
<mat-checkbox
formControlName="isBaseLendingRate"
matTooltip="{{ 'tooltips.Base lending rate for the organisation' | translate }}"
>
{{ 'labels.inputs.Is Base Lending Rate' | translate }}?
</mat-checkbox>
<mat-checkbox
formControlName="isActive"
matTooltip="{{ 'tooltips.Floating rate scheme to be activated' | translate }}"
>
{{ 'labels.inputs.Active' | translate }}
</mat-checkbox>
</div>
</div>
</div>
<br />
<!-- Floating Rate Periods Section -->
<div class="cfr-section">
<div class="cfr-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>{{ 'labels.inputs.Floating Rate Periods' | translate }}</h3>
<button mat-flat-button type="button" color="primary" class="cfr-add-btn" (click)="addFloatingRatePeriod()">
<fa-icon icon="plus"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
<div class="cfr-section__body cfr-section__body--no-padding">
<div class="cfr-table-wrapper">
<table mat-table [dataSource]="dataSource" matSort class="cfr-table">
<ng-container matColumnDef="fromDate">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Interest effective from date' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.From Date' | translate }}
</th>
<td mat-cell *matCellDef="let floatingRatePeriod">{{ floatingRatePeriod.fromDate | dateFormat }}</td>
</ng-container>
<mat-divider [inset]="true"></mat-divider>
<ng-container matColumnDef="interestRate">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Annualised Interest rate' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.Interest Rate' | translate }}
</th>
<td mat-cell *matCellDef="let floatingRatePeriod">{{ floatingRatePeriod.interestRate | formatNumber }}</td>
</ng-container>
<br />
<ng-container matColumnDef="isDifferential">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Interest rate mentioned is added to base lending rate' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.Is Differential' | translate }}?
</th>
<td mat-cell *matCellDef="let floatingRate">
@if (floatingRate.isDifferentialToBaseLendingRate) {
<fa-icon
icon="check-circle"
size="lg"
class="cfr-icon-yes"
matTooltip="{{ 'tooltips.Yes' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
@if (!floatingRate.isDifferentialToBaseLendingRate) {
<fa-icon
icon="times-circle"
size="lg"
class="cfr-icon-no"
matTooltip="{{ 'tooltips.No' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
</td>
</ng-container>
<div class="layout-row-wrap gap-70percent m-b-10 layout-lt-md-column">
<p class="mat-title flex-25">
{{ 'labels.inputs.Floating Rate Periods' | translate }}
<i
class="fas fa-question"
matTooltip="{{ 'tooltips.Floating interest rate and start date' | translate }}"
></i>
</p>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let floatingRatePeriod">
<button
type="button"
color="primary"
mat-icon-button
matTooltip="{{ 'tooltips.Edit Period' | translate }}"
matTooltipPosition="left"
(click)="editFloatingRatePeriod(floatingRatePeriod)"
>
<fa-icon icon="edit" size="lg"></fa-icon>
</button>
<button
type="button"
color="warn"
mat-icon-button
matTooltip="{{ 'tooltips.Remove Period' | translate }}"
matTooltipPosition="right"
(click)="deleteFloatingRatePeriod(floatingRatePeriod)"
>
<fa-icon icon="trash" size="lg"></fa-icon>
</button>
</td>
</ng-container>
<button mat-mini-fab type="button" color="primary" (click)="addFloatingRatePeriod()">
<fa-icon icon="plus"></fa-icon>
</button>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="fromDate">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Interest effective from date' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.From Date' | translate }}
</th>
<td mat-cell *matCellDef="let floatingRatePeriod">{{ floatingRatePeriod.fromDate | dateFormat }}</td>
</ng-container>
<ng-container matColumnDef="interestRate">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Annualised Interest rate' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.Interest Rate' | translate }}
</th>
<td mat-cell *matCellDef="let floatingRatePeriod">{{ floatingRatePeriod.interestRate | formatNumber }}</td>
</ng-container>
<ng-container matColumnDef="isDifferential">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Interest rate mentioned is added to base lending rate' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.Is Differential' | translate }}?
</th>
<td mat-cell *matCellDef="let floatingRate">
@if (floatingRate.isDifferentialToBaseLendingRate) {
<fa-icon
icon="check-circle"
size="lg"
class="is-differential"
matTooltip="{{ 'tooltips.Yes' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
@if (!floatingRate.isDifferentialToBaseLendingRate) {
<fa-icon
icon="times-circle"
size="lg"
class="not-is-differential"
matTooltip="{{ 'tooltips.No' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let floatingRatePeriod" class="gap-15percent">
<button
type="button"
class="flex-8"
color="primary"
mat-icon-button
matTooltip="{{ 'tooltips.Edit Period' | translate }}"
matTooltipPosition="left"
(click)="editFloatingRatePeriod(floatingRatePeriod)"
>
<fa-icon icon="edit" size="lg"></fa-icon>
</button>
<button
type="button"
class="flex-8"
color="warn"
mat-icon-button
matTooltip="{{ 'tooltips.Remove Period' | translate }}"
matTooltipPosition="right"
(click)="deleteFloatingRatePeriod(floatingRatePeriod)"
>
<fa-icon icon="trash" size="lg"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</mat-card-content>
</div>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
color="primary"
[disabled]="!floatingRateForm.valid"
*mifosxHasPermission="'CREATE_FLOATINGRATE'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<!-- Action Bar -->
<div class="cfr-action-bar">
<button type="button" mat-stroked-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-flat-button
type="submit"
color="primary"
[disabled]="!floatingRateForm.valid"
*mifosxHasPermission="'CREATE_FLOATINGRATE'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</form>
</div>
@@ -6,25 +6,181 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@media (width >=992px) {
.checkbox-wrapper {
margin-top: 8px;
.cfr-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
.checkbox {
padding: 0 0 17.5px;
.cfr-page-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 24px;
&__icon {
font-size: 20px;
color: #1353a0;
}
&__title {
font-size: 20px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
.cfr-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
margin-bottom: 20px;
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
.cfr-add-btn {
margin-left: auto;
font-size: 12px;
height: 32px;
padding: 0 14px;
border-radius: 6px;
fa-icon {
margin-right: 4px;
}
}
}
&__body {
padding: 20px 24px;
&--no-padding {
padding: 0;
}
}
}
// TODO: CONFIGURE THESE CLASSES GLOBALLY
.is-differential {
.cfr-field-full {
width: 100%;
max-width: 480px;
}
.cfr-checkbox-row {
display: flex;
align-items: center;
gap: 24px;
flex-wrap: wrap;
margin-top: 4px;
}
.cfr-table-wrapper {
border-bottom: 1px solid #e2e8f0;
overflow: auto;
}
.cfr-table {
width: 100%;
::ng-deep {
.mat-mdc-header-row {
background: #f8fafc;
min-height: 38px;
height: 38px;
}
.mat-mdc-header-cell {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #64748b;
border-bottom: 1px solid #e2e8f0;
padding: 0 14px;
}
.mat-mdc-row {
min-height: 40px;
height: 40px;
border-bottom: 1px solid #f1f5f9;
&:last-child {
border-bottom: none;
}
}
.mat-mdc-cell {
font-size: 13px;
color: #202124;
padding: 0 14px;
}
}
}
.cfr-icon-yes {
color: #32cd32;
}
.not-is-differential {
.cfr-icon-no {
color: #f44366;
}
table {
width: 100%;
.cfr-action-bar {
display: flex;
align-items: center;
gap: 10px;
justify-content: flex-end;
padding-top: 8px;
}
@media (max-width: 600px) {
.cfr-container {
padding: 16px 10px;
}
.cfr-field-full {
max-width: 100%;
}
.cfr-checkbox-row {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.cfr-section__header {
padding: 10px 16px;
flex-wrap: wrap;
}
.cfr-section__body {
padding: 16px;
}
.cfr-action-bar {
flex-direction: column;
button {
width: 100%;
}
}
}
@@ -38,7 +38,6 @@ import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.co
import { FloatingRatePeriodDialogComponent } from '../floating-rate-period-dialog/floating-rate-period-dialog.component';
import { MatTooltip } from '@angular/material/tooltip';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatDivider } from '@angular/material/divider';
import { MatMiniFabButton, MatIconButton, MatButton } from '@angular/material/button';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
@@ -56,7 +55,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
MatCheckbox,
MatDivider,
MatMiniFabButton,
FaIconComponent,
MatTable,
@@ -6,165 +6,170 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="floatingRateForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap responsive-column gap-15percent">
<mat-form-field class="flex-25">
<mat-label>{{ 'labels.inputs.Floating Rate Name' | translate }}</mat-label>
<input
matInput
required
matTooltip="{{ 'tooltips.Unique name for the floating rate scheme' | translate }}"
formControlName="name"
/>
@if (floatingRateForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Floating Rate Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="efr-container">
<div class="efr-page-header">
<i class="fas fa-percentage efr-page-header__icon"></i>
<h2 class="efr-page-header__title">{{ 'labels.buttons.Edit Floating Rate' | translate }}</h2>
</div>
<div class="checkbox-wrapper flex-25">
<mat-checkbox
formControlName="isBaseLendingRate"
matTooltip="{{ 'tooltips.Base lending rate for the organisation' | translate }}"
class="checkbox"
labelPosition="before"
>
{{ 'labels.inputs.Is Base Lending Rate' | translate }}?
</mat-checkbox>
</div>
<form [formGroup]="floatingRateForm" (ngSubmit)="submit()">
<!-- Rate Details Section -->
<div class="efr-section">
<div class="efr-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.heading.Rate Details' | translate }}</h3>
</div>
<div class="efr-section__body">
<mat-form-field appearance="outline" class="efr-field-full">
<mat-label>{{ 'labels.inputs.Floating Rate Name' | translate }}</mat-label>
<input
matInput
required
matTooltip="{{ 'tooltips.Unique name for the floating rate scheme' | translate }}"
formControlName="name"
/>
@if (floatingRateForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Floating Rate Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="checkbox-wrapper flex-15">
<mat-checkbox
formControlName="isActive"
matTooltip="{{ 'tooltips.Floating rate scheme to be activated' | translate }}"
class="checkbox"
labelPosition="before"
>
{{ 'labels.inputs.Active' | translate }}
</mat-checkbox>
</div>
<div class="efr-checkbox-row">
<mat-checkbox
formControlName="isBaseLendingRate"
matTooltip="{{ 'tooltips.Base lending rate for the organisation' | translate }}"
>
{{ 'labels.inputs.Is Base Lending Rate' | translate }}?
</mat-checkbox>
<mat-checkbox
formControlName="isActive"
matTooltip="{{ 'tooltips.Floating rate scheme to be activated' | translate }}"
>
{{ 'labels.inputs.Active' | translate }}
</mat-checkbox>
</div>
</div>
</div>
<br />
<mat-divider [inset]="true"></mat-divider>
<br />
<div class="layout-row-wrap gap-70percent m-b-10 layout-lt-md-column">
<p class="mat-title flex-25">
{{ 'labels.inputs.Floating Rate Periods' | translate }}
<i
class="fas fa-question"
matTooltip="{{
'tooltips.Floating interest rate and start date for this floating rate scheme' | translate
}}"
></i>
</p>
<button mat-mini-fab type="button" color="primary" (click)="addFloatingRatePeriod()">
<fa-icon icon="plus"></fa-icon>
</button>
</div>
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="fromDate">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Interest effective from date' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.From Date' | translate }}
</th>
<td mat-cell *matCellDef="let floatingRatePeriod">{{ floatingRatePeriod.fromDate | dateFormat }}</td>
</ng-container>
<ng-container matColumnDef="interestRate">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Annualised Interest rate' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.Interest Rate' | translate }}
</th>
<td mat-cell *matCellDef="let floatingRatePeriod">{{ floatingRatePeriod.interestRate }}</td>
</ng-container>
<ng-container matColumnDef="isDifferential">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Interest rate mentioned is added to base lending rate' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.Is Differential' | translate }}?
</th>
<td mat-cell *matCellDef="let floatingRate">
@if (floatingRate.isDifferentialToBaseLendingRate) {
<fa-icon
icon="check-circle"
size="lg"
class="is-differential"
matTooltip="{{ 'tooltips.Yes' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
@if (!floatingRate.isDifferentialToBaseLendingRate) {
<fa-icon
icon="times-circle"
size="lg"
class="not-is-differential"
matTooltip="{{ 'tooltips.No' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let floatingRatePeriod">
<button
type="button"
color="primary"
mat-icon-button
(click)="editFloatingRatePeriod(floatingRatePeriod)"
<!-- Floating Rate Periods Section -->
<div class="efr-section">
<div class="efr-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>{{ 'labels.inputs.Floating Rate Periods' | translate }}</h3>
<button mat-flat-button type="button" color="primary" class="efr-add-btn" (click)="addFloatingRatePeriod()">
<fa-icon icon="plus"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
<div class="efr-section__body efr-section__body--no-padding">
<div class="efr-table-wrapper">
<table mat-table [dataSource]="dataSource" matSort class="efr-table">
<ng-container matColumnDef="fromDate">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Interest effective from date' | translate }}"
mat-sort-header
>
<fa-icon icon="edit" size="lg"></fa-icon>
</button>
<button type="button" color="warn" mat-icon-button (click)="deleteFloatingRatePeriod(floatingRatePeriod)">
<fa-icon icon="trash" size="lg"></fa-icon>
</button>
</td>
</ng-container>
{{ 'labels.inputs.From Date' | translate }}
</th>
<td mat-cell *matCellDef="let floatingRatePeriod">{{ floatingRatePeriod.fromDate | dateFormat }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<ng-container matColumnDef="interestRate">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Annualised Interest rate' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.Interest Rate' | translate }}
</th>
<td mat-cell *matCellDef="let floatingRatePeriod">{{ floatingRatePeriod.interestRate }}</td>
</ng-container>
<ng-container matColumnDef="isDifferential">
<th
mat-header-cell
*matHeaderCellDef
matTooltip="{{ 'tooltips.Interest rate mentioned is added to base lending rate' | translate }}"
mat-sort-header
>
{{ 'labels.inputs.Is Differential' | translate }}?
</th>
<td mat-cell *matCellDef="let floatingRate">
@if (floatingRate.isDifferentialToBaseLendingRate) {
<fa-icon
icon="check-circle"
size="lg"
class="efr-icon-yes"
matTooltip="{{ 'tooltips.Yes' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
@if (!floatingRate.isDifferentialToBaseLendingRate) {
<fa-icon
icon="times-circle"
size="lg"
class="efr-icon-no"
matTooltip="{{ 'tooltips.No' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let floatingRatePeriod">
<button
type="button"
color="primary"
mat-icon-button
matTooltip="{{ 'tooltips.Edit Period' | translate }}"
matTooltipPosition="left"
(click)="editFloatingRatePeriod(floatingRatePeriod)"
>
<fa-icon icon="edit" size="lg"></fa-icon>
</button>
<button
type="button"
color="warn"
mat-icon-button
matTooltip="{{ 'tooltips.Remove Period' | translate }}"
matTooltipPosition="right"
(click)="deleteFloatingRatePeriod(floatingRatePeriod)"
>
<fa-icon icon="trash" size="lg"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</mat-card-content>
</div>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
color="primary"
[disabled]="!floatingRateForm.valid || (floatingRateForm.pristine && isFloatingRateFormPristine)"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<!-- Action Bar -->
<div class="efr-action-bar">
<button type="button" mat-stroked-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-flat-button
type="submit"
color="primary"
[disabled]="!floatingRateForm.valid || (floatingRateForm.pristine && isFloatingRateFormPristine)"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</form>
</div>
@@ -6,27 +6,181 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@media (width >= 992px) {
.checkbox-wrapper {
position: relative;
.efr-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
.checkbox {
padding: 0 0 17.5px;
position: absolute;
bottom: 0;
.efr-page-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 24px;
&__icon {
font-size: 20px;
color: #1353a0;
}
&__title {
font-size: 20px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
.efr-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
margin-bottom: 20px;
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
.efr-add-btn {
margin-left: auto;
font-size: 12px;
height: 32px;
padding: 0 14px;
border-radius: 6px;
fa-icon {
margin-right: 4px;
}
}
}
&__body {
padding: 20px 24px;
&--no-padding {
padding: 0;
}
}
}
// TODO: CONFIGURE THESE CLASSES GLOBALLY
.is-differential {
.efr-field-full {
width: 100%;
max-width: 480px;
}
.efr-checkbox-row {
display: flex;
align-items: center;
gap: 24px;
flex-wrap: wrap;
margin-top: 4px;
}
.efr-table-wrapper {
border-bottom: 1px solid #e2e8f0;
overflow: auto;
}
.efr-table {
width: 100%;
::ng-deep {
.mat-mdc-header-row {
background: #f8fafc;
min-height: 38px;
height: 38px;
}
.mat-mdc-header-cell {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #64748b;
border-bottom: 1px solid #e2e8f0;
padding: 0 14px;
}
.mat-mdc-row {
min-height: 40px;
height: 40px;
border-bottom: 1px solid #f1f5f9;
&:last-child {
border-bottom: none;
}
}
.mat-mdc-cell {
font-size: 13px;
color: #202124;
padding: 0 14px;
}
}
}
.efr-icon-yes {
color: #32cd32;
}
.not-is-differential {
.efr-icon-no {
color: #f44366;
}
table {
width: 100%;
.efr-action-bar {
display: flex;
align-items: center;
gap: 10px;
justify-content: flex-end;
padding-top: 8px;
}
@media (max-width: 600px) {
.efr-container {
padding: 16px 10px;
}
.efr-field-full {
max-width: 100%;
}
.efr-checkbox-row {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.efr-section__header {
padding: 10px 16px;
flex-wrap: wrap;
}
.efr-section__body {
padding: 16px;
}
.efr-action-bar {
flex-direction: column;
button {
width: 100%;
}
}
}
@@ -38,7 +38,6 @@ import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.co
import { FloatingRatePeriodDialogComponent } from '../floating-rate-period-dialog/floating-rate-period-dialog.component';
import { MatTooltip } from '@angular/material/tooltip';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatDivider } from '@angular/material/divider';
import { MatMiniFabButton, MatIconButton, MatButton } from '@angular/material/button';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
@@ -55,7 +54,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
MatCheckbox,
MatDivider,
MatMiniFabButton,
FaIconComponent,
MatTable,
@@ -6,12 +6,17 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<h1 mat-dialog-title>{{ 'labels.inputs.Floating Rate Periods' | translate }}</h1>
<div class="frpd-dialog">
<div class="frpd-header">
<div class="frpd-header__icon-wrap">
<i class="fas fa-calendar-alt"></i>
</div>
<h2 mat-dialog-title class="frpd-header__title">{{ 'labels.inputs.Floating Rate Periods' | translate }}</h2>
</div>
<mat-dialog-content>
<form [formGroup]="floatingRatePeriodForm">
<div class="layout-column">
<mat-form-field (click)="fromDatePicker.open()">
<mat-dialog-content>
<form [formGroup]="floatingRatePeriodForm" class="frpd-form">
<mat-form-field appearance="outline" class="frpd-field" (click)="fromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
<input required matInput formControlName="fromDate" [min]="minDate" [matDatepicker]="fromDatePicker" />
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
@@ -24,9 +29,10 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="frpd-field">
<mat-label>{{ 'labels.inputs.Interest Rate' | translate }}</mat-label>
<input matInput type="number" required formControlName="interestRate" min="0" />
<span matSuffix class="frpd-suffix">%</span>
@if (floatingRatePeriodForm.controls.interestRate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Interest Rate' | translate }} {{ 'labels.commons.is' | translate }}
@@ -35,21 +41,26 @@
}
</mat-form-field>
<mat-checkbox class="is-differential" formControlName="isDifferentialToBaseLendingRate" labelPosition="after">
{{ 'labels.inputs.Is Differential' | translate }}?
</mat-checkbox>
</div>
</form>
</mat-dialog-content>
<div class="frpd-checkbox-row">
<mat-checkbox formControlName="isDifferentialToBaseLendingRate" labelPosition="after">
{{ 'labels.inputs.Is Differential' | translate }}?
</mat-checkbox>
</div>
</form>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
<button
mat-raised-button
color="primary"
[disabled]="!floatingRatePeriodForm.valid || floatingRatePeriodForm.pristine"
(click)="submit()"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-dialog-actions>
<mat-dialog-actions align="end" class="frpd-actions">
<button mat-stroked-button mat-dialog-close class="frpd-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-flat-button
color="primary"
class="frpd-actions__submit"
[disabled]="!floatingRatePeriodForm.valid || floatingRatePeriodForm.pristine"
(click)="submit()"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-dialog-actions>
</div>
@@ -5,3 +5,71 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.frpd-dialog {
min-width: 380px;
}
.frpd-header {
display: flex;
align-items: center;
gap: 12px;
padding: 20px 24px 12px;
&__icon-wrap {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
background: #f0f9ff;
color: #0ea5e9;
font-size: 15px;
}
&__title {
margin: 0 !important;
padding: 0 !important;
font-size: 17px;
font-weight: 600;
color: #202124;
}
}
.frpd-form {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px 0;
}
.frpd-field {
width: 100%;
}
.frpd-suffix {
font-size: 13px;
font-weight: 500;
color: #5f6368;
padding-right: 4px;
}
.frpd-checkbox-row {
padding: 4px 0 8px;
}
.frpd-actions {
padding: 12px 24px 20px !important;
gap: 10px;
&__cancel {
border-radius: 8px;
font-weight: 500;
}
&__submit {
border-radius: 8px;
font-weight: 600;
}
}
@@ -6,23 +6,34 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_FLOATINGRATE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Floating Rate' | translate }}
</button>
</div>
<div class="container">
<div class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-percentage title-icon"></i>
{{ 'labels.inputs.Floating Rates' | translate }}
</h2>
<span class="table-count">{{ dataSource?.filteredData?.length || 0 }}</span>
</div>
<div class="table-actions">
<mat-form-field appearance="outline" class="table-search">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_FLOATINGRATE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Floating Rate' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let floatingRate">{{ floatingRate.name }}</td>
@@ -84,7 +95,7 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="select-row" [routerLink]="[row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row" [routerLink]="[row.id]"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
@@ -6,15 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
width: 100%;
@use '../../shared/styles/list-layout' as *;
.select-row:hover {
cursor: pointer;
}
}
// TODO: CONFIGURE THESE CLASSES GLOBALLY
.is-base-lending-rate-or-active {
color: #32cd32;
}
@@ -6,46 +6,49 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button mat-raised-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_FLOATINGRATE'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</div>
<div class="vfr-container">
<div class="vfr-top-bar">
<div class="vfr-top-bar__title-group">
<i class="fas fa-percentage vfr-top-bar__icon"></i>
<h2 class="vfr-top-bar__title">{{ floatingRateData.name }}</h2>
</div>
<button mat-flat-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_FLOATINGRATE'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</div>
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap layout-lt-md-column">
<div class="mat-body-strong flex-18">{{ 'labels.inputs.Floating Rate Name' | translate }}:</div>
<div class="flex-18">
{{ floatingRateData.name }}
</div>
<div class="mat-body-strong flex-20">{{ 'labels.inputs.Is Base Lending Rate' | translate }}:</div>
<div class="flex-10">
{{ floatingRateData.isBaseLendingRate | yesNo }}
</div>
<div class="mat-body-strong flex-14">{{ 'labels.inputs.Is Active' | translate }}:</div>
<div class="flex-10">
{{ floatingRateData.isActive | yesNo }}
</div>
<!-- Rate Details Section -->
<div class="vfr-section">
<div class="vfr-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.heading.Rate Details' | translate }}</h3>
</div>
<div class="vfr-section__body">
<div class="vfr-detail-row">
<span class="vfr-detail-row__label">{{ 'labels.inputs.Floating Rate Name' | translate }}</span>
<span class="vfr-detail-row__value">{{ floatingRateData.name }}</span>
</div>
<div class="vfr-detail-row">
<span class="vfr-detail-row__label">{{ 'labels.inputs.Is Base Lending Rate' | translate }}</span>
<span class="vfr-detail-row__value">{{ floatingRateData.isBaseLendingRate | yesNo }}</span>
</div>
<div class="vfr-detail-row">
<span class="vfr-detail-row__label">{{ 'labels.inputs.Is Active' | translate }}</span>
<span class="vfr-detail-row__value">{{ floatingRateData.isActive | yesNo }}</span>
</div>
</div>
</div>
<br />
<mat-divider [inset]="true"></mat-divider>
<br />
<p class="mat-title">{{ 'labels.inputs.Floating Rate Periods' | translate }}</p>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<!-- Floating Rate Periods Section -->
<div class="vfr-section">
<div class="vfr-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>{{ 'labels.inputs.Floating Rate Periods' | translate }}</h3>
</div>
<div class="vfr-section__body vfr-section__body--no-padding">
<div class="vfr-table-wrapper">
<table mat-table [dataSource]="dataSource" matSort class="vfr-table">
<ng-container matColumnDef="fromDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.From Date' | translate }}</th>
<td mat-cell *matCellDef="let floatingRate">{{ floatingRate.fromDate | dateFormat }}</td>
@@ -65,7 +68,7 @@
<fa-icon
icon="check-circle"
size="lg"
class="is-differential"
class="vfr-icon-yes"
matTooltip="{{ 'tooltips.Yes' | translate }}"
matTooltipPosition="right"
></fa-icon>
@@ -74,7 +77,7 @@
<fa-icon
icon="times-circle"
size="lg"
class="not-is-differential"
class="vfr-icon-no"
matTooltip="{{ 'tooltips.No' | translate }}"
matTooltipPosition="right"
></fa-icon>
@@ -85,9 +88,9 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</mat-card-content>
</mat-card>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
</div>
@@ -6,22 +6,173 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
width: 100%;
.vfr-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
// TODO: CONFIGURE THESE CLASSES GLOBALLY
.is-differential {
.vfr-top-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
flex-wrap: wrap;
gap: 12px;
&__title-group {
display: flex;
align-items: center;
gap: 10px;
}
&__icon {
font-size: 20px;
color: #1353a0;
}
&__title {
font-size: 20px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
.vfr-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
margin-bottom: 20px;
&__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
i {
font-size: 14px;
color: #0ea5e9;
}
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
&__body {
padding: 16px 24px;
&--no-padding {
padding: 0;
}
}
}
.vfr-detail-row {
display: flex;
padding: 6px 0;
border-bottom: 1px solid #f1f3f4;
&:last-child {
border-bottom: none;
}
&__label {
flex: 0 0 40%;
font-size: 13px;
font-weight: 500;
color: #5f6368;
}
&__value {
flex: 1;
font-size: 13px;
color: #202124;
}
}
.vfr-table-wrapper {
border-bottom: 1px solid #e2e8f0;
overflow: auto;
}
.vfr-table {
width: 100%;
::ng-deep {
.mat-mdc-header-row {
background: #f8fafc;
min-height: 38px;
height: 38px;
}
.mat-mdc-header-cell {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #64748b;
border-bottom: 1px solid #e2e8f0;
padding: 0 14px;
}
.mat-mdc-row {
min-height: 40px;
height: 40px;
border-bottom: 1px solid #f1f5f9;
&:last-child {
border-bottom: none;
}
}
.mat-mdc-cell {
font-size: 13px;
color: #202124;
padding: 0 14px;
}
}
}
.vfr-icon-yes {
color: #32cd32;
}
.not-is-differential {
.vfr-icon-no {
color: #f44366;
}
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
.m-r-10 {
margin-right: 10px;
}
@media (max-width: 600px) {
.vfr-container {
padding: 16px 10px;
}
.vfr-detail-row {
flex-direction: column;
gap: 2px;
&__label {
flex: none;
}
}
.vfr-section__header {
padding: 10px 16px;
}
.vfr-section__body {
padding: 12px 16px;
}
}
@@ -25,7 +25,6 @@ import {
MatRow
} from '@angular/material/table';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatDivider } from '@angular/material/divider';
import { DecimalPipe } from '@angular/common';
import { MatTooltip } from '@angular/material/tooltip';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
@@ -42,7 +41,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatDivider,
MatTable,
MatSort,
MatColumnDef,
@@ -6,149 +6,186 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-stepper
orientation="vertical"
[linear]="false"
class="mat-elevation-z8"
labelPosition="bottom"
#loanProductStepper
>
<ng-template matStepperIcon="number">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<div class="lp-container">
<div class="lp-header">
<i class="fas fa-hand-holding-usd lp-header__icon"></i>
<h2 class="lp-header__title">{{ 'labels.heading.Create Loan Product' | translate }}</h2>
</div>
<ng-template matStepperIcon="edit">
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="done">
<fa-icon icon="check" size="sm"></fa-icon>
</ng-template>
<ng-template matStepperIcon="error">
<fa-icon icon="exclamation-triangle" size="lg"></fa-icon>
</ng-template>
<ng-template matStepperIcon="preview">
<fa-icon icon="eye" size="sm"></fa-icon>
</ng-template>
<mat-step [stepControl]="loanProductDetailsForm">
<ng-template matStepLabel>{{ 'labels.inputs.DETAILS' | translate }}</ng-template>
<mifosx-loan-product-details-step
[loanProductsTemplate]="loanProductsTemplate"
></mifosx-loan-product-details-step>
</mat-step>
<mat-step [stepControl]="loanProductCurrencyForm">
<ng-template matStepLabel>{{ 'labels.inputs.CURRENCY' | translate }}</ng-template>
<mifosx-loan-product-currency-step
[loanProductsTemplate]="loanProductsTemplate"
></mifosx-loan-product-currency-step>
</mat-step>
<mat-step [stepControl]="loanProductSettingsForm">
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
<mifosx-loan-product-settings-step
[loanProductsTemplate]="loanProductsTemplate"
[isLinkedToFloatingInterestRates]="loanProductTermsForm.get('isLinkedToFloatingInterestRates')"
(advancePaymentStrategy)="advancePaymentStrategy($event)"
>
</mifosx-loan-product-settings-step>
</mat-step>
@if (isAdvancedPaymentStrategy) {
<mat-step [stepControl]="loanProductInterestRefundForm">
<ng-template matStepLabel>{{ 'labels.inputs.INTEREST REFUND' | translate }}</ng-template>
<mifosx-loan-product-interest-refund-step
<mat-tab-group class="lp-tabs" animationDuration="200ms" [preserveContent]="true" [(selectedIndex)]="selectedTabIndex">
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-info-circle lp-tab-icon"></i>
{{ 'labels.inputs.DETAILS' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-details-step
[loanProductsTemplate]="loanProductsTemplate"
(supportedInterestRefundTypes)="setSupportedInterestRefundTypes($event)"
>
</mifosx-loan-product-interest-refund-step>
<mifosx-stepper-buttons></mifosx-stepper-buttons>
</mat-step>
}
></mifosx-loan-product-details-step>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-coins lp-tab-icon"></i>
{{ 'labels.inputs.CURRENCY' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-currency-step
[loanProductsTemplate]="loanProductsTemplate"
></mifosx-loan-product-currency-step>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-cog lp-tab-icon"></i>
{{ 'labels.inputs.SETTINGS' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-settings-step
[loanProductsTemplate]="loanProductsTemplate"
[isLinkedToFloatingInterestRates]="loanProductTermsForm.get('isLinkedToFloatingInterestRates')"
(advancePaymentStrategy)="advancePaymentStrategy($event)"
></mifosx-loan-product-settings-step>
</div>
</mat-tab>
@if (isAdvancedPaymentStrategy) {
<mat-step>
<ng-template matStepLabel>{{ 'labels.inputs.PAYMENT ALLOCATION' | translate }}</ng-template>
<mifosx-loan-product-payment-strategy-step
[advancedPaymentAllocations]="advancedPaymentAllocations"
[advancedCreditAllocations]="advancedCreditAllocations"
[advancedPaymentAllocationTransactionTypes]="loanProductsTemplate.advancedPaymentAllocationTransactionTypes"
[paymentAllocationOrderDefault]="loanProductsTemplate.advancedPaymentAllocationTypes"
[advancedCreditAllocationTransactionTypes]="loanProductsTemplate.creditAllocationTransactionTypes"
[creditAllocationOrderDefault]="loanProductsTemplate.creditAllocationAllocationTypes"
(setPaymentAllocation)="setPaymentAllocation($event)"
(setCreditAllocation)="setCreditAllocation($event)"
>
</mifosx-loan-product-payment-strategy-step>
<mifosx-stepper-buttons></mifosx-stepper-buttons>
</mat-step>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-undo lp-tab-icon"></i>
{{ 'labels.inputs.INTEREST REFUND' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-interest-refund-step
[loanProductsTemplate]="loanProductsTemplate"
(supportedInterestRefundTypes)="setSupportedInterestRefundTypes($event)"
></mifosx-loan-product-interest-refund-step>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-exchange-alt lp-tab-icon"></i>
{{ 'labels.inputs.PAYMENT ALLOCATION' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-payment-strategy-step
[advancedPaymentAllocations]="advancedPaymentAllocations"
[advancedCreditAllocations]="advancedCreditAllocations"
[advancedPaymentAllocationTransactionTypes]="loanProductsTemplate.advancedPaymentAllocationTransactionTypes"
[paymentAllocationOrderDefault]="loanProductsTemplate.advancedPaymentAllocationTypes"
[advancedCreditAllocationTransactionTypes]="loanProductsTemplate.creditAllocationTransactionTypes"
[creditAllocationOrderDefault]="loanProductsTemplate.creditAllocationAllocationTypes"
(setPaymentAllocation)="setPaymentAllocation($event)"
(setCreditAllocation)="setCreditAllocation($event)"
></mifosx-loan-product-payment-strategy-step>
</div>
</mat-tab>
}
<mat-step [stepControl]="loanProductTermsForm">
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-sliders-h lp-tab-icon"></i>
{{ 'labels.inputs.TERMS' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-terms-step [loanProductsTemplate]="loanProductsTemplate"></mifosx-loan-product-terms-step>
</div>
</mat-tab>
<mifosx-loan-product-terms-step [loanProductsTemplate]="loanProductsTemplate"></mifosx-loan-product-terms-step>
</mat-step>
<mat-step>
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
<mifosx-loan-product-charges-step
[loanProductsTemplate]="loanProductsTemplate"
[currencyCode]="loanProductCurrencyForm.get('currencyCode')"
[multiDisburseLoan]="loanProductSettingsForm.get('multiDisburseLoan')"
>
</mifosx-loan-product-charges-step>
</mat-step>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-receipt lp-tab-icon"></i>
{{ 'labels.inputs.CHARGES' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-charges-step
[loanProductsTemplate]="loanProductsTemplate"
[currencyCode]="loanProductCurrencyForm.get('currencyCode')"
[multiDisburseLoan]="loanProductSettingsForm.get('multiDisburseLoan')"
></mifosx-loan-product-charges-step>
</div>
</mat-tab>
@if (isAdvancedPaymentStrategy) {
<mat-step [stepControl]="loanIncomeCapitalizationForm">
<ng-template matStepLabel>{{ 'labels.inputs.DEFERRED INCOME RECOGNITION' | translate }}</ng-template>
<mifosx-loan-product-capitalized-income-step
[deferredIncomeRecognition]="deferredIncomeRecognition"
[capitalizedIncomeCalculationTypeOptions]="loanProductsTemplate.capitalizedIncomeCalculationTypeOptions"
[capitalizedIncomeStrategyOptions]="loanProductsTemplate.capitalizedIncomeStrategyOptions"
[capitalizedIncomeTypeOptions]="loanProductsTemplate.capitalizedIncomeTypeOptions"
[buyDownFeeCalculationTypeOptions]="loanProductsTemplate.buyDownFeeCalculationTypeOptions"
[buyDownFeeStrategyOptions]="loanProductsTemplate.buyDownFeeStrategyOptions"
[buyDownFeeIncomeTypeOptions]="loanProductsTemplate.buyDownFeeIncomeTypeOptions"
(setViewChildForm)="setViewChildForm($event)"
>
</mifosx-loan-product-capitalized-income-step>
<mifosx-stepper-buttons></mifosx-stepper-buttons>
</mat-step>
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-clock lp-tab-icon"></i>
{{ 'labels.inputs.DEFERRED INCOME RECOGNITION' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-capitalized-income-step
[deferredIncomeRecognition]="deferredIncomeRecognition"
[capitalizedIncomeCalculationTypeOptions]="loanProductsTemplate.capitalizedIncomeCalculationTypeOptions"
[capitalizedIncomeStrategyOptions]="loanProductsTemplate.capitalizedIncomeStrategyOptions"
[capitalizedIncomeTypeOptions]="loanProductsTemplate.capitalizedIncomeTypeOptions"
[buyDownFeeCalculationTypeOptions]="loanProductsTemplate.buyDownFeeCalculationTypeOptions"
[buyDownFeeStrategyOptions]="loanProductsTemplate.buyDownFeeStrategyOptions"
[buyDownFeeIncomeTypeOptions]="loanProductsTemplate.buyDownFeeIncomeTypeOptions"
(setViewChildForm)="setViewChildForm($event)"
></mifosx-loan-product-capitalized-income-step>
</div>
</mat-tab>
}
<mat-step [stepControl]="loanProductAccountingForm">
<ng-template matStepLabel>{{ 'labels.inputs.ACCOUNTING' | translate }}</ng-template>
<mifosx-loan-product-accounting-step
[loanProductsTemplate]="loanProductsTemplate"
[accountingRuleData]="accountingRuleData"
[loanProductFormValid]="loanProductFormValid"
[deferredIncomeRecognition]="deferredIncomeRecognition"
>
</mifosx-loan-product-accounting-step>
</mat-step>
@if (loanProductFormValid) {
<mat-step state="preview" completed>
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
<mifosx-loan-product-preview-step
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-book lp-tab-icon"></i>
{{ 'labels.inputs.ACCOUNTING' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-accounting-step
[loanProductsTemplate]="loanProductsTemplate"
[accountingRuleData]="accountingRuleData"
[loanProduct]="loanProduct"
(submitEvent)="submit()"
>
</mifosx-loan-product-preview-step>
</mat-step>
[loanProductFormValid]="loanProductFormValid"
[deferredIncomeRecognition]="deferredIncomeRecognition"
></mifosx-loan-product-accounting-step>
</div>
</mat-tab>
@if (loanProductFormValid) {
<mat-tab>
<ng-template mat-tab-label>
<i class="fas fa-eye lp-tab-icon"></i>
{{ 'labels.inputs.PREVIEW' | translate }}
</ng-template>
<div class="lp-tab-content">
<mifosx-loan-product-preview-step
[loanProductsTemplate]="loanProductsTemplate"
[accountingRuleData]="accountingRuleData"
[loanProduct]="loanProduct"
(submitEvent)="submit()"
></mifosx-loan-product-preview-step>
</div>
</mat-tab>
}
</mat-stepper>
</mat-tab-group>
<div class="lp-action-bar">
<button mat-raised-button [routerLink]="['../']" class="lp-action-bar__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<div class="lp-action-bar__right">
@if (selectedTabIndex > 0) {
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
<i class="fas fa-arrow-left" style="margin-right: 8px;"></i>
{{ 'labels.buttons.Previous' | translate }}
</button>
}
@if (selectedTabIndex < totalTabs - 1) {
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
{{ 'labels.buttons.Next' | translate }}
<i class="fas fa-arrow-right" style="margin-left: 8px;"></i>
</button>
}
@if (loanProductFormValid && selectedTabIndex === totalTabs - 1) {
<button mat-raised-button color="primary" (click)="submit()">
<i class="fas fa-check" style="margin-right: 8px;"></i>
{{ 'labels.buttons.Submit' | translate }}
</button>
}
</div>
</div>
</div>
@@ -6,13 +6,85 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 86rem;
width: 96%;
padding-bottom: 30px;
$primary: #1353a0;
.lp-container {
max-width: 960px;
margin: 0 auto;
padding: 24px 16px;
}
fa-icon {
position: relative;
left: 5%;
.lp-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
&__icon {
font-size: 20px;
color: $primary;
}
&__title {
margin: 0;
font-size: 22px;
font-weight: 500;
}
}
.lp-tabs {
background: var(--md-sys-color-surface, #fff);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
::ng-deep {
.mat-mdc-tab-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.mat-mdc-tab {
min-width: 100px;
padding: 0 12px;
height: 52px;
}
.mat-mdc-tab-body-wrapper {
min-height: 200px;
}
.mat-mdc-tab-body {
overflow: visible !important;
}
.mat-mdc-tab-body-content {
overflow: visible !important;
}
}
}
.lp-tab-icon {
margin-right: 8px;
font-size: 14px;
}
.lp-tab-content {
padding: 24px 16px;
}
.lp-action-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding: 16px 0;
border-top: 1px solid rgba(0, 0, 0, 0.08);
&__cancel {
color: rgba(0, 0, 0, 0.6);
}
&__right {
display: flex;
gap: 12px;
}
}
@@ -34,11 +34,10 @@ import { Accounting } from 'app/core/utils/accounting';
import { StringEnumOptionData } from '../../../shared/models/option-data.model';
import { LoanProductDeferredIncomeRecognitionStepComponent } from '../loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component';
import { UntypedFormGroup } from '@angular/forms';
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { LoanProductPaymentStrategyStepComponent } from '../loan-product-stepper/loan-product-payment-strategy-step/loan-product-payment-strategy-step.component';
import { StepperButtonsComponent } from '../../../shared/steppers/stepper-buttons/stepper-buttons.component';
import { LoanProductPreviewStepComponent } from '../loan-product-stepper/loan-product-preview-step/loan-product-preview-step.component';
import { MatTabsModule } from '@angular/material/tabs';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -47,17 +46,13 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./create-loan-product.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatStepper,
MatStepperIcon,
MatTabsModule,
FaIconComponent,
MatStep,
MatStepLabel,
LoanProductDetailsStepComponent,
LoanProductCurrencyStepComponent,
LoanProductSettingsStepComponent,
LoanProductInterestRefundStepComponent,
LoanProductPaymentStrategyStepComponent,
StepperButtonsComponent,
LoanProductTermsStepComponent,
LoanProductChargesStepComponent,
LoanProductDeferredIncomeRecognitionStepComponent,
@@ -90,6 +85,7 @@ export class CreateLoanProductComponent implements OnInit {
loanProductsTemplate: any;
accountingRuleData: string[] = [];
itemsByDefault: any[] = [];
selectedTabIndex = 0;
isAdvancedPaymentStrategy = false;
paymentAllocation: PaymentAllocation[] = [];
@@ -240,6 +236,13 @@ export class CreateLoanProductComponent implements OnInit {
return this.loanProductAccountingStep.loanProductAccountingForm;
}
get totalTabs(): number {
let tabs = 6; // details, currency, settings, terms, charges, accounting
if (this.isAdvancedPaymentStrategy) tabs += 3; // interest refund, payment allocation, deferred income
if (this.loanProductFormValid) tabs += 1; // preview
return tabs;
}
get loanProductFormValid() {
if (this.isAdvancedPaymentStrategy) {
return (
@@ -7,41 +7,63 @@
-->
<form [formGroup]="loanProductAccountingForm">
<div class="layout-row-wrap gap-2px responsive-column">
<mat-radio-group
class="flex-98 layout-row gap-5percent layout-lt-md-column radio-group-spacing"
formControlName="accountingRule"
>
@for (accountingRule of accountingRuleData; track accountingRule; let i = $index) {
<mat-radio-button [value]="i + 1">
{{ 'labels.accounting.' + accountingRule | translate }}
</mat-radio-button>
}
</mat-radio-group>
<mat-divider class="flex-98"></mat-divider>
@if (loanProductAccountingForm.value.accountingRule >= 2 && loanProductAccountingForm.value.accountingRule <= 4) {
<div class="flex-100 layout-row-wrap responsive-column">
@if (isAccountingAccrualBased) {
<mat-checkbox class="flex-73" formControlName="enableAccrualActivityPosting">{{
<!-- Accounting Rule Section -->
<div class="lpa-section">
<div class="lpa-section-header">
<i class="fas fa-book lpa-section-icon"></i>
<h3>{{ 'labels.heading.Accounting Rule' | translate }}</h3>
</div>
<div class="lpa-section-body">
<div class="lpa-full-width">
<mat-radio-group
class="lpa-radio-group"
formControlName="accountingRule"
>
@for (accountingRule of accountingRuleData; track accountingRule; let i = $index) {
<mat-radio-button [value]="i + 1">
{{ 'labels.accounting.' + accountingRule | translate }}
</mat-radio-button>
}
</mat-radio-group>
</div>
@if (isAccountingAccrualBased) {
<div class="lpa-full-width lpa-checkbox-row">
<mat-checkbox formControlName="enableAccrualActivityPosting">{{
'labels.inputs.Enable Accrual Activity Posting on Installment Due Date' | translate
}}</mat-checkbox>
}
<h4 class="mat-h4 flex-98">
{{ 'labels.heading.Assets' | translate }} / {{ 'labels.heading.Liabilities' | translate }}
</h4>
</div>
}
</div>
</div>
@if (loanProductAccountingForm.value.accountingRule >= 2 && loanProductAccountingForm.value.accountingRule <= 4) {
<!-- Assets / Liabilities Section -->
<div class="lpa-section">
<div class="lpa-section-header">
<i class="fas fa-university lpa-section-icon"></i>
<h3>{{ 'labels.heading.Assets' | translate }} / {{ 'labels.heading.Liabilities' | translate }}</h3>
</div>
<div class="lpa-section-body lpa-grid">
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.fundSourceAccountId"
[glAccountList]="assetAndLiabilityAccountData"
[required]="true"
[inputLabel]="'Fund source'"
>
</mifosx-gl-account-selector>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Assets' | translate }}</h4>
</div>
</div>
<!-- Assets Section -->
<div class="lpa-section">
<div class="lpa-section-header">
<i class="fas fa-university lpa-section-icon"></i>
<h3>{{ 'labels.heading.Assets' | translate }}</h3>
</div>
<div class="lpa-section-body lpa-grid">
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.loanPortfolioAccountId"
[glAccountList]="assetAccountData"
[required]="true"
@@ -49,7 +71,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.transfersInSuspenseAccountId"
[glAccountList]="assetAccountData"
[required]="true"
@@ -59,37 +80,39 @@
@if (
loanProductAccountingForm.value.accountingRule === 3 || loanProductAccountingForm.value.accountingRule === 4
) {
<div class="flex-100 layout-row-wrap responsive-column">
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.receivableInterestAccountId"
[glAccountList]="assetAccountData"
[required]="true"
[inputLabel]="'Interest Receivable'"
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.receivableFeeAccountId"
[glAccountList]="assetAccountData"
[required]="true"
[inputLabel]="'Fees Receivable'"
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.receivablePenaltyAccountId"
[glAccountList]="assetAccountData"
[required]="true"
[inputLabel]="'Penalties Receivable'"
>
</mifosx-gl-account-selector>
</div>
<mifosx-gl-account-selector
[inputFormControl]="loanProductAccountingForm.controls.receivableInterestAccountId"
[glAccountList]="assetAccountData"
[required]="true"
[inputLabel]="'Interest Receivable'"
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
[inputFormControl]="loanProductAccountingForm.controls.receivableFeeAccountId"
[glAccountList]="assetAccountData"
[required]="true"
[inputLabel]="'Fees Receivable'"
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
[inputFormControl]="loanProductAccountingForm.controls.receivablePenaltyAccountId"
[glAccountList]="assetAccountData"
[required]="true"
[inputLabel]="'Penalties Receivable'"
>
</mifosx-gl-account-selector>
}
<mat-divider class="flex-98"></mat-divider>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Income' | translate }}</h4>
</div>
</div>
<!-- Income Section -->
<div class="lpa-section">
<div class="lpa-section-header">
<i class="fas fa-chart-line lpa-section-icon"></i>
<h3>{{ 'labels.heading.Income' | translate }}</h3>
</div>
<div class="lpa-section-body lpa-grid">
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.interestOnLoanAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -97,7 +120,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromFeeAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -105,7 +127,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromPenaltyAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -113,7 +134,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromRecoveryAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -121,7 +141,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromChargeOffInterestAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -129,7 +148,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromChargeOffFeesAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -137,7 +155,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromChargeOffPenaltyAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -145,7 +162,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromGoodwillCreditInterestAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -153,7 +169,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromGoodwillCreditFeesAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -161,7 +176,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromGoodwillCreditPenaltyAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -170,7 +184,6 @@
</mifosx-gl-account-selector>
@if (deferredIncomeRecognition?.capitalizedIncome?.enableIncomeCapitalization) {
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromCapitalizationAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -180,7 +193,6 @@
}
@if (deferredIncomeRecognition?.buyDownFee?.enableBuyDownFee) {
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.incomeFromBuyDownAccountId"
[glAccountList]="incomeAccountData"
[required]="true"
@@ -188,10 +200,17 @@
>
</mifosx-gl-account-selector>
}
<mat-divider class="flex-98"></mat-divider>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Expenses' | translate }}</h4>
</div>
</div>
<!-- Expenses Section -->
<div class="lpa-section">
<div class="lpa-section-header">
<i class="fas fa-money-bill-wave lpa-section-icon"></i>
<h3>{{ 'labels.heading.Expenses' | translate }}</h3>
</div>
<div class="lpa-section-body lpa-grid">
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.writeOffAccountId"
[glAccountList]="expenseAccountData"
[required]="true"
@@ -199,7 +218,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.goodwillCreditAccountId"
[glAccountList]="expenseAccountData"
[required]="true"
@@ -207,7 +225,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.chargeOffExpenseAccountId"
[glAccountList]="expenseAccountData"
[required]="true"
@@ -215,7 +232,6 @@
>
</mifosx-gl-account-selector>
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.chargeOffFraudExpenseAccountId"
[glAccountList]="expenseAccountData"
[required]="true"
@@ -227,7 +243,6 @@
deferredIncomeRecognition?.buyDownFee?.merchantBuyDownFee
) {
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.buyDownExpenseAccountId"
[glAccountList]="expenseAccountData"
[required]="true"
@@ -235,10 +250,17 @@
>
</mifosx-gl-account-selector>
}
<mat-divider class="flex-98"></mat-divider>
<h4 class="mat-h4 flex-98">{{ 'labels.heading.Liabilities' | translate }}</h4>
</div>
</div>
<!-- Liabilities Section -->
<div class="lpa-section">
<div class="lpa-section-header">
<i class="fas fa-file-invoice-dollar lpa-section-icon"></i>
<h3>{{ 'labels.heading.Liabilities' | translate }}</h3>
</div>
<div class="lpa-section-body lpa-grid">
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.overpaymentLiabilityAccountId"
[glAccountList]="liabilityAccountData"
[required]="true"
@@ -250,7 +272,6 @@
deferredIncomeRecognition?.buyDownFee?.enableBuyDownFee
) {
<mifosx-gl-account-selector
class="flex-48"
[inputFormControl]="loanProductAccountingForm.controls.deferredIncomeLiabilityAccountId"
[glAccountList]="liabilityAccountData"
[required]="true"
@@ -258,28 +279,42 @@
>
</mifosx-gl-account-selector>
}
<mat-divider fxFlex="flex-98"></mat-divider>
<mat-checkbox class="flex-73" formControlName="advancedAccountingRules">{{
'labels.heading.Advanced Accounting Rules' | translate
}}</mat-checkbox>
@if (loanProductAccountingForm.value.advancedAccountingRules) {
<div class="flex-100 layout-row-wrap responsive-column">
<h4 class="mat-h4 flex-33">
{{ 'labels.heading.Configure Fund Sources for Payment Channels' | translate }}
</h4>
<div class="flex-63">
<button
type="button"
mat-raised-button
color="primary"
(click)="add('PaymentFundSource', paymentChannelToFundSourceMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (paymentChannelToFundSourceMappings.value.length !== 0) {
<table class="flex-98 mat-elevation-z1" mat-table [dataSource]="paymentChannelToFundSourceMappings.value">
</div>
</div>
<!-- Advanced Accounting Rules Section -->
<div class="lpa-section">
<div class="lpa-section-header">
<i class="fas fa-cogs lpa-section-icon"></i>
<h3>{{ 'labels.heading.Advanced Accounting Rules' | translate }}</h3>
</div>
<div class="lpa-section-body">
<div class="lpa-full-width lpa-checkbox-row">
<mat-checkbox formControlName="advancedAccountingRules">{{
'labels.heading.Advanced Accounting Rules' | translate
}}</mat-checkbox>
</div>
</div>
@if (loanProductAccountingForm.value.advancedAccountingRules) {
<!-- Payment Fund Sources -->
<div class="lpa-subsection">
<div class="lpa-subsection-header">
<h4>{{ 'labels.heading.Configure Fund Sources for Payment Channels' | translate }}</h4>
<button
type="button"
mat-raised-button
color="primary"
class="lpa-add-btn"
(click)="add('PaymentFundSource', paymentChannelToFundSourceMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (paymentChannelToFundSourceMappings.value.length !== 0) {
<div class="lpa-table-wrapper">
<table mat-table [dataSource]="paymentChannelToFundSourceMappings.value">
<ng-container matColumnDef="paymentTypeId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Payment Type' | translate }}</th>
<td mat-cell *matCellDef="let paymentFundSource">
@@ -314,21 +349,28 @@
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
</table>
}
<h4 class="mat-h4 flex-33">{{ 'labels.heading.Map Fees to Specific Income Accounts' | translate }}</h4>
<div class="flex-63">
<button
type="button"
mat-raised-button
color="primary"
(click)="add('FeesIncome', feeToIncomeAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (feeToIncomeAccountMappings.value.length !== 0) {
<table class="flex-98 mat-elevation-z1" mat-table [dataSource]="feeToIncomeAccountMappings.value">
}
</div>
<!-- Fees to Income -->
<div class="lpa-subsection">
<div class="lpa-subsection-header">
<h4>{{ 'labels.heading.Map Fees to Specific Income Accounts' | translate }}</h4>
<button
type="button"
mat-raised-button
color="primary"
class="lpa-add-btn"
(click)="add('FeesIncome', feeToIncomeAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (feeToIncomeAccountMappings.value.length !== 0) {
<div class="lpa-table-wrapper">
<table mat-table [dataSource]="feeToIncomeAccountMappings.value">
<ng-container matColumnDef="chargeId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fees' | translate }}</th>
<td mat-cell *matCellDef="let feesIncome">
@@ -355,23 +397,28 @@
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
</table>
}
<h4 class="mat-h4 flex-33">
{{ 'labels.heading.Map Penalties to Specific Income Accounts' | translate }}
</h4>
<div class="flex-63">
<button
type="button"
mat-raised-button
color="primary"
(click)="add('PenaltyIncome', penaltyToIncomeAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (penaltyToIncomeAccountMappings.value.length !== 0) {
<table class="flex-98 mat-elevation-z1" mat-table [dataSource]="penaltyToIncomeAccountMappings.value">
}
</div>
<!-- Penalties to Income -->
<div class="lpa-subsection">
<div class="lpa-subsection-header">
<h4>{{ 'labels.heading.Map Penalties to Specific Income Accounts' | translate }}</h4>
<button
type="button"
mat-raised-button
color="primary"
class="lpa-add-btn"
(click)="add('PenaltyIncome', penaltyToIncomeAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (penaltyToIncomeAccountMappings.value.length !== 0) {
<div class="lpa-table-wrapper">
<table mat-table [dataSource]="penaltyToIncomeAccountMappings.value">
<ng-container matColumnDef="chargeId">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
<td mat-cell *matCellDef="let penaltyIncome">
@@ -406,25 +453,29 @@
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
</table>
}
<h4 class="mat-h4 flex-33">
{{ 'labels.heading.Map Charge-off reasons to Expense accounts' | translate }}
</h4>
<div class="flex-63">
<button
type="button"
mat-raised-button
color="primary"
[disabled]="!allowAddChargeOffReasonExpense"
(click)="add('ChargeOffReasonExpense', chargeOffReasonToExpenseAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (chargeOffReasonToExpenseAccountMappings.value.length !== 0) {
}
</div>
<!-- Charge-off Reasons to Expense -->
<div class="lpa-subsection">
<div class="lpa-subsection-header">
<h4>{{ 'labels.heading.Map Charge-off reasons to Expense accounts' | translate }}</h4>
<button
type="button"
mat-raised-button
color="primary"
class="lpa-add-btn"
[disabled]="!allowAddChargeOffReasonExpense"
(click)="add('ChargeOffReasonExpense', chargeOffReasonToExpenseAccountMappings)"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if (chargeOffReasonToExpenseAccountMappings.value.length !== 0) {
<div class="lpa-table-wrapper">
<table
class="flex-98 mat-elevation-z1"
mat-table
[dataSource]="chargeOffReasonToExpenseAccountMappings.value"
>
@@ -464,51 +515,44 @@
<tr mat-header-row *matHeaderRowDef="chargeOffReasonExpenseDisplayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: chargeOffReasonExpenseDisplayedColumns"></tr>
</table>
}
<mifosx-advanced-accounting-mapping-rule
class="flex-100 m-t-10"
[textField]="'Classification'"
[formType]="'BuydownFeeClassificationToIncome'"
[formArray]="buydownfeeClassificationToIncomeAccountMappings"
[textHeading]="'Buydown Fee classifications to Income accounts'"
[incomeAccountData]="incomeAccountData"
[accountingMappingOptions]="buydownFeeClassificationOptions"
(formChangeEvent)="formChangeEvent($event)"
></mifosx-advanced-accounting-mapping-rule>
<mifosx-advanced-accounting-mapping-rule
class="flex-100 m-t-10"
[textField]="'Classification'"
[formType]="'CapitalizedIncomeClassificationToIncome'"
[formArray]="capitalizedIncomeClassificationToIncomeAccountMappings"
[textHeading]="'Capitalized Income classifications to Income accounts'"
[incomeAccountData]="incomeAccountData"
[accountingMappingOptions]="capitalizedIncomeClassificationOptions"
(formChangeEvent)="formChangeEvent($event)"
></mifosx-advanced-accounting-mapping-rule>
<mifosx-advanced-accounting-mapping-rule
class="flex-100 m-t-10"
[textField]="'WriteOff Reason'"
[formType]="'WriteOffReasonToExpense'"
[formArray]="writeOffReasonsToExpenseMappings"
[textHeading]="'WriteOff reasons to Expense accounts'"
[expenseAccountData]="expenseAccountData"
[accountingMappingOptions]="writeOffReasonOptions"
(formChangeEvent)="formChangeEvent($event)"
></mifosx-advanced-accounting-mapping-rule>
</div>
}
</div>
}
</div>
</div>
}
</div>
<!-- Advanced Mapping Rule Components -->
<mifosx-advanced-accounting-mapping-rule
class="lpa-full-width"
[textField]="'Classification'"
[formType]="'BuydownFeeClassificationToIncome'"
[formArray]="buydownfeeClassificationToIncomeAccountMappings"
[textHeading]="'Buydown Fee classifications to Income accounts'"
[incomeAccountData]="incomeAccountData"
[accountingMappingOptions]="buydownFeeClassificationOptions"
(formChangeEvent)="formChangeEvent($event)"
></mifosx-advanced-accounting-mapping-rule>
<mifosx-advanced-accounting-mapping-rule
class="lpa-full-width"
[textField]="'Classification'"
[formType]="'CapitalizedIncomeClassificationToIncome'"
[formArray]="capitalizedIncomeClassificationToIncomeAccountMappings"
[textHeading]="'Capitalized Income classifications to Income accounts'"
[incomeAccountData]="incomeAccountData"
[accountingMappingOptions]="capitalizedIncomeClassificationOptions"
(formChangeEvent)="formChangeEvent($event)"
></mifosx-advanced-accounting-mapping-rule>
<mifosx-advanced-accounting-mapping-rule
class="lpa-full-width"
[textField]="'WriteOff Reason'"
[formType]="'WriteOffReasonToExpense'"
[formArray]="writeOffReasonsToExpenseMappings"
[textHeading]="'WriteOff reasons to Expense accounts'"
[expenseAccountData]="expenseAccountData"
[accountingMappingOptions]="writeOffReasonOptions"
(formChangeEvent)="formChangeEvent($event)"
></mifosx-advanced-accounting-mapping-rule>
}
</div>
}
<div class="layout-row align-center margin-t responsive-column gap-2percent">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext [disabled]="!loanProductFormValid">
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
</div>
</form>
@@ -6,40 +6,124 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
width: 100%;
}
// Section cards
.lpa-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
.mat-elevation-z1 {
margin: 1em 0 1.5em;
& + & {
margin-top: 16px;
}
}
h3,
h4 {
font-weight: 500;
// Section header
.lpa-section-header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
.lpa-section-icon {
color: #0ea5e9;
font-size: 15px;
}
mat-radio-group,
h4 {
margin: 1em 0;
// Section body
.lpa-section-body {
padding: 16px 24px;
}
mat-divider {
margin: 1em 0 2em;
// 2-column grid
.lpa-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
}
.margin-t {
margin-top: 1em;
// Full width utility
.lpa-full-width {
grid-column: 1 / -1;
}
.radio-group-spacing {
// Radio group
.lpa-radio-group {
display: flex;
gap: 2rem;
flex-direction: row;
gap: 24px;
flex-wrap: wrap;
}
// Checkbox row
.lpa-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
// Subsections inside advanced rules
.lpa-subsection {
padding: 16px 24px;
border-top: 1px solid #e8eaed;
}
.lpa-subsection-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
h4 {
font-size: 13px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
// Add buttons
.lpa-add-btn {
height: 32px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
}
// Table wrapper
.lpa-table-wrapper {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
margin-top: 8px;
table {
width: 100%;
}
}
// Responsive
@media (max-width: 600px) {
.lpa-grid {
grid-template-columns: 1fr;
}
.lpa-radio-group {
flex-direction: column;
}
@media (width <= 768px) {
.radio-group-spacing {
.lpa-subsection-header {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
}
@@ -22,7 +22,6 @@ import {
} from 'app/shared/models/general.model';
import { DeferredIncomeRecognition } from '../loan-product-payment-strategy-step/payment-allocation-model';
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
import { MatDivider } from '@angular/material/divider';
import { MatCheckbox } from '@angular/material/checkbox';
import { GlAccountSelectorComponent } from '../../../../shared/accounting/gl-account-selector/gl-account-selector.component';
import { MatIconButton } from '@angular/material/button';
@@ -39,7 +38,6 @@ import {
MatRowDef,
MatRow
} from '@angular/material/table';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FindPipe } from '../../../../pipes/find.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { AdvancedAccountingMappingRuleComponent } from './advanced-accounting-mapping-rule/advanced-accounting-mapping-rule.component';
@@ -53,7 +51,6 @@ import { AccountingMappingDTO, AdvancedMappingDTO } from '../../models/loan-prod
...STANDALONE_SHARED_IMPORTS,
MatRadioGroup,
MatRadioButton,
MatDivider,
MatCheckbox,
GlAccountSelectorComponent,
FaIconComponent,
@@ -68,8 +65,6 @@ import { AccountingMappingDTO, AdvancedMappingDTO } from '../../models/loan-prod
MatHeaderRow,
MatRowDef,
MatRow,
MatStepperPrevious,
MatStepperNext,
FindPipe,
AdvancedAccountingMappingRuleComponent
]
@@ -7,97 +7,112 @@
-->
<form [formGroup]="loanDeferredIncomeRecognitionForm">
<div class="layout-row-wrap gap-2percent layout-column-lt-md">
<mat-checkbox class="flex-98" labelPosition="before" formControlName="enableIncomeCapitalization">
{{ 'labels.inputs.Enable income capitalization' | translate }}
</mat-checkbox>
<!-- Income Capitalization -->
<div class="lpdi-section">
<div class="lpdi-section__header">
<i class="fas fa-chart-bar"></i>
<h3>{{ 'labels.inputs.Income Capitalization' | translate }}</h3>
</div>
<div class="lpdi-grid">
<div class="lpdi-field--full lpdi-checkbox-row">
<mat-checkbox labelPosition="before" formControlName="enableIncomeCapitalization">
{{ 'labels.inputs.Enable income capitalization' | translate }}
</mat-checkbox>
</div>
@if (enableIncomeCapitalization) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Income capitalization calculation type' | translate }}</mat-label>
<mat-select formControlName="capitalizedIncomeCalculationType" required>
@for (
capitalizedIncomeCalculationType of capitalizedIncomeCalculationTypeOptions;
track capitalizedIncomeCalculationType
) {
<mat-option [value]="capitalizedIncomeCalculationType.id">
{{ capitalizedIncomeCalculationType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
@if (enableIncomeCapitalization) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Income capitalization strategy' | translate }}</mat-label>
<mat-select formControlName="capitalizedIncomeStrategy" required>
@for (capitalizedIncomeStrategy of capitalizedIncomeStrategyOptions; track capitalizedIncomeStrategy) {
<mat-option [value]="capitalizedIncomeStrategy.id">
{{ capitalizedIncomeStrategy.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
@if (enableIncomeCapitalization) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Income type' | translate }}</mat-label>
<mat-select formControlName="capitalizedIncomeType" required>
@for (capitalizedIncomeType of capitalizedIncomeTypeOptions; track capitalizedIncomeType) {
<mat-option [value]="capitalizedIncomeType.id">
{{ capitalizedIncomeType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
@if (enableIncomeCapitalization) {
<mat-form-field appearance="outline" class="lpdi-field">
<mat-label>{{ 'labels.inputs.Income capitalization calculation type' | translate }}</mat-label>
<mat-select formControlName="capitalizedIncomeCalculationType" required>
@for (
capitalizedIncomeCalculationType of capitalizedIncomeCalculationTypeOptions;
track capitalizedIncomeCalculationType
) {
<mat-option [value]="capitalizedIncomeCalculationType.id">
{{ capitalizedIncomeCalculationType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="lpdi-field">
<mat-label>{{ 'labels.inputs.Income capitalization strategy' | translate }}</mat-label>
<mat-select formControlName="capitalizedIncomeStrategy" required>
@for (capitalizedIncomeStrategy of capitalizedIncomeStrategyOptions; track capitalizedIncomeStrategy) {
<mat-option [value]="capitalizedIncomeStrategy.id">
{{ capitalizedIncomeStrategy.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="lpdi-field">
<mat-label>{{ 'labels.inputs.Income type' | translate }}</mat-label>
<mat-select formControlName="capitalizedIncomeType" required>
@for (capitalizedIncomeType of capitalizedIncomeTypeOptions; track capitalizedIncomeType) {
<mat-option [value]="capitalizedIncomeType.id">
{{ capitalizedIncomeType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
</div>
</div>
<div class="layout-row-wrap gap-2percent layout-column-lt-md">
<mat-checkbox class="flex-98" labelPosition="before" formControlName="enableBuyDownFee">
{{ 'labels.inputs.Enable Buy down fee' | translate }}
</mat-checkbox>
@if (enableBuyDownFee) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Buy down fee calculation type' | translate }}</mat-label>
<mat-select formControlName="buyDownFeeCalculationType" required>
@for (buyDownFeeCalculationType of buyDownFeeCalculationTypeOptions; track buyDownFeeCalculationType) {
<mat-option [value]="buyDownFeeCalculationType.id">
{{ buyDownFeeCalculationType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
@if (enableBuyDownFee) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Buy down fee strategy' | translate }}</mat-label>
<mat-select formControlName="buyDownFeeStrategy" required>
@for (buyDownFeeStrategy of buyDownFeeStrategyOptions; track buyDownFeeStrategy) {
<mat-option [value]="buyDownFeeStrategy.id">
{{ buyDownFeeStrategy.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
@if (enableBuyDownFee) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Buy down fee income type' | translate }}</mat-label>
<mat-select formControlName="buyDownFeeIncomeType" required>
@for (buyDownFeeIncomeType of buyDownFeeIncomeTypeOptions; track buyDownFeeIncomeType) {
<mat-option [value]="buyDownFeeIncomeType.id">
{{ buyDownFeeIncomeType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
}
<!-- Buy Down Fee -->
<div class="lpdi-section">
<div class="lpdi-section__header">
<i class="fas fa-tag"></i>
<h3>{{ 'labels.inputs.Buy Down Fee' | translate }}</h3>
</div>
<div class="lpdi-grid">
<div class="lpdi-field--full lpdi-checkbox-row">
<mat-checkbox labelPosition="before" formControlName="enableBuyDownFee">
{{ 'labels.inputs.Enable Buy down fee' | translate }}
</mat-checkbox>
</div>
@if (enableBuyDownFee) {
<mat-checkbox class="flex-48" labelPosition="before" formControlName="merchantBuyDownFee">
{{ 'labels.inputs.Merchant Buy down fee' | translate }}
</mat-checkbox>
}
@if (enableBuyDownFee) {
<mat-form-field appearance="outline" class="lpdi-field">
<mat-label>{{ 'labels.inputs.Buy down fee calculation type' | translate }}</mat-label>
<mat-select formControlName="buyDownFeeCalculationType" required>
@for (buyDownFeeCalculationType of buyDownFeeCalculationTypeOptions; track buyDownFeeCalculationType) {
<mat-option [value]="buyDownFeeCalculationType.id">
{{ buyDownFeeCalculationType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="lpdi-field">
<mat-label>{{ 'labels.inputs.Buy down fee strategy' | translate }}</mat-label>
<mat-select formControlName="buyDownFeeStrategy" required>
@for (buyDownFeeStrategy of buyDownFeeStrategyOptions; track buyDownFeeStrategy) {
<mat-option [value]="buyDownFeeStrategy.id">
{{ buyDownFeeStrategy.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="lpdi-field">
<mat-label>{{ 'labels.inputs.Buy down fee income type' | translate }}</mat-label>
<mat-select formControlName="buyDownFeeIncomeType" required>
@for (buyDownFeeIncomeType of buyDownFeeIncomeTypeOptions; track buyDownFeeIncomeType) {
<mat-option [value]="buyDownFeeIncomeType.id">
{{ buyDownFeeIncomeType.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="lpdi-field lpdi-checkbox-row">
<mat-checkbox labelPosition="before" formControlName="merchantBuyDownFee">
{{ 'labels.inputs.Merchant Buy down fee' | translate }}
</mat-checkbox>
</div>
}
</div>
</div>
</form>
@@ -6,6 +6,49 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.margin-t {
margin-top: 1em;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-900: #202124;
.lpdi-section {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & { margin-top: 16px; }
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i { font-size: 14px; color: $accent; }
h3 { margin: 0; font-size: 14px; font-weight: 600; color: $gray-900; }
}
}
.lpdi-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.lpdi-field {
width: 100%;
&--full { grid-column: 1 / -1; }
}
.lpdi-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
@@ -6,165 +6,176 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
<mat-select #charge>
@for (
charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value : multiDisburseLoan.value;
track charge
) {
<mat-option [value]="charge">
{{ charge.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="flex-48 align-center">
<button type="button" mat-raised-button color="primary" (click)="addCharge(charge)" [disabled]="!charge.value">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
<!-- Charges -->
<div class="lpch-section">
<div class="lpch-section__header">
<i class="fas fa-receipt"></i>
<h3>{{ 'labels.inputs.Charges' | translate }}</h3>
</div>
<div class="lpch-body">
<div class="lpch-add-row">
<mat-form-field appearance="outline" class="lpch-add-row__select">
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
<mat-select #charge>
@for (
charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value : multiDisburseLoan.value;
track charge
) {
<mat-option [value]="charge">
{{ charge.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<table
class="flex-98 mat-elevation-z1"
mat-table
[dataSource]="chargesDataSource | chargesPenaltyFilter: false"
#chargesTable
[hidden]="chargesTable.dataSource.length === 0"
>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.name + ', ' + charge.currency.displaySymbol }}
</td>
</ng-container>
<button
type="button"
mat-flat-button
color="primary"
(click)="addCharge(charge)"
[disabled]="!charge.value"
class="lpch-add-row__btn"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
<ng-container matColumnDef="chargeCalculationType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.chargeCalculationType.value | translateKey: 'catalogs' }}
</td>
</ng-container>
@if ((chargesDataSource | chargesPenaltyFilter: false).length > 0) {
<div class="lpch-table-wrap">
<table
mat-table
[dataSource]="chargesDataSource | chargesPenaltyFilter: false"
class="lpch-table"
>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.name + ', ' + charge.currency.displaySymbol }}
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.amount | formatNumber }}
</td>
</ng-container>
<ng-container matColumnDef="chargeCalculationType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.chargeCalculationType.value | translateKey: 'catalogs' }}
</td>
</ng-container>
<ng-container matColumnDef="chargeTimeType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.chargeTimeType.value | translateKey: 'catalogs' }}
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.amount | formatNumber }}
</td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let charge">
<button mat-icon-button color="warn" (click)="deleteCharge(charge)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<ng-container matColumnDef="chargeTimeType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
<td mat-cell *matCellDef="let charge">
{{ charge.chargeTimeType.value | translateKey: 'catalogs' }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let charge">
<button mat-icon-button color="warn" (click)="deleteCharge(charge)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<mat-divider class="flex-98"></mat-divider>
<div class="flex-98">
<h3 class="mat-h3">{{ 'labels.inputs.Overdue Charges' | translate }}</h3>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
}
</div>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Overdue Charge' | translate }}</mat-label>
<mat-select #overdueCharge>
@for (
overdueCharge of overdueChargeData | chargesFilter: chargesDataSource : currencyCode.value;
track overdueCharge
) {
<mat-option [value]="overdueCharge">
{{ overdueCharge.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="flex-48 align-center">
<button
type="button"
mat-raised-button
color="primary"
(click)="addCharge(overdueCharge)"
[disabled]="!overdueCharge.value"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
<table
class="flex-98 mat-elevation-z1"
mat-table
[dataSource]="chargesDataSource | chargesPenaltyFilter: true"
#overdueChargesTable
[hidden]="overdueChargesTable.dataSource.length === 0"
>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
{{ overdueCharge.name + ', ' + overdueCharge.currency.displaySymbol }}
</td>
</ng-container>
<ng-container matColumnDef="chargeCalculationType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
{{ overdueCharge.chargeCalculationType.value | translateKey: 'catalogs' }}
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
{{ overdueCharge.amount | formatNumber }}
</td>
</ng-container>
<ng-container matColumnDef="chargeTimeType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
{{ overdueCharge.chargeTimeType.value | translateKey: 'catalogs' }}
</td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
<button mat-icon-button color="warn" (click)="deleteCharge(overdueCharge)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
<div class="layout-row responsive-column align-center gap-2px margin-t">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<!-- Overdue Charges -->
<div class="lpch-section">
<div class="lpch-section__header">
<i class="fas fa-exclamation-circle"></i>
<h3>{{ 'labels.inputs.Overdue Charges' | translate }}</h3>
</div>
<div class="lpch-body">
<div class="lpch-add-row">
<mat-form-field appearance="outline" class="lpch-add-row__select">
<mat-label>{{ 'labels.inputs.Overdue Charge' | translate }}</mat-label>
<mat-select #overdueCharge>
@for (
overdueCharge of overdueChargeData | chargesFilter: chargesDataSource : currencyCode.value;
track overdueCharge
) {
<mat-option [value]="overdueCharge">
{{ overdueCharge.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<button
type="button"
mat-flat-button
color="primary"
(click)="addCharge(overdueCharge)"
[disabled]="!overdueCharge.value"
class="lpch-add-row__btn"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
@if ((chargesDataSource | chargesPenaltyFilter: true).length > 0) {
<div class="lpch-table-wrap">
<table
mat-table
[dataSource]="chargesDataSource | chargesPenaltyFilter: true"
class="lpch-table"
>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
{{ overdueCharge.name + ', ' + overdueCharge.currency.displaySymbol }}
</td>
</ng-container>
<ng-container matColumnDef="chargeCalculationType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
{{ overdueCharge.chargeCalculationType.value | translateKey: 'catalogs' }}
</td>
</ng-container>
<ng-container matColumnDef="amount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
{{ overdueCharge.amount | formatNumber }}
</td>
</ng-container>
<ng-container matColumnDef="chargeTimeType">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
{{ overdueCharge.chargeTimeType.value | translateKey: 'catalogs' }}
</td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let overdueCharge">
<button mat-icon-button color="warn" (click)="deleteCharge(overdueCharge)">
<fa-icon icon="trash"></fa-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
}
</div>
</div>
@@ -6,22 +6,70 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-900: #202124;
.lpch-section {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & { margin-top: 16px; }
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i { font-size: 14px; color: $accent; }
h3 { margin: 0; font-size: 14px; font-weight: 600; color: $gray-900; }
}
}
.lpch-body {
padding: 16px 24px;
}
.lpch-add-row {
display: flex;
align-items: flex-start;
gap: 12px;
&__select {
flex: 1;
max-width: 400px;
}
&__btn {
margin-top: 4px;
height: 36px !important;
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
}
@media (max-width: 600px) {
flex-direction: column;
&__select { max-width: 100%; width: 100%; }
}
}
.lpch-table-wrap {
margin-top: 8px;
border: 1px solid $border;
border-radius: 8px;
overflow: hidden;
}
.lpch-table {
width: 100%;
}
.mat-elevation-z1 {
margin: 1em 0 1.5em;
}
h3 {
font-weight: 500;
}
mat-divider {
margin: 1em 0 2em;
}
.margin-t {
margin-top: 1em;
}
@@ -26,8 +26,6 @@ import {
MatRowDef,
MatRow
} from '@angular/material/table';
import { MatDivider } from '@angular/material/divider';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { ChargesFilterPipe } from '../../../../pipes/charges-filter.pipe';
import { ChargesPenaltyFilterPipe } from '../../../../pipes/charges-penalty-filter.pipe';
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
@@ -51,9 +49,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatHeaderRow,
MatRowDef,
MatRow,
MatDivider,
MatStepperPrevious,
MatStepperNext,
ChargesFilterPipe,
ChargesPenaltyFilterPipe,
FormatNumberPipe
@@ -7,94 +7,111 @@
-->
<form [formGroup]="loanProductCurrencyForm">
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select
formControlName="currencyCode"
matTooltip="{{ 'tooltips.The currency in which the loan will be disbursed' | translate }}"
required
>
@for (currency of currencyData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<div class="lpc-section">
<div class="lpc-section__header">
<i class="fas fa-coins"></i>
<h3>{{ 'labels.inputs.Currency' | translate }}</h3>
</div>
<div class="lpc-grid">
<mat-form-field appearance="outline" class="lpc-field">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select
formControlName="currencyCode"
matTooltip="{{ 'tooltips.The currency in which the loan will be disbursed' | translate }}"
required
>
@for (currency of currencyData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
<mat-error>
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
<input
type="number"
min="0"
matTooltip="{{ 'tooltips.Number of decimal places to be used to track and report' | translate }}"
matInput
formControlName="digitsAfterDecimal"
required
/>
<mat-error *ngIf="loanProductCurrencyForm.get('digitsAfterDecimal')?.hasError('required')">
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error *ngIf="loanProductCurrencyForm.get('digitsAfterDecimal')?.hasError('min')">
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.Minimum Value must be' | translate }}
<strong>0</strong>
</mat-error>
</mat-form-field>
<mat-checkbox labelPosition="before" formControlName="setMultiples" class="flex-98 margin-t align-center">
{{ 'labels.inputs.Set the multiples of the loan and its installment' | translate }}
</mat-checkbox>
@if (loanProductCurrencyForm.value.setMultiples) {
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Currency in multiples of' | translate }}</mat-label>
<mat-form-field appearance="outline" class="lpc-field">
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
<input
type="number"
min="1"
min="0"
matTooltip="{{ 'tooltips.Number of decimal places to be used to track and report' | translate }}"
matInput
matTooltip="{{ 'tooltips.Enter multiples of currency value' | translate }}"
formControlName="inMultiplesOf"
formControlName="digitsAfterDecimal"
required
/>
<mat-error *ngIf="loanProductCurrencyForm.get('inMultiplesOf')?.hasError('required')">
{{ 'labels.inputs.Currency in multiples of' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error *ngIf="loanProductCurrencyForm.get('inMultiplesOf')?.hasError('min')">
{{ 'labels.inputs.Currency in multiples of' | translate }}
{{ 'labels.commons.Minimum Value must be' | translate }} <strong>1</strong>
</mat-error>
@if (loanProductCurrencyForm.get('digitsAfterDecimal')?.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (loanProductCurrencyForm.get('digitsAfterDecimal')?.hasError('min')) {
<mat-error>
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.Minimum Value must be' | translate }}
<strong>0</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Installment in multiples of' | translate }}</mat-label>
<input type="number" min="1" matInput formControlName="installmentAmountInMultiplesOf" required />
<mat-error *ngIf="loanProductCurrencyForm.get('installmentAmountInMultiplesOf')?.hasError('required')">
{{ 'labels.inputs.Installment in multiples of' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error *ngIf="loanProductCurrencyForm.get('installmentAmountInMultiplesOf')?.hasError('min')">
{{ 'labels.inputs.Installment in multiples of' | translate }}
{{ 'labels.commons.Minimum Value must be' | translate }} <strong>1</strong>
</mat-error>
</mat-form-field>
}
</div>
</div>
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<div class="lpc-section">
<div class="lpc-section__header">
<i class="fas fa-calculator"></i>
<h3>{{ 'labels.inputs.Multiples' | translate }}</h3>
</div>
<div class="lpc-grid">
<div class="lpc-field--full lpc-checkbox-row">
<mat-checkbox labelPosition="before" formControlName="setMultiples">
{{ 'labels.inputs.Set the multiples of the loan and its installment' | translate }}
</mat-checkbox>
</div>
@if (loanProductCurrencyForm.value.setMultiples) {
<mat-form-field appearance="outline" class="lpc-field">
<mat-label>{{ 'labels.inputs.Currency in multiples of' | translate }}</mat-label>
<input
type="number"
min="1"
matInput
matTooltip="{{ 'tooltips.Enter multiples of currency value' | translate }}"
formControlName="inMultiplesOf"
required
/>
@if (loanProductCurrencyForm.get('inMultiplesOf')?.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Currency in multiples of' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (loanProductCurrencyForm.get('inMultiplesOf')?.hasError('min')) {
<mat-error>
{{ 'labels.inputs.Currency in multiples of' | translate }}
{{ 'labels.commons.Minimum Value must be' | translate }} <strong>1</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="lpc-field">
<mat-label>{{ 'labels.inputs.Installment in multiples of' | translate }}</mat-label>
<input type="number" min="1" matInput formControlName="installmentAmountInMultiplesOf" required />
@if (loanProductCurrencyForm.get('installmentAmountInMultiplesOf')?.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Installment in multiples of' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (loanProductCurrencyForm.get('installmentAmountInMultiplesOf')?.hasError('min')) {
<mat-error>
{{ 'labels.inputs.Installment in multiples of' | translate }}
{{ 'labels.commons.Minimum Value must be' | translate }} <strong>1</strong>
</mat-error>
}
</mat-form-field>
}
</div>
</div>
</form>
@@ -6,10 +6,49 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.margin-t {
margin-top: 1em;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-900: #202124;
.lpc-section {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & { margin-top: 16px; }
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i { font-size: 14px; color: $accent; }
h3 { margin: 0; font-size: 14px; font-weight: 600; color: $gray-900; }
}
}
.align-center {
.lpc-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.lpc-field {
width: 100%;
&--full { grid-column: 1 / -1; }
}
.lpc-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
@@ -10,7 +10,6 @@ import { Component, OnInit, Input, inject } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { MatTooltip } from '@angular/material/tooltip';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -22,9 +21,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
MatCheckbox,
MatStepperPrevious,
FaIconComponent,
MatStepperNext
FaIconComponent
]
})
export class LoanProductCurrencyStepComponent implements OnInit {
@@ -7,113 +7,117 @@
-->
<form [formGroup]="loanProductDetailsForm">
<div class="layout-row-wrap gap-2percent responsive-column align-start-center">
<mat-form-field class="flex-98">
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
<input
matInput
formControlName="name"
matTooltip="{{ 'tooltips.Product name is a unique identifier' | translate }}"
required
/>
<mat-error>
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<div class="lpd-section">
<div class="lpd-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.inputs.Product Name' | translate }} &amp; {{ 'labels.inputs.Description' | translate }}</h3>
</div>
<div class="lpd-grid">
<mat-form-field appearance="outline" class="lpd-field lpd-field--full">
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
<input
matInput
formControlName="name"
matTooltip="{{ 'tooltips.Product name is a unique identifier' | translate }}"
required
/>
<mat-error>
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Short Name' | translate }}</mat-label>
<input
matInput
formControlName="shortName"
maxlength="4"
matTooltip="{{ 'tooltips.Short name is a unique identifier' | translate }}"
required
/>
<mat-error>
{{ 'labels.inputs.Short Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline" class="lpd-field">
<mat-label>{{ 'labels.inputs.Short Name' | translate }}</mat-label>
<input
matInput
formControlName="shortName"
maxlength="4"
matTooltip="{{ 'tooltips.Short name is a unique identifier' | translate }}"
required
/>
<mat-error>
{{ 'labels.inputs.Short Name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.External Id' | translate }}</mat-label>
<input
matInput
formControlName="externalId"
matTooltip="{{ 'tooltips.External Id is a unique identifier' | translate }}"
/>
</mat-form-field>
<mat-form-field appearance="outline" class="lpd-field">
<mat-label>{{ 'labels.inputs.External Id' | translate }}</mat-label>
<input
matInput
formControlName="externalId"
matTooltip="{{ 'tooltips.External Id is a unique identifier' | translate }}"
/>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Fund' | translate }}</mat-label>
<mat-select matTooltip="{{ 'tooltips.Loan products may be assigned' | translate }}" formControlName="fundId">
@for (fund of fundData; track fund) {
<mat-option [value]="fund.id">
{{ fund.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="lpd-field">
<mat-label>{{ 'labels.inputs.Fund' | translate }}</mat-label>
<mat-select matTooltip="{{ 'tooltips.Loan products may be assigned' | translate }}" formControlName="fundId">
@for (fund of fundData; track fund) {
<mat-option [value]="fund.id">
{{ fund.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-checkbox
labelPosition="before"
matTooltip="{{ 'tooltips.A borrower loan counter (cycle)' | translate }}"
formControlName="includeInBorrowerCycle"
class="flex-48"
>
{{ 'labels.inputs.Include in Customer Loan Counter' | translate }}
</mat-checkbox>
<mat-form-field class="flex-48" (click)="startDatePicker.open()">
<mat-label>{{ 'labels.inputs.Start Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="startDatePicker"
matTooltip="{{ 'tooltips.loan product will be active and available to clients' | translate }}"
formControlName="startDate"
/>
<mat-datepicker-toggle matSuffix [for]="startDatePicker"></mat-datepicker-toggle>
<mat-datepicker #startDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field class="flex-48" (click)="closeDatePicker.open()">
<mat-label>{{ 'labels.inputs.Close Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="closeDatePicker"
matTooltip="{{ 'tooltips.loan product will become inactive and unavailable to clients' | translate }}"
formControlName="closeDate"
/>
<mat-datepicker-toggle matSuffix [for]="closeDatePicker"></mat-datepicker-toggle>
<mat-datepicker #closeDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field class="flex-98">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea
matInput
matTooltip="{{ 'tooltips.Provide additional information' | translate }}"
formControlName="description"
cdkTextareaAutosize
cdkAutosizeMinRows="2"
></textarea>
</mat-form-field>
<div class="lpd-field lpd-checkbox-row">
<mat-checkbox
labelPosition="before"
matTooltip="{{ 'tooltips.A borrower loan counter (cycle)' | translate }}"
formControlName="includeInBorrowerCycle"
>
{{ 'labels.inputs.Include in Customer Loan Counter' | translate }}
</mat-checkbox>
</div>
</div>
</div>
<div class="layout-row align-center gap-2percent margin-t responsive-column">
<button mat-raised-button matStepperPrevious disabled>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button matStepperNext>
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
<div class="lpd-section">
<div class="lpd-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>{{ 'labels.inputs.Availability' | translate }}</h3>
</div>
<div class="lpd-grid">
<mat-form-field appearance="outline" class="lpd-field" (click)="startDatePicker.open()">
<mat-label>{{ 'labels.inputs.Start Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="startDatePicker"
matTooltip="{{ 'tooltips.loan product will be active and available to clients' | translate }}"
formControlName="startDate"
/>
<mat-datepicker-toggle matSuffix [for]="startDatePicker"></mat-datepicker-toggle>
<mat-datepicker #startDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline" class="lpd-field" (click)="closeDatePicker.open()">
<mat-label>{{ 'labels.inputs.Close Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="closeDatePicker"
matTooltip="{{ 'tooltips.loan product will become inactive and unavailable to clients' | translate }}"
formControlName="closeDate"
/>
<mat-datepicker-toggle matSuffix [for]="closeDatePicker"></mat-datepicker-toggle>
<mat-datepicker #closeDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline" class="lpd-field lpd-field--full">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea
matInput
matTooltip="{{ 'tooltips.Provide additional information' | translate }}"
formControlName="description"
cdkTextareaAutosize
cdkAutosizeMinRows="2"
></textarea>
</mat-form-field>
</div>
</div>
</form>
@@ -6,6 +6,49 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.margin-t {
margin-top: 1em;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-900: #202124;
.lpd-section {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & { margin-top: 16px; }
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i { font-size: 14px; color: $accent; }
h3 { margin: 0; font-size: 14px; font-weight: 600; color: $gray-900; }
}
}
.lpd-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.lpd-field {
width: 100%;
&--full { grid-column: 1 / -1; }
}
.lpd-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
@@ -16,7 +16,6 @@ import { SettingsService } from 'app/settings/settings.service';
import { MatTooltip } from '@angular/material/tooltip';
import { MatCheckbox } from '@angular/material/checkbox';
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -29,9 +28,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatTooltip,
MatCheckbox,
CdkTextareaAutosize,
MatStepperPrevious,
FaIconComponent,
MatStepperNext
FaIconComponent
]
})
export class LoanProductDetailsStepComponent implements OnInit {
@@ -7,22 +7,28 @@
-->
<form [formGroup]="loanProductInterestRefundForm">
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Supported Interest Refund Types' | translate }}</mat-label>
<mat-select
multiple
formControlName="supportedInterestRefundTypes"
matTooltip="{{
'tooltips.Refund transactions where interest refund will automatically be calculated' | translate
}}"
>
@for (supportedTransaction of supportedInterestRefundTypesOptions; track supportedTransaction) {
<mat-option [value]="supportedTransaction.id">
{{ supportedTransaction.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="lpir-section">
<div class="lpir-section__header">
<i class="fas fa-undo"></i>
<h3>{{ 'labels.inputs.Supported Interest Refund Types' | translate }}</h3>
</div>
<div class="lpir-grid">
<mat-form-field appearance="outline" class="lpir-field">
<mat-label>{{ 'labels.inputs.Supported Interest Refund Types' | translate }}</mat-label>
<mat-select
multiple
formControlName="supportedInterestRefundTypes"
matTooltip="{{
'tooltips.Refund transactions where interest refund will automatically be calculated' | translate
}}"
>
@for (supportedTransaction of supportedInterestRefundTypesOptions; track supportedTransaction) {
<mat-option [value]="supportedTransaction.id">
{{ supportedTransaction.value | translateKey: 'catalogs' }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
</form>
@@ -6,6 +6,40 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.margin-t {
margin-top: 1em;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-900: #202124;
.lpir-section {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
background: #fff;
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i { font-size: 14px; color: $accent; }
h3 { margin: 0; font-size: 14px; font-weight: 600; color: $gray-900; }
}
}
.lpir-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.lpir-field {
width: 100%;
}
@@ -16,16 +16,3 @@
[supportedInterestRefundTypes]="loanProduct.supportedInterestRefundTypes"
>
</mifosx-loan-product-summary>
<div class="layout-row responsive-column align-center gap-2px margin-t">
<button mat-raised-button matStepperPrevious>
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<button mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!isValid" (click)="submitEvent.emit()">
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
@@ -9,7 +9,6 @@
import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { LoanProducts } from '../../loan-products';
import { LoanProductSummaryComponent } from '../../common/loan-product-summary/loan-product-summary.component';
import { MatStepperPrevious } from '@angular/material/stepper';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -20,7 +19,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
LoanProductSummaryComponent,
MatStepperPrevious,
FaIconComponent
]
})
@@ -8,32 +8,79 @@
@use 'assets/styles/colours' as *;
h3,
h4 {
font-weight: 500;
}
h4 {
margin: 1em 0;
}
mat-divider {
margin: 1em 0 2em;
}
.margin-v {
margin: 1em 0;
}
.margin-b {
margin: 0 0 1em;
}
.margin-t {
margin-top: 1em;
}
.by-default {
color: rgba($status-approved, 0.7);
font-weight: 700;
}
.lps-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
}
&__icon {
color: #0ea5e9;
font-size: 16px;
}
&__title {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
&__body {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
}
.lps-full-width {
grid-column: 1 / -1;
}
.lps-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
.lps-question-icon {
font-size: 12px;
color: #9aa0a6;
cursor: help;
}
.lps-overrides-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
@@ -21,10 +21,8 @@ import { CodeName, OptionData, StringEnumOptionData } from 'app/shared/models/op
import { ProcessingStrategyService } from '../../services/processing-strategy.service';
import { MatTooltip } from '@angular/material/tooltip';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatDivider } from '@angular/material/divider';
import { MatButton, MatIconButton } from '@angular/material/button';
import { MatIconButton } from '@angular/material/button';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -35,11 +33,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
MatCheckbox,
MatDivider,
MatIconButton,
FaIconComponent,
MatStepperPrevious,
MatStepperNext
FaIconComponent
]
})
export class LoanProductSettingsStepComponent implements OnInit {
@@ -6,27 +6,130 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
width: 100%;
.lpt-section {
border: 1px solid #e2e8f0;
border-radius: 10px;
overflow: hidden;
background: #fff;
& + & {
margin-top: 16px;
}
}
.mat-elevation-z1 {
margin: 1em 0 1.5em;
.lpt-section-header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: #f8f9fa;
border-bottom: 1px solid #e8eaed;
h3 {
font-size: 14px;
font-weight: 600;
color: #202124;
margin: 0;
}
}
h3,
h4 {
.lpt-section-icon {
color: #0ea5e9;
font-size: 15px;
}
.lpt-help-icon {
color: #9aa0a6;
font-size: 13px;
cursor: help;
}
.lpt-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.lpt-grid-3 {
grid-template-columns: repeat(3, 1fr);
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.lpt-grid-4 {
grid-template-columns: repeat(4, 1fr);
@media (max-width: 900px) {
grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.lpt-full-width {
grid-column: 1 / -1;
}
.lpt-checkbox-row {
display: flex;
align-items: center;
padding: 4px 0;
}
.lpt-subsection {
padding: 0 24px 16px;
&:first-of-type {
padding-top: 0;
}
}
.lpt-subsection-title {
font-size: 13px;
font-weight: 600;
color: #3c4043;
margin: 8px 0 12px;
display: flex;
align-items: center;
gap: 8px;
}
.lpt-subsection-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.lpt-subsection-title {
margin: 0;
}
}
.lpt-table-wrapper {
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
margin-bottom: 16px;
table {
width: 100%;
}
}
.lpt-label-field {
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
}
h4 {
margin: 1em 0;
}
mat-divider {
margin: 1em 0 2em;
}
.margin-t {
margin-top: 1em;
color: #3c4043;
padding: 8px 0;
}
@@ -27,7 +27,6 @@ import { ProcessingStrategyService } from '../../services/processing-strategy.se
import { TranslateService } from '@ngx-translate/core';
import { MatTooltip } from '@angular/material/tooltip';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatDivider } from '@angular/material/divider';
import { MatButton, MatIconButton } from '@angular/material/button';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import {
@@ -42,7 +41,6 @@ import {
MatRowDef,
MatRow
} from '@angular/material/table';
import { MatStepperPrevious, MatStepperNext } from '@angular/material/stepper';
import { FindPipe } from '../../../../pipes/find.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -54,7 +52,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
MatCheckbox,
MatDivider,
FaIconComponent,
MatTable,
MatColumnDef,
@@ -67,8 +64,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
MatHeaderRow,
MatRowDef,
MatRow,
MatStepperPrevious,
MatStepperNext,
FindPipe
]
})
@@ -23,8 +23,9 @@
<div class="container">
<div #filter class="layout-row">
<mat-form-field class="flex-fill">
<mat-form-field appearance="outline" class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<i matPrefix class="fas fa-search"></i>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
</div>
@@ -6,12 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="db-container">
<!-- Header -->
<div class="db-header">
<button mat-stroked-button [routerLink]="['../']" class="db-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Delinquency Buckets' | translate }}
</button>
<h2 class="db-header__title">{{ 'labels.buttons.Create Delinquency Bucket' | translate }}</h2>
</div>
<!-- Form Card -->
<div class="db-card">
<form [formGroup]="bucketForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-100">
<!-- Bucket Details Section -->
<div class="db-section">
<div class="db-section__header">
<i class="fas fa-layer-group"></i>
<h3>{{ 'labels.heading.Delinquency Buckets' | translate }}</h3>
</div>
<div class="db-grid">
<mat-form-field appearance="outline" class="db-field db-field--full">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (bucketForm.controls.name.hasError('required')) {
@@ -21,17 +35,21 @@
</mat-error>
}
</mat-form-field>
</div>
</div>
<h3 class="mat-h3 flex-40">{{ 'labels.heading.Delinquency Ranges' | translate }}</h3>
<div class="flex-40 layout-row align-start-center">
<button type="button" mat-raised-button color="primary" (click)="addDelinquencyRange()">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
<table mat-table [dataSource]="rangesDataSource" [hidden]="rangesDataSource.length === 0">
<!-- Delinquency Ranges Section -->
<div class="db-section">
<div class="db-section__header">
<i class="fas fa-sliders-h"></i>
<h3>{{ 'labels.heading.Delinquency Ranges' | translate }}</h3>
<button type="button" mat-flat-button color="primary" class="db-add-btn" (click)="addDelinquencyRange()">
<fa-icon icon="plus"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
<div class="db-ranges-body">
<table mat-table [dataSource]="rangesDataSource" [hidden]="rangesDataSource.length === 0" class="db-ranges-table">
<ng-container matColumnDef="classification">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Classification' | translate }}</th>
<td mat-cell *matCellDef="let row">
@@ -72,22 +90,32 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
@if (rangesDataSource.length === 0) {
<div class="db-empty">
<i class="fas fa-inbox"></i>
<p>No delinquency ranges added yet</p>
</div>
}
</div>
</div>
<!-- Actions -->
<div class="db-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="db-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!bucketForm.valid"
*mifosxHasPermission="'CREATE_DELINQUENCY_BUCKET'"
class="db-actions__submit"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,13 +6,208 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.content {
div {
margin: 0.8rem 0;
word-wrap: break-word;
.db-container {
max-width: 960px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.db-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.db-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.db-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.db-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.db-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.db-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
// Add button in section header
.db-add-btn {
margin-left: auto !important;
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 30px !important;
padding: 0 14px !important;
box-shadow: 0 1px 3px rgba($primary, 0.25) !important;
fa-icon { margin-right: 6px; font-size: 11px; }
}
// Ranges table
.db-ranges-body {
padding: 16px 24px;
}
.db-ranges-table {
width: 100%;
border: 1px solid $border;
border-radius: 8px;
overflow: hidden;
::ng-deep {
.mat-mdc-header-row {
background: #f8fafc;
min-height: 36px;
height: 36px;
}
.mat-mdc-header-cell {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #64748b;
border-bottom: 1px solid $border;
padding: 0 14px;
}
.mat-mdc-row {
min-height: 40px;
height: 40px;
border-bottom: 1px solid #f1f5f9;
&:last-child { border-bottom: none; }
}
.mat-mdc-cell {
font-size: 13px;
font-weight: 400;
color: $gray-900;
padding: 0 14px;
}
}
}
// Empty state
.db-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px 16px;
text-align: center;
i {
font-size: 28px;
color: #cbd5e1;
margin-bottom: 10px;
}
p {
font-size: 13px;
color: #94a3b8;
margin: 0;
}
}
// Actions
.db-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,37 +6,47 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button
mat-raised-button
color="primary"
[routerLink]="['create']"
*mifosxHasPermission="'CREATE_DELINQUENCY_BUCKET'"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Delinquency Bucket' | translate }}
</button>
</div>
<div class="container">
<div class="layout-row">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-layer-group title-icon"></i>
{{ 'labels.heading.Delinquency Buckets' | translate }}
</h2>
</div>
<div class="table-actions">
<div class="table-search">
<mat-form-field appearance="outline">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
</div>
<div class="table-buttons">
<button
mat-flat-button
color="primary"
class="btn-create"
[routerLink]="['create']"
*mifosxHasPermission="'CREATE_DELINQUENCY_BUCKET'"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Delinquency Bucket' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Name' | translate }}</th>
<td mat-cell *matCellDef="let delinquencyBucket">{{ delinquencyBucket.name }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
@@ -6,23 +6,4 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
.action-button {
margin-left: auto;
}
.search-button {
min-width: 30px;
margin: 4px;
}
}
table {
tr.select-row:hover {
cursor: pointer;
}
tr:nth-child(odd).td {
color: black;
}
}
@use '../../../shared/styles/list-layout' as *;
@@ -6,12 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="db-container">
<!-- Header -->
<div class="db-header">
<button mat-stroked-button [routerLink]="['../']" class="db-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Delinquency Buckets' | translate }}
</button>
<h2 class="db-header__title">{{ 'labels.buttons.Edit' | translate }} {{ delinquencyBucketData.name }}</h2>
</div>
<!-- Form Card -->
<div class="db-card">
<form [formGroup]="bucketForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-100">
<!-- Bucket Details Section -->
<div class="db-section">
<div class="db-section__header">
<i class="fas fa-layer-group"></i>
<h3>{{ 'labels.heading.Delinquency Buckets' | translate }}</h3>
</div>
<div class="db-grid">
<mat-form-field appearance="outline" class="db-field db-field--full">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (bucketForm.controls.name.hasError('required')) {
@@ -21,39 +35,34 @@
</mat-error>
}
</mat-form-field>
</div>
</div>
<h3 class="mat-h3 flex-40">{{ 'labels.heading.Delinquency Ranges' | translate }}</h3>
<div class="flex-40 layout-row align-start-center">
<button type="button" mat-raised-button color="primary" (click)="addDelinquencyRange()">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
<table mat-table [dataSource]="rangesDataSource" [hidden]="rangesDataSource.length === 0">
<!-- Delinquency Ranges Section -->
<div class="db-section">
<div class="db-section__header">
<i class="fas fa-sliders-h"></i>
<h3>{{ 'labels.heading.Delinquency Ranges' | translate }}</h3>
<button type="button" mat-flat-button color="primary" class="db-add-btn" (click)="addDelinquencyRange()">
<fa-icon icon="plus"></fa-icon>
{{ 'labels.buttons.Add' | translate }}
</button>
</div>
<div class="db-ranges-body">
<table mat-table [dataSource]="rangesDataSource" [hidden]="rangesDataSource.length === 0" class="db-ranges-table">
<ng-container matColumnDef="classification">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Classification' | translate }}</th>
<td mat-cell *matCellDef="let row">{{ row.classification }}</td>
<td mat-cell *matCellDef="let row">
{{ row.rangeId | find: delinquencyRangesData : 'id' : 'classification' }}
</td>
</ng-container>
<ng-container matColumnDef="minimumAgeDays">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Days From' | translate }}</th>
<td mat-cell *matCellDef="let row">{{ row.minimumAgeDays }}</td>
<td mat-cell *matCellDef="let row">
{{ row.rangeId | find: delinquencyRangesData : 'id' : 'minimumAgeDays' }}
</td>
</ng-container>
<ng-container matColumnDef="maximumAgeDays">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Days Till' | translate }}</th>
<td mat-cell *matCellDef="let row">{{ row.maximumAgeDays }}</td>
<td mat-cell *matCellDef="let row">
{{ row.rangeId | find: delinquencyRangesData : 'id' : 'maximumAgeDays' }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
@@ -75,22 +84,32 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
@if (rangesDataSource.length === 0) {
<div class="db-empty">
<i class="fas fa-inbox"></i>
<p>No delinquency ranges added yet</p>
</div>
}
</div>
</div>
<!-- Actions -->
<div class="db-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="db-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!dataWasChanged"
*mifosxHasPermission="'UPDATE_DELINQUENCY_BUCKET'"
class="db-actions__submit"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,13 +6,208 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.content {
div {
margin: 0.8rem 0;
word-wrap: break-word;
.db-container {
max-width: 960px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.db-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.db-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.db-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.db-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.db-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.db-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
// Add button in section header
.db-add-btn {
margin-left: auto !important;
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 30px !important;
padding: 0 14px !important;
box-shadow: 0 1px 3px rgba($primary, 0.25) !important;
fa-icon { margin-right: 6px; font-size: 11px; }
}
// Ranges table
.db-ranges-body {
padding: 16px 24px;
}
.db-ranges-table {
width: 100%;
border: 1px solid $border;
border-radius: 8px;
overflow: hidden;
::ng-deep {
.mat-mdc-header-row {
background: #f8fafc;
min-height: 36px;
height: 36px;
}
.mat-mdc-header-cell {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #64748b;
border-bottom: 1px solid $border;
padding: 0 14px;
}
.mat-mdc-row {
min-height: 40px;
height: 40px;
border-bottom: 1px solid #f1f5f9;
&:last-child { border-bottom: none; }
}
.mat-mdc-cell {
font-size: 13px;
font-weight: 400;
color: $gray-900;
padding: 0 14px;
}
}
}
// Empty state
.db-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px 16px;
text-align: center;
i {
font-size: 28px;
color: #cbd5e1;
margin-bottom: 10px;
}
p {
font-size: 13px;
color: #94a3b8;
margin: 0;
}
}
// Actions
.db-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,63 +6,78 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row layout-lt-md-column align-end gap-2percent">
<button mat-raised-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_DELINQUENCY_BUCKET'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
<button
mat-raised-button
color="warn"
(click)="deleteDelinquencyBucket()"
*mifosxHasPermission="'DELETE_DELINQUENCY_BUCKET'"
>
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Delete' | translate }}
</button>
</div>
<div class="vb-container">
<!-- Header -->
<div class="vb-header">
<div class="vb-header__left">
<button mat-stroked-button [routerLink]="['../']" class="vb-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Delinquency Buckets' | translate }}
</button>
<h2 class="vb-header__title">{{ delinquencyBucketData.name }}</h2>
</div>
<div class="vb-header__actions">
<button mat-flat-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_DELINQUENCY_BUCKET'" class="vb-header__edit">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
<button
mat-stroked-button
color="warn"
(click)="deleteDelinquencyBucket()"
*mifosxHasPermission="'DELETE_DELINQUENCY_BUCKET'"
class="vb-header__delete"
>
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Delete' | translate }}
</button>
</div>
</div>
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-100 layout-row m-b-10">
<div class="flex-30 mat-body-strong">
{{ 'labels.inputs.Name' | translate }}
</div>
<div class="flex-70">
{{ delinquencyBucketData.name }}
</div>
</div>
<div class="flex-60 mat-body-strong">
{{ 'labels.inputs.Classification' | translate }}
{{ 'labels.inputs.Name' | translate }}
</div>
<div class="mat-body-strong flex-20">
{{ 'labels.inputs.Days From' | translate }}
</div>
<div class="mat-body-strong flex-20">
{{ 'labels.inputs.Days Till' | translate }}
<!-- Detail Card -->
<div class="vb-card">
<!-- Bucket Info Section -->
<div class="vb-section">
<div class="vb-section__header">
<i class="fas fa-layer-group"></i>
<h3>{{ 'labels.heading.Delinquency Buckets' | translate }}</h3>
</div>
<div class="vb-section__body">
<div class="vb-row">
<span class="vb-row__label">{{ 'labels.inputs.Name' | translate }}</span>
<span class="vb-row__value">{{ delinquencyBucketData.name }}</span>
</div>
</div>
</div>
@for (range of delinquencyBucketData.ranges; track range) {
<div class="layout-row-wrap bucket-range">
<div class="flex-60">
{{ range.classification }}
<!-- Ranges Section -->
<div class="vb-section">
<div class="vb-section__header">
<i class="fas fa-sliders-h"></i>
<h3>{{ 'labels.heading.Delinquency Ranges' | translate }}</h3>
</div>
<div class="vb-section__body vb-section__body--no-pad">
@if (delinquencyBucketData.ranges && delinquencyBucketData.ranges.length > 0) {
<div class="vb-ranges-table">
<div class="vb-ranges-header">
<span class="vb-ranges-cell vb-ranges-cell--name">{{ 'labels.inputs.Classification' | translate }} {{ 'labels.inputs.Name' | translate }}</span>
<span class="vb-ranges-cell vb-ranges-cell--num">{{ 'labels.inputs.Days From' | translate }}</span>
<span class="vb-ranges-cell vb-ranges-cell--num">{{ 'labels.inputs.Days Till' | translate }}</span>
</div>
@for (range of delinquencyBucketData.ranges; track range) {
<div class="vb-ranges-row">
<span class="vb-ranges-cell vb-ranges-cell--name">{{ range.classification }}</span>
<span class="vb-ranges-cell vb-ranges-cell--num">{{ range.minimumAgeDays }}</span>
<span class="vb-ranges-cell vb-ranges-cell--num">{{ range.maximumAgeDays }}</span>
</div>
}
</div>
<div class="flex-20 r-value">
{{ range.minimumAgeDays }}
} @else {
<div class="vb-empty">
<i class="fas fa-inbox"></i>
<p>No delinquency ranges configured</p>
</div>
<div class="flex-20 r-value">
{{ range.maximumAgeDays }}
</div>
</div>
}
</mat-card-content>
</mat-card>
}
</div>
</div>
</div>
</div>
@@ -6,25 +6,210 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.content {
div {
margin: 0.8rem 0;
word-wrap: break-word;
}
.vb-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.vb-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-bottom: 20px;
flex-wrap: wrap;
&__left {
display: flex;
align-items: center;
gap: 14px;
}
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
&__actions {
display: flex;
gap: 8px;
}
&__edit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
}
&__delete {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
}
}
.bucket-range {
height: 2rem;
vertical-align: middle;
.vb-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.r-value {
margin: 0 !important;
padding: 0 !important;
text-align: right !important;
padding-right: 35px !important;
// Card
.vb-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.vb-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
&__body {
padding: 16px 24px;
&--no-pad {
padding: 0;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Detail rows
.vb-row {
display: flex;
padding: 10px 0;
border-bottom: 1px solid #f1f3f4;
&:last-child { border-bottom: none; }
&__label {
flex: 0 0 40%;
font-weight: 500;
font-size: 13px;
color: $gray-700;
}
&__value {
flex: 1;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
// Ranges table
.vb-ranges-table {
width: 100%;
}
.vb-ranges-header {
display: flex;
background: #f8fafc;
border-bottom: 1px solid $border;
padding: 8px 24px;
.vb-ranges-cell {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #64748b;
}
}
.vb-ranges-row {
display: flex;
padding: 10px 24px;
border-bottom: 1px solid #f1f5f9;
transition: background 0.1s;
&:last-child { border-bottom: none; }
&:hover { background: #f8fafc; }
.vb-ranges-cell {
font-size: 13px;
font-weight: 400;
color: $gray-900;
}
}
.vb-ranges-cell {
&--name {
flex: 3;
}
&--num {
flex: 1;
text-align: right;
}
}
// Empty state
.vb-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px 16px;
text-align: center;
i {
font-size: 28px;
color: #cbd5e1;
margin-bottom: 10px;
}
p {
font-size: 13px;
color: #94a3b8;
margin: 0;
}
}
@@ -6,56 +6,63 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="delinquencyRangeForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<mat-label>{{ 'labels.inputs.Classification' | translate }}</mat-label>
<input matInput required formControlName="classification" />
@if (delinquencyRangeForm.controls.classification.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Classification' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="cr-container">
<div class="cr-header">
<i class="fas fa-plus-circle cr-header__icon"></i>
<h2 class="cr-header__title">{{ 'labels.text.Create Delinquency Range' | translate }}</h2>
</div>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Days From' | translate }}</mat-label>
<input matInput type="number" required formControlName="minimumAgeDays" min="0" />
@if (delinquencyRangeForm.controls.minimumAgeDays.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Days From' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (delinquencyRangeForm.controls.minimumAgeDays.hasError('pattern')) {
<mat-error> {{ 'labels.inputs.Days From' | translate }} must be a positive number. </mat-error>
}
</mat-form-field>
<form [formGroup]="delinquencyRangeForm" (ngSubmit)="submit()">
<div class="cr-section">
<div class="cr-section__header">
<i class="fa fa-layer-group"></i>
<h3>{{ 'labels.inputs.Range Details' | translate }}</h3>
</div>
<div class="cr-grid">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Classification' | translate }}</mat-label>
<input matInput required formControlName="classification" />
@if (delinquencyRangeForm.controls.classification.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Classification' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Days Till' | translate }}</mat-label>
<input matInput type="number" required formControlName="maximumAgeDays" min="1" />
</mat-form-field>
</div>
</mat-card-content>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Days From' | translate }}</mat-label>
<input matInput type="number" required formControlName="minimumAgeDays" min="0" />
@if (delinquencyRangeForm.controls.minimumAgeDays.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Days From' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (delinquencyRangeForm.controls.minimumAgeDays.hasError('pattern')) {
<mat-error> {{ 'labels.inputs.Days From' | translate }} must be a positive number. </mat-error>
}
</mat-form-field>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
color="primary"
[disabled]="!delinquencyRangeForm.valid"
*mifosxHasPermission="'CREATE_DELINQUENCY_RANGE'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Days Till' | translate }}</mat-label>
<input matInput type="number" required formControlName="maximumAgeDays" min="1" />
</mat-form-field>
</div>
</div>
<div class="cr-action-bar">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
color="primary"
[disabled]="!delinquencyRangeForm.valid"
*mifosxHasPermission="'CREATE_DELINQUENCY_RANGE'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</form>
</div>
@@ -6,13 +6,34 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
.cr-container { max-width: 960px; margin: 0 auto; padding: 24px 16px; }
.content {
div {
margin: 0.8rem 0;
word-wrap: break-word;
}
.cr-header {
display: flex; align-items: center; gap: 10px; margin-bottom: 20px;
&__icon { font-size: 20px; color: #1353a0; }
&__title { margin: 0; font-size: 22px; font-weight: 500; }
}
.cr-section {
border: 1px solid #e2e8f0; border-radius: 10px; overflow: hidden; background: #fff;
&__header {
display: flex; align-items: center; gap: 10px; padding: 12px 24px;
background: #f8f9fa; border-bottom: 1px solid #e8eaed;
i { font-size: 14px; color: #0ea5e9; }
h3 { margin: 0; font-size: 14px; font-weight: 600; color: #202124; }
}
}
.cr-grid {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px;
}
.cr-action-bar {
display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px; padding: 16px 0;
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
@media (max-width: 600px) {
.cr-grid { grid-template-columns: 1fr; }
}

Some files were not shown because too many files have changed in this diff Show More