feat: letter-mgmt module + digital UPI/IMPS/ATM-POS reports + header redesign

letter-mgmt (new module):
- Notice page: overdue loan list with filters, KPI strip, bulk actions,
  call disposition logging (Promise-to-Pay, Not Reachable, etc.), Speed
  Post dispatch dialog with consignment + post-office tracking,
  PDF preview + download, call-contact dialog with primary / alt /
  family / nominee numbers and per-contact disposition history.
- Certificates: Loan Interest, Share, Balance, Form 60 (no-PAN flow),
  Form 15G/H (auto age-based), Hypothecation, NOC - each with shared
  preview dialog (six tone variants) and jsPDF generator using a
  common letterhead / summary-grid / signature-seal helper.

digital:
- IMPS module: report (P2A/P2P/Aadhaar tabs) + entry form with
  MMID/mobile lookup, NPCI switch resolve-to-A/C+IFSC BANL flow,
  sender autocomplete, hide sender field for P2P/Aadhaar, charges +
  18% GST + waive checkbox, per-sender today-only history dialog.
- UPI report with app-mix tab and full transaction filtering.
- ATM/POS/ECOM hub with three ticket cards routing to today/status/cycle.
- NEFT/RTGS entry: sender-account autocomplete, charges + GST + waive,
  field reorder (sender before amount), NEFT cap / RTGS floor
  validation, History button opening per-sender today's transactions.
- NEFT/RTGS report: horizontal-bar trend leaderboard replacing tall bars.

shell + theme:
- Header redesigned with IBM Plex Sans 12/500 typography, 11-column grid
  per row, top-accent hover indicator, bordered cells via grid-gap.
- Live KPI ticker below menu (CASH, TXN TODAY, CLEARING IN/OUT, NPA,
  CASA, CD RATIO, RBI CIRC, REPO) with marquee scroll, pause-on-hover,
  brand-blue gradient, amber bottom strip, pulsing red LIVE chip.
- Sidebar 'KYC Management' -> 'KYC & ReKYC Management'.
- Letter Mgmt / certificate menu items wired with green icon hints for
  items that have a working page.

access-control:
- MFA card layout compacted; method header inlines back button + title.

