diff --git a/Common-UI b/Common-UI
index 93c9157..e9304ae 160000
--- a/Common-UI
+++ b/Common-UI
@@ -1 +1 @@
-Subproject commit 93c915707165d8ebeefa5c62cc087b08184b261a
+Subproject commit e9304ae4c85709a82608e6f900429a857fc703ba
diff --git a/pom.xml b/pom.xml
index 6fc0be4..042ca81 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
4.0.0
com.iemr.tm-ui
tm-ui
- 3.6.0
+ 3.6.1
TM-UI
Piramal - tm: Module ui
war
diff --git a/src/app/app-modules/core/services/confirmation.service.ts b/src/app/app-modules/core/services/confirmation.service.ts
index 761dc63..456246f 100644
--- a/src/app/app-modules/core/services/confirmation.service.ts
+++ b/src/app/app-modules/core/services/confirmation.service.ts
@@ -11,7 +11,7 @@ import { CommonDialogComponent } from '../components/common-dialog/common-dialog
@Injectable()
export class ConfirmationService {
constructor(
- private dialog: MatDialog,
+ public dialog: MatDialog,
@Inject(DOCUMENT) doc: any,
) {}
diff --git a/src/app/app-modules/core/services/http-interceptor.service.ts b/src/app/app-modules/core/services/http-interceptor.service.ts
index 60b391e..97cfbb6 100644
--- a/src/app/app-modules/core/services/http-interceptor.service.ts
+++ b/src/app/app-modules/core/services/http-interceptor.service.ts
@@ -9,170 +9,335 @@ import {
HttpErrorResponse,
HttpHeaders,
} from '@angular/common/http';
-import { catchError, tap } from 'rxjs/operators';
+import { catchError, tap, finalize } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import { Router } from '@angular/router';
import { throwError } from 'rxjs/internal/observable/throwError';
+import { MatDialog } from '@angular/material/dialog';
import { SpinnerService } from './spinner.service';
import { ConfirmationService } from './confirmation.service';
import { environment } from 'src/environments/environment';
import { SessionStorageService } from 'Common-UI/src/registrar/services/session-storage.service';
+/**
+ * HTTP Interceptor Service
+ * Handles:
+ * - Request authorization headers
+ * - Response success/error handling
+ * - Session management and timeout
+ * - Loading spinner management
+ * - User feedback on errors
+ */
@Injectable({
providedIn: 'root',
})
export class HttpInterceptorService implements HttpInterceptor {
- timerRef: any;
- currentLanguageSet: any;
- donotShowSpinnerUrl = [
+ private sessionTimeoutRef: any;
+ private currentLanguageSet: any;
+ private readonly SESSION_TIMEOUT_DURATION = 27 * 60 * 1000; // 27 minutes
+ private readonly SESSION_WARNING_TIME = 1.5 * 60 * 1000; // 1.5 minutes before timeout
+ private readonly EXCLUDED_SPINNER_URLS = [
environment.syncDownloadProgressUrl,
environment.ioturl,
];
+
constructor(
private spinnerService: SpinnerService,
private router: Router,
private confirmationService: ConfirmationService,
private http: HttpClient,
- readonly sessionstorage: SessionStorageService,
+ readonly sessionStorage: SessionStorageService,
+ private matDialog: MatDialog,
) {}
intercept(
req: HttpRequest,
next: HttpHandler,
): Observable> {
- const key: any = sessionStorage.getItem('key');
- let modifiedReq = req;
- const isPlatformFeedback =
- req.url && req.url.toLowerCase().includes('/platform-feedback');
+ // Add authorization header
+ const modifiedReq = this.addAuthorizationHeader(req);
- if (isPlatformFeedback) {
- // For platform-feedback: remove Authorization and force JSON content-type
- const headers = req.headers
- .delete('Authorization')
- .set('Content-Type', 'application/json');
- modifiedReq = req.clone({ headers });
- } else {
- if (req.body instanceof FormData) {
- modifiedReq = req.clone({
- headers: req.headers.set('Authorization', key || ''),
- });
- } else {
- if (key !== undefined && key !== null) {
- modifiedReq = req.clone({
- headers: req.headers
- .set('Authorization', key)
- .set('Content-Type', 'application/json'),
- });
- } else {
- modifiedReq = req.clone({
- headers: req.headers.set('Authorization', ''),
- });
- }
- }
+ // Show spinner unless URL is in exclusion list
+ const shouldShowSpinner = !this.isExcludedUrl(req.url);
+ if (shouldShowSpinner) {
+ this.spinnerService.setLoading(true);
}
+
return next.handle(modifiedReq).pipe(
- tap((event: HttpEvent) => {
- if (req.url !== undefined && !req.url.includes('cti/getAgentState'))
- this.spinnerService.setLoading(true);
- if (event instanceof HttpResponse) {
- console.log(event.body);
- this.onSuccess(req.url, event.body);
+ tap((event: HttpEvent) => this.handleResponse(event)),
+ catchError((error: HttpErrorResponse) =>
+ this.handleError(error, req.url),
+ ),
+ finalize(() => {
+ if (shouldShowSpinner) {
this.spinnerService.setLoading(false);
- return event.body;
- }
- }),
- catchError((error: HttpErrorResponse) => {
- console.error(error);
- this.spinnerService.setLoading(false);
-
- if(error.status === 401){
- this.sessionstorage.clear();
- this.confirmationService.alert(this.currentLanguageSet.sessionExpiredPleaseLogin, 'error');
- setTimeout(() => this.router.navigate(['/login']), 0);
- } else if (error.status === 403) {
- this.confirmationService.alert(
- this.currentLanguageSet.accessDenied,
- 'error',
- );
- } else if (error.status === 500) {
- this.confirmationService.alert(
- this.currentLanguageSet.internaleServerError,
- 'error',
- );
- } else {
- this.confirmationService.alert(
- error.message || this.currentLanguageSet.somethingWentWrong,
- 'error',
- );
}
- return throwError(error.error);
}),
);
}
+ /**
+ * Adds authorization header to request
+ * Special handling for platform-feedback endpoint (no auth required)
+ */
+ private addAuthorizationHeader(req: HttpRequest): HttpRequest {
+ const authToken = sessionStorage.getItem('key');
+ const isPlatformFeedback = req.url
+ ?.toLowerCase()
+ .includes('/platform-feedback');
- private onSuccess(url: string, response: any): void {
- if (this.timerRef) clearTimeout(this.timerRef);
+ if (isPlatformFeedback) {
+ // Remove Authorization for feedback endpoint
+ return req.clone({
+ headers: req.headers
+ .delete('Authorization')
+ .set('Content-Type', 'application/json'),
+ });
+ }
- if (
- response.statusCode === 5002 &&
- url.indexOf('user/userAuthenticate') < 0
- ) {
- sessionStorage.clear();
- this.sessionstorage.clear();
- setTimeout(() => this.router.navigate(['/login']), 0);
- this.confirmationService.alert(response.errorMessage, 'error');
+ let headers = req.headers;
+
+ if (req.body instanceof FormData) {
+ headers = headers.set('Authorization', authToken || '');
} else {
- this.startTimer();
+ headers = headers
+ .set('Authorization', authToken || '')
+ .set('Content-Type', 'application/json');
+ }
+
+ return req.clone({ headers });
+ }
+
+ /**
+ * Check if URL is excluded from spinner display
+ */
+ private isExcludedUrl(url: string): boolean {
+ return (
+ url === undefined ||
+ url.includes('cti/getAgentState') ||
+ this.EXCLUDED_SPINNER_URLS.some((excludedUrl) => url.includes(excludedUrl))
+ );
+ }
+
+ /**
+ * Handle successful HTTP responses
+ * Checks for application-level errors in response body
+ */
+ private handleResponse(event: HttpEvent): void {
+ if (event instanceof HttpResponse) {
+ const response = event.body;
+
+ // Check for application-level errors (statusCode in body)
+ if (
+ response &&
+ typeof response === 'object' &&
+ response.statusCode === 5002 &&
+ !event.url?.includes('user/userAuthenticate')
+ ) {
+ this.handleSessionExpired();
+ return;
+ }
+
+ // Reset session timeout on successful response
+ this.resetSessionTimeout();
+ }
+ }
+
+ /**
+ * Handle HTTP errors with appropriate user-facing messages
+ */
+ private handleError(
+ error: HttpErrorResponse,
+ url: string,
+ ): Observable {
+ const errorMessage = this.getErrorMessage(error);
+
+ // Close all open dialogs before showing error
+ this.matDialog.closeAll();
+
+ // Show error based on status code
+ switch (error.status) {
+ case 401:
+ this.handleUnauthorized();
+ break;
+ case 403:
+ this.handleForbidden();
+ break;
+ case 404:
+ this.handleNotFound(errorMessage);
+ break;
+ case 500:
+ case 502:
+ case 503:
+ case 504:
+ this.handleServerError(error.status, errorMessage);
+ break;
+ default:
+ this.handleGenericError(errorMessage);
+ }
+
+ return throwError(() => error);
+ }
+
+ /**
+ * Extract error message from HTTP response
+ * Prioritizes server-provided messages over generic ones
+ */
+ private getErrorMessage(error: HttpErrorResponse): string {
+ // Try to get message from error response body
+ if (error.error) {
+ if (typeof error.error === 'string') {
+ return error.error;
+ }
+ if (
+ error.error.message &&
+ typeof error.error.message === 'string'
+ ) {
+ return error.error.message;
+ }
+ if (error.error.error && typeof error.error.error === 'string') {
+ return error.error.error;
+ }
}
+
+ // Fallback to HTTP status text
+ return error.statusText || 'Unknown error occurred';
+ }
+
+ /**
+ * Handle 401 Unauthorized - Session expired
+ */
+ private handleUnauthorized(): void {
+ this.confirmationService.alert(
+ this.currentLanguageSet?.sessionExpiredPleaseLogin ||
+ 'Session has expired, please login again.',
+ 'error',
+ );
+ this.handleSessionExpired();
+ }
+
+ /**
+ * Handle 403 Forbidden - Access denied
+ */
+ private handleForbidden(): void {
+ this.confirmationService.alert(
+ this.currentLanguageSet?.accessDenied ||
+ 'Access Denied. You do not have permission to access this resource.',
+ 'error',
+ );
+ this.handleSessionExpired();
}
- startTimer() {
- this.timerRef = setTimeout(
- () => {
- console.log('there', Date());
-
- if (
- sessionStorage.getItem('authenticationToken') &&
- sessionStorage.getItem('isAuthenticated')
- ) {
- this.confirmationService
- .alert(
- 'Your session is about to Expire. Do you need more time ? ',
- 'sessionTimeOut',
- )
- .afterClosed()
- .subscribe((result: any) => {
- if (result.action === 'continue') {
- this.http.post(environment.extendSessionUrl, {}).subscribe(
- (res: any) => {},
- (err: any) => {},
- );
- } else if (result.action === 'timeout') {
- clearTimeout(this.timerRef);
- sessionStorage.clear();
- this.sessionstorage.clear();
- this.confirmationService.alert(
- this.currentLanguageSet.sessionExpired,
- 'error',
- );
- this.router.navigate(['/login']);
- } else if (result.action === 'cancel') {
- setTimeout(() => {
- clearTimeout(this.timerRef);
- sessionStorage.clear();
- this.sessionstorage.clear();
- this.confirmationService.alert(
- this.currentLanguageSet.sessionExpired,
- 'error',
- );
- this.router.navigate(['/login']);
- }, result.remainingTime * 1000);
- }
- });
+ /**
+ * Handle 404 Not Found
+ */
+ private handleNotFound(errorMessage: string): void {
+ this.confirmationService.alert(
+ this.currentLanguageSet?.notFound || `Resource not found: ${errorMessage}`,
+ 'error',
+ );
+ }
+
+ /**
+ * Handle 5xx Server Errors
+ */
+ private handleServerError(status: number, errorMessage: string): void {
+ const message =
+ this.currentLanguageSet?.internaleServerError ||
+ `Server error (${status}): ${errorMessage}`;
+
+ this.confirmationService.alert(message, 'error');
+ }
+
+ /**
+ * Handle generic/unknown errors
+ */
+ private handleGenericError(errorMessage: string): void {
+ const message =
+ this.currentLanguageSet?.somethingWentWrong ||
+ `Something went wrong: ${errorMessage}`;
+
+ this.confirmationService.alert(message, 'error');
+ }
+
+ /**
+ * Handle session expiration - clear storage and redirect
+ */
+ private handleSessionExpired(): void {
+ this.clearSessionTimeout();
+ sessionStorage.clear();
+ this.sessionStorage.clear();
+ this.router.navigate(['/login']);
+ }
+
+ /**
+ * Reset session timeout on user activity
+ */
+ private resetSessionTimeout(): void {
+ this.clearSessionTimeout();
+ this.startSessionTimeout();
+ }
+
+ /**
+ * Start session timeout timer
+ * Warns user 1.5 minutes before actual timeout (27 minutes total)
+ */
+ private startSessionTimeout(): void {
+ this.sessionTimeoutRef = setTimeout(() => {
+ const isAuthenticated =
+ sessionStorage.getItem('authenticationToken') &&
+ sessionStorage.getItem('isAuthenticated');
+
+ if (isAuthenticated) {
+ this.showSessionTimeoutWarning();
+ }
+ }, this.SESSION_TIMEOUT_DURATION);
+ }
+
+ /**
+ * Show session timeout warning dialog
+ */
+ private showSessionTimeoutWarning(): void {
+ this.confirmationService
+ .alert(
+ 'Your session is about to expire. Do you need more time?',
+ 'sessionTimeOut',
+ )
+ .afterClosed()
+ .subscribe((result: any) => {
+ if (!result) return;
+
+ if (result.action === 'continue') {
+ this.extendSession();
+ } else if (result.action === 'timeout' || result.action === 'cancel') {
+ this.handleSessionExpired();
}
+ });
+ }
+
+ /**
+ * Extend user session by calling backend endpoint
+ */
+ private extendSession(): void {
+ this.http.post(environment.extendSessionUrl, {}).subscribe(
+ (res: any) => {
+ // Session extended successfully, restart timeout
+ this.resetSessionTimeout();
+ },
+ (err: any) => {
+ // Silently fail - let normal error handling take over
+ console.warn('Failed to extend session', err);
},
- 27 * 60 * 1000,
);
}
+
+ /**
+ * Clear session timeout
+ */
+ private clearSessionTimeout(): void {
+ if (this.sessionTimeoutRef) {
+ clearTimeout(this.sessionTimeoutRef);
+ this.sessionTimeoutRef = null;
+ }
+ }
}
diff --git a/src/app/app-modules/nurse-doctor/anc/anc.component.ts b/src/app/app-modules/nurse-doctor/anc/anc.component.ts
index 17d68ec..82e3ed3 100644
--- a/src/app/app-modules/nurse-doctor/anc/anc.component.ts
+++ b/src/app/app-modules/nurse-doctor/anc/anc.component.ts
@@ -134,6 +134,46 @@ export class AncComponent implements OnInit, OnChanges, OnDestroy, DoCheck {
visitCode: this.sessionstorage.getItem('visitCode'),
};
+ const immunizationForm = patientANCForm.get('patientANCImmunizationForm');
+
+ if (immunizationForm) {
+ const ttDateFields = [
+ 'dateReceivedForTT_1',
+ 'dateReceivedForTT_2',
+ 'dateReceivedForTT_3',
+ ];
+
+ ttDateFields.forEach((field) => {
+ const value = immunizationForm.get(field)?.value;
+
+ if (value) {
+ immunizationForm.patchValue({
+ [field]: this.normalizeToUTCMidnight(new Date(value)),
+ });
+ }
+ });
+ }
+
+ const ancDetailsForm = patientANCForm.get('patientANCDetailsForm');
+
+ if (ancDetailsForm) {
+ const lmpDateValue = ancDetailsForm.get('lmpDate')?.value;
+
+ if (lmpDateValue) {
+ ancDetailsForm.patchValue({
+ lmpDate: this.normalizeToUTCMidnight(new Date(lmpDateValue)),
+ });
+ }
+
+ const expDelDtValue = ancDetailsForm.get('expDelDt')?.value;
+
+ if (expDelDtValue) {
+ ancDetailsForm.patchValue({
+ expDelDt: this.normalizeToUTCMidnight(new Date(expDelDtValue)),
+ });
+ }
+ }
+
this.updateANCDetailsSubs = this.doctorService
.updateANCDetails(patientANCForm, temp)
.subscribe(
@@ -152,6 +192,16 @@ export class AncComponent implements OnInit, OnChanges, OnDestroy, DoCheck {
);
}
+ private normalizeToUTCMidnight(date: Date | null | undefined): string | null {
+ if (!date) return null;
+
+ const d = new Date(date);
+ const utcDate = new Date(
+ Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0)
+ );
+ return utcDate.toISOString();
+ }
+
getHRPDetails() {
const beneficiaryRegID = this.sessionstorage.getItem('beneficiaryRegID');
const visitCode = this.sessionstorage.getItem('visitCode');
diff --git a/src/app/app-modules/nurse-doctor/case-record/general-case-record/prescription/prescription.component.html b/src/app/app-modules/nurse-doctor/case-record/general-case-record/prescription/prescription.component.html
index a018ddb..ecd338d 100644
--- a/src/app/app-modules/nurse-doctor/case-record/general-case-record/prescription/prescription.component.html
+++ b/src/app/app-modules/nurse-doctor/case-record/general-case-record/prescription/prescription.component.html
@@ -45,7 +45,6 @@
name="form"
[(ngModel)]="currentPrescription.formName"
(selectionChange)="getFormValueChanged()"
- required
>
{{ item }}
@@ -157,7 +152,6 @@
name="unit"
[(ngModel)]="currentPrescription.unit"
[disabled]="!currentPrescription.drugID"
- required
>
0) {
+ formArray.removeAt(0);
+ }
+
+ for (let i = 0; i < temp.length; i++) {
+ formArray.push(this.initAllergyList());
+ }
+
+ this.allerySelectList = [];
+ this.previousSelectedAlleryList = [];
+
for (let i = 0; i < temp.length; i++) {
const allergyType = this.allergyMasterData.filter((item) => {
return item.allergyType === temp[i].allergyType;
});
+
if (allergyType.length > 0) temp[i].allergyType = allergyType[0];
- if (this.masterData.AllergicReactionTypes !== undefined) {
- temp[i].typeOfAllergicReactions =
- this.masterData.AllergicReactionTypes.filter((item: any) => {
- let flag = false;
- temp[i].typeOfAllergicReactions.forEach((element: any) => {
- if (element.name === item.name) flag = true;
- });
- return flag;
+ temp[i].typeOfAllergicReactions =
+ this.masterData.AllergicReactionTypes.filter((item: any) => {
+ let flag = false;
+ temp[i].typeOfAllergicReactions.forEach((element: any) => {
+ if (element.name === item.name) flag = true;
});
- }
+ return flag;
+ });
if (temp[i].otherAllergicReaction) temp[i].enableOtherAllergy = true;
+ const selectedAllergies = temp
+ .filter((t: any, idx: any) => idx !== i && t.allergyType)
+ .map((t: any) => t.allergyType.allergyType);
+
+ const availableAllergies = this.allergyMasterData.filter(
+ (item) => !selectedAllergies.includes(item.allergyType),
+ );
+
+ this.allerySelectList.push(availableAllergies.slice());
+
if (temp[i].allergyType) {
- const k: any = formArray.get('' + i);
+ this.previousSelectedAlleryList[i] = temp[i].allergyType;
+ }
+
+ const k: any = formArray.get('' + i);
+ if (k) {
k.patchValue(temp[i]);
k.markAsTouched();
- k.markAsDirty();
- this.filterAlleryList(temp[i].allergyType, i);
- if (
- k?.get('snomedTerm')?.value !== null &&
- k?.get('typeOfAllergicReactions')?.value !== null
- ) {
- k?.get('snomedTerm')?.enable();
- k?.get('typeOfAllergicReactions')?.enable();
+
+ if (temp[i].allergyType) {
+ k.get('snomedTerm')?.enable();
+ k.get('typeOfAllergicReactions')?.enable();
}
}
-
- if (i + 1 < temp.length) this.addAllergy();
}
}
}
diff --git a/src/app/app-modules/nurse-doctor/nurse-worklist-wrapper/nurse-mmu-tm-referred-worklist/nurse-mmu-tm-referred-worklist.component.ts b/src/app/app-modules/nurse-doctor/nurse-worklist-wrapper/nurse-mmu-tm-referred-worklist/nurse-mmu-tm-referred-worklist.component.ts
index e1e07a6..8346b6f 100644
--- a/src/app/app-modules/nurse-doctor/nurse-worklist-wrapper/nurse-mmu-tm-referred-worklist/nurse-mmu-tm-referred-worklist.component.ts
+++ b/src/app/app-modules/nurse-doctor/nurse-worklist-wrapper/nurse-mmu-tm-referred-worklist/nurse-mmu-tm-referred-worklist.component.ts
@@ -136,6 +136,10 @@ export class NurseMmuTmReferredWorklistComponent
this.dataSource.paginator = this.paginator;
},
(err: any) => {
+ if (err?.status == 401) {
+ // do nothing as http-interceptor will handle
+ return;
+ }
this.confirmationService.alert(err, 'error');
},
);
diff --git a/src/app/app-modules/nurse-doctor/pnc/pnc.component.ts b/src/app/app-modules/nurse-doctor/pnc/pnc.component.ts
index 13e14f6..49bed08 100644
--- a/src/app/app-modules/nurse-doctor/pnc/pnc.component.ts
+++ b/src/app/app-modules/nurse-doctor/pnc/pnc.component.ts
@@ -158,13 +158,25 @@ export class PncComponent implements OnInit, DoCheck, OnChanges, OnDestroy {
})[0];
}
- tempPNCData.dDate = new Date(tempPNCData.dateOfDelivery);
+ tempPNCData.dDate = this.normalizeToUTCMidnight(
+ new Date(tempPNCData.dateOfDelivery),
+ );
const patchPNCdata = Object.assign({}, tempPNCData);
this.patientPNCForm.patchValue(tempPNCData);
});
}
+ private normalizeToUTCMidnight(date: Date | null | undefined): string | null {
+ if (!date) return null;
+
+ const d = new Date(date);
+ const utcDate = new Date(
+ Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0)
+ );
+ return utcDate.toISOString();
+ }
+
updatePatientPNC(patientPNCForm: any) {
const temp = {
beneficiaryRegID: this.sessionstorage.getItem('beneficiaryRegID'),
@@ -174,6 +186,14 @@ export class PncComponent implements OnInit, DoCheck, OnChanges, OnDestroy {
visitCode: this.sessionstorage.getItem('visitCode'),
};
+ const dDate = patientPNCForm.get('dDate')?.value;
+ if (dDate) {
+ patientPNCForm.patchValue({
+ dateOfDelivery: this.normalizeToUTCMidnight(new Date(dDate)),
+ dDate: this.normalizeToUTCMidnight(new Date(dDate)),
+ });
+ }
+
this.doctorService.updatePNCDetails(patientPNCForm, temp).subscribe(
(res: any) => {
if (res.statusCode === 200 && res.data !== null) {
diff --git a/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.html b/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.html
index 81a8c18..03a6483 100644
--- a/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.html
+++ b/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.html
@@ -786,7 +786,7 @@
name="form"
[(ngModel)]="tempform"
(selectionChange)="getFormValueChanged()"
- required
+
>
[(ngModel)]="tempDrugName"
(keyup)="filterMedicine(tempDrugName)"
(blur)="reEnterMedicine()"
- required
[matAutocomplete]="autoGroup"
/>
name="dose"
[(ngModel)]="currentPrescription.dose"
[disabled]="!currentPrescription.drugID"
- required
>
name="frequency"
[(ngModel)]="currentPrescription.frequency"
[disabled]="!currentPrescription.drugID"
- required
>
name="duration"
[(ngModel)]="currentPrescription.duration"
[disabled]="!currentPrescription.drugID"
- required
>
name="unit"
[(ngModel)]="currentPrescription.unit"
[disabled]="!currentPrescription.drugID"
- required
>
name="quantity"
[(ngModel)]="currentPrescription.qtyPrescribed"
[disabled]="!currentPrescription.drugID"
- required
>