From cdb51e2f5716fb9449713c97969b90d99a87b032 Mon Sep 17 00:00:00 2001 From: Jesse Washburn Date: Mon, 6 Jan 2025 12:52:11 -0500 Subject: [PATCH 1/6] added user info to header of examination --- .../health/health-event-dialog.component.html | 24 ++++++++++- .../health/health-event-dialog.component.ts | 40 ++++++++++++++++++- src/app/health/health.component.ts | 2 +- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/app/health/health-event-dialog.component.html b/src/app/health/health-event-dialog.component.html index 5879b833db..3a95cbe9d1 100644 --- a/src/app/health/health-event-dialog.component.html +++ b/src/app/health/health-event-dialog.component.html @@ -1,6 +1,26 @@ -

{{event.date | date: 'medium'}}

+

Examination for {{userDetail.firstName ? userDetail.firstName + ' ' + userDetail.middleName + ' ' + userDetail.lastName : 'N/A'}} submitted on {{event.date | date: 'medium'}}

+

Performed by: {{performedBy}}

+ +

Patient Information

+

Name: {{userDetail.firstName ? userDetail.firstName + ' ' + userDetail.middleName + ' ' + userDetail.lastName : 'N/A'}}

+

Date: {{event.date | date: 'medium'}}

+

DOB: {{(userDetail.birthDate | date: 'longDate') || 'N/A'}}

+

Email: {{userDetail.email || 'N/A'}}

+

Phone: {{userDetail.phoneNumber || 'N/A'}}

+

Language: {{userDetail.language || 'N/A'}}

+

Birthplace: {{userDetail.birthplace || 'N/A'}}

+

Emergency Contact

+

Name: {{healthDetail?.emergencyContactName || 'N/A'}}

+

Type: {{healthDetail?.emergencyContactType || 'N/A'}}

+

Contact: {{healthDetail?.emergencyContact || 'N/A'}}

+ +

Special Needs

+ + +

Notes

+ + -

Performed by: {{performedBy}}

Vitals

Temperature: {{event.temperature}} °C

Pulse: {{event.pulse}} bpm

diff --git a/src/app/health/health-event-dialog.component.ts b/src/app/health/health-event-dialog.component.ts index 0c7fb1bb84..4b801242ba 100644 --- a/src/app/health/health-event-dialog.component.ts +++ b/src/app/health/health-event-dialog.component.ts @@ -7,6 +7,7 @@ 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'; @Component({ templateUrl: './health-event-dialog.component.html' @@ -14,6 +15,8 @@ import { UserService } from '../shared/user.service'; export class HealthEventDialogComponent implements OnInit, OnDestroy { event: any; + events: any[] = []; + additionalInfo: any = {}; hasConditionAndTreatment = false; conditionAndTreatmentFields = conditionAndTreatmentFields; conditions: string; @@ -24,15 +27,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 ? @@ -77,4 +84,35 @@ 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 + ) + } + }), {}); + } + + } diff --git a/src/app/health/health.component.ts b/src/app/health/health.component.ts index 6c9ddbd9f7..9a18cafa8f 100644 --- a/src/app/health/health.component.ts +++ b/src/app/health/health.component.ts @@ -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' }); From a917483ca3d70db15ef078a29064e27a70bce876 Mon Sep 17 00:00:00 2001 From: Jesse Washburn Date: Mon, 6 Jan 2025 13:02:14 -0500 Subject: [PATCH 2/6] added section headers --- .../health/health-event-dialog.component.html | 38 +++++++++---------- .../health/health-event-dialog.component.ts | 17 ++++++++- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/app/health/health-event-dialog.component.html b/src/app/health/health-event-dialog.component.html index 3a95cbe9d1..a27df63a23 100644 --- a/src/app/health/health-event-dialog.component.html +++ b/src/app/health/health-event-dialog.component.html @@ -21,28 +21,28 @@

Notes

-

Vitals

-

Temperature: {{event.temperature}} °C

-

Pulse: {{event.pulse}} bpm

-

Blood Pressure: {{event.bp}}

-

Height: {{event.height}} cm

