feat(clients, alm): add CIN/MCA verification & authorized signatories tab; refactor ALM report print and export
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s

This commit is contained in:
2026-08-13 15:05:16 +05:30
parent 75df8865dd
commit 5581d4180d
29 changed files with 3002 additions and 2453 deletions
@@ -11,9 +11,21 @@
<div>
<mifosx-file-upload flex="60%" (change)="onFileSelect($event)" acceptFilter=".png,.jpeg,.jpg"></mifosx-file-upload>
@if (imagePreview) {
<div class="image-preview">
<span class="image-preview__label">Preview</span>
<img class="image-preview__img" [src]="imagePreview" alt="Selected profile image preview" />
<span class="image-preview__name">{{ image.name }}</span>
</div>
}
@if (previewError) {
<div class="image-preview__error">{{ previewError }}</div>
}
<mat-dialog-actions align="end">
<button mat-raised-button mat-dialog-close>{{ 'labels.buttons.Cancel' | translate }}</button>
<button mat-raised-button color="primary" [disabled]="!image" [mat-dialog-close]="image">
<button mat-raised-button color="primary" [disabled]="!image || !imagePreview" [mat-dialog-close]="image">
{{ 'labels.buttons.Confirm' | translate }}
</button>
</mat-dialog-actions>
@@ -5,3 +5,45 @@
* 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/.
*/
.image-preview {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
margin: 12px 0 4px;
}
.image-preview__label {
align-self: flex-start;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.4px;
text-transform: uppercase;
color: rgb(0 0 0 / 60%);
}
.image-preview__img {
/* Capped in vh as well as px so a tall portrait photo cannot push the Cancel/Confirm footer
below the fold on a short viewport — `contain` keeps the whole frame visible rather than
cropping it, since the point of the preview is to check the picture before it goes live. */
max-width: 100%;
max-height: min(260px, 40vh);
object-fit: contain;
border-radius: 10px;
background: rgb(0 0 0 / 4%);
border: 1px solid rgb(0 0 0 / 12%);
}
.image-preview__name {
font-size: 12px;
color: rgb(0 0 0 / 60%);
overflow-wrap: anywhere;
text-align: center;
}
.image-preview__error {
margin: 12px 0 4px;
font-size: 13px;
color: #b3261e;
}
@@ -33,6 +33,16 @@ export class UploadImageDialogComponent {
/** Client Image */
image: File;
/**
* Data URL of the selected image, rendered as a preview so the officer sees what they picked
* before confirming — the upload replaces the client's profile picture immediately on confirm,
* and picking the wrong file is otherwise only discovered once it is already live.
*/
imagePreview: string | null = null;
/** Set when the picked file is not a readable image, in place of a preview. */
previewError = '';
/**
* Sets file form control value.
* @param {any} $event file change event.
@@ -40,6 +50,29 @@ export class UploadImageDialogComponent {
onFileSelect($event: any) {
if ($event.target.files.length > 0) {
this.image = $event.target.files[0];
this.buildPreview(this.image);
}
}
/** Reads the picked file into a data URL for the preview. */
private buildPreview(file: File) {
this.imagePreview = null;
this.previewError = '';
// The picker's accept filter is a hint the OS dialog can be talked out of, so re-check here
// rather than handing a non-image to FileReader and rendering a broken <img>.
if (!file.type?.startsWith('image/')) {
this.previewError = 'Selected file is not an image. Choose a PNG or JPEG file.';
return;
}
const reader = new FileReader();
reader.onload = () => {
this.imagePreview = reader.result as string;
};
reader.onerror = () => {
this.previewError = 'Could not read the selected file.';
};
reader.readAsDataURL(file);
}
}