WEB-751 feat: add draw signature capability for clients (#3212)
This commit is contained in:
@@ -18,6 +18,7 @@ import { UnassignStaffDialogComponent } from './custom-dialogs/unassign-staff-di
|
||||
import { UploadSignatureDialogComponent } from './custom-dialogs/upload-signature-dialog/upload-signature-dialog.component';
|
||||
import { ViewSignatureDialogComponent } from './custom-dialogs/view-signature-dialog/view-signature-dialog.component';
|
||||
import { DeleteSignatureDialogComponent } from './custom-dialogs/delete-signature-dialog/delete-signature-dialog.component';
|
||||
import { DrawSignatureDialogComponent } from './custom-dialogs/draw-signature-dialog/draw-signature-dialog.component';
|
||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||
import { UploadImageDialogComponent } from './custom-dialogs/upload-image-dialog/upload-image-dialog.component';
|
||||
import { CaptureImageDialogComponent } from './custom-dialogs/capture-image-dialog/capture-image-dialog.component';
|
||||
@@ -313,6 +314,8 @@ export class ClientsViewComponent implements OnInit {
|
||||
viewSignatureDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.upload) {
|
||||
this.uploadSignature();
|
||||
} else if (response.draw) {
|
||||
this.drawSignature();
|
||||
} else if (response.delete) {
|
||||
this.deleteSignature();
|
||||
}
|
||||
@@ -334,6 +337,20 @@ export class ClientsViewComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens draw pad for client signature
|
||||
*/
|
||||
private drawSignature() {
|
||||
const drawSignatureDialogRef = this.dialog.open(DrawSignatureDialogComponent);
|
||||
drawSignatureDialogRef.afterClosed().subscribe((signature: File) => {
|
||||
if (signature) {
|
||||
this.clientsService.uploadClientSignatureImage(this.clientViewData.id, signature).subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes client signature
|
||||
*/
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<div class="layout-column gap-2px">
|
||||
<h1 mat-dialog-title>{{ 'labels.heading.Draw Client Signature' | translate }}</h1>
|
||||
|
||||
<div class="signature-canvas-container">
|
||||
<canvas #signatureCanvas class="signature-canvas"></canvas>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<button mat-stroked-button color="primary" (click)="clear()">
|
||||
{{ 'labels.buttons.Clear' | translate }}
|
||||
</button>
|
||||
</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]="isEmpty" (click)="confirm()">
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
.signature-canvas-container {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.signature-canvas {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
display: block;
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, ViewChild, ElementRef, AfterViewInit, OnDestroy, inject } from '@angular/core';
|
||||
import { MatDialogRef, MatDialogTitle, MatDialogActions, MatDialogClose } from '@angular/material/dialog';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/** External Imports */
|
||||
import SignaturePad from 'signature_pad';
|
||||
|
||||
/**
|
||||
* Draw signature dialog component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-draw-signature-dialog',
|
||||
templateUrl: './draw-signature-dialog.component.html',
|
||||
styleUrls: ['./draw-signature-dialog.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDialogTitle,
|
||||
MatDialogActions,
|
||||
MatDialogClose
|
||||
]
|
||||
})
|
||||
export class DrawSignatureDialogComponent implements AfterViewInit, OnDestroy {
|
||||
dialogRef = inject<MatDialogRef<DrawSignatureDialogComponent>>(MatDialogRef);
|
||||
|
||||
/** Canvas element reference */
|
||||
@ViewChild('signatureCanvas', { static: true }) signatureCanvas: ElementRef<HTMLCanvasElement>;
|
||||
|
||||
/** Signature pad instance */
|
||||
private signaturePad: SignaturePad;
|
||||
|
||||
/** Whether the pad has content */
|
||||
isEmpty = true;
|
||||
|
||||
ngAfterViewInit() {
|
||||
const canvas = this.signatureCanvas.nativeElement;
|
||||
this.signaturePad = new SignaturePad(canvas, {
|
||||
backgroundColor: 'rgb(255, 255, 255)'
|
||||
});
|
||||
|
||||
this.signaturePad.addEventListener('endStroke', () => {
|
||||
this.isEmpty = this.signaturePad.isEmpty();
|
||||
});
|
||||
|
||||
this.resizeCanvas();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.signaturePad) {
|
||||
this.signaturePad.off();
|
||||
}
|
||||
}
|
||||
|
||||
/** Resizes canvas to match its display size for crisp rendering */
|
||||
private resizeCanvas() {
|
||||
const canvas = this.signatureCanvas.nativeElement;
|
||||
const ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
canvas.width = canvas.offsetWidth * ratio;
|
||||
canvas.height = canvas.offsetHeight * ratio;
|
||||
canvas.getContext('2d').scale(ratio, ratio);
|
||||
this.signaturePad.clear();
|
||||
}
|
||||
|
||||
/** Clears the signature pad */
|
||||
clear() {
|
||||
this.signaturePad.clear();
|
||||
this.isEmpty = true;
|
||||
}
|
||||
|
||||
/** Converts the drawn signature to a File and closes the dialog */
|
||||
confirm() {
|
||||
if (this.signaturePad.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const canvas = this.signatureCanvas.nativeElement;
|
||||
canvas.toBlob((blob: Blob) => {
|
||||
const file = new File([blob], 'clientSignature.png', { type: 'image/png' });
|
||||
this.dialogRef.close(file);
|
||||
}, 'image/png');
|
||||
}
|
||||
}
|
||||
+3
@@ -31,5 +31,8 @@
|
||||
<button mat-raised-button color="primary" [mat-dialog-close]="{ upload: true }">
|
||||
{{ 'labels.buttons.Upload' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" [mat-dialog-close]="{ draw: true }">
|
||||
{{ 'labels.buttons.Draw' | translate }}
|
||||
</button>
|
||||
}
|
||||
</mat-dialog-actions>
|
||||
|
||||
Reference in New Issue
Block a user