From efdaec4a656cbbc0b1f1119a75ada7dee0902bc9 Mon Sep 17 00:00:00 2001 From: Jack Wong <108699279+bergomi02@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:35:44 -0700 Subject: [PATCH] PRIME-2764 Update validation to require only one email address for share GPID page (#2558) * initial commit * reverse previous changes * update validation rule * bug fix * apply fix to next step page * more bug fix * fix PR issues * no space allow in email * fix PR issue - trim space * fix PR issue * fix PR issue --------- Co-authored-by: Alan Leung --- .../lib/modules/forms/trim-space.directive.ts | 34 +++++ .../next-steps/next-steps.component.html | 4 +- .../pages/next-steps/next-steps.component.ts | 133 +++++++++++------- ...pharmanet-enrolment-summary.component.html | 2 +- .../pharmanet-enrolment-summary.component.ts | 115 +++++++++------ .../email-form/email-form.component.html | 3 +- .../forms/email-form/email-form.component.ts | 19 ++- .../src/app/shared/shared.module.ts | 3 + 8 files changed, 211 insertions(+), 102 deletions(-) create mode 100644 prime-angular-frontend/src/app/lib/modules/forms/trim-space.directive.ts diff --git a/prime-angular-frontend/src/app/lib/modules/forms/trim-space.directive.ts b/prime-angular-frontend/src/app/lib/modules/forms/trim-space.directive.ts new file mode 100644 index 0000000000..caf740966e --- /dev/null +++ b/prime-angular-frontend/src/app/lib/modules/forms/trim-space.directive.ts @@ -0,0 +1,34 @@ +import { DefaultValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { + Directive, + ElementRef, + HostListener, + Renderer2, + forwardRef, +} from '@angular/core'; + +@Directive({ + selector: 'input[appTrimSpace]', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: forwardRef(() => TrimSpaceInputDirective), + }, + ], +}) + +export class TrimSpaceInputDirective extends DefaultValueAccessor { + @HostListener('input', ['$event']) input($event: InputEvent) { + const target = $event.target as HTMLInputElement; + const start = target.selectionStart; + + target.value = target.value.trim(); + target.setSelectionRange(start, start); + this.onChange(target.value); + } + + constructor(renderer: Renderer2, elementRef: ElementRef) { + super(renderer, elementRef, false); + } +} diff --git a/prime-angular-frontend/src/app/modules/enrolment/pages/next-steps/next-steps.component.html b/prime-angular-frontend/src/app/modules/enrolment/pages/next-steps/next-steps.component.html index dda9a337a2..e56df94d53 100644 --- a/prime-angular-frontend/src/app/modules/enrolment/pages/next-steps/next-steps.component.html +++ b/prime-angular-frontend/src/app/modules/enrolment/pages/next-steps/next-steps.component.html @@ -64,7 +64,7 @@ -
+
@@ -72,7 +72,7 @@ [formGroupName]="i"> diff --git a/prime-angular-frontend/src/app/modules/enrolment/pages/next-steps/next-steps.component.ts b/prime-angular-frontend/src/app/modules/enrolment/pages/next-steps/next-steps.component.ts index dd6809f0b4..bba310e82e 100644 --- a/prime-angular-frontend/src/app/modules/enrolment/pages/next-steps/next-steps.component.ts +++ b/prime-angular-frontend/src/app/modules/enrolment/pages/next-steps/next-steps.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; -import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { AuthService } from '@auth/shared/services/auth.service'; @@ -35,6 +35,7 @@ import { exhaustMap } from 'rxjs/operators'; export class NextStepsComponent extends BaseEnrolmentProfilePage implements OnInit { public title: string; public enrolment: Enrolment; + public emailForm: FormGroup; public hasReadAgreement: boolean; public CareSettingEnum = CareSettingEnum; @@ -104,39 +105,39 @@ export class NextStepsComponent extends BaseEnrolmentProfilePage implements OnIn } public get communityHealthEmails(): FormArray { - return this.form.get('communityHealthEmails') as FormArray; + return this.emailForm.get('communityHealthEmails') as FormArray; } public get pharmacistEmails(): FormArray { - return this.form.get('pharmacistEmails') as FormArray; + return this.emailForm.get('pharmacistEmails') as FormArray; } public get healthAuthorityFraserEmails(): FormArray { - return this.form.get('healthAuthorityFraserEmails') as FormArray; + return this.emailForm.get('healthAuthorityFraserEmails') as FormArray; } public get healthAuthorityNorthernEmails(): FormArray { - return this.form.get('healthAuthorityNorthernEmails') as FormArray; + return this.emailForm.get('healthAuthorityNorthernEmails') as FormArray; } public get healthAuthorityIslandEmails(): FormArray { - return this.form.get('healthAuthorityIslandEmails') as FormArray; + return this.emailForm.get('healthAuthorityIslandEmails') as FormArray; } public get healthAuthorityInteriorEmails(): FormArray { - return this.form.get('healthAuthorityInteriorEmails') as FormArray; + return this.emailForm.get('healthAuthorityInteriorEmails') as FormArray; } public get healthAuthorityPHSAEmails(): FormArray { - return this.form.get('healthAuthorityPHSAEmails') as FormArray; + return this.emailForm.get('healthAuthorityPHSAEmails') as FormArray; } public get healthAuthorityVancouverCoastalEmails(): FormArray { - return this.form.get('healthAuthorityVancouverCoastalEmails') as FormArray; + return this.emailForm.get('healthAuthorityVancouverCoastalEmails') as FormArray; } public get deviceProviderEmails(): FormArray { - return this.form.get('deviceProviderEmails') as FormArray; + return this.emailForm.get('deviceProviderEmails') as FormArray; } public getAgreementDescription() { @@ -171,38 +172,52 @@ export class NextStepsComponent extends BaseEnrolmentProfilePage implements OnIn } public sendProvisionerAccessLink() { - const data: DialogOptions = { - title: 'Confirm Email', - message: `Are you sure you want to send your Approval Notification?`, - actionText: 'Send', - }; - this.busy = this.dialog.open(ConfirmDialogComponent, { data }) - .afterClosed() - .pipe( - exhaustMap((result: boolean) => { - if (result) { - this.complete = true; - - let emailPairs = this.careSettingConfigs.map((config) => { - return { - emails: config.formArray.value.map(email => email.email), - careSettingCode: config.settingCode, - healthAuthorityCode: config.settingCode === CareSettingEnum.HEALTH_AUTHORITY ? config.healthAuthorityCode : null, - } - }); - return this.enrolmentResource.sendProvisionerAccessLink(emailPairs, this.enrolment.id); - } else { - return EMPTY; - } - }) - ) - .subscribe(() => { - this.toastService.openSuccessToast('Email was successfully sent'); - this.router.navigate([EnrolmentRoutes.PHARMANET_ENROLMENT_SUMMARY], - { relativeTo: this.route.parent, queryParams: { initialEnrolment: this.initialEnrolment } }); - }); - this.onPageChange({ atEnd: true }); + if (!this.atLeastOneEmailFilled()) { + const data: DialogOptions = { + title: 'Missing Email', + message: `Please enter at least one email for the Approval Notification.`, + cancelText: 'Close', + actionType: 'warn', + actionHide: true, + }; + this.dialog.open(ConfirmDialogComponent, { data }).afterClosed(); + + } else { + + const data: DialogOptions = { + title: 'Confirm Email', + message: `Are you sure you want to send your Approval Notification?`, + actionText: 'Send', + }; + this.busy = this.dialog.open(ConfirmDialogComponent, { data }) + .afterClosed() + .pipe( + exhaustMap((result: boolean) => { + if (result) { + this.complete = true; + + let emailPairs = this.careSettingConfigs.map((config) => { + return { + emails: config.formArray.value.map(email => email.email), + careSettingCode: config.settingCode, + healthAuthorityCode: config.settingCode === CareSettingEnum.HEALTH_AUTHORITY ? config.healthAuthorityCode : null, + } + }); + + return this.enrolmentResource.sendProvisionerAccessLink(emailPairs.filter((ep) => ep.emails && ep.emails[0]), this.enrolment.id); + } else { + return EMPTY; + } + }) + ) + .subscribe(() => { + this.toastService.openSuccessToast('Email was successfully sent'); + this.router.navigate([EnrolmentRoutes.PHARMANET_ENROLMENT_SUMMARY], + { relativeTo: this.route.parent, queryParams: { initialEnrolment: this.initialEnrolment } }); + }); + this.onPageChange({ atEnd: true }); + } } public getEmailsGroup(careSettingCode: number, healthAuthorityCode: number) { @@ -387,7 +402,7 @@ export class NextStepsComponent extends BaseEnrolmentProfilePage implements OnIn } protected createFormInstance(): void { - this.form = this.buildEmailGroup(); + this.emailForm = this.buildEmailGroup(); } protected nextRouteAfterSubmit() { @@ -409,15 +424,31 @@ export class NextStepsComponent extends BaseEnrolmentProfilePage implements OnIn private buildEmailGroup(): FormGroup { return this.fb.group({ - communityHealthEmails: this.fb.array([], [Validators.required]), - pharmacistEmails: this.fb.array([], [Validators.required]), - healthAuthorityFraserEmails: this.fb.array([], [Validators.required]), - healthAuthorityInteriorEmails: this.fb.array([], [Validators.required]), - healthAuthorityIslandEmails: this.fb.array([], [Validators.required]), - healthAuthorityNorthernEmails: this.fb.array([], [Validators.required]), - healthAuthorityPHSAEmails: this.fb.array([], [Validators.required]), - healthAuthorityVancouverCoastalEmails: this.fb.array([], [Validators.required]), - deviceProviderEmails: this.fb.array([], [Validators.required]), + communityHealthEmails: this.fb.array([], []), + pharmacistEmails: this.fb.array([], []), + healthAuthorityFraserEmails: this.fb.array([], []), + healthAuthorityInteriorEmails: this.fb.array([], []), + healthAuthorityIslandEmails: this.fb.array([], []), + healthAuthorityNorthernEmails: this.fb.array([], []), + healthAuthorityPHSAEmails: this.fb.array([], []), + healthAuthorityVancouverCoastalEmails: this.fb.array([], []), + deviceProviderEmails: this.fb.array([], []), }); } + + public atLeastOneEmailFilled(): boolean { + let emailFilled = false; + + Object.keys(this.emailForm.controls).forEach((emailArrayKey) => { + const emailArray = this.emailForm.controls[emailArrayKey] as FormArray; + Object.keys(emailArray.controls).forEach((emailKey) => { + let emailControl = emailArray.controls[emailKey] as FormGroup; + if (emailControl.controls['email'].value && emailControl.controls['email'].value !== "") { + emailFilled = true; + } + }); + }); + + return emailFilled; + } } diff --git a/prime-angular-frontend/src/app/modules/enrolment/pages/pharmanet-enrolment-summary/pharmanet-enrolment-summary.component.html b/prime-angular-frontend/src/app/modules/enrolment/pages/pharmanet-enrolment-summary/pharmanet-enrolment-summary.component.html index e50a7ad0c8..86bff046c6 100644 --- a/prime-angular-frontend/src/app/modules/enrolment/pages/pharmanet-enrolment-summary/pharmanet-enrolment-summary.component.html +++ b/prime-angular-frontend/src/app/modules/enrolment/pages/pharmanet-enrolment-summary/pharmanet-enrolment-summary.component.html @@ -174,7 +174,7 @@ [formGroupName]="i"> diff --git a/prime-angular-frontend/src/app/modules/enrolment/pages/pharmanet-enrolment-summary/pharmanet-enrolment-summary.component.ts b/prime-angular-frontend/src/app/modules/enrolment/pages/pharmanet-enrolment-summary/pharmanet-enrolment-summary.component.ts index fcf15b6895..2df55efb94 100644 --- a/prime-angular-frontend/src/app/modules/enrolment/pages/pharmanet-enrolment-summary/pharmanet-enrolment-summary.component.ts +++ b/prime-angular-frontend/src/app/modules/enrolment/pages/pharmanet-enrolment-summary/pharmanet-enrolment-summary.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, Inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { FormGroup, FormControl, FormBuilder, Validators, FormArray } from '@angular/forms'; +import { FormGroup, FormControl, FormBuilder, FormArray } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { exhaustMap } from 'rxjs/operators'; @@ -8,7 +8,7 @@ import { EMPTY } from 'rxjs'; import { Config } from '@config/config.model'; import { APP_CONFIG, AppConfig } from 'app/app-config.module'; -import { FormControlValidators } from '@lib/validators/form-control.validators'; +import { FormUtilsService } from '@core/services/form-utils.service'; import { ToastService } from '@core/services/toast.service'; import { ConfirmDialogComponent } from '@shared/components/dialogs/confirm-dialog/confirm-dialog.component'; import { DialogOptions } from '@shared/components/dialogs/dialog-options.model'; @@ -189,37 +189,52 @@ export class PharmanetEnrolmentSummaryComponent extends BaseEnrolmentPage implem } public sendProvisionerAccessLink() { - const data: DialogOptions = { - title: 'Confirm Email', - message: `Are you sure you want to send your Approval Notification?`, - actionText: 'Send', - }; - this.busy = this.dialog.open(ConfirmDialogComponent, { data }) - .afterClosed() - .pipe( - exhaustMap((result: boolean) => { - if (result) { - let emailPairs = this.careSettingConfigs.map((config) => { - return { - emails: config.formArray.value.map(email => email.email), - careSettingCode: config.settingCode, - healthAuthorityCode: config.healthAuthorityCode, - } - }); - return this.enrolmentResource.sendProvisionerAccessLink(emailPairs, this.enrolment.id); - } else { - return EMPTY; - } - }) - ) - .subscribe(() => { - let emails = new Array(); - this.careSettingConfigs.forEach((config) => { - emails.push(config.formArray.value.map(email => email.email)); + + if (!this.atLeastOneEmailFilled()) { + const data: DialogOptions = { + title: 'Missing Email', + message: `Please enter at least one email for the Approval Notification.`, + cancelText: 'Close', + actionType: 'warn', + actionHide: true, + }; + this.dialog.open(ConfirmDialogComponent, { data }).afterClosed(); + + } else { + const data: DialogOptions = { + title: 'Confirm Email', + message: `Are you sure you want to send your Approval Notification?`, + actionText: 'Send', + }; + this.busy = this.dialog.open(ConfirmDialogComponent, { data }) + .afterClosed() + .pipe( + exhaustMap((result: boolean) => { + if (result) { + let emailPairs = this.careSettingConfigs.map((config) => { + return { + emails: config.formArray.value.map(email => email.email), + careSettingCode: config.settingCode, + healthAuthorityCode: config.healthAuthorityCode, + } + }); + return this.enrolmentResource.sendProvisionerAccessLink(emailPairs.filter((ep) => ep.emails && ep.emails[0]), this.enrolment.id); + } else { + return EMPTY; + } + }) + ) + .subscribe(() => { + let emails = new Array(); + this.careSettingConfigs.forEach((config) => { + if (config.formArray.value[0].email) { + emails.push(config.formArray.value.map(email => email.email)); + } + }); + this.toastService.openSuccessToast(`Email was successfully sent to ${emails.join(", ")}`); + this.emailForm.reset(); }); - this.toastService.openSuccessToast(`Email was successfully sent to ${emails.join(", ")}`); - this.emailForm.reset(); - }); + } } //No long in used at the moment. @@ -415,11 +430,13 @@ export class PharmanetEnrolmentSummaryComponent extends BaseEnrolmentPage implem } protected addEmail(emailsArray: FormArray, email?: string): void { + const emailForm = this.fb.group({ email: ['', []] }); emailForm.patchValue({ email }); emailsArray.push(emailForm); + } protected createFormInstance(): void { @@ -443,15 +460,31 @@ export class PharmanetEnrolmentSummaryComponent extends BaseEnrolmentPage implem private buildEmailGroup(): FormGroup { return this.fb.group({ - communityHealthEmails: this.fb.array([], [Validators.required]), - pharmacistEmails: this.fb.array([], [Validators.required]), - healthAuthorityFraserEmails: this.fb.array([], [Validators.required]), - healthAuthorityInteriorEmails: this.fb.array([], [Validators.required]), - healthAuthorityIslandEmails: this.fb.array([], [Validators.required]), - healthAuthorityNorthernEmails: this.fb.array([], [Validators.required]), - healthAuthorityPHSAEmails: this.fb.array([], [Validators.required]), - healthAuthorityVancouverCoastalEmails: this.fb.array([], [Validators.required]), - deviceProviderEmails: this.fb.array([], [Validators.required]), + communityHealthEmails: this.fb.array([], []), + pharmacistEmails: this.fb.array([], []), + healthAuthorityFraserEmails: this.fb.array([], []), + healthAuthorityInteriorEmails: this.fb.array([], []), + healthAuthorityIslandEmails: this.fb.array([], []), + healthAuthorityNorthernEmails: this.fb.array([], []), + healthAuthorityPHSAEmails: this.fb.array([], []), + healthAuthorityVancouverCoastalEmails: this.fb.array([], []), + deviceProviderEmails: this.fb.array([], []), }); } + + public atLeastOneEmailFilled(): boolean { + let emailFilled = false; + + Object.keys(this.emailForm.controls).forEach((emailArrayKey) => { + const emailArray = this.emailForm.controls[emailArrayKey] as FormArray; + Object.keys(emailArray.controls).forEach((emailKey) => { + let emailControl = emailArray.controls[emailKey] as FormGroup; + if (emailControl.controls['email'].value && emailControl.controls['email'].value !== "") { + emailFilled = true; + } + }); + }); + + return emailFilled; + } } diff --git a/prime-angular-frontend/src/app/shared/components/forms/email-form/email-form.component.html b/prime-angular-frontend/src/app/shared/components/forms/email-form/email-form.component.html index a8f4e0af32..9218dde2be 100644 --- a/prime-angular-frontend/src/app/shared/components/forms/email-form/email-form.component.html +++ b/prime-angular-frontend/src/app/shared/components/forms/email-form/email-form.component.html @@ -8,7 +8,8 @@ + maxlength="50" + appTrimSpace> Required Must be a valid email address diff --git a/prime-angular-frontend/src/app/shared/components/forms/email-form/email-form.component.ts b/prime-angular-frontend/src/app/shared/components/forms/email-form/email-form.component.ts index df2f647fb5..f2b21d5921 100644 --- a/prime-angular-frontend/src/app/shared/components/forms/email-form/email-form.component.ts +++ b/prime-angular-frontend/src/app/shared/components/forms/email-form/email-form.component.ts @@ -3,7 +3,6 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormUtilsService } from '@core/services/form-utils.service'; import { ViewportService } from '@core/services/viewport.service'; -import { FormControlValidators } from '@lib/validators/form-control.validators'; import { NextStepsFormState } from '@paper-enrolment/pages/next-steps-page/next-steps-form-state.class'; @Component({ @@ -17,7 +16,8 @@ export class EmailFormComponent implements OnInit { @Input() public formState: NextStepsFormState; @Input() public showRemoveButton: boolean = true; @Input() public index: number; - @Input() public validate: boolean = false; + @Input() public validateFormat: boolean = false; + @Input() public required: boolean = false; @Output() public remove: EventEmitter; constructor( @@ -40,13 +40,20 @@ export class EmailFormComponent implements OnInit { } public ngOnInit(): void { - if (this.validate) { - this.setEmailValidators(); + if (this.validateFormat) { + this.setEmailFormatValidator(); } + if (this.required) { + this.setRequireValidator(); + } + } + + private setEmailFormatValidator(): void { + this.formUtilsService.setValidators(this.email, [Validators.email]) } - private setEmailValidators(): void { - this.formUtilsService.setValidators(this.email, [Validators.required, FormControlValidators.email]) + private setRequireValidator(): void { + this.formUtilsService.setValidators(this.email, [Validators.required]) } } diff --git a/prime-angular-frontend/src/app/shared/shared.module.ts b/prime-angular-frontend/src/app/shared/shared.module.ts index 28c05b9956..06651b04f4 100644 --- a/prime-angular-frontend/src/app/shared/shared.module.ts +++ b/prime-angular-frontend/src/app/shared/shared.module.ts @@ -56,6 +56,7 @@ import { PageFooterComponent } from '@shared/components/pages/page-footer/page-f import { NotificationInfoSummaryDirective } from '@shared/components/forms/contact-information-form/notification-info-summary.directive'; import { DialogContentDirective } from '@shared/components/dialogs/dialog-content.directive'; import { UpperCaseInputDirective } from '@lib/modules/forms/to-uppercase.directive'; +import { TrimSpaceInputDirective } from '@lib/modules/forms/trim-space.directive'; import { FormIconGroupComponent } from '@shared/components/form-icon-group/form-icon-group.component'; import { AlertComponent } from '@shared/components/alerts/alert/alert.component'; import { EnrolleePropertyComponent } from '@shared/components/enrollee/enrollee-property/enrollee-property.component'; @@ -169,6 +170,7 @@ import { TransferHASiteComponent } from './components/dialogs/content/transfer-h PageSubheader2MoreInfoDirective, NotificationInfoSummaryDirective, UpperCaseInputDirective, + TrimSpaceInputDirective, PageFooterComponent, DialogContentDirective, FormIconGroupComponent, @@ -293,6 +295,7 @@ import { TransferHASiteComponent } from './components/dialogs/content/transfer-h PageSubheader2MoreInfoDirective, NotificationInfoSummaryDirective, UpperCaseInputDirective, + TrimSpaceInputDirective, PageFooterComponent, DialogContentDirective, FormIconGroupComponent,