diff --git a/src/app/app-provider-admin/provider-admin/activities/services/employee-parking-place-mapping.service.ts b/src/app/app-provider-admin/provider-admin/activities/services/employee-parking-place-mapping.service.ts index 2dbdebb8..183ca9fe 100644 --- a/src/app/app-provider-admin/provider-admin/activities/services/employee-parking-place-mapping.service.ts +++ b/src/app/app-provider-admin/provider-admin/activities/services/employee-parking-place-mapping.service.ts @@ -124,4 +124,8 @@ export class EmployeeParkingPlaceMappingService { observe: 'response', }); } + + activateOrDeActivateSignature(data: any) { + return this.http.post(environment.signatureStatus, data); + } } diff --git a/src/app/app-provider-admin/provider-admin/configurations/configurations.module.ts b/src/app/app-provider-admin/provider-admin/configurations/configurations.module.ts index a7119996..78b36204 100644 --- a/src/app/app-provider-admin/provider-admin/configurations/configurations.module.ts +++ b/src/app/app-provider-admin/provider-admin/configurations/configurations.module.ts @@ -47,6 +47,9 @@ import { WrapupTimeConfigurationService } from 'src/app/core/services/ProviderAd import { CoreModule } from 'src/app/core/core.module'; import { EmailConfigurationComponent } from './email-configuration/email-configuration.component'; import { EmailConfigurationService } from 'src/app/core/services/ProviderAdminServices/email-configuration-services.service'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatInputModule } from '@angular/material/input'; @NgModule({ declarations: [ @@ -83,6 +86,9 @@ import { EmailConfigurationService } from 'src/app/core/services/ProviderAdminSe MatIconModule, MatNativeDateModule, CoreModule, + MatPaginatorModule, + MatSortModule, + MatInputModule, ], providers: [ SnomedMasterService, diff --git a/src/app/app-provider-admin/provider-admin/configurations/sms-template/sms-template.component.html b/src/app/app-provider-admin/provider-admin/configurations/sms-template/sms-template.component.html index 1c384d10..cdcfa1a0 100644 --- a/src/app/app-provider-admin/provider-admin/configurations/sms-template/sms-template.component.html +++ b/src/app/app-provider-admin/provider-admin/configurations/sms-template/sms-template.component.html @@ -186,12 +186,12 @@

Create SMS Template

required [readonly]="isReadonly" minlength="20" - maxlength="200" + maxlength="300" #sms name="smsTemplate" > {{ sms.value.length ? sms.value.length : 0 }}/200{{ sms.value.length ? sms.value.length : 0 }}/300 diff --git a/src/app/app-provider-admin/provider-admin/configurations/sms-template/sms-template.component.ts b/src/app/app-provider-admin/provider-admin/configurations/sms-template/sms-template.component.ts index f04794b9..02808972 100644 --- a/src/app/app-provider-admin/provider-admin/configurations/sms-template/sms-template.component.ts +++ b/src/app/app-provider-admin/provider-admin/configurations/sms-template/sms-template.component.ts @@ -392,7 +392,7 @@ export class SmsTemplateComponent implements OnInit, AfterViewInit { saveSMStemplate(form_values: any) { const requestObject = { - createdBy: this.commonData.Userdata.userName, + createdBy: this.commonData.uname, providerServiceMapID: this.providerServiceMapID, smsParameterMaps: this.smsParameterData.data, smsTemplate: form_values.smsTemplate, diff --git a/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.css b/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.css index 413bee2c..a90741d3 100644 --- a/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.css +++ b/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.css @@ -7,4 +7,49 @@ border:none; margin:0; padding:0; +} +.mat-header-cell, +.mat-cell { + text-align: left; /* or left/right depending on need */ + vertical-align: middle; +} +.title { + margin: 0; + padding: 10px; + margin-bottom: 10px; + color: white; + font-weight:700; + font-size: 18px; + background-color: #0277bd !important; + } + + .message { + font: 500 20px / 32px Roboto, "Helvetica Neue", sans-serif; + padding:0px 24px 0px 24px; + + } + + .info { + background: #0277bd; + } + + .signature-container { + display: flex; + justify-content: center; /* center image horizontally */ + margin: 20px 0; /* space top & bottom */ +} + +.signature-img { + max-width: 100%; + max-height: 200px; +} + +.action { + display: flex; + justify-content: flex-end; /* align button right */ + margin: 20px 20px 10px 0; /* spacing: top right bottom left */ +} + +.ok-btn { + margin-right: 10px; /* small gap from edge */ } \ No newline at end of file diff --git a/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.html b/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.html index 9436c4c5..4e711208 100644 --- a/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.html +++ b/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.html @@ -8,79 +8,117 @@

User Signature Mapping

Designation - + {{ item.designationName }}
-
- - UserName - - - {{ item.userName }} - - - -
-
- - - - -
-
+
img -
-
- +
-
+ + +
+ +
+ + + search + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Role {{ element.role }} Username {{ element.username }} Upload + + + Download + + View + + Active + + +
+ +
+ + + + +

{{ data.username }}'s Signature

+ +
+ signature +
+ +
+ +
+
+ +
\ No newline at end of file diff --git a/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.ts b/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.ts index d64048ea..fa18af58 100644 --- a/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.ts +++ b/src/app/app-provider-admin/provider-admin/configurations/user-signature-mapping/user-signature-mapping.component.ts @@ -19,20 +19,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup, - FormControl, - FormArray, - NgForm, Validators, } from '@angular/forms'; import { ConfirmationDialogsService } from 'src/app/core/services/dialog/confirmation.service'; import { EmployeeParkingPlaceMappingService } from '../../activities/services/employee-parking-place-mapping.service'; import { dataService } from 'src/app/core/services/dataService/data.service'; import { SessionStorageService } from 'Common-UI/src/registrar/services/session-storage.service'; - +import { MatDialog } from '@angular/material/dialog'; +import { map, Observable, forkJoin, switchMap, takeUntil, Subject } from 'rxjs'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; @Component({ selector: 'app-user-signature-mapping', templateUrl: './user-signature-mapping.component.html', @@ -50,6 +51,12 @@ export class UserSignatureMappingComponent implements OnInit { public imagePath: any; imgURL: any; enableDownloadButton = false; + displayedColumns: string[] = ['role', 'username', 'upload', 'download', 'view', 'active']; + userSignatureTable: any[] = []; + selectedUser: any; + dataSource = new MatTableDataSource([]); + private destroy$ = new Subject(); + constructor( private fb: FormBuilder, @@ -57,14 +64,42 @@ export class UserSignatureMappingComponent implements OnInit { private alertMessage: ConfirmationDialogsService, private dataService: dataService, readonly sessionstorage: SessionStorageService, - ) {} + private dialog: MatDialog, + + ) { } + + @ViewChild('signatureDialog') signatureDialog!: TemplateRef; + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild(MatSort) sort!: MatSort; ngOnInit() { this.signUploadForm = this.createSignUploadForm(); this.createdBy = this.dataService.uname; this.serviceProviderID = this.sessionstorage.getItem('service_providerID'); this.getDesignations(); + + this.dataSource.filterPredicate = (data: any, filter: string) => { + const dataStr = (data.role + data.username).toLowerCase(); + return dataStr.includes(filter); + }; + } + + ngAfterViewInit() { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + } + + ngAfterViewChecked() { + if (this.paginator && this.dataSource.paginator !== this.paginator) { + this.dataSource.paginator = this.paginator; + } } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + createSignUploadForm() { return this.fb.group({ designation: [null, Validators.required], @@ -87,42 +122,91 @@ export class UserSignatureMappingComponent implements OnInit { }, ); } + + getUserNames() { const reqObj = { designationID: this.designation.designationID, serviceProviderID: this.serviceProviderID, }; + this.employeeParkingPlaceMappingService .getUserNameBasedOnDesig(reqObj) + .pipe( + switchMap((response: any) => { + if (!response?.data) return []; + this.userNames = response.data; + + // map users to observables + const requests = this.userNames.map((user: any) => + this.employeeParkingPlaceMappingService + .checkUsersignatureExist(user.userID) + .pipe( + map((signRes: any) => ({ + role: this.designation.designationName, + username: user.userName, + userID: user.userID, + signatureStatus: user?.signatureStatus || 'InActive', + signatureUrl: signRes?.data?.signatureUrl || null, + signExist: String(signRes?.data?.response).toLowerCase() === 'true', + active: signRes?.data?.active || false, + statusID: String(signRes?.data?.response).toLowerCase() === 'true', + })) + ) + ); + + return forkJoin(requests).pipe( + map(results => results as any[]) + ); // run all requests in parallel and cast result to any[] + }), + takeUntil(this.destroy$) + ) .subscribe( - (response: any) => { - if (response && response.data) this.userNames = response.data; - }, - (err) => { - console.log('usernames not fetched'); + (usersWithSignatures: any[]) => { + this.userSignatureTable = usersWithSignatures; + this.dataSource.data = this.userSignatureTable; }, + () => console.log('Usernames not fetched') ); } + + + // When a user is selected checkUsersignExist() { this.imgURL = null; + this.selectedUser = this.username; // Store selected user for table + this.employeeParkingPlaceMappingService .checkUsersignatureExist(this.username.userID) .subscribe( (response: any) => { this.signExist = response.data.response; - if (this.signExist === 'true') { - this.enableDownloadButton = true; - } else { - this.enableDownloadButton = false; - } + this.enableDownloadButton = this.signExist === 'true'; + + // Fill table data (example format) + this.userSignatureTable = [ + { + role: this.designation.designationName, + username: this.username.userName, + signatureUrl: response.data.signatureUrl || null, + active: response.data.active || false, + userID: this.username.userID, + statusID: this.username.statusID + }, + ]; + this.dataSource.data = this.userSignatureTable }, (err) => { console.log('Error while fetching response'); - }, + } ); } + public message: any; - preview(files: any) { + + preview(files: any, row: any) { + + this.selectedUser = row.userID; if (files.length === 0) return; const imgType = files[0].type; @@ -138,6 +222,7 @@ export class UserSignatureMappingComponent implements OnInit { this.checkImageSize(reader); }; } + checkImageSize(reader: any) { if (this.imagePath.size > 20000) { this.alertMessage.alert('Image size should be less than 20kb'); @@ -165,13 +250,14 @@ export class UserSignatureMappingComponent implements OnInit { createdBy: this.createdBy, fileName: this.imagePath.name, fileType: this.imagePath.type, - userID: this.username.userID, + userID: this.selectedUser, fileContent: this.imgURL !== undefined ? this.imgURL.split(',')[1] : '', }; this.employeeParkingPlaceMappingService.uploadSignature(signObj).subscribe( (response) => { this.alertMessage.alert('Saved successfully', 'success'); - this.signUploadForm.reset(); + this.getUserNames(); // Refresh table to show new signature + this.imgURL = null; this.signExist = null; this.enableDownloadButton = false; @@ -182,25 +268,135 @@ export class UserSignatureMappingComponent implements OnInit { }, ); } - downloadSign() { + + private processDownloadResponse(response: any): { filename: string; url: string } { + let filename = 'signature.png'; + const contentDisposition = response.headers.get('content-disposition'); + + if (contentDisposition) { + const utf8FilenameRegex = /filename\*\=UTF-8''(.+)/; + const asciiFilenameRegex = /filename="?([^"]+)"?/; + + let matches = utf8FilenameRegex.exec(contentDisposition); + if (matches?.[1]) { + filename = decodeURIComponent(matches[1]); + } else { + matches = asciiFilenameRegex.exec(contentDisposition); + if (matches?.[1]) { + filename = decodeURIComponent(matches[1]); + } + } + } + // const blob = new Blob([response.body!], { type: response.body!.type }); + const body: Blob | null = response.body as Blob; + if (!body) { + throw new Error('Empty response body'); + } + const blob = new Blob([body], { type: (body as any).type || 'application/octet-stream' }); + const url = URL.createObjectURL(blob); + + return { filename, url }; + } + + downloadSign(row: any) { this.employeeParkingPlaceMappingService - .downloadSign(this.username.userID) + .downloadSign(row?.userID) .subscribe( - (response: any) => { - const filename = response.headers.get('filename'); - // let blobResponse = response.body.blob(); - const blob = new Blob([response], { type: response.type }); - console.log(blob, 'blob'); - const url = window.URL.createObjectURL(blob); + (response) => { + const { filename, url } = this.processDownloadResponse(response); + const a = document.createElement('a'); a.href = url; a.download = filename; - document.body.appendChild(a); a.click(); + + URL.revokeObjectURL(url); }, (err) => { this.alertMessage.alert(err.errorMessage, 'error'); + } + ); + } + + downloadSignature(userID: any): Observable { + return this.employeeParkingPlaceMappingService.downloadSign(userID).pipe( + map((response) => { + const { url } = this.processDownloadResponse(response); + return url; + }) + ); + } + + // View signature popup + openSignatureDialog(row: any) { + if (!row.userID) { + this.alertMessage.alert('No signature uploaded for this user'); + return; + } + + this.downloadSignature(row.userID).subscribe( + (signatureUrl: string) => { + // Open dialog with image + this.dialogRef = this.dialog.open(this.signatureDialog, { + width: '400px', + data: { + username: row.username, + signatureUrl: signatureUrl + }, + }); + }, + (err) => { + this.alertMessage.alert('Error fetching signature', 'error'); + } + ); + } + applyFilter(event: Event) { + const filterValue = (event.target as HTMLInputElement).value; + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + // Toggle signature status + toggleActive(row: any, action: boolean) { + const req = { userID: row.userID, active: action, role: this.designation.designationName, username: row.username }; + this.alertMessage + .confirm( + 'confirm', + 'Are you sure you want to ' + (action ? 'activate' : 'deactivate') + ' this signature?', + ).subscribe( + (res) => { + if (res) { + + this.employeeParkingPlaceMappingService + .activateOrDeActivateSignature(req) + .subscribe( + + () => { + if (action) + this.alertMessage.alert('Signature has been Activated', 'success') + else + this.alertMessage.alert('Signature has been Deactivated', 'success'); + + this.getUserNames(); // Refresh table to show new signature + + }, + (err) => { + this.alertMessage.alert('Error updating status', 'error'); + row.active = !row.active; // rollback on failure + } + ); + } + }, + (err) => { + console.log(err); }, ); } + + dialogRef: any; + + closeDialog(): void { + if (this.dialogRef) { + this.dialogRef.close(); + } + } }