From c83bbd8ea5a29af40fd8a44da673e4588d2082f9 Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Tue, 11 Aug 2026 18:28:20 +0000 Subject: [PATCH 1/5] fix: restrict visit categories to supported flows + guard general history forms [37][32][4][35] The visit-category dropdown offered categories MMU has no flow for (Childhood & Adolescent, Family Planning, Neonatal); picking one stranded the user on Visit Details with no Next/Submit. handleVisitType() only implements 8 categories, so restrict the dropdown to those, and hide the adult NCD categories (NCD screening / NCD care) from beneficiaries under 30. [37] NCD Care History rendered blank: general-opd-history.loadFormData() runs on every ngOnChanges and assigned each section form unconditionally, so a transient stale/wrong parent form during a category switch nulled them all. Guard it to keep the last good reference until the correct form arrives. Note: [31] "stepper missing on category change" was caused by selecting one of the now-removed unsupported categories, so it is covered by the same whitelist. --- .../general-opd-history.component.ts | 70 ++++++++----------- .../visit-details/visit-details.component.ts | 41 ++++++++++- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/app/app-modules/nurse-doctor/history/general-opd-history/general-opd-history.component.ts b/src/app/app-modules/nurse-doctor/history/general-opd-history/general-opd-history.component.ts index aca936e5..50a36454 100644 --- a/src/app/app-modules/nurse-doctor/history/general-opd-history/general-opd-history.component.ts +++ b/src/app/app-modules/nurse-doctor/history/general-opd-history/general-opd-history.component.ts @@ -163,45 +163,37 @@ export class GeneralOpdHistoryComponent } loadFormData() { - this.pastHistory = this.nurseGeneralHistoryForm.get( - 'pastHistory' - ) as FormGroup; - this.comorbidityHistory = this.nurseGeneralHistoryForm.get( - 'comorbidityHistory' - ) as FormGroup; - this.medicationHistory = this.nurseGeneralHistoryForm.get( - 'medicationHistory' - ) as FormGroup; - this.personalHistory = this.nurseGeneralHistoryForm.get( - 'personalHistory' - ) as FormGroup; - this.familyHistory = this.nurseGeneralHistoryForm.get( - 'familyHistory' - ) as FormGroup; - this.menstrualHistory = this.nurseGeneralHistoryForm.get( - 'menstrualHistory' - ) as FormGroup; - this.perinatalHistory = this.nurseGeneralHistoryForm.get( - 'perinatalHistory' - ) as FormGroup; - this.pastObstericHistory = this.nurseGeneralHistoryForm.get( - 'pastObstericHistory' - ) as FormGroup; - this.immunizationHistory = this.nurseGeneralHistoryForm.get( - 'immunizationHistory' - ) as FormGroup; - this.otherVaccines = this.nurseGeneralHistoryForm.get( - 'otherVaccines' - ) as FormGroup; - this.feedingHistory = this.nurseGeneralHistoryForm.get( - 'feedingHistory' - ) as FormGroup; - this.developmentHistory = this.nurseGeneralHistoryForm.get( - 'developmentHistory' - ) as FormGroup; - this.physicalActivityHistory = this.nurseGeneralHistoryForm.get( - 'physicalActivityHistory' - ) as FormGroup; + const form = this.nurseGeneralHistoryForm; + if (!form) return; + // Only overwrite a section form when its control actually exists on the + // parent. loadFormData runs on every ngOnChanges, so during a visit-category + // switch the parent form can momentarily be the wrong/stale group whose + // get() returns null; assigning that null blanked the whole History section + // (and could crash the child [formGroup] bindings). Keeping the previous + // good reference lets the correct form win once it arrives. + const pick = (name: string): FormGroup | undefined => { + const ctrl = form.get(name); + return ctrl ? (ctrl as FormGroup) : undefined; + }; + this.pastHistory = pick('pastHistory') ?? this.pastHistory; + this.comorbidityHistory = + pick('comorbidityHistory') ?? this.comorbidityHistory; + this.medicationHistory = + pick('medicationHistory') ?? this.medicationHistory; + this.personalHistory = pick('personalHistory') ?? this.personalHistory; + this.familyHistory = pick('familyHistory') ?? this.familyHistory; + this.menstrualHistory = pick('menstrualHistory') ?? this.menstrualHistory; + this.perinatalHistory = pick('perinatalHistory') ?? this.perinatalHistory; + this.pastObstericHistory = + pick('pastObstericHistory') ?? this.pastObstericHistory; + this.immunizationHistory = + pick('immunizationHistory') ?? this.immunizationHistory; + this.otherVaccines = pick('otherVaccines') ?? this.otherVaccines; + this.feedingHistory = pick('feedingHistory') ?? this.feedingHistory; + this.developmentHistory = + pick('developmentHistory') ?? this.developmentHistory; + this.physicalActivityHistory = + pick('physicalActivityHistory') ?? this.physicalActivityHistory; } ngOnChanges(changes: any) { diff --git a/src/app/app-modules/nurse-doctor/visit-details/visit-details/visit-details.component.ts b/src/app/app-modules/nurse-doctor/visit-details/visit-details/visit-details.component.ts index 7a053ee6..16f7bb22 100644 --- a/src/app/app-modules/nurse-doctor/visit-details/visit-details/visit-details.component.ts +++ b/src/app/app-modules/nurse-doctor/visit-details/visit-details/visit-details.component.ts @@ -116,6 +116,42 @@ export class PatientVisitDetailsComponent this.beneficiaryDetailsSubscription.unsubscribe(); } + // MMU only builds a flow (steps + forms) for these visit categories in + // workarea.handleVisitType(). The backend master list also returns categories + // that MMU has no flow for (Childhood & Adolescent, Family Planning, Neonatal, + // …); picking one left the user stranded on Visit Details with no Next/Submit. + // Restrict the dropdown to what MMU actually supports. + private readonly supportedVisitCategories = [ + 'General OPD (QC)', + 'Cancer Screening', + 'General OPD', + 'NCD screening', + 'PNC', + 'ANC', + 'NCD care', + 'COVID-19 Screening', + ]; + + // Keep only categories MMU implements, and hide the adult NCD categories from + // beneficiaries under 30 (matching the age >= 30 auto-selection below) so a + // young beneficiary can't pick NCD screening / NCD care. + private filterSupportedCategories(categories: any[]): any[] { + const age = this.beneficiary?.ageVal; + return (categories || []).filter((item: any) => { + const name = item?.visitCategory; + if (!this.supportedVisitCategories.includes(name)) return false; + if ( + (name === 'NCD screening' || name === 'NCD care') && + age !== null && + age !== undefined && + age < 30 + ) { + return false; + } + return true; + }); + } + visitCategorySubscription: any; getVisitReasonAndCategory() { this.visitCategorySubscription = @@ -127,8 +163,9 @@ export class PatientVisitDetailsComponent this.templateNurseMasterData ); this.templateVisitReasons = this.templateNurseMasterData.visitReasons; - this.templateVisitCategories = - this.templateNurseMasterData.visitCategories; + this.templateVisitCategories = this.filterSupportedCategories( + this.templateNurseMasterData.visitCategories + ); this.templateFilterVisitCategories = this.templateVisitCategories; if ( From 47a6aa10965949a4cb52b55fb7cde4fa60881f9a Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Fri, 14 Aug 2026 16:58:27 +0000 Subject: [PATCH 2/5] fix(workarea): don't force re-entrant CD in hideAll (fixes stepper rebuild on category change) hideAll() ran changeDetectorRef.detectChanges() (added in migration; absent in main). Because hideAll runs inside the visitCategory valueChanges handler, forcing CD while the show* flags flip true->false throws ExpressionChangedAfterItHasBeenCheckedError on a category *change*, aborting handleVisitType() before it rebuilds the steps -> stepper collapses to Visit Details and Next stops working. Remove the forced CD; the normal change-detection pass rebuilds the (reactive Zard) stepper. Bug [31]. --- .../nurse-doctor/workarea/workarea.component.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts b/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts index a4851fba..80465374 100644 --- a/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts +++ b/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts @@ -1060,10 +1060,13 @@ export class WorkareaComponent this.showPNC = false; this.showCaseRecord = false; this.showRefer = false; - - if (this.attendantType === 'nurse') { - this.changeDetectorRef.detectChanges(); - } + // NOTE: no forced changeDetectorRef.detectChanges() here. It was added during + // the migration but hideAll() runs inside the visitCategory valueChanges + // handler (i.e. mid change-detection). Forcing CD there while the show* flags + // flip true->false throws ExpressionChangedAfterItHasBeenCheckedError on a + // category *change*, aborting handleVisitType() before it rebuilds the steps + // (stepper collapses to Visit Details, Next dies — bug [31]). The normal CD + // pass after this handler rebuilds the stepper (Zard steps are reactive). } submitPatientMedicalDetailsForm(medicalForm: any) { From 74e0a6efabf63c546ed6c4cf64ae24e0e308295e Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Sun, 16 Aug 2026 11:48:32 +0000 Subject: [PATCH 3/5] fix(workarea): surface an alert for unmapped visit categories instead of a blank page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handleVisitType had no else on the category if/else-if chain, so a visit category the app has no flow for (or a value mismatch like a bare 'Screening') fell through silently and rendered a blank page with no sections — the "Screening section is missing" symptom QA hit. Add a defensive else that alerts the user to re-select the category. No behavior change for the supported categories, which are all handled above. --- .../nurse-doctor/workarea/workarea.component.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts b/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts index 80465374..2103f3ad 100644 --- a/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts +++ b/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts @@ -1021,6 +1021,21 @@ export class WorkareaComponent this.referMode = new String(mode); this.caseRecordMode = new String(mode); } + } else { + // Defensive: an unmapped visit category (a master-data value MMU has no + // flow for, or a mismatch such as a bare 'Screening' reason value reaching + // here as a category) would otherwise fall through silently and leave a + // blank page with no sections — the "Screening section is missing" symptom. + // Surface a clear message instead. All supported categories are handled + // above and never reach this branch. + setTimeout(() => + this.confirmationService.alert( + this.currentLanguageSet?.alerts?.info + ?.visitCategoryNotSupported || + 'This visit category is not supported. Please re-select the visit category.', + 'info' + ) + ); } } else if (this.specialistFlag === '100') { this.showOnlyTMReferred(); From 7ecc7dfb9ec74d79c77874fec964d3cb924a82e2 Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Wed, 19 Aug 2026 04:52:21 +0000 Subject: [PATCH 4/5] chore(nurse-doctor): remove explanatory comments per review --- Common-UI | 2 +- .../general-opd-history.component.ts | 6 ------ .../visit-details/visit-details.component.ts | 8 -------- .../nurse-doctor/workarea/workarea.component.ts | 13 ------------- 4 files changed, 1 insertion(+), 28 deletions(-) diff --git a/Common-UI b/Common-UI index 209356cf..96503ea0 160000 --- a/Common-UI +++ b/Common-UI @@ -1 +1 @@ -Subproject commit 209356cf324fb19ca5705e62c58931062468147f +Subproject commit 96503ea0089896bf6908c4302ba0c67252230958 diff --git a/src/app/app-modules/nurse-doctor/history/general-opd-history/general-opd-history.component.ts b/src/app/app-modules/nurse-doctor/history/general-opd-history/general-opd-history.component.ts index 50a36454..8a47c511 100644 --- a/src/app/app-modules/nurse-doctor/history/general-opd-history/general-opd-history.component.ts +++ b/src/app/app-modules/nurse-doctor/history/general-opd-history/general-opd-history.component.ts @@ -165,12 +165,6 @@ export class GeneralOpdHistoryComponent loadFormData() { const form = this.nurseGeneralHistoryForm; if (!form) return; - // Only overwrite a section form when its control actually exists on the - // parent. loadFormData runs on every ngOnChanges, so during a visit-category - // switch the parent form can momentarily be the wrong/stale group whose - // get() returns null; assigning that null blanked the whole History section - // (and could crash the child [formGroup] bindings). Keeping the previous - // good reference lets the correct form win once it arrives. const pick = (name: string): FormGroup | undefined => { const ctrl = form.get(name); return ctrl ? (ctrl as FormGroup) : undefined; diff --git a/src/app/app-modules/nurse-doctor/visit-details/visit-details/visit-details.component.ts b/src/app/app-modules/nurse-doctor/visit-details/visit-details/visit-details.component.ts index 16f7bb22..b8fa3d2d 100644 --- a/src/app/app-modules/nurse-doctor/visit-details/visit-details/visit-details.component.ts +++ b/src/app/app-modules/nurse-doctor/visit-details/visit-details/visit-details.component.ts @@ -116,11 +116,6 @@ export class PatientVisitDetailsComponent this.beneficiaryDetailsSubscription.unsubscribe(); } - // MMU only builds a flow (steps + forms) for these visit categories in - // workarea.handleVisitType(). The backend master list also returns categories - // that MMU has no flow for (Childhood & Adolescent, Family Planning, Neonatal, - // …); picking one left the user stranded on Visit Details with no Next/Submit. - // Restrict the dropdown to what MMU actually supports. private readonly supportedVisitCategories = [ 'General OPD (QC)', 'Cancer Screening', @@ -132,9 +127,6 @@ export class PatientVisitDetailsComponent 'COVID-19 Screening', ]; - // Keep only categories MMU implements, and hide the adult NCD categories from - // beneficiaries under 30 (matching the age >= 30 auto-selection below) so a - // young beneficiary can't pick NCD screening / NCD care. private filterSupportedCategories(categories: any[]): any[] { const age = this.beneficiary?.ageVal; return (categories || []).filter((item: any) => { diff --git a/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts b/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts index 2103f3ad..a636fcc1 100644 --- a/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts +++ b/src/app/app-modules/nurse-doctor/workarea/workarea.component.ts @@ -1022,12 +1022,6 @@ export class WorkareaComponent this.caseRecordMode = new String(mode); } } else { - // Defensive: an unmapped visit category (a master-data value MMU has no - // flow for, or a mismatch such as a bare 'Screening' reason value reaching - // here as a category) would otherwise fall through silently and leave a - // blank page with no sections — the "Screening section is missing" symptom. - // Surface a clear message instead. All supported categories are handled - // above and never reach this branch. setTimeout(() => this.confirmationService.alert( this.currentLanguageSet?.alerts?.info @@ -1075,13 +1069,6 @@ export class WorkareaComponent this.showPNC = false; this.showCaseRecord = false; this.showRefer = false; - // NOTE: no forced changeDetectorRef.detectChanges() here. It was added during - // the migration but hideAll() runs inside the visitCategory valueChanges - // handler (i.e. mid change-detection). Forcing CD there while the show* flags - // flip true->false throws ExpressionChangedAfterItHasBeenCheckedError on a - // category *change*, aborting handleVisitType() before it rebuilds the steps - // (stepper collapses to Visit Details, Next dies — bug [31]). The normal CD - // pass after this handler rebuilds the stepper (Zard steps are reactive). } submitPatientMedicalDetailsForm(medicalForm: any) { From 317ab6226854c666ad6569493e12d6004dd9b89c Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Wed, 19 Aug 2026 16:21:53 +0000 Subject: [PATCH 5/5] chore: bump Common-UI to include #82 (registration mandatory fields) --- Common-UI | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common-UI b/Common-UI index 96503ea0..a5b78ec4 160000 --- a/Common-UI +++ b/Common-UI @@ -1 +1 @@ -Subproject commit 96503ea0089896bf6908c4302ba0c67252230958 +Subproject commit a5b78ec4b7ce8bfc369a88d23ccecce9c345e0e1