icons:
- Register faTruck/TruckFast, faEllipsisV/H, faCommentDots.
- Add IBM Plex Sans Google Fonts import to index.html.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-05-19 10:09:06 +05:30
co-authored by Claude Opus 4.7
parent 4322f37c6d
commit 325228c514
138 changed files with 27473 additions and 3997 deletions
@@ -6,41 +6,62 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="activateClientForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field (click)="activationDatePicker.open()">
<mat-label>{{ 'labels.inputs.Activated On Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="activationDatePicker"
required
formControlName="activationDate"
/>
<mat-datepicker-toggle matSuffix [for]="activationDatePicker"></mat-datepicker-toggle>
<mat-datepicker #activationDatePicker></mat-datepicker>
@if (activateClientForm.controls.activationDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Activated On Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<div class="ca-root">
<!-- HERO -->
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="check-circle"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.heading.Activate Client' | translate }}</h2>
<p class="ca-hero-sub">
Activating moves this client from <strong>Pending</strong> to <strong>Active</strong>. Once active, the client
can hold accounts, take loans and transact normally.
</p>
</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]="!activateClientForm.valid">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<form [formGroup]="activateClientForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline" (click)="activationDatePicker.open()">
<mat-label>{{ 'labels.inputs.Activated On Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="activationDatePicker"
required
formControlName="activationDate"
/>
<mat-datepicker-toggle matSuffix [for]="activationDatePicker"></mat-datepicker-toggle>
<mat-datepicker #activationDatePicker></mat-datepicker>
@if (activateClientForm.controls.activationDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Activated On Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
<div class="ca-notice">
<fa-icon icon="info-circle"></fa-icon>
<span>
The activation date must be on or before today's business date. After confirmation, account opening and
onboarding workflows are unlocked for this client.
</span>
</div>
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon>
<span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!activateClientForm.valid">
<fa-icon icon="check"></fa-icon>
<span>{{ 'labels.buttons.Confirm' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 30rem;
@use 'assets/styles/client-action' as ca;
@include ca.client-action-styles;
/* Activate-specific green accent (success / go) */
:host {
--ca-accent: #15803d;
--ca-accent-l: #dcfce7;
}
@@ -15,6 +15,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ClientsService } from 'app/clients/clients.service';
import { Dates } from 'app/core/utils/dates';
import { SettingsService } from 'app/settings/settings.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -25,7 +26,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './activate-client.component.html',
styleUrls: ['./activate-client.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
]
})
export class ActivateClientComponent implements OnInit {
@@ -6,121 +6,150 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="clientChargeForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
<mat-select required formControlName="chargeId">
@for (clientCharge of clientChargeOptions; track clientCharge) {
<mat-option [value]="clientCharge.id">
{{ clientCharge.name + ' (' + clientCharge.currency.name + ')' }}
</mat-option>
}
</mat-select>
@if (clientChargeForm.controls.chargeId.hasError('required')) {
<div class="ca-root">
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="receipt"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.buttons.Add Charge' | translate }}</h2>
<p class="ca-hero-sub">
Attach a one-time fee or a recurring charge to this client. The charge can be linked to a specific account or
held at the client level for ad-hoc collection.
</p>
</div>
</div>
<form [formGroup]="clientChargeForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
<mat-select required formControlName="chargeId">
@for (clientCharge of clientChargeOptions; track clientCharge) {
<mat-option [value]="clientCharge.id">{{
clientCharge.name + ' (' + clientCharge.currency.name + ')'
}}</mat-option>
}
</mat-select>
@if (clientChargeForm.controls.chargeId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
@if (chargeDetails) {
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input type="number" required matInput formControlName="amount" />
@if (clientChargeForm.controls.amount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Charge' | translate }} {{ 'labels.commons.is' | translate }}
{{ 'labels.inputs.Amount' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
@if (chargeDetails) {
<div class="layout-column">
<mat-form-field>
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input type="number" required matInput formControlName="amount" />
@if (clientChargeForm.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>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Charge Calculation' | translate }}</mat-label>
<mat-select formControlName="chargeCalculationType">
@for (chargeCalculation of chargeDetails.chargeCalculationTypeOptions; track chargeCalculation) {
<mat-option [value]="chargeCalculation.id">
{{ chargeCalculation.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Charge time type' | translate }}</mat-label>
<mat-select formControlName="chargeTimeType">
@for (chargeTime of chargeDetails.chargeTimeTypeOptions; track chargeTime) {
<mat-option [value]="chargeTime.id">
{{ chargeTime.value }}
</mat-option>
}
</mat-select>
</mat-form-field>
@if (clientChargeForm.contains('dueDate')) {
<mat-form-field (click)="dueDatePicker.open()">
<mat-label>{{ 'labels.inputs.Due for collection on' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="dueDatePicker"
required
formControlName="dueDate"
/>
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
<mat-datepicker #dueDatePicker></mat-datepicker>
@if (clientChargeForm.controls.dueDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Due for collection on' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (clientChargeForm.contains('feeOnMonthDay')) {
<mat-form-field (click)="feeOnMonthDayPicker.open()">
<mat-label>{{ 'labels.inputs.Due On' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="feeOnMonthDayPicker"
required
formControlName="feeOnMonthDay"
/>
<mat-datepicker-toggle matSuffix [for]="feeOnMonthDayPicker"></mat-datepicker-toggle>
<mat-datepicker #feeOnMonthDayPicker></mat-datepicker>
@if (clientChargeForm.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 (clientChargeForm.contains('feeInterval')) {
<mat-form-field>
<mat-label>{{ 'labels.inputs.Repeats Every' | translate }}</mat-label>
<input matInput formControlName="feeInterval" />
</mat-form-field>
}
</div>
}
</div>
<mat-card-actions class="layout-row layout-xs-column layout-align-center gap-5px">
<button type="button" mat-raised-button [routerLink]="['../../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!clientChargeForm.valid">
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</mat-card-content>
</form>
</mat-card>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge Calculation' | translate }}</mat-label>
<mat-select formControlName="chargeCalculationType">
@for (chargeCalculation of chargeDetails.chargeCalculationTypeOptions; track chargeCalculation) {
<mat-option [value]="chargeCalculation.id">{{ chargeCalculation.value }}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Charge time type' | translate }}</mat-label>
<mat-select formControlName="chargeTimeType">
@for (chargeTime of chargeDetails.chargeTimeTypeOptions; track chargeTime) {
<mat-option [value]="chargeTime.id">{{ chargeTime.value }}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
@if (clientChargeForm.contains('dueDate')) {
<div class="ca-form-row">
<mat-form-field appearance="outline" (click)="dueDatePicker.open()">
<mat-label>{{ 'labels.inputs.Due for collection on' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="dueDatePicker"
required
formControlName="dueDate"
/>
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
<mat-datepicker #dueDatePicker></mat-datepicker>
@if (clientChargeForm.controls.dueDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Due for collection on' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
}
@if (clientChargeForm.contains('feeOnMonthDay')) {
<div class="ca-form-row">
<mat-form-field appearance="outline" (click)="feeOnMonthDayPicker.open()">
<mat-label>{{ 'labels.inputs.Due On' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="feeOnMonthDayPicker"
required
formControlName="feeOnMonthDay"
/>
<mat-datepicker-toggle matSuffix [for]="feeOnMonthDayPicker"></mat-datepicker-toggle>
<mat-datepicker #feeOnMonthDayPicker></mat-datepicker>
@if (clientChargeForm.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>
</div>
}
@if (clientChargeForm.contains('feeInterval')) {
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Repeats Every' | translate }}</mat-label>
<input matInput formControlName="feeInterval" />
</mat-form-field>
</div>
}
<div class="ca-notice">
<fa-icon icon="info-circle"></fa-icon>
<span>
The calculation type and timing determine when this charge is auto-applied (e.g. monthly fee, annual
membership, ad-hoc). Verify the amount and due date before submitting.
</span>
</div>
}
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon><span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!clientChargeForm.valid">
<fa-icon icon="check"></fa-icon><span>{{ 'labels.buttons.Submit' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
width: 37rem;
@use 'assets/styles/client-action' as ca;
@include ca.client-action-styles;
/* Add Charge — amber accent (financial / fee) */
:host {
--ca-accent: #b45309;
--ca-accent-l: #fef3c7;
}
@@ -21,6 +21,7 @@ import { Router, ActivatedRoute, RouterLink } from '@angular/router';
import { ClientsService } from 'app/clients/clients.service';
import { Dates } from 'app/core/utils/dates';
import { SettingsService } from 'app/settings/settings.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -31,7 +32,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './add-client-charge.component.html',
styleUrls: ['./add-client-charge.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
]
})
export class AddClientChargeComponent implements OnInit {
@@ -6,81 +6,118 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="clientCollateralForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<mat-label>{{ 'labels.inputs.Collateral' | translate }}</mat-label>
<mat-select required formControlName="collateralId">
@for (clientCollateral of clientCollateralOptions; track clientCollateral) {
<mat-option [value]="clientCollateral.id">
{{ clientCollateral.name }}
</mat-option>
}
</mat-select>
@if (clientCollateralForm.controls.collateralId.hasError('required')) {
<div class="ca-root">
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="cube"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.buttons.Create Collateral' | translate }}</h2>
<p class="ca-hero-sub">
Record an asset pledged by this client as security for credit (e.g. property, vehicle, deposit). Collateral
values feed loan eligibility and LTV calculations.
</p>
</div>
</div>
<form [formGroup]="clientCollateralForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Collateral' | translate }}</mat-label>
<mat-select required formControlName="collateralId">
@for (clientCollateral of clientCollateralOptions; track clientCollateral) {
<mat-option [value]="clientCollateral.id">{{ clientCollateral.name }}</mat-option>
}
</mat-select>
@if (clientCollateralForm.controls.collateralId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Collateral' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
@if (collateralDetails) {
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.name' | translate }}</mat-label>
<input matInput required formControlName="name" />
</mat-form-field>
</div>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Type/Quality' | translate }}</mat-label>
<input matInput required formControlName="quality" />
</mat-form-field>
</div>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Unit Type' | translate }}</mat-label>
<input matInput required formControlName="unitType" />
</mat-form-field>
</div>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Base Price' | translate }}</mat-label>
<input matInput required formControlName="basePrice" />
</mat-form-field>
</div>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Percentage To Base' | translate }}</mat-label>
<input matInput required formControlName="pctToBase" />
</mat-form-field>
</div>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Quantity' | translate }}</mat-label>
<input matInput required formControlName="quantity" />
@if (clientCollateralForm.controls.quantity.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Collateral' | translate }} {{ 'labels.commons.is' | translate }}
{{ 'labels.inputs.Quantity' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
@if (collateralDetails) {
<div class="layout-column">
<mat-form-field>
<mat-label>{{ 'labels.inputs.name' | translate }}</mat-label>
<input matInput required formControlName="name" />
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Type/Quality' | translate }}</mat-label>
<input matInput required formControlName="quality" />
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Unit Type' | translate }}</mat-label>
<input matInput required formControlName="unitType" />
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Base Price' | translate }}</mat-label>
<input matInput required formControlName="basePrice" />
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Percentage To Base' | translate }}</mat-label>
<input matInput required formControlName="pctToBase" />
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Quantity' | translate }}</mat-label>
<input matInput required formControlName="quantity" />
@if (clientCollateralForm.controls.quantity.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Quantity' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Total' | translate }}</mat-label>
<input matInput required formControlName="totalValue" />
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Total Collateral Value' | translate }}</mat-label>
<input matInput required formControlName="totalCollateralValue" />
</mat-form-field>
</div>
}
</div>
</mat-card-content>
<mat-card-actions class="layout-row layout-xs-column layout-align-center gap-5px">
<button type="button" mat-raised-button [routerLink]="['../../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!clientCollateralForm.valid">
{{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Total' | translate }}</mat-label>
<input matInput required formControlName="totalValue" />
</mat-form-field>
</div>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Total Collateral Value' | translate }}</mat-label>
<input matInput required formControlName="totalCollateralValue" />
</mat-form-field>
</div>
<div class="ca-notice">
<fa-icon icon="info-circle"></fa-icon>
<span>
Total Collateral Value = Base Price × Percentage-to-Base × Quantity. Verify the figures before submitting —
this is used by underwriting and LTV checks.
</span>
</div>
}
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon><span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!clientCollateralForm.valid">
<fa-icon icon="check"></fa-icon><span>{{ 'labels.buttons.Submit' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
width: 37rem;
@use 'assets/styles/client-action' as ca;
@include ca.client-action-styles;
/* Create Collateral — purple accent (asset / security) */
:host {
--ca-accent: #6d28d9;
--ca-accent-l: #ede9fe;
}
@@ -17,6 +17,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ClientsService } from 'app/clients/clients.service';
import { ProductsService } from 'app/products/products.service';
import { SettingsService } from 'app/settings/settings.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -24,7 +25,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './add-client-collateral.component.html',
styleUrls: ['./add-client-collateral.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
]
})
export class AddClientCollateralComponent implements OnInit {
@@ -6,53 +6,72 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20">
<mat-card>
<form [formGroup]="clientScreenReportForm" (ngSubmit)="generate()">
<mat-card-content>
<div>
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Client Screen Reports' | translate }}</mat-label>
<mat-select formControlName="templateId">
@for (template of templatesData; track template) {
<mat-option [value]="template.id">
{{ template.name }}
</mat-option>
}
</mat-select>
@if (clientScreenReportForm.controls.templateId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Screen Report' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<div class="ca-root ca-root--wide">
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="chart-bar"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.buttons.Client Screen Reports' | translate }}</h2>
<p class="ca-hero-sub">
Generate an on-screen client report from one of the configured templates. The rendered output is printable and
can be saved as PDF from the browser print dialog.
</p>
</div>
</div>
<form [formGroup]="clientScreenReportForm" (ngSubmit)="generate()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Client Screen Reports' | translate }}</mat-label>
<mat-select formControlName="templateId">
@for (template of templatesData; track template) {
<mat-option [value]="template.id">{{ template.name }}</mat-option>
}
</mat-form-field>
</div>
</mat-card-content>
</mat-select>
@if (clientScreenReportForm.controls.templateId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Screen Report' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</div>
<mat-card-actions class="layout-row align-center gap-5px actions-spacing responsive-column">
<button type="button" mat-raised-button [routerLink]="['../../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!clientScreenReportForm.valid">
<fa-icon icon="cogs" size="sm" class="m-r-10"></fa-icon>{{ 'labels.buttons.Generate Report' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
</div>
<div #output class="container m-t-20">
<mat-card class="layout-column gap-3percent">
<div class="layout-align-end">
<button mat-raised-button color="primary" [disabled]="!template" (click)="print()">
<fa-icon icon="file" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Print' | translate }}
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon><span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!clientScreenReportForm.valid">
<fa-icon icon="cogs"></fa-icon><span>{{ 'labels.buttons.Generate Report' | translate }}</span>
</button>
</div>
</form>
<div #screenReport class="layout-align-center">
<p>{{ 'labels.text.Please select a screen report.' | translate }}</p>
<div #output class="ca-card">
<div class="ca-card-body">
<div class="csr-output-head">
<div>
<div class="csr-output-eyebrow">Report Output</div>
<div class="csr-output-title">
{{ template ? template.name : ('labels.text.Please select a screen report.' | translate) }}
</div>
</div>
<button type="button" class="ca-btn ca-btn--confirm" [disabled]="!template" (click)="print()">
<fa-icon icon="file"></fa-icon>
<span>{{ 'labels.buttons.Print' | translate }}</span>
</button>
</div>
<div #screenReport class="csr-output-body">
@if (!template) {
<div class="csr-empty">
<div class="csr-empty-ic"><fa-icon icon="chart-bar"></fa-icon></div>
<p>{{ 'labels.text.Please select a screen report.' | translate }}</p>
</div>
}
</div>
</div>
</mat-card>
</div>
</div>
@@ -6,32 +6,79 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
@use 'assets/styles/client-action' as ca;
.print {
align-self: flex-end;
}
@include ca.client-action-styles;
/* Screen Reports — indigo accent (analytics / reporting) */
:host {
--ca-accent: #3730a3;
--ca-accent-l: #e0e7ff;
}
::ng-deep mat-card {
margin-bottom: 1rem;
padding: 1rem;
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
border: 1px solid var(--border-color, #ddd);
border-radius: 8px;
background-color: var(--card-background, #fff);
transition:
background-color 0.3s ease,
border-color 0.3s ease;
/* Wider container for the report output */
.ca-root--wide {
max-width: 1100px;
}
::ng-deep .container.m-t-20 mat-card p {
display: block;
margin-top: -1.9rem;
/* Report output header inside the second card */
.csr-output-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
padding-bottom: 14px;
margin-bottom: 14px;
border-bottom: 1px dashed var(--ca-g200);
flex-wrap: wrap;
}
.actions-spacing {
margin-top: 1rem;
padding: 0 1rem;
.csr-output-eyebrow {
font-size: 10.5px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--ca-g500);
margin-bottom: 2px;
}
.csr-output-title {
font-size: 15px;
font-weight: 700;
color: var(--ca-g900);
}
.csr-output-body {
background: var(--ca-g50);
border: 1px solid var(--ca-g200);
border-radius: 10px;
padding: 18px;
min-height: 140px;
font-size: 13px;
color: var(--ca-g900);
overflow-x: auto;
}
.csr-empty {
text-align: center;
padding: 28px 16px;
}
.csr-empty-ic {
width: 52px;
height: 52px;
margin: 0 auto 10px;
border-radius: 14px;
background: var(--ca-accent-l);
color: var(--ca-accent);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 22px;
}
.csr-empty p {
margin: 0;
font-size: 13px;
color: var(--ca-g500);
}
@@ -6,58 +6,79 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="closeClientForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field (click)="closureDatePicker.open()">
<mat-label>{{ 'labels.inputs.Closed On Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="closureDatePicker"
required
formControlName="closureDate"
/>
<mat-datepicker-toggle matSuffix [for]="closureDatePicker"></mat-datepicker-toggle>
<mat-datepicker #closureDatePicker></mat-datepicker>
@if (closeClientForm.controls.closureDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Closed On Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="ca-root">
<!-- HERO -->
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="times-circle"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.heading.Close Client' | translate }}</h2>
<p class="ca-hero-sub">
Closing the client moves them to a <strong>Closed</strong> state. Outstanding loans or active savings must be
settled or transferred before closure is permitted.
</p>
</div>
</div>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Closure Reason' | translate }}</mat-label>
<mat-select formControlName="closureReasonId">
@for (reason of closureData; track reason) {
<mat-option [value]="reason.id">
{{ reason.name }}
</mat-option>
}
</mat-select>
@if (closeClientForm.controls.closureReasonId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Closure Reason' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<form [formGroup]="closeClientForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline" (click)="closureDatePicker.open()">
<mat-label>{{ 'labels.inputs.Closed On Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="closureDatePicker"
required
formControlName="closureDate"
/>
<mat-datepicker-toggle matSuffix [for]="closureDatePicker"></mat-datepicker-toggle>
<mat-datepicker #closureDatePicker></mat-datepicker>
@if (closeClientForm.controls.closureDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Closed On Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</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="warn" [disabled]="!closeClientForm.valid">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Closure Reason' | translate }}</mat-label>
<mat-select formControlName="closureReasonId">
@for (reason of closureData; track reason) {
<mat-option [value]="reason.id">{{ reason.name }}</mat-option>
}
</mat-select>
@if (closeClientForm.controls.closureReasonId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Closure Reason' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
<div class="ca-notice">
<fa-icon icon="exclamation-triangle"></fa-icon>
<span>
This action is <strong>irreversible</strong>. Once closed, the client cannot transact or open new accounts.
Use <em>Reactivate</em> only if the closure was made in error.
</span>
</div>
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon>
<span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!closeClientForm.valid">
<fa-icon icon="check"></fa-icon>
<span>{{ 'labels.buttons.Confirm' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
@use 'assets/styles/client-action' as ca;
@include ca.client-action-styles;
/* Close-specific red accent (destructive) */
:host {
--ca-accent: #c0392b;
--ca-accent-l: #fee2e2;
}
@@ -15,6 +15,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ClientsService } from 'app/clients/clients.service';
import { Dates } from 'app/core/utils/dates';
import { SettingsService } from 'app/settings/settings.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -25,7 +26,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './close-client.component.html',
styleUrls: ['./close-client.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
]
})
export class CloseClientComponent implements OnInit {
@@ -6,11 +6,23 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="reactivateClientForm" (ngSubmit)="submit()">
<mat-card-content>
<mat-form-field class="flex-fill" (click)="reactivationDatePicker.open()">
<div class="ca-root">
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="redo"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.heading.Reactivate Client' | translate }}</h2>
<p class="ca-hero-sub">
Bring a previously <strong>Closed</strong> client back to <strong>Active</strong>. After reactivation the client
may transact and open accounts again.
</p>
</div>
</div>
<form [formGroup]="reactivateClientForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline" (click)="reactivationDatePicker.open()">
<mat-label>{{ 'labels.inputs.Re Activation On Date' | translate }}</mat-label>
<input
matInput
@@ -29,16 +41,24 @@
</mat-error>
}
</mat-form-field>
</mat-card-content>
</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]="!reactivateClientForm.valid">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<div class="ca-notice">
<fa-icon icon="info-circle"></fa-icon>
<span>
Reactivation re-opens the client record. Closed loans and savings stay closed; new accounts can now be
created.
</span>
</div>
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon><span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!reactivateClientForm.valid">
<fa-icon icon="check"></fa-icon><span>{{ 'labels.buttons.Confirm' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
@use 'assets/styles/client-action' as ca;
@include ca.client-action-styles;
/* Reactivate-specific green accent */
:host {
--ca-accent: #15803d;
--ca-accent-l: #dcfce7;
}
@@ -15,6 +15,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ClientsService } from 'app/clients/clients.service';
import { Dates } from 'app/core/utils/dates';
import { SettingsService } from 'app/settings/settings.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -25,7 +26,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './reactivate-client.component.html',
styleUrls: ['./reactivate-client.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
]
})
export class ReactivateClientComponent implements OnInit {
@@ -6,58 +6,79 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="rejectClientForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field (click)="rejectionDatePicker.open()">
<mat-label>{{ 'labels.inputs.Rejection Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="rejectionDatePicker"
required
formControlName="rejectionDate"
/>
<mat-datepicker-toggle matSuffix [for]="rejectionDatePicker"></mat-datepicker-toggle>
<mat-datepicker #rejectionDatePicker></mat-datepicker>
@if (rejectClientForm.controls.rejectionDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Rejection Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="ca-root">
<!-- HERO -->
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="ban"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.heading.Reject Client' | translate }}</h2>
<p class="ca-hero-sub">
Reject this client application when onboarding cannot be approved (e.g. failed KYC, regulatory hit, document
mismatch). The reason is recorded for audit and compliance.
</p>
</div>
</div>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Rejection Reason' | translate }}</mat-label>
<mat-select formControlName="rejectionReasonId">
@for (reason of rejectionData; track reason) {
<mat-option [value]="reason.id">
{{ reason.name }}
</mat-option>
}
</mat-select>
@if (rejectClientForm.controls.rejectionReasonId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Rejection Reason' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<form [formGroup]="rejectClientForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline" (click)="rejectionDatePicker.open()">
<mat-label>{{ 'labels.inputs.Rejection Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="rejectionDatePicker"
required
formControlName="rejectionDate"
/>
<mat-datepicker-toggle matSuffix [for]="rejectionDatePicker"></mat-datepicker-toggle>
<mat-datepicker #rejectionDatePicker></mat-datepicker>
@if (rejectClientForm.controls.rejectionDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Rejection Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</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="warn" [disabled]="!rejectClientForm.valid">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Rejection Reason' | translate }}</mat-label>
<mat-select formControlName="rejectionReasonId">
@for (reason of rejectionData; track reason) {
<mat-option [value]="reason.id">{{ reason.name }}</mat-option>
}
</mat-select>
@if (rejectClientForm.controls.rejectionReasonId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Rejection Reason' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
<div class="ca-notice">
<fa-icon icon="exclamation-triangle"></fa-icon>
<span>
Rejection moves the client to a <strong>Rejected</strong> state and is final. Use <em>Undo Rejection</em> if
the decision was made in error.
</span>
</div>
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon>
<span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!rejectClientForm.valid">
<fa-icon icon="check"></fa-icon>
<span>{{ 'labels.buttons.Confirm' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
@use 'assets/styles/client-action' as ca;
@include ca.client-action-styles;
/* Reject-specific red accent (final / blocked) */
:host {
--ca-accent: #c0392b;
--ca-accent-l: #fee2e2;
}
@@ -15,6 +15,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ClientsService } from 'app/clients/clients.service';
import { Dates } from 'app/core/utils/dates';
import { SettingsService } from 'app/settings/settings.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -25,7 +26,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './reject-client.component.html',
styleUrls: ['./reject-client.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
]
})
export class RejectClientComponent implements OnInit {
@@ -6,63 +6,92 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="transferClientForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select required formControlName="destinationOfficeId">
@for (office of officeData; track office) {
<mat-option [value]="office.id">
{{ office.name }}
</mat-option>
}
</mat-select>
@if (transferClientForm.controls.destinationOfficeId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Office' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<div class="ca-root">
<!-- HERO -->
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="exchange"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.heading.Transfer Client' | translate }}</h2>
<p class="ca-hero-sub">
Move this client to a different office or branch. The destination office will need to
<strong>accept</strong> the proposed transfer before it takes effect. The client's accounts move with them.
</p>
</div>
</div>
<form [formGroup]="transferClientForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select required formControlName="destinationOfficeId">
@for (office of officeData; track office) {
<mat-option [value]="office.id">{{ office.name }}</mat-option>
}
</mat-form-field>
</mat-select>
@if (transferClientForm.controls.destinationOfficeId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Office' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
<mat-form-field (click)="transferDatePicker.open()">
<mat-label>{{ 'labels.inputs.Transfer Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="transferDatePicker"
required
formControlName="transferDate"
/>
<mat-datepicker-toggle matSuffix [for]="transferDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transferDatePicker></mat-datepicker>
@if (transferClientForm.controls.transferDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Transfer Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="ca-form-row">
<mat-form-field appearance="outline" (click)="transferDatePicker.open()">
<mat-label>{{ 'labels.inputs.Transfer Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="transferDatePicker"
required
formControlName="transferDate"
/>
<mat-datepicker-toggle matSuffix [for]="transferDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transferDatePicker></mat-datepicker>
@if (transferClientForm.controls.transferDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Transfer Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
<textarea matInput formControlName="note" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
</mat-form-field>
</div>
</mat-card-content>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
<textarea
matInput
formControlName="note"
cdkTextareaAutosize
cdkAutosizeMinRows="2"
placeholder="Optional — reason or context for the transfer"
></textarea>
</mat-form-field>
</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]="!transferClientForm.valid">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<div class="ca-notice">
<fa-icon icon="info-circle"></fa-icon>
<span>
The transfer is created as <strong>proposed</strong> until the destination office accepts it. Until then it
can be withdrawn from the client's <em>Actions → Undo Transfer</em> menu.
</span>
</div>
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon>
<span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!transferClientForm.valid">
<fa-icon icon="paper-plane"></fa-icon>
<span>{{ 'labels.buttons.Confirm' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
@use 'assets/styles/client-action' as ca;
@include ca.client-action-styles;
/* Transfer-specific blue accent (informational / routing) */
:host {
--ca-accent: #1d4ed8;
--ca-accent-l: #eff6ff;
}
@@ -16,6 +16,7 @@ import { ClientsService } from 'app/clients/clients.service';
import { Dates } from 'app/core/utils/dates';
import { SettingsService } from 'app/settings/settings.service';
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -27,7 +28,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./transfer-client.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
CdkTextareaAutosize
CdkTextareaAutosize,
FaIconComponent
]
})
export class TransferClientComponent implements OnInit {
@@ -6,11 +6,22 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="undoClientRejectionForm" (ngSubmit)="submit()">
<mat-card-content>
<mat-form-field class="flex-fill" (click)="reopenedDatePicker.open()">
<div class="ca-root">
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="undo"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.heading.Undo Client Rejection' | translate }}</h2>
<p class="ca-hero-sub">
Reverse a prior rejection and re-open this client back to the <strong>Pending</strong> state for further review.
</p>
</div>
</div>
<form [formGroup]="undoClientRejectionForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline" (click)="reopenedDatePicker.open()">
<mat-label>{{ 'labels.inputs.Reopened On Date' | translate }}</mat-label>
<input
matInput
@@ -29,16 +40,24 @@
</mat-error>
}
</mat-form-field>
</mat-card-content>
</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]="!undoClientRejectionForm.valid">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<div class="ca-notice">
<fa-icon icon="info-circle"></fa-icon>
<span
>The client returns to <strong>Pending</strong> — you can then activate, withdraw or re-reject as
appropriate.</span
>
</div>
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon><span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!undoClientRejectionForm.valid">
<fa-icon icon="check"></fa-icon><span>{{ 'labels.buttons.Confirm' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -15,6 +15,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ClientsService } from 'app/clients/clients.service';
import { Dates } from 'app/core/utils/dates';
import { SettingsService } from 'app/settings/settings.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -25,7 +26,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './undo-client-rejection.component.html',
styleUrls: ['./undo-client-rejection.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
]
})
export class UndoClientRejectionComponent implements OnInit {
@@ -6,38 +6,54 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="clientSavingsAccountForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Default Savings Account' | translate }}</mat-label>
<mat-select formControlName="savingsAccountId">
@for (account of savingsAccounts; track account) {
<mat-option [value]="account.id">
{{ account.accountNo }}
</mat-option>
}
</mat-select>
@if (clientSavingsAccountForm.controls.savingsAccountId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Account' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<div class="ca-root">
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="piggy-bank"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.buttons.Update Default Savings' | translate }}</h2>
<p class="ca-hero-sub">
Pick which savings account is the <strong>default</strong> for this client. Auto-debit instructions, fee
collections and loan disbursals will use this account unless explicitly overridden.
</p>
</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]="!clientSavingsAccountForm.valid">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<form [formGroup]="clientSavingsAccountForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Default Savings Account' | translate }}</mat-label>
<mat-select formControlName="savingsAccountId">
@for (account of savingsAccounts; track account) {
<mat-option [value]="account.id">{{ account.accountNo }}</mat-option>
}
</mat-select>
@if (clientSavingsAccountForm.controls.savingsAccountId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Account' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
<div class="ca-notice">
<fa-icon icon="info-circle"></fa-icon>
<span>
Changing the default does not move any existing balances or scheduled instructions — it only affects
<strong>new</strong> debits/credits that look up the client's default account.
</span>
</div>
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon><span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!clientSavingsAccountForm.valid">
<fa-icon icon="check"></fa-icon><span>{{ 'labels.buttons.Confirm' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
@use 'assets/styles/client-action' as ca;
@include ca.client-action-styles;
/* Update Default Savings — teal accent */
:host {
--ca-accent: #0e7e6b;
--ca-accent-l: #ccfbf1;
}
@@ -13,6 +13,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
/** Custom Services */
import { ClientsService } from 'app/clients/clients.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -23,7 +24,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './update-client-savings-account.component.html',
styleUrls: ['./update-client-savings-account.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
]
})
export class UpdateClientSavingsAccountComponent implements OnInit {
@@ -6,58 +6,79 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="withdrawClientForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field (click)="withdrawalDatePicker.open()">
<mat-label>{{ 'labels.inputs.Withdrawal Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="withdrawalDatePicker"
required
formControlName="withdrawalDate"
/>
<mat-datepicker-toggle matSuffix [for]="withdrawalDatePicker"></mat-datepicker-toggle>
<mat-datepicker #withdrawalDatePicker></mat-datepicker>
@if (withdrawClientForm.controls.withdrawalDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Withdrawal Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="ca-root">
<!-- HERO -->
<div class="ca-hero">
<div class="ca-hero-icon"><fa-icon icon="undo"></fa-icon></div>
<div class="ca-hero-text">
<div class="ca-hero-eyebrow">Client Action</div>
<h2 class="ca-hero-title">{{ 'labels.heading.Withdraw Client' | translate }}</h2>
<p class="ca-hero-sub">
Withdraw a <strong>Pending</strong> client when onboarding will not proceed (e.g. customer changed their mind).
The record is preserved with a withdrawn status and can be re-opened later if needed.
</p>
</div>
</div>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Withdrawal Reason' | translate }}</mat-label>
<mat-select formControlName="withdrawalReasonId">
@for (reason of withdrawalData; track reason) {
<mat-option [value]="reason.id">
{{ reason.name }}
</mat-option>
}
</mat-select>
@if (withdrawClientForm.controls.withdrawalReasonId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Withdrawal Reason' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<form [formGroup]="withdrawClientForm" (ngSubmit)="submit()" class="ca-card">
<div class="ca-card-body">
<div class="ca-form-row">
<mat-form-field appearance="outline" (click)="withdrawalDatePicker.open()">
<mat-label>{{ 'labels.inputs.Withdrawal Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="withdrawalDatePicker"
required
formControlName="withdrawalDate"
/>
<mat-datepicker-toggle matSuffix [for]="withdrawalDatePicker"></mat-datepicker-toggle>
<mat-datepicker #withdrawalDatePicker></mat-datepicker>
@if (withdrawClientForm.controls.withdrawalDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Withdrawal Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</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]="!withdrawClientForm.valid">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<div class="ca-form-row">
<mat-form-field appearance="outline">
<mat-label>{{ 'labels.inputs.Withdrawal Reason' | translate }}</mat-label>
<mat-select formControlName="withdrawalReasonId">
@for (reason of withdrawalData; track reason) {
<mat-option [value]="reason.id">{{ reason.name }}</mat-option>
}
</mat-select>
@if (withdrawClientForm.controls.withdrawalReasonId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Withdrawal Reason' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
<div class="ca-notice">
<fa-icon icon="info-circle"></fa-icon>
<span>
A reason is required for audit. The client record is retained and can be reactivated later via the
<strong>Undo Withdrawal</strong> action.
</span>
</div>
</div>
<div class="ca-actions">
<button type="button" class="ca-btn ca-btn--cancel" [routerLink]="['../../']">
<fa-icon icon="times"></fa-icon>
<span>{{ 'labels.buttons.Cancel' | translate }}</span>
</button>
<button type="submit" class="ca-btn ca-btn--confirm" [disabled]="!withdrawClientForm.valid">
<fa-icon icon="check"></fa-icon>
<span>{{ 'labels.buttons.Confirm' | translate }}</span>
</button>
</div>
</form>
</div>
@@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
@use 'assets/styles/client-action' as ca;
@include ca.client-action-styles;
/* Withdraw-specific amber accent (caution / pause) */
:host {
--ca-accent: #b45309;
--ca-accent-l: #fef3c7;
}
@@ -15,6 +15,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ClientsService } from 'app/clients/clients.service';
import { Dates } from 'app/core/utils/dates';
import { SettingsService } from 'app/settings/settings.service';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -25,7 +26,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './withdraw-client.component.html',
styleUrls: ['./withdraw-client.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS
...STANDALONE_SHARED_IMPORTS,
FaIconComponent
]
})
export class WithdrawClientComponent implements OnInit {