-

Weight: {{event.weight}} kg

-

Vision: {{event.vision}}

-

Hearing: {{event.hearing}}

+

Vitals

+

Temperature: {{event.temperature}} °C

+

Pulse: {{event.pulse}} bpm

+

Blood Pressure: {{event.bp}}

+

Height: {{event.height}} cm

+

Weight: {{event.weight}} kg

+

Vision: {{event.vision}}

+

Hearing: {{event.hearing}}

-

Conditions

+

Conditions

{{conditions}}

-

Other Notes

-

Observations & Notes: -

Diagnosis: -

Treatments: -

Medications: -

Immunizations: -

Allergies: -

X-rays: -

Lab Tests: -

Referrals: +

Other Notes

+

Observations & Notes:


+

Diagnosis:


+

Treatments:


+

Medications:


+

Immunizations:


+

Allergies:


+

X-rays:


+

Lab Tests:


+

Referrals:

diff --git a/src/app/health/health-event-dialog.component.ts b/src/app/health/health-event-dialog.component.ts index 4b801242ba..e04fb3fe17 100644 --- a/src/app/health/health-event-dialog.component.ts +++ b/src/app/health/health-event-dialog.component.ts @@ -113,6 +113,21 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { } }), {}); } - + + 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; + } } From 9cdd8aab1891d28d3730a811e553e6b8346d3649 Mon Sep 17 00:00:00 2001 From: Jesse Washburn Date: Mon, 6 Jan 2025 13:04:03 -0500 Subject: [PATCH 3/6] linting fix --- src/app/health/health-event-dialog.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/health/health-event-dialog.component.ts b/src/app/health/health-event-dialog.component.ts index e04fb3fe17..ccbbf93f3d 100644 --- a/src/app/health/health-event-dialog.component.ts +++ b/src/app/health/health-event-dialog.component.ts @@ -113,7 +113,7 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { } }), {}); } - + isLastSection(section: string): boolean { const sections = [ 'notes', From b90b8a2d51e86638a6550c5c855d13cafb0da7eb Mon Sep 17 00:00:00 2001 From: Jesse Washburn Date: Mon, 6 Jan 2025 13:45:44 -0500 Subject: [PATCH 4/6] added export functionality --- .../health/health-event-dialog.component.html | 37 ++++---- .../health/health-event-dialog.component.ts | 88 +++++++++++++++++++ 2 files changed, 105 insertions(+), 20 deletions(-) diff --git a/src/app/health/health-event-dialog.component.html b/src/app/health/health-event-dialog.component.html index a27df63a23..6f60733a31 100644 --- a/src/app/health/health-event-dialog.component.html +++ b/src/app/health/health-event-dialog.component.html @@ -1,26 +1,22 @@

Examination for {{userDetail.firstName ? userDetail.firstName + ' ' + userDetail.middleName + ' ' + userDetail.lastName : 'N/A'}} submitted on {{event.date | date: 'medium'}}

Performed by: {{performedBy}}

- -

Patient Information

-

Name: {{userDetail.firstName ? userDetail.firstName + ' ' + userDetail.middleName + ' ' + userDetail.lastName : 'N/A'}}

-

Date: {{event.date | date: 'medium'}}

-

DOB: {{(userDetail.birthDate | date: 'longDate') || 'N/A'}}

-

Email: {{userDetail.email || 'N/A'}}

-

Phone: {{userDetail.phoneNumber || 'N/A'}}

-

Language: {{userDetail.language || 'N/A'}}

-

Birthplace: {{userDetail.birthplace || 'N/A'}}

-

Emergency Contact

-

Name: {{healthDetail?.emergencyContactName || 'N/A'}}

-

Type: {{healthDetail?.emergencyContactType || 'N/A'}}

-

Contact: {{healthDetail?.emergencyContact || 'N/A'}}

- -

Special Needs

- - -

Notes

- - +

Patient Information

+

Name: {{userDetail.firstName ? userDetail.firstName + ' ' + userDetail.middleName + ' ' + userDetail.lastName : 'N/A'}}

+

Date: {{event.date | date: 'medium'}}

+

DOB: {{(userDetail.birthDate | date: 'longDate') || 'N/A'}}

+

Email: {{userDetail.email || 'N/A'}}

+

Phone: {{userDetail.phoneNumber || 'N/A'}}

+

Language: {{userDetail.language || 'N/A'}}

+

Birthplace: {{userDetail.birthplace || 'N/A'}}

+

Emergency Contact

+

Name: {{healthDetail?.emergencyContactName || 'N/A'}}

+

Type: {{healthDetail?.emergencyContactType || 'N/A'}}

+

Contact: {{healthDetail?.emergencyContact || 'N/A'}}

+

Special Needs

+ +

Notes

+

Vitals

Temperature: {{event.temperature}} °C

Pulse: {{event.pulse}} bpm

@@ -49,4 +45,5 @@

Other Notes + diff --git a/src/app/health/health-event-dialog.component.ts b/src/app/health/health-event-dialog.component.ts index ccbbf93f3d..31623675ab 100644 --- a/src/app/health/health-event-dialog.component.ts +++ b/src/app/health/health-event-dialog.component.ts @@ -8,6 +8,8 @@ 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' @@ -29,6 +31,7 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { isDestroyed = false; userDetail = this.userService.get(); healthDetail: any = {}; + constructor( @Inject(MAT_DIALOG_DATA) public data: any, @@ -130,4 +133,89 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { 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'); + } + } From db7fd7d665f2b58d74897158a9d801bcd736ac00 Mon Sep 17 00:00:00 2001 From: Jesse Washburn Date: Mon, 6 Jan 2025 13:46:54 -0500 Subject: [PATCH 5/6] linting fix --- .../health/health-event-dialog.component.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/app/health/health-event-dialog.component.ts b/src/app/health/health-event-dialog.component.ts index 31623675ab..20095f2d5f 100644 --- a/src/app/health/health-event-dialog.component.ts +++ b/src/app/health/health-event-dialog.component.ts @@ -31,7 +31,7 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { isDestroyed = false; userDetail = this.userService.get(); healthDetail: any = {}; - + constructor( @Inject(MAT_DIALOG_DATA) public data: any, @@ -134,8 +134,8 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { } exportAsPDF() { - const fullName = this.userDetail.firstName - ? `${this.userDetail.firstName} ${this.userDetail.middleName || ''} ${this.userDetail.lastName || ''}` + 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 = { @@ -144,16 +144,16 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { header: { fontSize: 18, bold: true, - margin: [0, 10, 0, 10] + margin: [ 0, 10, 0, 10 ] }, subheader: { fontSize: 14, bold: true, - margin: [0, 10, 0, 5] + margin: [ 0, 10, 0, 5 ] }, text: { fontSize: 12, - margin: [0, 5, 0, 5] + margin: [ 0, 5, 0, 5 ] }, small: { fontSize: 10 @@ -186,13 +186,13 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { 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.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' }); @@ -216,6 +216,6 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { } }); pdfMake.createPdf(docDefinition).download('Examination_Report.pdf'); - } + } } From 5333db0d736f791bd272157fdb26425124889317 Mon Sep 17 00:00:00 2001 From: Jesse Washburn Date: Mon, 6 Jan 2025 13:49:46 -0500 Subject: [PATCH 6/6] linting fix --- src/app/health/health-event-dialog.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/health/health-event-dialog.component.ts b/src/app/health/health-event-dialog.component.ts index 20095f2d5f..b42cb78519 100644 --- a/src/app/health/health-event-dialog.component.ts +++ b/src/app/health/health-event-dialog.component.ts @@ -32,7 +32,6 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { userDetail = this.userService.get(); healthDetail: any = {}; - constructor( @Inject(MAT_DIALOG_DATA) public data: any, private router: Router, @@ -103,7 +102,6 @@ export class HealthEventDialogComponent implements OnInit, OnDestroy { }); } - processEventData() { this.additionalInfo = this.events.reduce((info, { date, selfExamination, conditions, hasInfo, ...event }) => ({ ...info,