Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

myHealth: smoother examinations (fixes #8036, #8037, #8012) #8043

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 38 additions & 21 deletions src/app/health/health-event-dialog.component.html
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
<h3 mat-dialog-title>{{event.date | date: 'medium'}}</h3>
<h3 mat-dialog-title>Examination for {{userDetail.firstName ? userDetail.firstName + ' ' + userDetail.middleName + ' ' + userDetail.lastName : 'N/A'}} submitted on {{event.date | date: 'medium'}}</h3>
<h3 *ngIf="!event.selfExamination" mat-dialog-title i18n>Performed by: {{performedBy}}</h3>
<mat-dialog-content>
<p *ngIf="!event.selfExamination"><strong class="mat-body-strong" i18n>Performed by: </strong>{{performedBy}}</p>
<h4 class="primary-text-color" *ngIf="hasVital" i18n>Vitals</h4>
<p *ngIf="event.temperature"><strong class="mat-body-strong" i18n>Temperature: </strong>{{event.temperature}} °C</p>
<p *ngIf="event.pulse"><strong class="mat-body-strong" i18n>Pulse: </strong>{{event.pulse}} bpm</p>
<p *ngIf="event.bp"><strong class="mat-body-strong" i18n>Blood Pressure: </strong>{{event.bp}}</p>
<p *ngIf="event.height"><strong class="mat-body-strong" i18n>Height: </strong>{{event.height}} cm</p>
<p *ngIf="event.weight"><strong class="mat-body-strong" i18n>Weight: </strong>{{event.weight}} kg</p>
<p *ngIf="event.vision"><strong class="mat-body-strong" i18n>Vision: </strong>{{event.vision}}</p>
<p *ngIf="event.hearing"><strong class="mat-body-strong" i18n>Hearing: </strong>{{event.hearing}}</p>
<h4 class="primary-text-color" i18n>Patient Information</h4>
<p><b>Name: </b>{{userDetail.firstName ? userDetail.firstName + ' ' + userDetail.middleName + ' ' + userDetail.lastName : 'N/A'}}</p>
<p><b>Date: </b>{{event.date | date: 'medium'}}</p>
<p><b>DOB: </b>{{(userDetail.birthDate | date: 'longDate') || 'N/A'}}</p>
<p><b>Email: </b>{{userDetail.email || 'N/A'}}</p>
<p><b>Phone: </b>{{userDetail.phoneNumber || 'N/A'}}</p>
<p><b>Language: </b>{{userDetail.language || 'N/A'}}</p>
<p><b>Birthplace: </b>{{userDetail.birthplace || 'N/A'}}</p>
<h4 class="primary-text-color" i18n>Emergency Contact</h4>
<p><b i18n>Name: </b>{{healthDetail?.emergencyContactName || 'N/A'}}</p>
<p><b i18n>Type: </b>{{healthDetail?.emergencyContactType || 'N/A'}}</p>
<p><b i18n>Contact: </b>{{healthDetail?.emergencyContact || 'N/A'}}</p>
<h4 class="primary-text-color" i18n>Special Needs</h4>
<td-markdown [content]="healthDetail?.specialNeeds || 'N/A'"></td-markdown>
<h4 class="primary-text-color" i18n>Notes</h4>
<td-markdown [content]="healthDetail?.notes || 'N/A'"></td-markdown>
<h3 class="primary-text-color" *ngIf="hasVital" i18n>Vitals</h3>
<p *ngIf="event.temperature"><b i18n>Temperature: </b>{{event.temperature}} °C</p>
<p *ngIf="event.pulse"><b i18n>Pulse: </b>{{event.pulse}} bpm</p>
<p *ngIf="event.bp"><b i18n>Blood Pressure: </b>{{event.bp}}</p>
<p *ngIf="event.height"><b i18n>Height: </b>{{event.height}} cm</p>
<p *ngIf="event.weight"><b i18n>Weight: </b>{{event.weight}} kg</p>
<p *ngIf="event.vision"><b i18n>Vision: </b>{{event.vision}}</p>
<p *ngIf="event.hearing"><b i18n>Hearing: </b>{{event.hearing}}</p>
<ng-container *ngIf="conditions">
<h4 class="primary-text-color" i18n>Conditions</h4>
<h3 class="primary-text-color" i18n>Conditions</h3>
<p>{{conditions}}</p>
</ng-container>
<h4 class="primary-text-color" *ngIf="hasConditionAndTreatment" i18n>Other Notes</h4>
<p *ngIf="event.notes"><strong class="mat-body-strong" i18n>Observations & Notes: </strong><td-markdown [content]="event.notes"></td-markdown>
<p *ngIf="event.diagnosis"><strong class="mat-body-strong" i18n>Diagnosis: </strong><td-markdown [content]="event.diagnosis"></td-markdown>
<p *ngIf="event.treatments"><strong class="mat-body-strong" i18n>Treatments: </strong><td-markdown [content]="event.treatments"></td-markdown>
<p *ngIf="event.medications"><strong class="mat-body-strong" i18n>Medications: </strong><td-markdown [content]="event.medications"></td-markdown>
<p *ngIf="event.immunizations"><strong class="mat-body-strong" i18n>Immunizations: </strong><td-markdown [content]="event.immunizations"></td-markdown>
<p *ngIf="event.allergies"><strong class="mat-body-strong" i18n>Allergies: </strong><td-markdown [content]="event.allergies"></td-markdown>
<p *ngIf="event.xrays"><strong class="mat-body-strong" i18n>X-rays: </strong><td-markdown [content]="event.xrays"></td-markdown>
<p *ngIf="event.tests"><strong class="mat-body-strong" i18n>Lab Tests: </strong><td-markdown [content]="event.tests"></td-markdown>
<p *ngIf="event.referrals"><strong class="mat-body-strong" i18n>Referrals: </strong><td-markdown [content]="event.referrals"></td-markdown>
<h3 class="primary-text-color" *ngIf="hasConditionAndTreatment" i18n>Other Notes</h3>
<div *ngIf="event.notes"><p><b i18n>Observations & Notes: </b><td-markdown [content]="event.notes"></td-markdown></p><hr *ngIf="!isLastSection('notes')"></div>
<div *ngIf="event.diagnosis"><p><b i18n>Diagnosis: </b><td-markdown [content]="event.diagnosis"></td-markdown></p><hr *ngIf="!isLastSection('diagnosis')"></div>
<div *ngIf="event.treatments"><p><b i18n>Treatments: </b><td-markdown [content]="event.treatments"></td-markdown></p><hr *ngIf="!isLastSection('treatments')"></div>
<div *ngIf="event.medications"><p><b i18n>Medications: </b><td-markdown [content]="event.medications"></td-markdown></p><hr *ngIf="!isLastSection('medications')"></div>
<div *ngIf="event.immunizations"><p><b i18n>Immunizations: </b><td-markdown [content]="event.immunizations"></td-markdown></p><hr *ngIf="!isLastSection('immunizations')"></div>
<div *ngIf="event.allergies"><p><b i18n>Allergies: </b><td-markdown [content]="event.allergies"></td-markdown></p><hr *ngIf="!isLastSection('allergies')"></div>
<div *ngIf="event.xrays"><p><b i18n>X-rays: </b><td-markdown [content]="event.xrays"></td-markdown></p><hr *ngIf="!isLastSection('xrays')"></div>
<div *ngIf="event.tests"><p><b i18n>Lab Tests: </b><td-markdown [content]="event.tests"></td-markdown></p><hr *ngIf="!isLastSection('tests')"></div>
<div *ngIf="event.referrals"><p><b i18n>Referrals: </b><td-markdown [content]="event.referrals"></td-markdown></p></div>
</mat-dialog-content>
<mat-dialog-actions>
<button type="button" mat-raised-button mat-dialog-close i18n>Close</button>
<button type="button" color="primary" mat-dialog-close (click)="editExam(event)" *ngIf="canUpdate" mat-raised-button>
<span i18n>Edit <ng-container *ngIf="minutes">({{ minutes }}:{{ seconds }})</ng-container></span>
</button>
<button mat-raised-button color="accent" (click)="exportAsPDF()">Export</button>
</mat-dialog-actions>
141 changes: 140 additions & 1 deletion src/app/health/health-event-dialog.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import { switchMap, takeWhile } from 'rxjs/operators';
import { UsersService } from '../users/users.service';
import { CouchService } from '../shared/couchdb.service';
import { UserService } from '../shared/user.service';
import { HealthService } from './health.service';
import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';

@Component({
templateUrl: './health-event-dialog.component.html'
})
export class HealthEventDialogComponent implements OnInit, OnDestroy {

event: any;
events: any[] = [];
additionalInfo: any = {};
hasConditionAndTreatment = false;
conditionAndTreatmentFields = conditionAndTreatmentFields;
conditions: string;
Expand All @@ -24,15 +29,19 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy {
seconds: string;
timeLimit = 300000;
isDestroyed = false;
userDetail = this.userService.get();
healthDetail: any = {};

constructor(
@Inject(MAT_DIALOG_DATA) public data: any,
private router: Router,
private usersService: UsersService,
private couchService: CouchService,
private userService: UserService
private userService: UserService,
private healthService: HealthService
) {
this.event = this.data.event || {};
this.healthDetail = data.healthDetail || {};
this.conditions = Object.entries(this.event.conditions || {})
.filter(([ condition, active ]) => active).map(([ condition, active ]) => condition).sort().join(', ');
this.hasConditionAndTreatment = this.event.hasInfo !== undefined ?
Expand Down Expand Up @@ -77,4 +86,134 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy {
this.seconds = parseInt(seconds, 10) < 10 ? '0' + seconds : seconds;
}

initData() {
this.healthService.getHealthData(this.userDetail._id).pipe(
switchMap(([ { profile, events, userKey } ]: any[]) => {
this.userDetail = { ...profile, ...this.userDetail };
this.healthDetail = profile;
this.events = events || [];
return userKey
? this.couchService.findAll('health', { selector: { profileId: userKey } })
: of([]);
})
).subscribe(eventDocs => {
this.events.push(...eventDocs);
this.processEventData();
});
}

processEventData() {
this.additionalInfo = this.events.reduce((info, { date, selfExamination, conditions, hasInfo, ...event }) => ({
...info,
[date]: {
selfExamination,
hasConditions: conditions && Object.values(conditions).some(Boolean),
hasInfo: hasInfo === true || Object.entries(event).some(
([ key, value ]) => conditionAndTreatmentFields.includes(key) && value
)
}
}), {});
}

isLastSection(section: string): boolean {
const sections = [
'notes',
'diagnosis',
'treatments',
'medications',
'immunizations',
'allergies',
'xrays',
'tests',
'referrals'
];
const activeSections = sections.filter(sec => this.event[sec]);
return activeSections[activeSections.length - 1] === section;
}

exportAsPDF() {
const fullName = this.userDetail.firstName
? `${this.userDetail.firstName} ${this.userDetail.middleName || ''} ${this.userDetail.lastName || ''}`
: 'N/A';
const submissionDate = this.event.date ? new Date(this.event.date).toLocaleDateString() : 'N/A';
const docDefinition: any = {
content: [],
styles: {
header: {
fontSize: 18,
bold: true,
margin: [ 0, 10, 0, 10 ]
},
subheader: {
fontSize: 14,
bold: true,
margin: [ 0, 10, 0, 5 ]
},
text: {
fontSize: 12,
margin: [ 0, 5, 0, 5 ]
},
small: {
fontSize: 10
}
},
defaultStyle: {
columnGap: 10
}
};
docDefinition.content.push({
text: `Examination for ${fullName} submitted on ${submissionDate}`,
style: 'header',
alignment: 'center'
});
docDefinition.content.push({ text: 'General Information', style: 'subheader' });
docDefinition.content.push({ text: `Name: ${fullName}`, style: 'text' });
docDefinition.content.push({ text: `Date: ${submissionDate}`, style: 'text' });
docDefinition.content.push({ text: `DOB: ${this.userDetail.birthDate ? new Date(this.userDetail.birthDate).toLocaleDateString() : 'N/A'}`, style: 'text' });
docDefinition.content.push({ text: `Email: ${this.userDetail.email || 'N/A'}`, style: 'text' });
docDefinition.content.push({ text: `Phone: ${this.userDetail.phoneNumber || 'N/A'}`, style: 'text' });
docDefinition.content.push({ text: `Language: ${this.userDetail.language || 'N/A'}`, style: 'text' });
docDefinition.content.push({ text: `Birthplace: ${this.userDetail.birthplace || 'N/A'}`, style: 'text' });
docDefinition.content.push({ text: 'Emergency Contact', style: 'subheader' });
docDefinition.content.push({ text: `Name: ${this.healthDetail?.emergencyContactName || 'N/A'}`, style: 'text' });
docDefinition.content.push({ text: `Type: ${this.healthDetail?.emergencyContactType || 'N/A'}`, style: 'text' });
docDefinition.content.push({ text: `Contact: ${this.healthDetail?.emergencyContact || 'N/A'}`, style: 'text' });
docDefinition.content.push({ text: 'Special Needs', style: 'subheader' });
docDefinition.content.push({ text: this.healthDetail?.specialNeeds || 'N/A', style: 'text' });
docDefinition.content.push({ text: 'Notes', style: 'subheader' });
docDefinition.content.push({ text: this.healthDetail?.notes || 'N/A', style: 'text' });
if (this.hasVital) {
docDefinition.content.push({ text: 'Vitals', style: 'subheader' });
if (this.event.temperature) { docDefinition.content.push({ text: `Temperature: ${this.event.temperature} °C`, style: 'text' }); }
if (this.event.pulse) { docDefinition.content.push({ text: `Pulse: ${this.event.pulse} bpm`, style: 'text' }); }
if (this.event.bp) { docDefinition.content.push({ text: `Blood Pressure: ${this.event.bp}`, style: 'text' }); }
if (this.event.height) { docDefinition.content.push({ text: `Height: ${this.event.height} cm`, style: 'text' }); }
if (this.event.weight) { docDefinition.content.push({ text: `Weight: ${this.event.weight} kg`, style: 'text' }); }
if (this.event.vision) { docDefinition.content.push({ text: `Vision: ${this.event.vision}`, style: 'text' }); }
if (this.event.hearing) { docDefinition.content.push({ text: `Hearing: ${this.event.hearing}`, style: 'text' }); }
}
if (this.conditions) {
docDefinition.content.push({ text: 'Conditions', style: 'subheader' });
docDefinition.content.push({ text: this.conditions, style: 'text' });
}
const details = [
{ label: 'Observations & Notes', value: this.event.notes },
{ label: 'Diagnosis', value: this.event.diagnosis },
{ label: 'Treatments', value: this.event.treatments },
{ label: 'Medications', value: this.event.medications },
{ label: 'Immunizations', value: this.event.immunizations },
{ label: 'Allergies', value: this.event.allergies },
{ label: 'X-rays', value: this.event.xrays },
{ label: 'Lab Tests', value: this.event.tests },
{ label: 'Referrals', value: this.event.referrals }
];
details.forEach(detail => {
if (detail.value) {
docDefinition.content.push({ text: detail.label, style: 'subheader' });
docDefinition.content.push({ text: detail.value, style: 'text' });
}
});
pdfMake.createPdf(docDefinition).download('Examination_Report.pdf');
}

}
2 changes: 1 addition & 1 deletion src/app/health/health.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class HealthComponent implements OnInit, AfterViewChecked, OnDestroy {
: of([ event ])
).subscribe(([ eventDoc ]) => {
this.dialog.open(HealthEventDialogComponent, {
data: { event: eventDoc, user: this.userDetail._id, route: this.route },
data: { event: eventDoc, user: this.userDetail._id, route: this.route, healthDetail: this.healthDetail },
width: '50vw',
maxHeight: '90vh'
});
Expand Down
Loading