From 5255c0a3e9ea919ddd1f32862c212ca39926df8c Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Tue, 11 Aug 2026 19:07:51 +0000 Subject: [PATCH 1/5] fix: pagination alignment and readable search error message [25] The smart paginator composed z-pagination inside a justify-end row, but z-pagination's default host classes (mx-auto w-full justify-center) stretched it full-width and wrapped it below the rows-per-page selector. Override with mx-0 w-auto justify-end so it stays inline and aligned; standalone z-pagination is unaffected. [9] Registrar search error handlers passed the raw HttpErrorResponse object to confirmationService.alert(), which rendered as "[object Object]". Extract error.error.errorMessage || error.message before alerting (all search paths). --- v2/registrar/search/search.component.ts | 30 ++++++++++++++++++++----- v2/ui/paginator/paginator.component.ts | 1 + 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/v2/registrar/search/search.component.ts b/v2/registrar/search/search.component.ts index 751c7a7..d19f08b 100644 --- a/v2/registrar/search/search.component.ts +++ b/v2/registrar/search/search.component.ts @@ -178,7 +178,10 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes this.handleESSearchResponse(response); }, (error: any) => { - this.confirmationService.alert(error, 'error'); + this.confirmationService.alert( + error?.error?.errorMessage || error?.message || 'Something went wrong', + 'error', + ); } ); } @@ -220,7 +223,10 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes this.handleESSearchResponse(response); }, (error: any) => { - this.confirmationService.alert(error, 'error'); + this.confirmationService.alert( + error?.error?.errorMessage || error?.message || 'Something went wrong', + 'error', + ); } ); } else { @@ -381,7 +387,10 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes console.log('hi', JSON.stringify(beneficiaryList, null, 4)); }, (error) => { - this.confirmationService.alert(error, 'error'); + this.confirmationService.alert( + error?.error?.errorMessage || error?.message || 'Something went wrong', + 'error', + ); }, ); } else { @@ -554,7 +563,10 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes else this.confirmationService.alert(result.status, 'warn'); }, (error) => { - this.confirmationService.alert(error, 'error'); + this.confirmationService.alert( + error?.error?.errorMessage || error?.message || 'Something went wrong', + 'error', + ); }, ); } @@ -614,7 +626,10 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes console.log('ES Advanced Search Result:', JSON.stringify(response, null, 4)); }, (error) => { - this.confirmationService.alert(error, 'error'); + this.confirmationService.alert( + error?.error?.errorMessage || error?.message || 'Something went wrong', + 'error', + ); }, ); } else { @@ -639,7 +654,10 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes console.log(JSON.stringify(beneficiaryList, null, 4)); }, (error) => { - this.confirmationService.alert(error, 'error'); + this.confirmationService.alert( + error?.error?.errorMessage || error?.message || 'Something went wrong', + 'error', + ); }, ); } diff --git a/v2/ui/paginator/paginator.component.ts b/v2/ui/paginator/paginator.component.ts index d6559e7..7a2b236 100644 --- a/v2/ui/paginator/paginator.component.ts +++ b/v2/ui/paginator/paginator.component.ts @@ -71,6 +71,7 @@ import { mergeClasses } from '../utils/merge-classes'; } From f6c5f33e2d15843271785463a04d28d3ff3b9cbc Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Thu, 13 Aug 2026 17:35:56 +0000 Subject: [PATCH 2/5] fix(search): fallback message so "not found" alert isn't blank The no-results info alerts read currentLanguageSet.alerts.info.beneficiarynotfound (and .phoneDetails) with no fallback; in the registrar context that key can be unresolved, showing an empty "Info" dialog. Add optional-chaining + a literal fallback so a real message always shows. --- v2/registrar/search/search.component.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/v2/registrar/search/search.component.ts b/v2/registrar/search/search.component.ts index d19f08b..f23295b 100644 --- a/v2/registrar/search/search.component.ts +++ b/v2/registrar/search/search.component.ts @@ -376,7 +376,8 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes if (!beneficiaryList || beneficiaryList.length <= 0) { this.resetWorklist(); this.confirmationService.alert( - this.currentLanguageSet.alerts.info.beneficiarynotfound, + this.currentLanguageSet?.alerts?.info?.beneficiarynotfound || + 'Beneficiary not found', 'info', ); } else { @@ -395,7 +396,8 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes ); } else { this.confirmationService.alert( - this.currentLanguageSet.alerts.info.phoneDetails, + this.currentLanguageSet?.alerts?.info?.phoneDetails || + 'Please enter the required search details', 'info', ); } From d21b2c77c2b347ff3f9063cf7246a4b312c3f388 Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Fri, 14 Aug 2026 06:59:23 +0000 Subject: [PATCH 3/5] fix(search): correct beneficiaryNotFound key casing so alert isn't blank The non-ES advanced-search no-results alert read alerts.info.beneficiaryNotFound (camelCase) but the language key is beneficiarynotfound (lowercase) -> undefined -> blank "Info" dialog. Use the correct key with optional chaining + literal fallback. --- v2/registrar/search/search.component.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/v2/registrar/search/search.component.ts b/v2/registrar/search/search.component.ts index f23295b..d6629c0 100644 --- a/v2/registrar/search/search.component.ts +++ b/v2/registrar/search/search.component.ts @@ -618,7 +618,9 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes this.resetWorklist(); this.quicksearchTerm = null; this.confirmationService.alert( - this.currentLanguageSet.alerts.info.beneficiaryNotFound, + this.currentLanguageSet?.alerts?.info?.beneficiarynotfound || + this.currentLanguageSet?.alerts?.info?.beneficiaryNotFound || + 'Beneficiary not found', 'info', ); } else { @@ -647,7 +649,9 @@ export class SearchComponent implements OnInit, DoCheck, AfterViewChecked, OnDes this.resetWorklist(); this.quicksearchTerm = null; this.confirmationService.alert( - this.currentLanguageSet.alerts.info.beneficiaryNotFound, + this.currentLanguageSet?.alerts?.info?.beneficiarynotfound || + this.currentLanguageSet?.alerts?.info?.beneficiaryNotFound || + 'Beneficiary not found', 'info', ); } else { From a8eda027a1a606bad04046fdd3ab429023554014 Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Sat, 15 Aug 2026 18:14:37 +0000 Subject: [PATCH 4/5] fix(registration): enforce mandatory fields so an empty form can't be submitted The migration to reactive forms + Zard kept the red-asterisk label ([zRequired]="item.isRequired") but stopped attaching a validator to the control, so mainForm stayed valid while empty: Next advanced and Submit (gated on !mainForm.valid) stayed enabled, generating a blank beneficiary ID. - personal/location/other-information: attach Validators.required in the addControl loop for every field flagged isRequired in the master config, alongside the existing pattern/min/max validators. Merge onto pre-seeded controls (age/ageAtMarriage) since FormGroup.addControl no-ops when the control already exists. - registration: block nextStep() while the active step group is invalid (markAllAsTouched to surface the required messages) and add a defensive invalid-guard in submitBeneficiaryDetails(). --- .../location-information.component.ts | 32 +++++++-------- .../other-information.component.ts | 26 +++++++------ .../personal-information.component.ts | 31 +++++++++++---- .../registration/registration.component.ts | 39 +++++++++++++++++++ 4 files changed, 92 insertions(+), 36 deletions(-) diff --git a/v2/registrar/registration/location-information/location-information.component.ts b/v2/registrar/registration/location-information/location-information.component.ts index 7632bc4..8a060c0 100644 --- a/v2/registrar/registration/location-information/location-information.component.ts +++ b/v2/registrar/registration/location-information/location-information.component.ts @@ -79,26 +79,26 @@ export class LocationInformationComponent { ngOnInit() { this.formData.forEach((item: any) => { + // Attach Validators.required for isRequired fields (migration dropped this; + // see personal-information for the full explanation). + const validators = []; + if (item.isRequired) { + validators.push(Validators.required); + } if (item.fieldName && item.allowText) { - this.locationInfoFormGroup.addControl( - item.fieldName, - new FormControl(null, [ - Validators.pattern(this.allowTextValidator(item.allowText)), - Validators.minLength(parseInt(item?.allowMin)), - Validators.maxLength(parseInt(item?.allowMax)), - ]), - ); - } else { - this.locationInfoFormGroup.addControl( - item.fieldName, - new FormControl(null), + validators.push( + Validators.pattern(this.allowTextValidator(item.allowText)), + Validators.minLength(parseInt(item?.allowMin)), + Validators.maxLength(parseInt(item?.allowMax)) ); + } else if (item.options) { // Initialize filtered list with all options - if (item.options) { - this.filteredOptions[item.fieldName] = [...item.options]; - } + this.filteredOptions[item.fieldName] = [...item.options]; } - + this.locationInfoFormGroup.addControl( + item.fieldName, + new FormControl(null, validators) + ); }); this.locationInfoFormGroup.addControl('stateID', new FormControl()); this.locationInfoFormGroup.addControl('districtID', new FormControl()); diff --git a/v2/registrar/registration/other-information/other-information.component.ts b/v2/registrar/registration/other-information/other-information.component.ts index 7003c7e..bd236e4 100644 --- a/v2/registrar/registration/other-information/other-information.component.ts +++ b/v2/registrar/registration/other-information/other-information.component.ts @@ -81,21 +81,23 @@ export class OtherInformationComponent { ngOnInit() { console.log('this.otherInfoSubscription', this.otherInfoSubscription); this.formData.forEach((item: any) => { + // Attach Validators.required for isRequired fields (migration dropped this; + // see personal-information for the full explanation). + const validators = []; + if (item.isRequired) { + validators.push(Validators.required); + } if (item.fieldName && item.allowText) { - this.otherInfoFormGroup.addControl( - item.fieldName, - new FormControl(null, [ - Validators.pattern(this.allowTextValidator(item.allowText)), - Validators.minLength(parseInt(item?.allowMin)), - Validators.maxLength(parseInt(item?.allowMax)), - ]), - ); - } else { - this.otherInfoFormGroup.addControl( - item.fieldName, - new FormControl(null), + validators.push( + Validators.pattern(this.allowTextValidator(item.allowText)), + Validators.minLength(parseInt(item?.allowMin)), + Validators.maxLength(parseInt(item?.allowMax)) ); } + this.otherInfoFormGroup.addControl( + item.fieldName, + new FormControl(null, validators) + ); }); console.log('otherInfoFormGroup Data', this.otherInfoFormGroup); if (this.patientRevisit) diff --git a/v2/registrar/registration/personal-information/personal-information.component.ts b/v2/registrar/registration/personal-information/personal-information.component.ts index ce793af..c8a76db 100644 --- a/v2/registrar/registration/personal-information/personal-information.component.ts +++ b/v2/registrar/registration/personal-information/personal-information.component.ts @@ -104,19 +104,34 @@ export class PersonalInformationComponent { this.isEnableES = environment.isEnableES || false; this.fetchLanguageResponse(); this.formData.forEach((item: any) => { + // A field flagged isRequired in the registration master config must get + // Validators.required. The migration dropped this — the label showed a red * + // (zRequired) but no validator was attached to the reactive control, so the + // form stayed valid while empty and could be submitted blank. + const validators = []; + if (item.isRequired) { + validators.push(Validators.required); + } if (item.fieldName && item.allowText) { - this.personalInfoFormGroup.addControl( - item.fieldName, - new FormControl(null, [ - Validators.pattern(this.allowTextValidator(item.allowText)), - Validators.minLength(parseInt(item?.allowMin)), - Validators.maxLength(parseInt(item?.allowMax)), - ]) + validators.push( + Validators.pattern(this.allowTextValidator(item.allowText)), + Validators.minLength(parseInt(item?.allowMin)), + Validators.maxLength(parseInt(item?.allowMax)) ); + } + // age / ageAtMarriage are pre-seeded on the group by the parent (for the + // ageAtMarriage cross-field validator). addControl() no-ops on an existing + // control, so merge validators onto it instead of silently dropping them. + const existing = this.personalInfoFormGroup.get(item.fieldName); + if (existing) { + if (validators.length) { + existing.addValidators(validators); + existing.updateValueAndValidity({ emitEvent: false }); + } } else { this.personalInfoFormGroup.addControl( item.fieldName, - new FormControl(null) + new FormControl(null, validators) ); } }); diff --git a/v2/registrar/registration/registration.component.ts b/v2/registrar/registration/registration.component.ts index 506ce34..cbde07a 100644 --- a/v2/registrar/registration/registration.component.ts +++ b/v2/registrar/registration/registration.component.ts @@ -172,7 +172,35 @@ export class RegistrationComponent { return this.currentStep >= this.enabledStepKeys.length - 1; } + // Map a step key to its backing FormGroup so navigation/submit can check validity. + private stepFormGroup(key: string): FormGroup | null { + switch (key) { + case 'personal': + return this.personalInfoFormGroup; + case 'location': + return this.locationInfoFormGroup; + case 'other': + return this.otherInfoFormGroup; + case 'abha': + return this.abhaInfoFormGroup; + default: + return null; + } + } + nextStep() { + // Block advancing while the current step has unfilled mandatory fields, and + // surface the errors (markAllAsTouched) so the required messages render. + const group = this.stepFormGroup(this.activeStepKey); + if (group && group.invalid) { + group.markAllAsTouched(); + this.confirmationService.alert( + this.currentLanguageSet?.alerts?.info?.mandatoryFields || + 'Please fill all the mandatory fields', + 'info' + ); + return; + } if (this.currentStep < this.enabledStepKeys.length - 1) { this.currentStep++; } @@ -335,6 +363,17 @@ export class RegistrationComponent { submitBeneficiaryDetails() { + // Defensive guard: the Submit button is disabled while invalid, but never save + // a blank/partial beneficiary if it is reached programmatically. + if (this.mainForm.invalid) { + this.mainForm.markAllAsTouched(); + this.confirmationService.alert( + this.currentLanguageSet?.alerts?.info?.mandatoryFields || + 'Please fill all the mandatory fields', + 'info' + ); + return; + } console.log('registration data', this.mainForm); const newDate = this.dateFormatChange(); const valueToSend = this.mainForm.value; From 454db76c5e91fbd38fca6f53da4aeac67bee7c03 Mon Sep 17 00:00:00 2001 From: gkbishnoi07 Date: Wed, 19 Aug 2026 06:54:18 +0000 Subject: [PATCH 5/5] chore(registration): drop comments + resolve SonarCloud smells (Number.parseInt, optional chain) --- .../location-information.component.ts | 7 ++----- .../other-information/other-information.component.ts | 6 ++---- .../personal-information.component.ts | 11 ++--------- v2/registrar/registration/registration.component.ts | 7 +------ 4 files changed, 7 insertions(+), 24 deletions(-) diff --git a/v2/registrar/registration/location-information/location-information.component.ts b/v2/registrar/registration/location-information/location-information.component.ts index 8a060c0..1051584 100644 --- a/v2/registrar/registration/location-information/location-information.component.ts +++ b/v2/registrar/registration/location-information/location-information.component.ts @@ -79,8 +79,6 @@ export class LocationInformationComponent { ngOnInit() { this.formData.forEach((item: any) => { - // Attach Validators.required for isRequired fields (migration dropped this; - // see personal-information for the full explanation). const validators = []; if (item.isRequired) { validators.push(Validators.required); @@ -88,11 +86,10 @@ export class LocationInformationComponent { if (item.fieldName && item.allowText) { validators.push( Validators.pattern(this.allowTextValidator(item.allowText)), - Validators.minLength(parseInt(item?.allowMin)), - Validators.maxLength(parseInt(item?.allowMax)) + Validators.minLength(Number.parseInt(item?.allowMin)), + Validators.maxLength(Number.parseInt(item?.allowMax)) ); } else if (item.options) { - // Initialize filtered list with all options this.filteredOptions[item.fieldName] = [...item.options]; } this.locationInfoFormGroup.addControl( diff --git a/v2/registrar/registration/other-information/other-information.component.ts b/v2/registrar/registration/other-information/other-information.component.ts index bd236e4..a16e769 100644 --- a/v2/registrar/registration/other-information/other-information.component.ts +++ b/v2/registrar/registration/other-information/other-information.component.ts @@ -81,8 +81,6 @@ export class OtherInformationComponent { ngOnInit() { console.log('this.otherInfoSubscription', this.otherInfoSubscription); this.formData.forEach((item: any) => { - // Attach Validators.required for isRequired fields (migration dropped this; - // see personal-information for the full explanation). const validators = []; if (item.isRequired) { validators.push(Validators.required); @@ -90,8 +88,8 @@ export class OtherInformationComponent { if (item.fieldName && item.allowText) { validators.push( Validators.pattern(this.allowTextValidator(item.allowText)), - Validators.minLength(parseInt(item?.allowMin)), - Validators.maxLength(parseInt(item?.allowMax)) + Validators.minLength(Number.parseInt(item?.allowMin)), + Validators.maxLength(Number.parseInt(item?.allowMax)) ); } this.otherInfoFormGroup.addControl( diff --git a/v2/registrar/registration/personal-information/personal-information.component.ts b/v2/registrar/registration/personal-information/personal-information.component.ts index c8a76db..a289839 100644 --- a/v2/registrar/registration/personal-information/personal-information.component.ts +++ b/v2/registrar/registration/personal-information/personal-information.component.ts @@ -104,10 +104,6 @@ export class PersonalInformationComponent { this.isEnableES = environment.isEnableES || false; this.fetchLanguageResponse(); this.formData.forEach((item: any) => { - // A field flagged isRequired in the registration master config must get - // Validators.required. The migration dropped this — the label showed a red * - // (zRequired) but no validator was attached to the reactive control, so the - // form stayed valid while empty and could be submitted blank. const validators = []; if (item.isRequired) { validators.push(Validators.required); @@ -115,13 +111,10 @@ export class PersonalInformationComponent { if (item.fieldName && item.allowText) { validators.push( Validators.pattern(this.allowTextValidator(item.allowText)), - Validators.minLength(parseInt(item?.allowMin)), - Validators.maxLength(parseInt(item?.allowMax)) + Validators.minLength(Number.parseInt(item?.allowMin)), + Validators.maxLength(Number.parseInt(item?.allowMax)) ); } - // age / ageAtMarriage are pre-seeded on the group by the parent (for the - // ageAtMarriage cross-field validator). addControl() no-ops on an existing - // control, so merge validators onto it instead of silently dropping them. const existing = this.personalInfoFormGroup.get(item.fieldName); if (existing) { if (validators.length) { diff --git a/v2/registrar/registration/registration.component.ts b/v2/registrar/registration/registration.component.ts index cbde07a..35e594e 100644 --- a/v2/registrar/registration/registration.component.ts +++ b/v2/registrar/registration/registration.component.ts @@ -172,7 +172,6 @@ export class RegistrationComponent { return this.currentStep >= this.enabledStepKeys.length - 1; } - // Map a step key to its backing FormGroup so navigation/submit can check validity. private stepFormGroup(key: string): FormGroup | null { switch (key) { case 'personal': @@ -189,10 +188,8 @@ export class RegistrationComponent { } nextStep() { - // Block advancing while the current step has unfilled mandatory fields, and - // surface the errors (markAllAsTouched) so the required messages render. const group = this.stepFormGroup(this.activeStepKey); - if (group && group.invalid) { + if (group?.invalid) { group.markAllAsTouched(); this.confirmationService.alert( this.currentLanguageSet?.alerts?.info?.mandatoryFields || @@ -363,8 +360,6 @@ export class RegistrationComponent { submitBeneficiaryDetails() { - // Defensive guard: the Submit button is disabled while invalid, but never save - // a blank/partial beneficiary if it is reached programmatically. if (this.mainForm.invalid) { this.mainForm.markAllAsTouched(); this.confirmationService.alert(