adding ability to request health system if missing.

This commit is contained in:
Jason Kulatunga 2024-05-15 15:57:03 -07:00
parent 3b6d262c8d
commit baa4958854
No known key found for this signature in database
13 changed files with 284 additions and 1 deletions

View File

@ -0,0 +1,8 @@
package models
type RequestHealthSystem struct {
Email string `json:"email"`
Name string `json:"name"`
Website string `json:"website"`
StreetAddress string `json:"street_address"`
}

View File

@ -0,0 +1,53 @@
package handler
import (
"fmt"
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"net/url"
)
func HealthSystemRequest(c *gin.Context) {
var healthSystemRequest models.RequestHealthSystem
if err := c.ShouldBindJSON(&healthSystemRequest); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
//submit support request to Google Form
//https://medium.com/front-end-augustus-study-notes/custom-google-form-en-f7be4c27a98b
//source: https://docs.google.com/forms/d/e/1FAIpQLScH77CFpd3XAuwlUASFDJkOR54pZmt4AHqHa4xtZMGLXb1JIA/viewform?usp=sf_link
formUrl := "https://docs.google.com/forms/u/0/d/e/1FAIpQLScH77CFpd3XAuwlUASFDJkOR54pZmt4AHqHa4xtZMGLXb1JIA/formResponse"
supportRequestResponse, err := http.PostForm(formUrl, url.Values{
"entry.583209151": {healthSystemRequest.Email},
"entry.1753315374": {healthSystemRequest.Name},
"entry.1863832106": {healthSystemRequest.Website},
"entry.751017705": {healthSystemRequest.StreetAddress},
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
defer supportRequestResponse.Body.Close()
body, err := ioutil.ReadAll(supportRequestResponse.Body)
if err != nil {
//handle read response error
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
} else if supportRequestResponse.StatusCode != 200 {
//handle non 200 response
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": fmt.Sprintf("status code error: %d %s", supportRequestResponse.StatusCode, supportRequestResponse.Status)})
return
}
fmt.Printf("%s\n", string(body))
c.JSON(http.StatusOK, gin.H{"success": true})
}

View File

@ -94,6 +94,7 @@ func (ae *AppEngine) Setup() (*gin.RouterGroup, *gin.Engine) {
api.GET("/glossary/code", handler.GlossarySearchByCode) api.GET("/glossary/code", handler.GlossarySearchByCode)
api.POST("/support/request", handler.SupportRequest) api.POST("/support/request", handler.SupportRequest)
api.POST("/healthsystem/request", handler.HealthSystemRequest)
secure := api.Group("/secure").Use(middleware.RequireAuth()) secure := api.Group("/secure").Use(middleware.RequireAuth())
{ {

View File

@ -0,0 +1,96 @@
<form *ngIf="!submitSuccess; else requestSuccess" (ngSubmit)="submitForm()" #supportRequestForm="ngForm">
<div class="modal-header">
<h6 class="modal-title">Request a Health System?</h6>
<button type="button" class="btn close" aria-label="Close" (click)="activeModal.dismiss('cancel')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
Sharing your email and health system helps us prioritize our EHR integrations.
<div class="col-12">
<p class="mg-t-10 mg-b-5">Your email address<span ngbTooltip="required" class="text-danger">*</span></p>
<input
class="form-control" placeholder="my@emailaddress.com"
[(ngModel)]="formRequestHealthSystem.email" name="email" #email="ngModel" required email
>
<div *ngIf="email.invalid && (email.dirty || email.touched)" class="alert alert-danger">
<div *ngIf="email.errors?.['required']">
Email is required.
</div>
<div *ngIf="email.errors?.['email']">
Email must be valid.
</div>
</div>
</div>
<div class="col-12">
<p class="mg-t-10 mg-b-5">Health system name<span ngbTooltip="required" class="text-danger">*</span></p>
<input
class="form-control" placeholder="Ascension, Mayo Clinic"
[(ngModel)]="formRequestHealthSystem.name" name="name" #name="ngModel" required minlength="4"
>
<div *ngIf="name.invalid && (name.dirty || name.touched)" class="alert alert-danger">
<div *ngIf="name.errors?.['required']">
Health system name is required.
</div>
<div *ngIf="name.errors?.['minlength']">
Health system name must be at least 4 characters long.
</div>
</div>
</div>
<div class="col-12">
<p class="mg-t-10 mg-b-5">Website<span ngbTooltip="required" class="text-danger">*</span></p>
<input
class="form-control"
[(ngModel)]="formRequestHealthSystem.website" name="website" #website="ngModel" required minlength="4"
>
<div *ngIf="website.invalid && (website.dirty || website.touched)" class="alert alert-danger">
<div *ngIf="website.errors?.['required']">
Website is required.
</div>
<div *ngIf="website.errors?.['minlength']">
Website must be at least 4 characters long.
</div>
</div>
</div>
<div class="col-12">
<p class="mg-t-10 mg-b-5">Street Address</p>
<input
class="form-control"
[(ngModel)]="formRequestHealthSystem.street_address" name="street_address" #street_address="ngModel"
>
</div>
<div *ngIf="errorMsg" class="alert alert-danger mt-3" role="alert">
<strong>Error</strong> {{errorMsg}}
</div>
</div>
<div class="modal-footer">
<button [disabled]="!supportRequestForm.form.valid || loading" type="submit" class="btn btn-indigo">Submit</button>
<button (click)="activeModal.dismiss('cancel')" type="button" class="btn btn-outline-light">Cancel</button>
</div>
</form>
<ng-template #requestSuccess>
<div class="tx-center pd-y-20 pd-x-20">
<i class="far fa-check-square tx-100 tx-success lh-1 mg-t-20 d-inline-block"></i>
<h4 class="tx-success mg-b-20">Success!</h4>
<p class="mg-b-20 mg-x-20">Your request has been recorded. <br/>Thanks!</p>
<button type="button" (click)="activeModal.close('success')" class="btn btn-success pd-x-25">Close</button>
</div>
</ng-template>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormRequestHealthSystemComponent } from './form-request-health-system.component';
describe('FormRequestHealthSystemComponent', () => {
let component: FormRequestHealthSystemComponent;
let fixture: ComponentFixture<FormRequestHealthSystemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FormRequestHealthSystemComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(FormRequestHealthSystemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,55 @@
import { Component, OnInit } from '@angular/core';
import {NgbActiveModal, NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {SupportRequest} from '../../models/fasten/support-request';
import {FormRequestHealthSystem} from '../../models/fasten/form-request-health-system';
import {environment} from '../../../environments/environment';
import {versionInfo} from '../../../environments/versions';
import {FastenApiService} from '../../services/fasten-api.service';
@Component({
selector: 'app-form-request-health-system',
templateUrl: './form-request-health-system.component.html',
styleUrls: ['./form-request-health-system.component.scss']
})
export class FormRequestHealthSystemComponent implements OnInit {
formRequestHealthSystem: FormRequestHealthSystem = null
loading: boolean = false
submitSuccess: boolean = false
errorMsg: string = ""
constructor(
private fastenApi: FastenApiService,
public activeModal: NgbActiveModal,
) { }
ngOnInit(): void {
this.resetForm()
}
resetForm() {
this.submitSuccess = false
let requestForm = new FormRequestHealthSystem()
requestForm.email = ''
requestForm.name = ''
requestForm.street_address = ''
requestForm.website = ''
this.formRequestHealthSystem = requestForm
}
submitForm() {
this.loading = true
this.fastenApi.requestHealthSystem(this.formRequestHealthSystem).subscribe((resp: any) => {
this.loading = false
this.submitSuccess = true
//show success toast? close modal?
},
(err)=>{
this.loading = false
console.error("an error occurred during request submission",err)
this.errorMsg = err || "An error occurred while submitting your request. Please try again later."
})
}
}

View File

@ -36,6 +36,7 @@ import {FhirCardModule} from './fhir-card/fhir-card.module';
import {FhirDatatableModule} from './fhir-datatable/fhir-datatable.module'; import {FhirDatatableModule} from './fhir-datatable/fhir-datatable.module';
import { MedicalRecordWizardAddEncounterComponent } from './medical-record-wizard-add-encounter/medical-record-wizard-add-encounter.component'; import { MedicalRecordWizardAddEncounterComponent } from './medical-record-wizard-add-encounter/medical-record-wizard-add-encounter.component';
import { MedicalRecordWizardAddLabResultsComponent } from './medical-record-wizard-add-lab-results/medical-record-wizard-add-lab-results.component'; import { MedicalRecordWizardAddLabResultsComponent } from './medical-record-wizard-add-lab-results/medical-record-wizard-add-lab-results.component';
import { FormRequestHealthSystemComponent } from './form-request-health-system/form-request-health-system.component';
@NgModule({ @NgModule({
imports: [ imports: [
@ -87,6 +88,7 @@ import { MedicalRecordWizardAddLabResultsComponent } from './medical-record-wiza
MedicalSourcesConnectedComponent, MedicalSourcesConnectedComponent,
MedicalSourcesCategoryLookupPipe, MedicalSourcesCategoryLookupPipe,
MedicalSourcesCardComponent, MedicalSourcesCardComponent,
FormRequestHealthSystemComponent,
], ],
exports: [ exports: [
ComponentsSidebarComponent, ComponentsSidebarComponent,

View File

@ -0,0 +1,7 @@
import { FormRequestHealthSystem } from './form-request-health-system';
describe('FormRequestHealthSystem', () => {
it('should create an instance', () => {
expect(new FormRequestHealthSystem()).toBeTruthy();
});
});

View File

@ -0,0 +1,6 @@
export class FormRequestHealthSystem {
name: string
email: string
website: string
street_address: string
}

View File

@ -73,12 +73,21 @@
> >
<app-medical-sources-card class="col-sm-3 mg-b-20 px-3" <app-medical-sources-card class="col-sm-3 mg-b-20 px-3"
*ngFor="let lighthouseBrand of availableLighthouseBrandList" *ngFor="let lighthouseBrand of availableLighthouseBrandList"
[sourceInfo]="lighthouseBrand" [sourceInfo]="lighthouseBrand"
[status]="status[lighthouseBrand.brand.id]" [status]="status[lighthouseBrand.brand.id]"
(onClick)="connectModalHandler(contentModalRef, $event)" (onClick)="connectModalHandler(contentModalRef, $event)"
></app-medical-sources-card> ></app-medical-sources-card>
<div *ngIf="availableLighthouseBrandList.length == 0" class="mg-b-60 w-100">
<div class="card card-body bg-gray-200 bd-0">
<p class="card-text">
<strong>No results</strong> Try another search term or <a (click)="showRequestHealthSystemModal()" class="link">request this health system</a>.
</p>
</div>
</div>
</div><!-- row --> </div><!-- row -->
<div class="ht-40"></div> <div class="ht-40"></div>
@ -144,7 +153,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h6>Fasten Does not natively support <a href="https://en.wikipedia.org/wiki/Consolidated_Clinical_Document_Architecture" externalLink >CCDA</a> Health Records</h6> <h6>Fasten does not natively support <a href="https://en.wikipedia.org/wiki/Consolidated_Clinical_Document_Architecture" externalLink >CCDA</a> Health Records</h6>
<p>However we can convert it automatically using software generously donated by the team at <a href="https://www.health-samurai.io/" externalLink>Health Samurai</a><br/><br/> <p>However we can convert it automatically using software generously donated by the team at <a href="https://www.health-samurai.io/" externalLink>Health Samurai</a><br/><br/>
This converter is hosted by <a href="https://www.fastenhealth.com/" externalLink>Fasten Health, Inc.</a> and is subject to our <a href="https://policy.fastenhealth.com/privacy_policy.html" externalLink>Privacy Policy</a>. This converter is hosted by <a href="https://www.fastenhealth.com/" externalLink>Fasten Health, Inc.</a> and is subject to our <a href="https://policy.fastenhealth.com/privacy_policy.html" externalLink>Privacy Policy</a>.

View File

@ -18,6 +18,7 @@ import {FormControl, FormGroup} from '@angular/forms';
import * as _ from 'lodash'; import * as _ from 'lodash';
import {PatientAccessBrand} from '../../models/patient-access-brands'; import {PatientAccessBrand} from '../../models/patient-access-brands';
import {PlatformService} from '../../services/platform.service'; import {PlatformService} from '../../services/platform.service';
import {FormRequestHealthSystemComponent} from '../../components/form-request-health-system/form-request-health-system.component';
export const sourceConnectWindowTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120) export const sourceConnectWindowTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120)
@ -354,6 +355,17 @@ export class MedicalSourcesComponent implements OnInit {
}) })
} }
showRequestHealthSystemModal(): Promise<boolean> {
return this.modalService.open(FormRequestHealthSystemComponent).result.then<boolean>(
(result) => {
//convert button clicked, .close()
return true
}
).catch((reason) => {
// x or cancel button clicked, .dismiss()
return false
})
}
} }

View File

@ -27,6 +27,7 @@ import {SupportRequest} from '../models/fasten/support-request';
import { import {
List List
} from 'fhir/r4'; } from 'fhir/r4';
import {FormRequestHealthSystem} from '../models/fasten/form-request-health-system';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@ -348,4 +349,14 @@ export class FastenApiService {
); );
} }
requestHealthSystem(requestHealth: FormRequestHealthSystem): Observable<any> {
return this._httpClient.post<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/healthsystem/request`, requestHealth)
.pipe(
map((response: ResponseWrapper) => {
// @ts-ignore
return {}
})
);
}
} }