Skip to content

Commit b7409ef

Browse files
committed
Refactor health event form to typed controls
1 parent 1a0957d commit b7409ef

1 file changed

Lines changed: 103 additions & 58 deletions

File tree

src/app/health/health-event.component.ts

Lines changed: 103 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, OnInit, HostListener } from '@angular/core';
22
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
3-
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
3+
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
44
import { HealthService } from './health.service';
55
import { conditions, conditionAndTreatmentFields } from './health.constants';
66
import { UserService } from '../shared/user.service';
@@ -16,21 +16,47 @@ import { CanComponentDeactivate } from '../shared/unsaved-changes.guard';
1616
import { warningMsg } from '../shared/unsaved-changes.component';
1717
import { debounce } from 'rxjs/operators';
1818

19+
interface HealthEventFormModel {
20+
temperature: FormControl<number | null>;
21+
pulse: FormControl<number | null>;
22+
bp: FormControl<string | null>;
23+
height: FormControl<number | null>;
24+
weight: FormControl<number | null>;
25+
vision: FormControl<string | null>;
26+
hearing: FormControl<string | null>;
27+
notes: FormControl<string | null>;
28+
diagnosis: FormControl<string | null>;
29+
treatments: FormControl<string | null>;
30+
medications: FormControl<string | null>;
31+
immunizations: FormControl<string | null>;
32+
allergies: FormControl<string | null>;
33+
xrays: FormControl<string | null>;
34+
tests: FormControl<string | null>;
35+
referrals: FormControl<string | null>;
36+
conditions: FormControl<Record<string, boolean>>;
37+
}
38+
39+
type HealthEventFormValue = {
40+
[Key in keyof HealthEventFormModel]: HealthEventFormModel[Key] extends FormControl<infer Value>
41+
? Value
42+
: never;
43+
};
44+
1945
@Component({
2046
templateUrl: './health-event.component.html',
2147
styleUrls: [ './health-update.scss' ]
2248
})
2349
export class HealthEventComponent implements OnInit, CanComponentDeactivate {
2450

25-
healthForm: UntypedFormGroup;
51+
healthForm: FormGroup<HealthEventFormModel>;
2652
conditions = conditions;
2753
dialogPrompt: MatDialogRef<DialogsPromptComponent>;
2854
event: any = {};
2955
initialFormValues: any;
3056
hasUnsavedChanges = false;
3157

3258
constructor(
33-
private fb: UntypedFormBuilder,
59+
private fb: FormBuilder,
3460
private healthService: HealthService,
3561
private router: Router,
3662
private route: ActivatedRoute,
@@ -40,24 +66,24 @@ export class HealthEventComponent implements OnInit, CanComponentDeactivate {
4066
private dialog: MatDialog,
4167
private planetMessageService: PlanetMessageService
4268
) {
43-
this.healthForm = this.fb.group({
44-
temperature: [ '', Validators.min(1) ],
45-
pulse: [ '', Validators.min(1) ],
46-
bp: [ '', CustomValidators.bpValidator ],
47-
height: [ '', Validators.min(1) ],
48-
weight: [ '', Validators.min(1) ],
49-
vision: [ '' ],
50-
hearing: [ '' ],
51-
notes: '',
52-
diagnosis: '',
53-
treatments: '',
54-
medications: '',
55-
immunizations: '',
56-
allergies: '',
57-
xrays: '',
58-
tests: '',
59-
referrals: '',
60-
conditions: {}
69+
this.healthForm = this.fb.group<HealthEventFormModel>({
70+
temperature: this.fb.control<number | null>(null, { validators: Validators.min(1) }),
71+
pulse: this.fb.control<number | null>(null, { validators: Validators.min(1) }),
72+
bp: this.fb.control<string | null>('', { validators: CustomValidators.bpValidator }),
73+
height: this.fb.control<number | null>(null, { validators: Validators.min(1) }),
74+
weight: this.fb.control<number | null>(null, { validators: Validators.min(1) }),
75+
vision: this.fb.control<string | null>(''),
76+
hearing: this.fb.control<string | null>(''),
77+
notes: this.fb.control<string | null>(''),
78+
diagnosis: this.fb.control<string | null>(''),
79+
treatments: this.fb.control<string | null>(''),
80+
medications: this.fb.control<string | null>(''),
81+
immunizations: this.fb.control<string | null>(''),
82+
allergies: this.fb.control<string | null>(''),
83+
xrays: this.fb.control<string | null>(''),
84+
tests: this.fb.control<string | null>(''),
85+
referrals: this.fb.control<string | null>(''),
86+
conditions: this.fb.control<Record<string, boolean>>({})
6187
});
6288
}
6389

@@ -85,25 +111,15 @@ export class HealthEventComponent implements OnInit, CanComponentDeactivate {
85111
}
86112

87113
private captureInitialState() {
88-
const formValue = this.healthForm.value;
89-
const numericFields = [ 'temperature', 'pulse', 'height', 'weight' ];
90-
const processedForm = Object.keys(formValue).reduce((acc, key) => {
91-
if (numericFields.includes(key)) {
92-
acc[key] = formValue[key] === '' || formValue[key] === null ? undefined : Number(formValue[key]);
93-
} else if (key === 'conditions') {
94-
acc[key] = this.processConditions(formValue[key] || {});
95-
} else {
96-
acc[key] = formValue[key];
97-
}
98-
return acc;
99-
}, {});
114+
const formValue = this.healthForm.getRawValue();
115+
const processedForm = this.transformFormValue(formValue);
100116

101117
this.initialFormValues = JSON.stringify(processedForm);
102118
}
103119

104-
private processConditions(inputConditions: any) {
105-
const processedConditions = Object.keys(inputConditions || {}).reduce((acc, key) => {
106-
if (inputConditions[key]) {
120+
private processConditions(inputConditions: Record<string, boolean> | null | undefined): Record<string, boolean> {
121+
const processedConditions = Object.keys(inputConditions || {}).reduce<Record<string, boolean>>((acc, key) => {
122+
if (inputConditions && inputConditions[key]) {
107123
acc[key] = true;
108124
}
109125
return acc;
@@ -116,18 +132,8 @@ export class HealthEventComponent implements OnInit, CanComponentDeactivate {
116132
.pipe(
117133
debounce(() => race(interval(200), of(true)))
118134
)
119-
.subscribe(formValue => {
120-
const numericFields = [ 'temperature', 'pulse', 'height', 'weight' ];
121-
const processedForm = Object.keys(formValue).reduce((acc, key) => {
122-
if (numericFields.includes(key)) {
123-
acc[key] = formValue[key] === '' || formValue[key] === null ? undefined : Number(formValue[key]);
124-
} else if (key === 'conditions') {
125-
acc[key] = this.processConditions(formValue[key] || {});
126-
} else {
127-
acc[key] = formValue[key];
128-
}
129-
return acc;
130-
}, {});
135+
.subscribe((formValue: HealthEventFormValue) => {
136+
const processedForm = this.transformFormValue(formValue);
131137

132138
const currentState = JSON.stringify(processedForm);
133139
this.hasUnsavedChanges = currentState !== this.initialFormValues;
@@ -138,7 +144,7 @@ export class HealthEventComponent implements OnInit, CanComponentDeactivate {
138144
if (!this.healthForm.valid) {
139145
return;
140146
}
141-
const checkFields = [ 'temperature', 'pulse', 'bp', 'height', 'weight' ];
147+
const checkFields: Array<keyof HealthEventFormModel> = [ 'temperature', 'pulse', 'bp', 'height', 'weight' ];
142148
const promptFields = checkFields.filter((field) => !this.isFieldValueExpected(field))
143149
.map(field => ({ field, value: this.healthForm.controls[field].value }));
144150
if (promptFields.length) {
@@ -151,20 +157,37 @@ export class HealthEventComponent implements OnInit, CanComponentDeactivate {
151157
}
152158
}
153159

154-
isEmptyForm() {
155-
const isConditionsEmpty = (values) => typeof values === 'object' && Object.values(values).every(value => !value);
160+
isEmptyForm() {
161+
const isConditionsEmpty = (values: Record<string, boolean>) =>
162+
typeof values === 'object' && Object.values(values).every(value => !value);
163+
156164
return Object.values(this.healthForm.controls)
157-
.every(({ value }) => value === null || /^\s*$/.test(value) || isConditionsEmpty(value));
165+
.every((control) => {
166+
const value = control.value;
167+
if (value === null || value === undefined) {
168+
return true;
169+
}
170+
if (typeof value === 'string') {
171+
return /^\s*$/.test(value);
172+
}
173+
if (typeof value === 'number') {
174+
return false;
175+
}
176+
return isConditionsEmpty(value as Record<string, boolean>);
177+
});
158178
}
159179

160180
goBack() {
161181
// Let the router guard handle the unsaved changes prompt
162182
this.router.navigate([ '..' ], { relativeTo: this.route });
163183
}
164184

165-
conditionChange(condition) {
166-
const currentConditions = this.healthForm.controls.conditions.value;
167-
this.healthForm.controls.conditions.setValue({ ...currentConditions, [condition]: currentConditions[condition] !== true });
185+
conditionChange(condition: string) {
186+
const currentConditions = this.healthForm.controls.conditions.value || {};
187+
this.healthForm.controls.conditions.setValue({
188+
...currentConditions,
189+
[condition]: currentConditions[condition] !== true
190+
});
168191
}
169192

170193
showWarning(invalidFields) {
@@ -190,7 +213,7 @@ export class HealthEventComponent implements OnInit, CanComponentDeactivate {
190213
});
191214
}
192215

193-
isFieldValueExpected(field) {
216+
isFieldValueExpected(field: keyof HealthEventFormModel) {
194217
const value = this.healthForm.controls[field].value;
195218
const limits = {
196219
'temperature': { min: 30, max: 45 },
@@ -209,20 +232,42 @@ export class HealthEventComponent implements OnInit, CanComponentDeactivate {
209232
}
210233

211234
saveEvent() {
235+
const formValue = this.healthForm.getRawValue();
236+
212237
return this.healthService.addEvent(
213238
this.route.snapshot.params.id,
214239
this.userService.get()._id,
215240
this.event,
216241
{
217-
...this.healthForm.value,
242+
...formValue,
218243
selfExamination: this.route.snapshot.params.id === this.userService.get()._id,
219244
createdBy: this.userService.get()._id,
220245
planetCode: this.stateService.configuration.code,
221-
hasInfo: conditionAndTreatmentFields.some(key => this.healthForm.value[key] !== '')
246+
hasInfo: conditionAndTreatmentFields.some(key => {
247+
const value = formValue[key as keyof HealthEventFormValue];
248+
return typeof value === 'string' ? value !== null && value !== '' : value !== null;
249+
})
222250
}
223251
);
224252
}
225253

254+
private transformFormValue(formValue: HealthEventFormValue) {
255+
const numericFields: Array<keyof Pick<HealthEventFormValue, 'temperature' | 'pulse' | 'height' | 'weight'>> =
256+
[ 'temperature', 'pulse', 'height', 'weight' ];
257+
258+
return (Object.keys(formValue) as Array<keyof HealthEventFormValue>).reduce((acc, key) => {
259+
const value = formValue[key];
260+
if (numericFields.includes(key)) {
261+
acc[key] = value === null ? undefined : Number(value);
262+
} else if (key === 'conditions') {
263+
acc[key] = this.processConditions(value);
264+
} else {
265+
acc[key] = value;
266+
}
267+
return acc;
268+
}, {} as Record<keyof HealthEventFormValue, unknown>);
269+
}
270+
226271
canDeactivate(): boolean {
227272
return !this.hasUnsavedChanges;
228273
}

0 commit comments

Comments
 (0)