feat: edit client — save per tab, DOB re-KYC alert, audit logs, KYC improvements

- Edit button per tab with Save & Next confirmation dialog
- DOB change triggers re-KYC alert, resets PAN verification, auto-fills remarks
- Mobile/email changes logged as audit notes via client notes API
- Redesigned confirmation dialog with icon + title, type-based theming
- KYC re-edit flow: remarks mandatory for VERIFIED records, re-edit API command
- Load existing photo & signature in edit KYC, immediate upload on change
- Populate all KYC fields (document, AML, form60/15) from API on edit
- Non-individual PAN verification uses same flow as individual
- Fix table overflow when sidebar panel is open
- Add fr unit to stylelint allowed-list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-07 15:08:07 +05:30
co-authored by Claude Opus 4.6
parent b890d24bb7
commit 79e455c4ed
14 changed files with 1699 additions and 398 deletions
@@ -6,13 +6,42 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<h1 mat-dialog-title>{{ data.heading }}</h1>
<div mat-dialog-content>
<p>{{ data.dialogContext }} ?</p>
<div class="confirm-dialog" [class]="'confirm-dialog confirm-dialog--' + (data.type || 'basic')">
<!-- Icon + Title -->
<div class="confirm-header">
<div class="confirm-header__icon-wrap">
@switch (data.type) {
@case ('warn') {
<i class="fa fa-exclamation-triangle"></i>
}
@case ('Strong') {
<i class="fa fa-trash-alt"></i>
}
@default {
<i class="fa fa-pen-to-square"></i>
}
}
</div>
<h2 class="confirm-header__title">{{ data.heading }}</h2>
</div>
<!-- Body -->
<mat-dialog-content class="confirm-body">
<p class="confirm-body__text">{{ data.dialogContext }}</p>
</mat-dialog-content>
<!-- Actions -->
<mat-dialog-actions class="confirm-actions">
<button mat-stroked-button class="confirm-actions__cancel" mat-dialog-close>
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-flat-button
class="confirm-actions__confirm confirm-actions__confirm--{{ data.type || 'basic' }}"
[mat-dialog-close]="{ confirm: true }"
>
<i class="fa fa-check confirm-actions__btn-icon"></i>
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-dialog-actions>
</div>
<mat-dialog-actions align="end">
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
<button mat-raised-button color="{{ color }}" [mat-dialog-close]="{ confirm: true }">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-dialog-actions>
@@ -5,3 +5,145 @@
* 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: #1a73e8;
$primary-light: #e8f0fe;
$amber: #f59e0b;
$amber-light: #fef7e0;
$red: #ef4444;
$red-light: #fef2f2;
$gray-100: #f1f3f4;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
$white: #fff;
.confirm-dialog {
display: flex;
flex-direction: column;
max-width: 460px;
min-width: 380px;
}
// ─── Header ───
.confirm-header {
display: flex;
align-items: center;
gap: 14px;
padding: 22px 24px 16px;
border-bottom: 1px solid $gray-200;
&__icon-wrap {
width: 42px;
height: 42px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.confirm-dialog--basic & {
background: $primary-light;
i {
color: $primary;
}
}
.confirm-dialog--warn & {
background: $amber-light;
i {
color: $amber;
}
}
.confirm-dialog--Strong & {
background: $red-light;
i {
color: $red;
}
}
i {
font-size: 18px;
}
}
&__title {
margin: 0;
padding: 0;
font-size: 17px;
font-weight: 700;
color: $gray-900;
letter-spacing: -0.2px;
line-height: 1.25;
}
}
// ─── Body ───
.confirm-body {
padding: 18px 24px !important;
overflow: visible !important;
max-height: none !important;
&__text {
font-size: 13.5px;
line-height: 1.65;
color: $gray-700;
margin: 0;
}
}
// ─── Actions ───
.confirm-actions {
padding: 14px 24px 20px !important;
border-top: 1px solid $gray-200;
margin: 0 !important;
display: flex !important;
justify-content: flex-end !important;
align-items: center !important;
gap: 10px !important;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
padding: 0 20px !important;
height: 38px !important;
color: $gray-700 !important;
border-color: $gray-200 !important;
}
&__confirm {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
padding: 0 24px !important;
height: 38px !important;
letter-spacing: 0.2px;
color: $white !important;
&--basic {
background: $primary !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
}
&--warn {
background: $amber !important;
box-shadow: 0 1px 3px rgba($amber, 0.3) !important;
}
&--Strong {
background: $red !important;
box-shadow: 0 1px 3px rgba($red, 0.3) !important;
}
}
&__btn-icon {
margin-right: 6px;
font-size: 12px;
}
}
@@ -7,22 +7,21 @@
*/
/** Angular Imports */
import { Component, OnInit, inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
MatDialogTitle,
MatDialogContent,
MatDialogActions,
MatDialogClose
} from '@angular/material/dialog';
import { DialogData } from 'app/core/dialogs/dialog-data.model';
import { Dialogs } from 'app/core/dialogs/dialogs';
import { CdkScrollable } from '@angular/cdk/scrolling';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
* Delete dialog component.
* Confirmation dialog component.
* Supports types: 'basic' (blue), 'warn' (amber), 'Strong' (red/destructive).
*/
@Component({
selector: 'mifosx-confirmation-dialog',
@@ -30,25 +29,13 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./confirmation-dialog.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatDialogTitle,
CdkScrollable,
MatDialogContent,
MatDialogActions,
MatDialogClose
]
})
export class ConfirmationDialogComponent implements OnInit {
export class ConfirmationDialogComponent {
dialogRef = inject<MatDialogRef<ConfirmationDialogComponent>>(MatDialogRef);
private dialogs = inject(Dialogs);
data = inject<DialogData>(MAT_DIALOG_DATA);
color: string;
ngOnInit() {
this.setColor();
}
setColor() {
this.color = this.dialogs.setColor(this.data.type);
}
}
+150 -42
View File
@@ -23,6 +23,9 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
border-radius: 12px;
border: 1px solid $border;
box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
min-width: 0;
max-width: 100%;
box-sizing: border-box;
// Legacy layout support
.layout-column,
@@ -45,14 +48,21 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
flex: 1;
min-width: 0;
&.m-r-30 { margin-right: 12px; }
&.m-r-30 {
margin-right: 12px;
}
mat-form-field {
min-width: 200px;
flex: 1;
&.search-box { width: auto; }
+ mat-form-field { margin-left: 12px; }
&.search-box {
width: auto;
}
+ mat-form-field {
margin-left: 12px;
}
}
mat-checkbox {
@@ -60,7 +70,9 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
flex-shrink: 0;
margin-left: 8px;
&.m-r-10 { margin-right: 8px; }
&.m-r-10 {
margin-right: 8px;
}
}
}
@@ -70,14 +82,18 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
flex-shrink: 0;
margin-left: auto;
&.gap-25px { gap: 10px; }
&.gap-25px {
gap: 10px;
}
button {
white-space: nowrap;
fa-icon {
&.m-r-10,
&.m-r-20 { margin-right: 6px; }
&.m-r-20 {
margin-right: 6px;
}
}
}
}
@@ -92,11 +108,16 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
.select-row {
cursor: pointer;
&:hover { background-color: $hover-bg; }
&:hover {
background-color: $hover-bg;
}
}
th,
td { padding: 10px 16px; }
td {
padding: 10px 16px;
}
th {
font-weight: 600;
@@ -110,7 +131,10 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
tr {
border-bottom: 1px solid #f1f5f9;
&:last-child { border-bottom: none; }
&:last-child {
border-bottom: none;
}
}
td {
@@ -125,7 +149,9 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
background: transparent;
}
mat-progress-bar { margin: 0; }
mat-progress-bar {
margin: 0;
}
}
// ===== Modern Table Header =====
@@ -167,10 +193,21 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
span {
border-radius: 2px;
&:nth-child(1) { background: $brand; }
&:nth-child(2) { background: #0ea5e9; }
&:nth-child(3) { background: #10b981; }
&:nth-child(4) { background: #cbd5e1; }
&:nth-child(1) {
background: $brand;
}
&:nth-child(2) {
background: #0ea5e9;
}
&:nth-child(3) {
background: #10b981;
}
&:nth-child(4) {
background: #cbd5e1;
}
}
}
}
@@ -198,7 +235,9 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
max-width: 360px;
::ng-deep {
.mat-mdc-form-field-flex { height: 38px; }
.mat-mdc-form-field-flex {
height: 38px;
}
.mat-mdc-form-field-infix {
padding: 6px 0;
@@ -206,8 +245,13 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
}
.mdc-notched-outline {
.mdc-notched-outline__leading { border-radius: 8px 0 0 8px; }
.mdc-notched-outline__trailing { border-radius: 0 8px 8px 0; }
.mdc-notched-outline__leading {
border-radius: 8px 0 0 8px;
}
.mdc-notched-outline__trailing {
border-radius: 0 8px 8px 0;
}
}
.mat-mdc-form-field-icon-prefix {
@@ -215,7 +259,9 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
color: $text-muted;
}
input { font-size: 13px; }
input {
font-size: 13px;
}
}
}
@@ -237,7 +283,10 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
height: 36px;
padding: 0 16px;
fa-icon { margin-right: 6px; font-size: 12px; }
fa-icon {
margin-right: 6px;
font-size: 12px;
}
}
.btn-import {
@@ -259,7 +308,7 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
.table-container {
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
overflow: hidden auto;
}
.table-loader {
@@ -294,7 +343,9 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
border-bottom: 1px solid #f1f5f9;
transition: background 0.1s;
&:last-child { border-bottom: none; }
&:last-child {
border-bottom: none;
}
&:nth-child(even) {
background: #fafbfc;
@@ -314,7 +365,11 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
font-weight: 400;
color: $text;
padding: 0 12px;
font-family: 'Inter', 'Roboto', -apple-system, sans-serif;
font-family:
Inter,
Roboto,
-apple-system,
sans-serif;
}
}
}
@@ -324,9 +379,15 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
font-weight: 500;
font-size: 13px;
cursor: pointer;
font-family: 'Inter', 'Roboto', -apple-system, sans-serif;
font-family:
Inter,
Roboto,
-apple-system,
sans-serif;
&:hover { text-decoration: underline; }
&:hover {
text-decoration: underline;
}
}
.cell-mono {
@@ -350,7 +411,11 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
letter-spacing: 0.02em;
text-transform: capitalize;
white-space: nowrap;
font-family: 'Inter', 'Roboto', -apple-system, sans-serif;
font-family:
Inter,
Roboto,
-apple-system,
sans-serif;
&::before {
content: '';
@@ -367,7 +432,9 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
background: #ecfdf5 !important;
color: #059669 !important;
&::before { background: #059669; }
&::before {
background: #059669;
}
}
.status-active-overdue,
@@ -375,7 +442,9 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
background: #fef2f2 !important;
color: #dc2626 !important;
&::before { background: #dc2626; }
&::before {
background: #dc2626;
}
}
.status-submitted-and-pending-approval,
@@ -383,14 +452,18 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
background: #fffbeb !important;
color: #d97706 !important;
&::before { background: #d97706; }
&::before {
background: #d97706;
}
}
.status-overpaid {
background: #eff6ff !important;
color: #2563eb !important;
&::before { background: #2563eb; }
&::before {
background: #2563eb;
}
}
}
@@ -402,8 +475,13 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
vertical-align: middle;
margin-right: 6px;
&.person { color: #2563eb; }
&.entity { color: #7c3aed; }
&.person {
color: #2563eb;
}
&.entity {
color: #7c3aed;
}
}
.legal-form-badge {
@@ -550,10 +628,15 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
min-width: 150px;
margin-bottom: 8px;
+ mat-form-field { margin-left: 0; }
+ mat-form-field {
margin-left: 0;
}
}
mat-checkbox { margin-left: 0; margin-top: 8px; }
mat-checkbox {
margin-left: 0;
margin-top: 8px;
}
}
.action-button {
@@ -561,25 +644,50 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
width: 100%;
justify-content: center;
&.gap-25px { gap: 10px; }
&.gap-25px {
gap: 10px;
}
}
}
}
.table-actions { flex-direction: column; align-items: stretch; }
.table-search { max-width: 100%; }
.table-buttons { margin-left: 0; justify-content: stretch; }
.table-buttons button { flex: 1; }
.table-actions {
flex-direction: column;
align-items: stretch;
}
.table-search {
max-width: 100%;
}
.table-buttons {
margin-left: 0;
justify-content: stretch;
}
.table-buttons button {
flex: 1;
}
}
// Legacy table styles
table {
width: 100%;
tr.select-row:hover { cursor: pointer; }
tr.select-row:hover {
cursor: pointer;
}
}
// Utility classes
.m-r-10 { margin-right: 10px !important; }
.m-r-20 { margin-right: 20px !important; }
.m-r-30 { margin-right: 30px !important; }
.m-r-10 {
margin-right: 10px !important;
}
.m-r-20 {
margin-right: 20px !important;
}
.m-r-30 {
margin-right: 30px !important;
}