Adding UI for manually creating conditions (and associated resources) (#92)
This commit is contained in:
parent
3692fd462f
commit
5afb6ef659
|
@ -31,6 +31,7 @@
|
||||||
"@ng-bootstrap/ng-bootstrap": "10.0.0",
|
"@ng-bootstrap/ng-bootstrap": "10.0.0",
|
||||||
"@panva/oauth4webapi": "1.2.0",
|
"@panva/oauth4webapi": "1.2.0",
|
||||||
"@swimlane/ngx-datatable": "^20.0.0",
|
"@swimlane/ngx-datatable": "^20.0.0",
|
||||||
|
"@types/fhir": "^0.0.35",
|
||||||
"asmcrypto.js": "^2.3.2",
|
"asmcrypto.js": "^2.3.2",
|
||||||
"bootstrap": "^4.4.1",
|
"bootstrap": "^4.4.1",
|
||||||
"chart.js": "2.9.4",
|
"chart.js": "2.9.4",
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {SourceDetailComponent} from './pages/source-detail/source-detail.compone
|
||||||
import {PatientProfileComponent} from './pages/patient-profile/patient-profile.component';
|
import {PatientProfileComponent} from './pages/patient-profile/patient-profile.component';
|
||||||
import {MedicalHistoryComponent} from './pages/medical-history/medical-history.component';
|
import {MedicalHistoryComponent} from './pages/medical-history/medical-history.component';
|
||||||
import {ReportLabsComponent} from './pages/report-labs/report-labs.component';
|
import {ReportLabsComponent} from './pages/report-labs/report-labs.component';
|
||||||
|
import {ResourceCreatorComponent} from './pages/resource-creator/resource-creator.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ const routes: Routes = [
|
||||||
{ path: 'source/:source_id/resource/:resource_type/:resource_id', component: ResourceDetailComponent, canActivate: [ IsAuthenticatedAuthGuard] },
|
{ path: 'source/:source_id/resource/:resource_type/:resource_id', component: ResourceDetailComponent, canActivate: [ IsAuthenticatedAuthGuard] },
|
||||||
{ path: 'sources', component: MedicalSourcesComponent, canActivate: [ IsAuthenticatedAuthGuard] },
|
{ path: 'sources', component: MedicalSourcesComponent, canActivate: [ IsAuthenticatedAuthGuard] },
|
||||||
{ path: 'sources/callback/:source_type', component: MedicalSourcesComponent, canActivate: [ IsAuthenticatedAuthGuard] },
|
{ path: 'sources/callback/:source_type', component: MedicalSourcesComponent, canActivate: [ IsAuthenticatedAuthGuard] },
|
||||||
|
{ path: 'resource/create', component: ResourceCreatorComponent, canActivate: [ IsAuthenticatedAuthGuard] },
|
||||||
|
|
||||||
|
|
||||||
{ path: 'patient-profile', component: PatientProfileComponent, canActivate: [ IsAuthenticatedAuthGuard] },
|
{ path: 'patient-profile', component: PatientProfileComponent, canActivate: [ IsAuthenticatedAuthGuard] },
|
||||||
|
|
|
@ -16,10 +16,9 @@ import { far } from '@fortawesome/free-regular-svg-icons';
|
||||||
import { ResourceDetailComponent } from './pages/resource-detail/resource-detail.component';
|
import { ResourceDetailComponent } from './pages/resource-detail/resource-detail.component';
|
||||||
import { AuthSignupComponent } from './pages/auth-signup/auth-signup.component';
|
import { AuthSignupComponent } from './pages/auth-signup/auth-signup.component';
|
||||||
import { AuthSigninComponent } from './pages/auth-signin/auth-signin.component';
|
import { AuthSigninComponent } from './pages/auth-signin/auth-signin.component';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { NgxDropzoneModule } from 'ngx-dropzone';
|
import { NgxDropzoneModule } from 'ngx-dropzone';
|
||||||
import { IsAuthenticatedAuthGuard } from './auth-guards/is-authenticated-auth-guard';
|
import { IsAuthenticatedAuthGuard } from './auth-guards/is-authenticated-auth-guard';
|
||||||
import {FastenApiService} from './services/fasten-api.service';
|
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
import { SourceDetailComponent } from './pages/source-detail/source-detail.component';
|
import { SourceDetailComponent } from './pages/source-detail/source-detail.component';
|
||||||
import { HighlightModule, HIGHLIGHT_OPTIONS } from 'ngx-highlightjs';
|
import { HighlightModule, HIGHLIGHT_OPTIONS } from 'ngx-highlightjs';
|
||||||
|
@ -30,6 +29,7 @@ import { PatientProfileComponent } from './pages/patient-profile/patient-profile
|
||||||
import { MedicalHistoryComponent } from './pages/medical-history/medical-history.component';
|
import { MedicalHistoryComponent } from './pages/medical-history/medical-history.component';
|
||||||
import { ReportLabsComponent } from './pages/report-labs/report-labs.component';
|
import { ReportLabsComponent } from './pages/report-labs/report-labs.component';
|
||||||
import {PipesModule} from './pipes/pipes.module';
|
import {PipesModule} from './pipes/pipes.module';
|
||||||
|
import { ResourceCreatorComponent } from './pages/resource-creator/resource-creator.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -46,9 +46,11 @@ import {PipesModule} from './pipes/pipes.module';
|
||||||
PatientProfileComponent,
|
PatientProfileComponent,
|
||||||
MedicalHistoryComponent,
|
MedicalHistoryComponent,
|
||||||
ReportLabsComponent,
|
ReportLabsComponent,
|
||||||
|
ResourceCreatorComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
@ -59,7 +61,7 @@ import {PipesModule} from './pipes/pipes.module';
|
||||||
NgxDropzoneModule,
|
NgxDropzoneModule,
|
||||||
HighlightModule,
|
HighlightModule,
|
||||||
MomentModule,
|
MomentModule,
|
||||||
PipesModule
|
PipesModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -84,6 +84,9 @@ export class FhirResourceComponent implements OnInit, OnChanges {
|
||||||
// case "CarePlan": {
|
// case "CarePlan": {
|
||||||
// return ListCarePlanComponent;
|
// return ListCarePlanComponent;
|
||||||
// }
|
// }
|
||||||
|
// case "CareTeam": {
|
||||||
|
// return CareTeamComponent;
|
||||||
|
// }
|
||||||
// case "Communication": {
|
// case "Communication": {
|
||||||
// return ListCommunicationComponent;
|
// return ListCommunicationComponent;
|
||||||
// }
|
// }
|
||||||
|
@ -135,8 +138,11 @@ export class FhirResourceComponent implements OnInit, OnChanges {
|
||||||
case "Procedure": {
|
case "Procedure": {
|
||||||
return ProcedureComponent;
|
return ProcedureComponent;
|
||||||
}
|
}
|
||||||
// case "Practitioner": {
|
case "Practitioner": {
|
||||||
// return PractitionerComponent;
|
return PractitionerComponent;
|
||||||
|
}
|
||||||
|
// case "PractitionerRole": {
|
||||||
|
// return PractitionerRoleComponent;
|
||||||
// }
|
// }
|
||||||
// case "ServiceRequest": {
|
// case "ServiceRequest": {
|
||||||
// return ListServiceRequestComponent;
|
// return ListServiceRequestComponent;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
<div #collapse="ngbCollapse" [(ngbCollapse)]="isCollapsed" class="card-body">
|
<div #collapse="ngbCollapse" [(ngbCollapse)]="isCollapsed" class="card-body">
|
||||||
<p class="az-content-text mg-b-20">Describes the event of a patient being administered a vaccine or a record of an immunization as reported by a patient, a clinician or another party.</p>
|
<p class="az-content-text mg-b-20">A person who is directly or indirectly involved in the provisioning of healthcare.</p>
|
||||||
<fhir-ui-table [displayModel]="displayModel" [tableData]="tableData"></fhir-ui-table>
|
<fhir-ui-table [displayModel]="displayModel" [tableData]="tableData"></fhir-ui-table>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="showDetails" class="card-footer">
|
<div *ngIf="showDetails" class="card-footer">
|
||||||
|
|
|
@ -21,14 +21,7 @@ export class PractitionerComponent implements OnInit, FhirResourceComponentInter
|
||||||
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
||||||
this.tableData = [
|
this.tableData = [
|
||||||
// {
|
|
||||||
// label: 'Identifiers',
|
|
||||||
// testId: 'identifier',
|
|
||||||
// data: identifier && <Identifier fhirData={identifier} />,
|
|
||||||
// status: identifier,
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
label: 'Gender',
|
label: 'Gender',
|
||||||
data: this.displayModel?.gender,
|
data: this.displayModel?.gender,
|
||||||
|
@ -51,18 +44,29 @@ export class PractitionerComponent implements OnInit, FhirResourceComponentInter
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// label: 'Address',
|
// label: 'Address',
|
||||||
// testId: 'address',
|
// data: this.displayModel?.address.,
|
||||||
// data: address && <Address fhirData={address} />,
|
// status: !!this.displayModel?.address,
|
||||||
// status: address,
|
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// label: 'Telephone',
|
// label: 'Telephone',
|
||||||
// testId: 'telecom',
|
// data: this.displayModel.telecom,
|
||||||
// data: telecom && <Telecom fhirData={telecom} />,
|
// enabled: !!this.displayModel.telecom,
|
||||||
// status: telecom,
|
|
||||||
// },
|
// },
|
||||||
];
|
];
|
||||||
|
for(let idCoding of this.displayModel.identifier){
|
||||||
|
this.tableData.push({
|
||||||
|
label: `Identifier (${idCoding.system})`,
|
||||||
|
data: idCoding.display || idCoding.value,
|
||||||
|
enabled: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for(let telecom of this.displayModel.telecom){
|
||||||
|
this.tableData.push({
|
||||||
|
label: telecom.system,
|
||||||
|
data: telecom.value,
|
||||||
|
enabled: !!telecom.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
markForCheck(){
|
markForCheck(){
|
||||||
this.changeRef.markForCheck()
|
this.changeRef.markForCheck()
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<ng-template #rt let-r="result" let-t="term">
|
||||||
|
<span>{{r.text}}</span> <span *ngIf="r.subtext"> ({{r.subtext}})</span>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
[class.is-invalid]="showError"
|
||||||
|
[(ngModel)]="searchResult"
|
||||||
|
[ngbTypeahead]="search"
|
||||||
|
[inputFormatter]="formatter"
|
||||||
|
[resultTemplate]="rt"
|
||||||
|
(ngModelChange)="typeAheadChangeEvent($event)"
|
||||||
|
(click)="typeAheadClickEvent($event)"
|
||||||
|
(focus)="typeAheadFocusEvent($event)"
|
||||||
|
placeholder="Search"
|
||||||
|
#instance="ngbTypeahead"
|
||||||
|
/>
|
||||||
|
<small *ngIf="searching" class="form-text text-muted">searching...</small>
|
||||||
|
<div class="invalid-feedback" *ngIf="searchFailed">Sorry, suggestions could not be loaded.</div>
|
||||||
|
|
||||||
|
<div *ngIf="debugMode" class="alert alert-warning">
|
||||||
|
<pre><code [highlight]="searchResult | json"></code></pre>
|
||||||
|
</div>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NlmTypeaheadComponent } from './nlm-typeahead.component';
|
||||||
|
|
||||||
|
describe('NlmTypeaheadComponent', () => {
|
||||||
|
let component: NlmTypeaheadComponent;
|
||||||
|
let fixture: ComponentFixture<NlmTypeaheadComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ NlmTypeaheadComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(NlmTypeaheadComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,218 @@
|
||||||
|
import {Component, EventEmitter, Input, OnInit, Optional, Output, Self, ViewChild} from '@angular/core';
|
||||||
|
import {merge, Observable, ObservableInput, of, Subject} from 'rxjs';
|
||||||
|
import {catchError, debounceTime, distinctUntilChanged, filter, switchMap, tap} from 'rxjs/operators';
|
||||||
|
import {NlmClinicalTableSearchService, NlmSearchResults} from '../../services/nlm-clinical-table-search.service';
|
||||||
|
import {
|
||||||
|
ControlValueAccessor,
|
||||||
|
NgControl,
|
||||||
|
} from '@angular/forms';
|
||||||
|
import {NgbTypeahead} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
export enum NlmSearchType {
|
||||||
|
Allergy = 'Allergy',
|
||||||
|
AllergyReaction = 'AllergyReaction',
|
||||||
|
Condition = 'Condition',
|
||||||
|
MedicalContactIndividualProfession = 'MedicalContactIndividualProfession',
|
||||||
|
MedicalContactIndividual = 'MedicalContactIndividual',
|
||||||
|
MedicalContactOrganization = 'MedicalContactOrganization',
|
||||||
|
MedicalContactOrganizationType = 'MedicalContactOrganizationType',
|
||||||
|
Medication = 'Medication',
|
||||||
|
MedicationWhyStopped = 'MedicationWhyStopped',
|
||||||
|
Procedure = 'Procedure',
|
||||||
|
Vaccine = 'Vaccine',
|
||||||
|
|
||||||
|
PrePopulated = 'PrePopulated'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-nlm-typeahead',
|
||||||
|
templateUrl: './nlm-typeahead.component.html',
|
||||||
|
styleUrls: ['./nlm-typeahead.component.scss'],
|
||||||
|
providers: [
|
||||||
|
// {
|
||||||
|
// provide: NG_VALUE_ACCESSOR,
|
||||||
|
// multi:true,
|
||||||
|
// useExisting: NlmTypeaheadComponent
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// provide: NG_VALIDATORS,
|
||||||
|
// multi:true,
|
||||||
|
// useExisting: NlmTypeaheadComponent
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class NlmTypeaheadComponent implements ControlValueAccessor {
|
||||||
|
@Input() searchType: NlmSearchType = NlmSearchType.Condition;
|
||||||
|
@Input() debugMode: Boolean = false;
|
||||||
|
@Input() openOnFocus: Boolean = false;
|
||||||
|
@Input() prePopulatedOptions: NlmSearchResults[] = []
|
||||||
|
|
||||||
|
@ViewChild('instance', { static: true }) instance: NgbTypeahead;
|
||||||
|
focus$ = new Subject<string>();
|
||||||
|
click$ = new Subject<string>();
|
||||||
|
searching = false;
|
||||||
|
searchFailed = false;
|
||||||
|
|
||||||
|
searchResult: any = {};
|
||||||
|
onChange = (searchResult) => {};
|
||||||
|
onTouched = () => {};
|
||||||
|
touched = false;
|
||||||
|
disabled = false;
|
||||||
|
|
||||||
|
constructor(@Self() @Optional() public control: NgControl, private nlmClinicalTableSearchService: NlmClinicalTableSearchService) {
|
||||||
|
this.control && (this.control.valueAccessor = this);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatter = (x: { text: string }) => x.text;
|
||||||
|
search = (text$: Observable<string>) => {
|
||||||
|
let searchOpFn
|
||||||
|
switch (this.searchType) {
|
||||||
|
case NlmSearchType.Allergy:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchAllergy
|
||||||
|
this.openOnFocus = true
|
||||||
|
break
|
||||||
|
case NlmSearchType.AllergyReaction:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchAllergyReaction
|
||||||
|
this.openOnFocus = true
|
||||||
|
break
|
||||||
|
case NlmSearchType.Condition:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchCondition
|
||||||
|
break
|
||||||
|
case NlmSearchType.MedicalContactIndividualProfession:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchMedicalContactIndividualProfession
|
||||||
|
this.openOnFocus = true
|
||||||
|
break
|
||||||
|
case NlmSearchType.MedicalContactIndividual:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchMedicalContactIndividual
|
||||||
|
break
|
||||||
|
case NlmSearchType.MedicalContactOrganization:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchMedicalContactOrganization
|
||||||
|
break
|
||||||
|
case NlmSearchType.MedicalContactOrganizationType:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchMedicalContactOrganizationType
|
||||||
|
this.openOnFocus = true
|
||||||
|
break
|
||||||
|
case NlmSearchType.Medication:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchMedication
|
||||||
|
break
|
||||||
|
case NlmSearchType.MedicationWhyStopped:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchMedicationWhyStopped
|
||||||
|
this.openOnFocus = true
|
||||||
|
break
|
||||||
|
case NlmSearchType.Procedure:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchProcedure
|
||||||
|
break
|
||||||
|
case NlmSearchType.Vaccine:
|
||||||
|
searchOpFn = this.nlmClinicalTableSearchService.searchVaccine
|
||||||
|
this.openOnFocus = true
|
||||||
|
break
|
||||||
|
case NlmSearchType.PrePopulated:
|
||||||
|
// searchOpFn = this.nlmClinicalTableSearchService.searchVaccine
|
||||||
|
console.log("PREPOPUlATED", this.prePopulatedOptions)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.error(`unknown search type: ${this.searchType}`)
|
||||||
|
return of([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/ng-bootstrap/ng-bootstrap/issues/917
|
||||||
|
// Note that the this argument is undefined so you need to explicitly bind it to a desired "this" target.
|
||||||
|
// https://ng-bootstrap.github.io/#/components/typeahead/api
|
||||||
|
searchOpFn = searchOpFn.bind(this.nlmClinicalTableSearchService)
|
||||||
|
|
||||||
|
|
||||||
|
const debouncedText$ = text$.pipe(debounceTime(200), distinctUntilChanged());
|
||||||
|
const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
|
||||||
|
const inputFocus$ = this.focus$;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$).pipe(
|
||||||
|
tap(() => { this.searching = true }),
|
||||||
|
switchMap((term): ObservableInput<any> => {
|
||||||
|
|
||||||
|
console.log("searching for", term)
|
||||||
|
|
||||||
|
//must use bind
|
||||||
|
return searchOpFn(term).pipe(
|
||||||
|
tap(() => {this.searchFailed = false}),
|
||||||
|
catchError(() => {
|
||||||
|
this.searchFailed = true;
|
||||||
|
return of([]);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
tap(() => {this.searching = false}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typeAheadChangeEvent(event){
|
||||||
|
this.markAsTouched()
|
||||||
|
console.log("bubbling modelChange event", event)
|
||||||
|
if(typeof event === 'string'){
|
||||||
|
if (event.length === 0) {
|
||||||
|
this.onChange(null);
|
||||||
|
} else {
|
||||||
|
this.onChange({text: event});
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
this.onChange(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `openOnFocus` is true, we want to show dropdown/typeahead when the field is clicked or in focus, even if there is no text entered.
|
||||||
|
//See:https://ng-bootstrap.github.io/#/components/typeahead/examples#focus
|
||||||
|
typeAheadClickEvent($event){
|
||||||
|
if(this.openOnFocus){
|
||||||
|
this.click$.next($event.target.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
typeAheadFocusEvent($event){
|
||||||
|
if(this.openOnFocus){
|
||||||
|
this.focus$.next($event.target.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Methods related to ControlValueAccessor
|
||||||
|
See: https://blog.angular-university.io/angular-custom-form-controls/
|
||||||
|
See: http://prideparrot.com/blog/archive/2019/2/applying_validation_custom_form_component
|
||||||
|
This is what allows ngModel and formControlName to be used with this component
|
||||||
|
*/
|
||||||
|
|
||||||
|
writeValue(searchResult: any) {
|
||||||
|
this.searchResult = searchResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(onChange: any) {
|
||||||
|
this.onChange = onChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(onTouched: any) {
|
||||||
|
this.onTouched = onTouched;
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsTouched() {
|
||||||
|
if (!this.touched) {
|
||||||
|
this.onTouched();
|
||||||
|
this.touched = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState(disabled: boolean) {
|
||||||
|
this.disabled = disabled;
|
||||||
|
}
|
||||||
|
public get invalid(): boolean {
|
||||||
|
return this.control ? this.control.invalid : false;
|
||||||
|
}
|
||||||
|
public get showError(): boolean {
|
||||||
|
if (!this.control) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { dirty, touched } = this.control;
|
||||||
|
|
||||||
|
return this.invalid ? (dirty || touched) : false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
{{conditionDisplayModel?.sort_title ? conditionDisplayModel?.sort_title : (conditionGroup | fhirPath: "Condition.code.text.first()":"Condition.code.coding.display.first()")}}
|
{{conditionDisplayModel?.sort_title ? conditionDisplayModel?.sort_title : (conditionGroup | fhirPath: "Condition.code.text.first()":"Condition.code.coding.display.first()")}}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
{{conditionDisplayModel?.sort_date ? (conditionDisplayModel?.sort_date | date) : (conditionGroup | fhirPath: "Condition.onsetPeriod.start":"Condition.onsetDateTime" | date) }} - {{conditionGroup | fhirPath: "Condition.onsetPeriod.end" | date}}
|
{{conditionDisplayModel?.onset_datetime | date }} <span *ngIf="conditionDisplayModel.abatement_datetime">- {{conditionDisplayModel.abatement_datetime | date}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- card-header -->
|
</div><!-- card-header -->
|
||||||
|
@ -32,12 +32,13 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
<!-- <div class="col-12 mt-3 mb-2 tx-indigo">-->
|
<div *ngIf="conditionDisplayModel" class="col-12 mt-3 mb-2">
|
||||||
<!-- <h5>Initial Presentation</h5>-->
|
<p class="tx-indigo">Initial Presentation</p>
|
||||||
<!-- </div>-->
|
<p>
|
||||||
<!-- <div class="col-12">-->
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||||
<!-- Acute right knee pain and tenderness around the joint line - this was likely caused by acute renal failure.-->
|
</p>
|
||||||
<!-- </div>-->
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row pt-2" *ngIf="conditionGroup?.related_resources?.length > 0">
|
<div class="row pt-2" *ngIf="conditionGroup?.related_resources?.length > 0">
|
||||||
|
@ -58,7 +59,6 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<ng-container *ngFor="let encounter of encounters">
|
<ng-container *ngFor="let encounter of encounters">
|
||||||
|
|
||||||
<div routerLink="/source/{{encounter?.source_id}}/resource/{{encounter?.source_resource_id}}" class="col-6 mt-3 mb-2 tx-indigo">
|
<div routerLink="/source/{{encounter?.source_id}}/resource/{{encounter?.source_resource_id}}" class="col-6 mt-3 mb-2 tx-indigo">
|
||||||
<strong>{{encounter.period_start | date}}</strong>
|
<strong>{{encounter.period_start | date}}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {DeviceModel} from '../../../lib/models/resources/device-model';
|
||||||
import {DiagnosticReportModel} from '../../../lib/models/resources/diagnostic-report-model';
|
import {DiagnosticReportModel} from '../../../lib/models/resources/diagnostic-report-model';
|
||||||
import {FastenDisplayModel} from '../../../lib/models/fasten/fasten-display-model';
|
import {FastenDisplayModel} from '../../../lib/models/fasten/fasten-display-model';
|
||||||
import * as _ from "lodash";
|
import * as _ from "lodash";
|
||||||
|
import {ConditionModel} from '../../../lib/models/resources/condition-model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-report-medical-history-condition',
|
selector: 'app-report-medical-history-condition',
|
||||||
|
@ -50,7 +51,7 @@ export class ReportMedicalHistoryConditionComponent implements OnInit {
|
||||||
* */
|
* */
|
||||||
@Input() conditionGroup: ResourceFhir
|
@Input() conditionGroup: ResourceFhir
|
||||||
|
|
||||||
conditionDisplayModel: FastenDisplayModel
|
conditionDisplayModel: Partial<ConditionModel>
|
||||||
|
|
||||||
//lookup table for all resources
|
//lookup table for all resources
|
||||||
resourcesLookup: {[name:string]: FastenDisplayModel} = {}
|
resourcesLookup: {[name:string]: FastenDisplayModel} = {}
|
||||||
|
@ -94,12 +95,14 @@ export class ReportMedicalHistoryConditionComponent implements OnInit {
|
||||||
|
|
||||||
let telecomEmails =_.find(practitionerModel.telecom, {"system": "email"})
|
let telecomEmails =_.find(practitionerModel.telecom, {"system": "email"})
|
||||||
let email = _.get(telecomEmails, '[0].value')
|
let email = _.get(telecomEmails, '[0].value')
|
||||||
|
let qualification = _.find(practitionerModel.qualification, {"system": "http://nucc.org/provider-taxonomy"})
|
||||||
|
|
||||||
involvedInCareMap[id] = _.mergeWith(
|
involvedInCareMap[id] = _.mergeWith(
|
||||||
{},
|
{},
|
||||||
involvedInCareMap[id],
|
involvedInCareMap[id],
|
||||||
{
|
{
|
||||||
displayName: practitionerModel.name?.family && practitionerModel.name?.given ? `${practitionerModel.name?.family }, ${practitionerModel.name?.given}` : practitionerModel.name?.text,
|
displayName: practitionerModel.name?.family && practitionerModel.name?.given ? `${practitionerModel.name?.family }, ${practitionerModel.name?.given}` : practitionerModel.name?.text,
|
||||||
role: practitionerModel.name?.prefix || practitionerModel.name?.suffix,
|
role: qualification?.display || practitionerModel.name?.prefix || practitionerModel.name?.suffix,
|
||||||
email: email,
|
email: email,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -41,7 +41,7 @@ import { ReportMedicalHistoryConditionComponent } from './report-medical-history
|
||||||
import { ReportLabsObservationComponent } from './report-labs-observation/report-labs-observation.component';
|
import { ReportLabsObservationComponent } from './report-labs-observation/report-labs-observation.component';
|
||||||
import { ChartsModule } from 'ng2-charts';
|
import { ChartsModule } from 'ng2-charts';
|
||||||
import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component';
|
import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component';
|
||||||
import {FormsModule} from '@angular/forms';
|
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
import { BinaryComponent } from './fhir/resources/binary/binary.component';
|
import { BinaryComponent } from './fhir/resources/binary/binary.component';
|
||||||
import { PdfComponent } from './fhir/datatypes/pdf/pdf.component';
|
import { PdfComponent } from './fhir/datatypes/pdf/pdf.component';
|
||||||
import { ImgComponent } from './fhir/datatypes/img/img.component';
|
import { ImgComponent } from './fhir/datatypes/img/img.component';
|
||||||
|
@ -62,9 +62,8 @@ import { MedicationRequestComponent } from './fhir/resources/medication-request/
|
||||||
import { ProcedureComponent } from './fhir/resources/procedure/procedure.component';
|
import { ProcedureComponent } from './fhir/resources/procedure/procedure.component';
|
||||||
import { DiagnosticReportComponent } from './fhir/resources/diagnostic-report/diagnostic-report.component';
|
import { DiagnosticReportComponent } from './fhir/resources/diagnostic-report/diagnostic-report.component';
|
||||||
import { PractitionerComponent } from './fhir/resources/practitioner/practitioner.component';
|
import { PractitionerComponent } from './fhir/resources/practitioner/practitioner.component';
|
||||||
import {FhirPathPipe} from '../pipes/fhir-path.pipe';
|
|
||||||
import {FilterPipe} from '../pipes/filter.pipe';
|
|
||||||
import {PipesModule} from '../pipes/pipes.module';
|
import {PipesModule} from '../pipes/pipes.module';
|
||||||
|
import { NlmTypeaheadComponent } from './nlm-typeahead/nlm-typeahead.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -74,11 +73,12 @@ import {PipesModule} from '../pipes/pipes.module';
|
||||||
NgbModule,
|
NgbModule,
|
||||||
NgbCollapseModule,
|
NgbCollapseModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
MomentModule,
|
MomentModule,
|
||||||
TreeModule,
|
TreeModule,
|
||||||
ChartsModule,
|
ChartsModule,
|
||||||
HighlightModule,
|
HighlightModule,
|
||||||
PipesModule
|
PipesModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ComponentsSidebarComponent,
|
ComponentsSidebarComponent,
|
||||||
|
@ -135,6 +135,7 @@ import {PipesModule} from '../pipes/pipes.module';
|
||||||
ProcedureComponent,
|
ProcedureComponent,
|
||||||
DiagnosticReportComponent,
|
DiagnosticReportComponent,
|
||||||
PractitionerComponent,
|
PractitionerComponent,
|
||||||
|
NlmTypeaheadComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ComponentsSidebarComponent,
|
ComponentsSidebarComponent,
|
||||||
|
@ -174,17 +175,18 @@ import {PipesModule} from '../pipes/pipes.module';
|
||||||
BinaryComponent,
|
BinaryComponent,
|
||||||
FhirResourceComponent,
|
FhirResourceComponent,
|
||||||
FhirResourceOutletDirective,
|
FhirResourceOutletDirective,
|
||||||
FallbackComponent,
|
FallbackComponent,
|
||||||
ImmunizationComponent,
|
ImmunizationComponent,
|
||||||
BadgeComponent,
|
BadgeComponent,
|
||||||
TableComponent,
|
TableComponent,
|
||||||
CodingComponent,
|
CodingComponent,
|
||||||
AllergyIntoleranceComponent,
|
AllergyIntoleranceComponent,
|
||||||
MedicationComponent,
|
MedicationComponent,
|
||||||
MedicationRequestComponent,
|
MedicationRequestComponent,
|
||||||
ProcedureComponent,
|
ProcedureComponent,
|
||||||
DiagnosticReportComponent,
|
DiagnosticReportComponent,
|
||||||
PractitionerComponent
|
PractitionerComponent,
|
||||||
|
NlmTypeaheadComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,197 @@
|
||||||
|
import {IResourceRaw} from './resource_fhir';
|
||||||
|
import {CodingModel} from '../../../lib/models/datatypes/coding-model';
|
||||||
|
import {NlmSearchResults} from '../../services/nlm-clinical-table-search.service';
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "condition": {
|
||||||
|
// "data": {
|
||||||
|
// "id": "14673",
|
||||||
|
// "text": "Hepatitis C",
|
||||||
|
// "link": "http://www.nlm.nih.gov/medlineplus/hepatitisc.html",
|
||||||
|
// "identifier": {
|
||||||
|
// "icd10": "R19.7"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "status": "active",
|
||||||
|
// "started": {
|
||||||
|
// "year": 2023,
|
||||||
|
// "month": 2,
|
||||||
|
// "day": 23
|
||||||
|
// },
|
||||||
|
// "stopped": {
|
||||||
|
// "year": 2023,
|
||||||
|
// "month": 2,
|
||||||
|
// "day": 24
|
||||||
|
// },
|
||||||
|
// "description": "hello world"
|
||||||
|
// },
|
||||||
|
// "medications": [
|
||||||
|
// {
|
||||||
|
// "data": {
|
||||||
|
// "id": "1171721",
|
||||||
|
// "text": "DIOVAN (Oral Pill)"
|
||||||
|
// },
|
||||||
|
// "status": "active",
|
||||||
|
// "dosage": {},
|
||||||
|
// "started": {
|
||||||
|
// "year": 2023,
|
||||||
|
// "month": 2,
|
||||||
|
// "day": 15
|
||||||
|
// },
|
||||||
|
// "stopped": {
|
||||||
|
// "year": 2023,
|
||||||
|
// "month": 2,
|
||||||
|
// "day": 16
|
||||||
|
// },
|
||||||
|
// "whystopped": {
|
||||||
|
// "id": "STP-4",
|
||||||
|
// "text": "Replaced by better drug"
|
||||||
|
// },
|
||||||
|
// "resupply": {
|
||||||
|
// "year": 2023,
|
||||||
|
// "month": 2,
|
||||||
|
// "day": 9
|
||||||
|
// },
|
||||||
|
// "instructions": "dfdsf"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "procedures": [
|
||||||
|
// {
|
||||||
|
// "data": {
|
||||||
|
// "id": "5592",
|
||||||
|
// "text": "Abscess drainage",
|
||||||
|
// "link": "http://www.nlm.nih.gov/medlineplus/abscesses.html",
|
||||||
|
// "identifier": { "icd9": "" }
|
||||||
|
// },
|
||||||
|
// "whendone": {
|
||||||
|
// "year": 2023,
|
||||||
|
// "month": 2,
|
||||||
|
// "day": 16
|
||||||
|
// },
|
||||||
|
// "comment": "dfsdf"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "practitioners": [
|
||||||
|
// {
|
||||||
|
// "contactType": "search",
|
||||||
|
// "name": "",
|
||||||
|
// "data": {
|
||||||
|
// "id": "1689675621",
|
||||||
|
// "text": "HAZEN, F.",
|
||||||
|
// "provider_type": "Pharmacist",
|
||||||
|
// "provider_address": "2562 MONROE BLVD, OGDEN, UT 84740",
|
||||||
|
// "provider_fax": "(801) 399-1154",
|
||||||
|
// "provider_phone": "(801) 399-1151"
|
||||||
|
// },
|
||||||
|
// "profession": {
|
||||||
|
// "id": "CLIN",
|
||||||
|
// "text": "Clinical psychologist"
|
||||||
|
// },
|
||||||
|
// "phone": "",
|
||||||
|
// "fax": "",
|
||||||
|
// "email": "",
|
||||||
|
// "comment": "df"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "locations": [
|
||||||
|
// {
|
||||||
|
// "name": "",
|
||||||
|
// "contactType": "search",
|
||||||
|
// "data": {
|
||||||
|
// "id": "1689935025",
|
||||||
|
// "text": "D & D SPECIAL CARE",
|
||||||
|
// "provider_type": "Assisted Living Facility",
|
||||||
|
// "provider_address": "5760 NW 40TH TER, COCONUT CREEK, FL 33073",
|
||||||
|
// "provider_fax": "",
|
||||||
|
// "provider_phone": "(954) 675-3395"
|
||||||
|
// },
|
||||||
|
// "phone": "",
|
||||||
|
// "fax": "",
|
||||||
|
// "email": "",
|
||||||
|
// "comment": "sfds"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "name": "sdfsdf",
|
||||||
|
// "contactType": "manual",
|
||||||
|
// "data": {},
|
||||||
|
// "phone": "sdfsdf",
|
||||||
|
// "fax": "sdfsdf",
|
||||||
|
// "email": "sdfsdf",
|
||||||
|
// "comment": "sdfsdf"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
|
||||||
|
export interface ResourceCreate {
|
||||||
|
condition: ResourceCreateCondition,
|
||||||
|
"medications": ResourceCreateMedication[],
|
||||||
|
"procedures": ResourceCreateProcedure[],
|
||||||
|
"practitioners": ResourceCreatePractitioner[],
|
||||||
|
"organizations": ResourceCreateOrganization[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceCreateCondition {
|
||||||
|
"data": NlmSearchResults,
|
||||||
|
"status": "active" | "inactive",
|
||||||
|
"started": ResourceCreateDate,
|
||||||
|
"stopped": ResourceCreateDate,
|
||||||
|
"description": string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceCreateDate {
|
||||||
|
year: number
|
||||||
|
month: number
|
||||||
|
day: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceCreateMedication {
|
||||||
|
"data": NlmSearchResults,
|
||||||
|
"status": "active" | "inactive",
|
||||||
|
"dosage": {},
|
||||||
|
"started": ResourceCreateDate,
|
||||||
|
"stopped": ResourceCreateDate,
|
||||||
|
"whystopped": NlmSearchResults
|
||||||
|
"requester": string,
|
||||||
|
"instructions": string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceCreateProcedure {
|
||||||
|
"data": NlmSearchResults,
|
||||||
|
"whendone": ResourceCreateDate,
|
||||||
|
"comment": string,
|
||||||
|
"performer": string,
|
||||||
|
"location": string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceCreatePractitioner {
|
||||||
|
"id"?: string,
|
||||||
|
"identifier": CodingModel[]
|
||||||
|
"name": string,
|
||||||
|
"profession": NlmSearchResults,
|
||||||
|
"phone": string,
|
||||||
|
"fax": string,
|
||||||
|
"email": string,
|
||||||
|
"address": Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResourceCreateOrganization {
|
||||||
|
"id"?: string,
|
||||||
|
"identifier": CodingModel[]
|
||||||
|
"type": NlmSearchResults,
|
||||||
|
"name": string,
|
||||||
|
"phone": string,
|
||||||
|
"fax": string,
|
||||||
|
"email": string,
|
||||||
|
"address": Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Address {
|
||||||
|
line1?: string
|
||||||
|
line2?: string
|
||||||
|
city?: string
|
||||||
|
state?: string
|
||||||
|
zip?: string
|
||||||
|
country?: string
|
||||||
|
}
|
|
@ -26,6 +26,9 @@
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<h1 class="az-dashboard-title">Condition</h1>
|
<h1 class="az-dashboard-title">Condition</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<a class="float-right btn btn-outline-indigo" routerLink="/resource/create">Add Condition</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Condition List -->
|
<!-- Condition List -->
|
||||||
|
|
|
@ -0,0 +1,543 @@
|
||||||
|
<div class="az-content">
|
||||||
|
<div class="container">
|
||||||
|
<div class="az-content-body pd-lg-l-40 d-flex flex-column">
|
||||||
|
<div class="az-content-breadcrumb">
|
||||||
|
<span>Components</span>
|
||||||
|
<span>Forms</span>
|
||||||
|
<span>Form Elements</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="az-content-title">Create a Record</h2>
|
||||||
|
|
||||||
|
<!-- Editor Button -->
|
||||||
|
<div class="row mt-5 mb-3">
|
||||||
|
<div class="col-12">
|
||||||
|
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<strong>Warning!</strong> This form is in early-alpha and is not ready for general use. You will encounter bugs and missing features.
|
||||||
|
Please open a <a href="https://github.com/fastenhealth/fasten-onprem/issues/new?title=Resource+Create+-+Your+Feature+Or+Bug+Here">Github Issue</a> if you find any bugs or have any suggestions.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
Enable Debug mode: <input type="checkbox" [(ngModel)]="debugMode"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="debugMode" class="alert alert-warning">
|
||||||
|
<pre><code [highlight]="form.getRawValue() | json"></code></pre>
|
||||||
|
<strong>Form Status: {{ form.status }}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="az-content-label mg-b-5">Condition</div>
|
||||||
|
<p class="mg-b-20">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna </p>
|
||||||
|
|
||||||
|
|
||||||
|
<form (ngSubmit)="onSubmit()" [formGroup]="form">
|
||||||
|
<div *ngIf="debugMode" class="alert alert-warning">
|
||||||
|
<strong>Condition Status: {{form.get('condition').status}}</strong>
|
||||||
|
</div>
|
||||||
|
<ng-container formGroupName="condition">
|
||||||
|
<div class="row row-sm">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<p class="mg-b-10">Medical condition<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<app-nlm-typeahead formControlName="data" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Status<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<select formControlName="status" class="form-control">
|
||||||
|
<option value="" hidden selected>Select Status</option>
|
||||||
|
<option value="active">Active</option>
|
||||||
|
<option value="inactive">Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Started<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<input formControlName="started" class="form-control" placeholder="yyyy-mm-dd" name="dp" ngbDatepicker #cds="ngbDatepicker" (click)="cds.toggle()">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Stopped</p>
|
||||||
|
<input formControlName="stopped" [minDate]="form.get('condition').get('started').value" class="form-control" placeholder="yyyy-mm-dd" name="dp" ngbDatepicker #cde="ngbDatepicker" (click)="cde.toggle()">
|
||||||
|
</div><!-- col -->
|
||||||
|
</div><!-- row -->
|
||||||
|
|
||||||
|
<div class="row row-sm mg-t-20">
|
||||||
|
<div class="col-lg">
|
||||||
|
<p class="mg-b-10">Description/Comment</p>
|
||||||
|
<textarea formControlName="description" rows="3" class="form-control" placeholder="Textarea"></textarea>
|
||||||
|
</div><!-- col -->
|
||||||
|
</div><!-- row -->
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<hr class="mg-y-30">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg">
|
||||||
|
<div class="card card-fhir-resource" >
|
||||||
|
<div class="card-header" (click)="collapsePanel['medication'] = !collapsePanel['medication']">
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Medications</h6>
|
||||||
|
<p class="card-text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div #collapse="ngbCollapse" [(ngbCollapse)]="collapsePanel['medication']" class="card-body">
|
||||||
|
|
||||||
|
<ng-container formArrayName="medications">
|
||||||
|
<div class="card mg-t-10 pd-20" [formGroup]="medicationGroup" *ngFor="let medicationGroup of medications.controls; let i = index">
|
||||||
|
<div *ngIf="debugMode" class="alert alert-warning">
|
||||||
|
<strong>Medication Status: {{medicationGroup.status}}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="tx-right">
|
||||||
|
<span class="cursor-pointer" (click)="deleteMedication(i)" aria-hidden="true"><i class="fas fa-trash"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="row row-sm">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<p class="mg-b-10">Medication name<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<app-nlm-typeahead formControlName="data" searchType="Medication" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-3 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Status<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<select formControlName="status" class="form-control">
|
||||||
|
<option value="" hidden selected>Select Status</option>
|
||||||
|
<option value="active">Active</option>
|
||||||
|
<option value="stopped">Stopped</option>
|
||||||
|
</select> </div><!-- col -->
|
||||||
|
<div class="col-lg-3 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Dosage</p>
|
||||||
|
<input formControlName="dosage" class="form-control" placeholder="Input box" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-3 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Started<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<input formControlName="started" class="form-control" placeholder="yyyy-mm-dd" ngbDatepicker #ds="ngbDatepicker" (click)="ds.toggle()">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-3 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Stopped</p>
|
||||||
|
<input [minDate]="medicationGroup.get('started').value" formControlName="stopped" class="form-control" placeholder="yyyy-mm-dd" ngbDatepicker #dstop="ngbDatepicker" (click)="dstop.toggle()">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-3 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Why Stopped</p>
|
||||||
|
<app-nlm-typeahead formControlName="whystopped" searchType="MedicationWhyStopped" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-3 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Prescribing Practitioner<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<select class="form-control" formControlName="requester">
|
||||||
|
<option value="" hidden selected>Select Practitioner</option>
|
||||||
|
<option value="" (click)="openPractitionerModal(practitionerCreateModal, medicationGroup, 'requester')">New Practitioner</option>
|
||||||
|
<optgroup *ngIf="practitioners.controls.length" class="divider"></optgroup>
|
||||||
|
<option *ngFor="let practitioner of practitioners.controls; let i = index" [value]="practitioner.value.id">
|
||||||
|
{{practitioner.value.name}} ({{practitioner.value.profession.text}})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div><!-- col -->
|
||||||
|
</div><!-- row -->
|
||||||
|
<div class="row row-sm mg-t-20">
|
||||||
|
<div class="col-lg">
|
||||||
|
<p class="mg-b-10">Instructions</p>
|
||||||
|
<textarea formControlName="instructions" rows="3" class="form-control" placeholder="Textarea"></textarea>
|
||||||
|
</div><!-- col -->
|
||||||
|
</div><!-- row -->
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<div class="row pt-2">
|
||||||
|
<div class="col-lg-4 col-md-3">
|
||||||
|
<button type="button" (click)="addMedication()" class="btn btn-outline-indigo btn-block">Add Medication</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<a class="float-right" (click)="collapsePanel['medication'] = !collapsePanel['medication']">{{collapsePanel['medication'] ? 'expand' : 'collapse'}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="mg-y-30">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg">
|
||||||
|
<div class="card card-fhir-resource" >
|
||||||
|
<div class="card-header" (click)="collapsePanel['procedure'] = !collapsePanel['procedure']">
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Major Surgeries and Implants</h6>
|
||||||
|
<p class="card-text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div #collapse="ngbCollapse" [(ngbCollapse)]="collapsePanel['procedure']" class="card-body">
|
||||||
|
<ng-container formArrayName="procedures">
|
||||||
|
<div class="card mg-t-10 pd-20" [formGroup]="procedureGroup" *ngFor="let procedureGroup of procedures.controls; let i = index">
|
||||||
|
<div *ngIf="debugMode" class="alert alert-warning">
|
||||||
|
<strong>Procedure Status: {{procedureGroup.status}}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="tx-right">
|
||||||
|
<span class="cursor-pointer" (click)="deleteProcedure(i)" aria-hidden="true"><i class="fas fa-trash"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="row row-sm">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<p class="mg-b-10">Surgery or Implant<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<app-nlm-typeahead formControlName="data" searchType="Procedure" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-4 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">When done<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<input formControlName="whendone" class="form-control" placeholder="yyyy-mm-dd" ngbDatepicker #dwd="ngbDatepicker" (click)="dwd.toggle()">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Performed By</p>
|
||||||
|
<select class="form-control" formControlName="performer">
|
||||||
|
<option value="" hidden selected>Select Practitioner</option>
|
||||||
|
<option value="" (click)="openPractitionerModal(practitionerCreateModal, procedureGroup, 'performer')">New Practitioner</option>
|
||||||
|
<optgroup *ngIf="practitioners.controls.length" class="divider"></optgroup>
|
||||||
|
<option *ngFor="let practitioner of practitioners.controls; let i = index" [value]="practitioner.value.id">
|
||||||
|
{{practitioner.value.name}} ({{practitioner.value.profession.text}})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Location</p>
|
||||||
|
<select class="form-control" formControlName="location">
|
||||||
|
<option value="" hidden selected>Select Location</option>
|
||||||
|
<option value="" (click)="openOrganizationModal(organizationCreateModal, procedureGroup, 'location')">New Organization</option>
|
||||||
|
<optgroup *ngIf="organizations.controls.length" class="divider"></optgroup>
|
||||||
|
<option *ngFor="let organization of organizations.controls; let i = index" [value]="organization.value.id">
|
||||||
|
{{organization.value.name}} ({{organization.value.address}})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-12 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Comments</p>
|
||||||
|
<textarea formControlName="comment" class="form-control" placeholder="Input box" rows="3"></textarea>
|
||||||
|
</div><!-- col -->
|
||||||
|
</div><!-- row -->
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row pt-2">
|
||||||
|
<div class="col-lg-4 col-md-3">
|
||||||
|
<button type="button" (click)="addProcedure()" class="btn btn-outline-indigo btn-block">Add Surgery or Implant</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<a class="float-right" (click)="collapsePanel['procedure'] = !collapsePanel['procedure']">{{collapsePanel['procedure'] ? 'expand' : 'collapse'}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="mg-y-30">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg">
|
||||||
|
<div class="card card-fhir-resource" >
|
||||||
|
<div class="card-header" (click)="collapsePanel['practitioner'] = !collapsePanel['practitioner']">
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Medical Practitioners</h6>
|
||||||
|
<p class="card-text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div #collapse="ngbCollapse" [(ngbCollapse)]="collapsePanel['practitioner']" class="card-body">
|
||||||
|
|
||||||
|
<ng-container formArrayName="practitioners">
|
||||||
|
<div class="card mg-t-10 pd-20" [formGroup]="practitionerGroup" *ngFor="let practitionerGroup of practitioners.controls; let i = index">
|
||||||
|
<div class="tx-right">
|
||||||
|
<span class="cursor-pointer" (click)="deletePractitioner(i)" aria-hidden="true"><i class="fas fa-trash"></i></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row row-sm">
|
||||||
|
<input formControlName="id" class="form-control" type="hidden">
|
||||||
|
|
||||||
|
<div class="col-lg-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Name<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<input formControlName="name" class="form-control" readonly placeholder="Input box" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Type<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<app-nlm-typeahead formControlName="profession" searchType="MedicalContactIndividualProfession" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Telephone</p>
|
||||||
|
<input formControlName="phone" class="form-control" placeholder="(123) 456-7890" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Fax</p>
|
||||||
|
<input formControlName="fax" class="form-control" placeholder="(123) 456-7890" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Email</p>
|
||||||
|
<input formControlName="email" class="form-control" placeholder="email@example.com" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Address</p>
|
||||||
|
<input formControlName="address" class="form-control" placeholder="Input box" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
|
||||||
|
</div><!-- row -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="row pt-2">
|
||||||
|
<div class="col-lg-4 col-md-4">
|
||||||
|
<button type="button" (click)="openPractitionerModal(practitionerCreateModal)" class="btn btn-outline-indigo btn-block">Add Practitioner</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer">
|
||||||
|
<a class="float-right" (click)="collapsePanel['practitioner'] = !collapsePanel['practitioner']">{{collapsePanel['practitioner'] ? 'expand' : 'collapse'}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="mg-y-30">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg">
|
||||||
|
<div class="card card-fhir-resource" >
|
||||||
|
<div class="card-header" (click)="collapsePanel['organization'] = !collapsePanel['organization']">
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Medical Location/Organizations</h6>
|
||||||
|
<p class="card-text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div #collapse="ngbCollapse" [(ngbCollapse)]="collapsePanel['organization']" class="card-body">
|
||||||
|
|
||||||
|
<ng-container formArrayName="locations">
|
||||||
|
<div class="card mg-t-10 pd-20" [formGroup]="organizationGroup" *ngFor="let organizationGroup of organizations.controls; let i = index">
|
||||||
|
<div class="tx-right">
|
||||||
|
<span class="cursor-pointer" (click)="deleteOrganization(i)" aria-hidden="true"><i class="fas fa-trash"></i></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row row-sm">
|
||||||
|
<input formControlName="id" class="form-control" type="hidden">
|
||||||
|
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Name<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<input formControlName="name" readonly class="form-control" placeholder="Input box" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Type</p>
|
||||||
|
<app-nlm-typeahead formControlName="type" searchType="MedicalContactOrganizationType" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Telephone</p>
|
||||||
|
<input formControlName="phone" class="form-control" placeholder="(123) 456-7890" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Fax</p>
|
||||||
|
<input formControlName="fax" class="form-control" placeholder="(123) 456-7890" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Email</p>
|
||||||
|
<input formControlName="email" class="form-control" placeholder="email@example.com" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
|
||||||
|
</div><!-- row -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="row pt-2">
|
||||||
|
<div class="col-lg-4 col-md-4">
|
||||||
|
<button type="button" (click)="openOrganizationModal(organizationCreateModal)" class="btn btn-outline-indigo btn-block">Add Organization</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer">
|
||||||
|
<a class="float-right" (click)="collapsePanel['organization'] = !collapsePanel['organization']">{{collapsePanel['organization'] ? 'expand' : 'collapse'}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="mg-y-30">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg">
|
||||||
|
<div class="card card-fhir-resource" >
|
||||||
|
<div class="card-header" (click)="collapsePanel['attachments'] = !collapsePanel['attachments']">
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Notes & Attachments</h6>
|
||||||
|
<p class="card-text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div #collapse="ngbCollapse" [(ngbCollapse)]="collapsePanel['attachments']" class="card-body">
|
||||||
|
<div class="row row-sm">
|
||||||
|
<div class="col-lg">
|
||||||
|
<p class="mg-b-10">Name</p>
|
||||||
|
<input disabled class="form-control" placeholder="Input box" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-lg mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Type</p>
|
||||||
|
<input disabled class="form-control" placeholder="Input box" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
|
||||||
|
</div><!-- row -->
|
||||||
|
<div class="row pt-2">
|
||||||
|
<div class="col-lg-4 col-md-3">
|
||||||
|
<button disabled type="button" class="btn btn-outline-indigo btn-block">Add Note or Attachment</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<a class="float-right" (click)="collapsePanel['attachments'] = !collapsePanel['attachments']">{{collapsePanel['attachments'] ? 'expand' : 'collapse'}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="mg-t-20 mg-b-20 btn btn-az-primary btn-rounded btn-block " type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div><!-- az-content-body -->
|
||||||
|
</div><!-- container -->
|
||||||
|
</div><!-- az-content -->
|
||||||
|
|
||||||
|
|
||||||
|
<ng-template #practitionerCreateModal let-modal>
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-practitioner">New Practitioner</h4>
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="modal.close()"><span aria-hidden="true">×</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<div *ngIf="debugMode" class="alert alert-warning">
|
||||||
|
<pre><code [highlight]="newPractitionerForm.getRawValue() | json"></code></pre>
|
||||||
|
<strong>New Practitioner Form Status: {{ newPractitionerForm.status }}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row row-sm">
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<p class="mg-b-10">Name<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<form [formGroup]="newPractitionerTypeaheadForm">
|
||||||
|
<app-nlm-typeahead formControlName="data" searchType="MedicalContactIndividual" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</form>
|
||||||
|
<span *ngFor="let extId of newPractitionerForm.get('identifier').getRawValue()" class="badge badge-pill badge-primary">{{extId.type?.coding[0].code}}: {{extId.value}}</span>
|
||||||
|
</div><!-- col -->
|
||||||
|
<ng-container [formGroup]="newPractitionerForm">
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Type<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<app-nlm-typeahead formControlName="profession" searchType="MedicalContactIndividualProfession" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</div><!-- col -->
|
||||||
|
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Telephone</p>
|
||||||
|
<input formControlName="phone" class="form-control" placeholder="(123) 456-7890" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Fax</p>
|
||||||
|
<input formControlName="fax" class="form-control" placeholder="(123) 456-7890" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Email</p>
|
||||||
|
<input formControlName="email" class="form-control" placeholder="email@example.com" type="text" email>
|
||||||
|
</div><!-- col -->
|
||||||
|
<ng-container formGroupName="address">
|
||||||
|
<div class="col-12 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Address</p>
|
||||||
|
<input formControlName="line1" class="form-control" placeholder="Line 1" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-12 pd-t-10 mg-t-10 mg-lg-t-0">
|
||||||
|
<input formControlName="line2" class="form-control" placeholder="Line 2" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">City</p>
|
||||||
|
<input formControlName="city" class="form-control" placeholder="City" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">State</p>
|
||||||
|
<input formControlName="state" class="form-control" placeholder="State" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Zip/Postal</p>
|
||||||
|
<input formControlName="zip" class="form-control" placeholder="Zip" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Country</p>
|
||||||
|
<input formControlName="country" class="form-control" placeholder="Country" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div><!-- row -->
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-az-primary" (click)="modal.dismiss()">Add Practitioner</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ng-template #organizationCreateModal let-modal>
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-location">New Location</h4>
|
||||||
|
<button type="button" class="close" aria-label="Close" (click)="modal.close()"><span aria-hidden="true">×</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<div *ngIf="debugMode" class="alert alert-warning">
|
||||||
|
<pre><code [highlight]="newOrganizationForm.getRawValue() | json"></code></pre>
|
||||||
|
<strong>New Organization Form Status: {{ newOrganizationForm.status }}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row row-sm">
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<p class="mg-b-10">Name<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<form [formGroup]="newOrganizationTypeaheadForm">
|
||||||
|
<app-nlm-typeahead formControlName="data" searchType="MedicalContactOrganization" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</form>
|
||||||
|
<span *ngFor="let extId of newOrganizationForm.get('identifier').getRawValue()" class="badge badge-pill badge-primary">{{extId.type?.coding[0].code}}: {{extId.value}}</span>
|
||||||
|
</div><!-- col -->
|
||||||
|
|
||||||
|
<ng-container [formGroup]="newOrganizationForm">
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Type<span ngbTooltip="required" class="text-danger">*</span></p>
|
||||||
|
<app-nlm-typeahead formControlName="type" searchType="MedicalContactOrganizationType" [debugMode]="debugMode"></app-nlm-typeahead>
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Telephone</p>
|
||||||
|
<input formControlName="phone" class="form-control" placeholder="(123) 456-7890" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Fax</p>
|
||||||
|
<input formControlName="fax" class="form-control" placeholder="(123) 456-7890" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Email</p>
|
||||||
|
<input formControlName="email" class="form-control" placeholder="email@example.com" type="text" email>
|
||||||
|
</div><!-- col -->
|
||||||
|
<ng-container formGroupName="address">
|
||||||
|
<div class="col-12 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Address</p>
|
||||||
|
<input formControlName="line1" class="form-control" placeholder="Line 1" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-12 pd-t-10 mg-t-10 mg-lg-t-0">
|
||||||
|
<input formControlName="line2" class="form-control" placeholder="Line 2" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">City</p>
|
||||||
|
<input formControlName="city" class="form-control" placeholder="City" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">State</p>
|
||||||
|
<input formControlName="state" class="form-control" placeholder="State" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Zip/Postal</p>
|
||||||
|
<input formControlName="zip" class="form-control" placeholder="Zip" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
<div class="col-6 mg-t-10 mg-lg-t-0">
|
||||||
|
<p class="mg-b-10">Country</p>
|
||||||
|
<input formControlName="country" class="form-control" placeholder="Country" type="text">
|
||||||
|
</div><!-- col -->
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-az-primary" (click)="modal.dismiss()">Add Location</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ResourceCreatorComponent } from './resource-creator.component';
|
||||||
|
|
||||||
|
describe('ResourceCreatorComponent', () => {
|
||||||
|
let component: ResourceCreatorComponent;
|
||||||
|
let fixture: ComponentFixture<ResourceCreatorComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ ResourceCreatorComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ResourceCreatorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,379 @@
|
||||||
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
|
import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@angular/forms';
|
||||||
|
import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {
|
||||||
|
ResourceCreateOrganization,
|
||||||
|
ResourceCreatePractitioner,
|
||||||
|
} from '../../models/fasten/resource_create';
|
||||||
|
import {uuidV4} from '../../../lib/utils/uuid';
|
||||||
|
import {NlmSearchResults} from '../../services/nlm-clinical-table-search.service';
|
||||||
|
import {GenerateR4Bundle} from './resource-creator.utilities';
|
||||||
|
import {FastenApiService} from '../../services/fasten-api.service';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
|
||||||
|
export interface MedicationModel {
|
||||||
|
data: {},
|
||||||
|
status: string,
|
||||||
|
dosage: string,
|
||||||
|
started: null,
|
||||||
|
stopped: null,
|
||||||
|
whystopped: string,
|
||||||
|
resupply: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ContactType {
|
||||||
|
ContactTypeSearch = 'search',
|
||||||
|
ContactTypeManual = 'manual',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-resource-creator',
|
||||||
|
templateUrl: './resource-creator.component.html',
|
||||||
|
styleUrls: ['./resource-creator.component.scss']
|
||||||
|
})
|
||||||
|
export class ResourceCreatorComponent implements OnInit {
|
||||||
|
debugMode = false;
|
||||||
|
collapsePanel: {[name: string]: boolean} = {}
|
||||||
|
|
||||||
|
|
||||||
|
@Input() form!: FormGroup;
|
||||||
|
get isValid() { return true; }
|
||||||
|
|
||||||
|
// model: any = {
|
||||||
|
// condition: {
|
||||||
|
// data: {},
|
||||||
|
// status: null,
|
||||||
|
// started: null,
|
||||||
|
// stopped: null,
|
||||||
|
// description: null,
|
||||||
|
// },
|
||||||
|
// medication: []
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
constructor(private router: Router, private modalService: NgbModal, private fastenApi: FastenApiService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
|
||||||
|
|
||||||
|
//https://stackoverflow.com/questions/52038071/creating-nested-form-groups-using-angular-reactive-forms
|
||||||
|
//https://www.danywalls.com/creating-dynamic-forms-in-angular-a-step-by-step-guide
|
||||||
|
//https://www.telerik.com/blogs/angular-basics-creating-dynamic-forms-using-formarray-angular
|
||||||
|
//https://angular.io/guide/reactive-forms#creating-dynamic-forms
|
||||||
|
//https://angular.io/guide/dynamic-form
|
||||||
|
this.form = new FormGroup({
|
||||||
|
condition: new FormGroup({
|
||||||
|
data: new FormControl<NlmSearchResults>(null, Validators.required),
|
||||||
|
status: new FormControl(null, Validators.required),
|
||||||
|
started: new FormControl(null, Validators.required),
|
||||||
|
stopped: new FormControl(null),
|
||||||
|
description: new FormControl(null),
|
||||||
|
}),
|
||||||
|
|
||||||
|
medications: new FormArray([]),
|
||||||
|
procedures: new FormArray([]),
|
||||||
|
practitioners: new FormArray([]),
|
||||||
|
organizations: new FormArray([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resetOrganizationForm()
|
||||||
|
// this.resetPractitionerForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
get medications(): FormArray {
|
||||||
|
return this.form.controls["medications"] as FormArray;
|
||||||
|
}
|
||||||
|
addMedication(){
|
||||||
|
const medicationGroup = new FormGroup({
|
||||||
|
data: new FormControl<NlmSearchResults>(null, Validators.required),
|
||||||
|
status: new FormControl(null, Validators.required),
|
||||||
|
dosage: new FormControl({
|
||||||
|
value: '', disabled: true
|
||||||
|
}),
|
||||||
|
started: new FormControl(null, Validators.required),
|
||||||
|
stopped: new FormControl(null),
|
||||||
|
whystopped: new FormControl(null),
|
||||||
|
requester: new FormControl(null, Validators.required),
|
||||||
|
instructions: new FormControl(null),
|
||||||
|
});
|
||||||
|
|
||||||
|
medicationGroup.get("data").valueChanges.subscribe(val => {
|
||||||
|
medicationGroup.get("dosage").enable();
|
||||||
|
//TODO: find a way to create dependant dosage information based on medication data.
|
||||||
|
});
|
||||||
|
|
||||||
|
this.medications.push(medicationGroup);
|
||||||
|
}
|
||||||
|
deleteMedication(index: number) {
|
||||||
|
this.medications.removeAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get procedures(): FormArray {
|
||||||
|
return this.form.controls["procedures"] as FormArray;
|
||||||
|
}
|
||||||
|
addProcedure(){
|
||||||
|
const procedureGroup = new FormGroup({
|
||||||
|
data: new FormControl<NlmSearchResults>(null, Validators.required),
|
||||||
|
whendone: new FormControl(null, Validators.required),
|
||||||
|
performer: new FormControl(null),
|
||||||
|
location: new FormControl(null),
|
||||||
|
comment: new FormControl('')
|
||||||
|
});
|
||||||
|
|
||||||
|
this.procedures.push(procedureGroup);
|
||||||
|
}
|
||||||
|
deleteProcedure(index: number) {
|
||||||
|
this.procedures.removeAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get practitioners(): FormArray {
|
||||||
|
return this.form.controls["practitioners"] as FormArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPractitioner(practitioner: ResourceCreatePractitioner){
|
||||||
|
const practitionerGroup = new FormGroup({
|
||||||
|
id: new FormControl(practitioner.id, Validators.required),
|
||||||
|
identifier: new FormControl(practitioner.identifier),
|
||||||
|
profession: new FormControl(practitioner.profession, Validators.required),
|
||||||
|
name: new FormControl(practitioner.name, Validators.required),
|
||||||
|
phone: new FormControl(practitioner.phone, Validators.pattern('[- +()0-9]+')),
|
||||||
|
fax: new FormControl(practitioner.fax, Validators.pattern('[- +()0-9]+')),
|
||||||
|
email: new FormControl(practitioner.email, Validators.email),
|
||||||
|
address: new FormGroup({
|
||||||
|
line1: new FormControl(practitioner.address.line1),
|
||||||
|
line2: new FormControl(practitioner.address.line2),
|
||||||
|
city: new FormControl(practitioner.address.city),
|
||||||
|
state: new FormControl(practitioner.address.state),
|
||||||
|
zip: new FormControl(practitioner.address.zip),
|
||||||
|
country: new FormControl(practitioner.address.country),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.practitioners.push(practitionerGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePractitioner(index: number) {
|
||||||
|
this.practitioners.removeAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get organizations(): FormArray {
|
||||||
|
return this.form.controls["organizations"] as FormArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
addOrganization(organization: ResourceCreateOrganization){
|
||||||
|
const organizationGroup = new FormGroup({
|
||||||
|
id: new FormControl(organization.id, Validators.required),
|
||||||
|
identifier: new FormControl(organization.identifier),
|
||||||
|
name: new FormControl(organization.name, Validators.required),
|
||||||
|
type: new FormControl(organization.type),
|
||||||
|
phone: new FormControl(organization.phone, Validators.pattern('[- +()0-9]+')),
|
||||||
|
fax: new FormControl(organization.fax, Validators.pattern('[- +()0-9]+')),
|
||||||
|
email: new FormControl(organization.email, Validators.email),
|
||||||
|
address: new FormControl(organization.address),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.organizations.push(organizationGroup);
|
||||||
|
}
|
||||||
|
deleteOrganization(index: number) {
|
||||||
|
this.organizations.removeAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
console.log(this.form.getRawValue())
|
||||||
|
this.form.markAllAsTouched()
|
||||||
|
if (this.form.valid) {
|
||||||
|
console.log('form submitted');
|
||||||
|
|
||||||
|
let bundle = GenerateR4Bundle(this.form.getRawValue());
|
||||||
|
|
||||||
|
let bundleJsonStr = JSON.stringify(bundle);
|
||||||
|
let bundleBlob = new Blob([bundleJsonStr], { type: 'application/json' });
|
||||||
|
let bundleFile = new File([ bundleBlob ], 'bundle.json');
|
||||||
|
this.fastenApi.createManualSource(bundleFile).subscribe((resp) => {
|
||||||
|
console.log(resp)
|
||||||
|
this.router.navigate(['/medical-history'])
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Modal Helpers
|
||||||
|
newPractitionerTypeaheadForm: FormGroup
|
||||||
|
newPractitionerForm: FormGroup //ResourceCreatePractitioner
|
||||||
|
|
||||||
|
newOrganizationTypeaheadForm: FormGroup
|
||||||
|
newOrganizationForm: FormGroup //ResourceCreateOrganization
|
||||||
|
|
||||||
|
openPractitionerModal(content, formGroup?: AbstractControl, controlName?: string) {
|
||||||
|
this.resetPractitionerForm()
|
||||||
|
this.modalService.open(content, {
|
||||||
|
ariaLabelledBy: 'modal-practitioner',
|
||||||
|
beforeDismiss: () => {
|
||||||
|
console.log("validate Practitioner form")
|
||||||
|
this.newPractitionerForm.markAllAsTouched()
|
||||||
|
this.newPractitionerTypeaheadForm.markAllAsTouched()
|
||||||
|
return this.newPractitionerForm.valid
|
||||||
|
},
|
||||||
|
}).result.then(
|
||||||
|
() => {
|
||||||
|
console.log('Closed without saving');
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
console.log('Closing, saving form');
|
||||||
|
//add this to the list of organization
|
||||||
|
let result = this.newPractitionerForm.getRawValue()
|
||||||
|
result.id = uuidV4();
|
||||||
|
this.addPractitioner(result);
|
||||||
|
if(formGroup && controlName){
|
||||||
|
//set this practitioner to the current select box
|
||||||
|
formGroup.get(controlName).setValue(result.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
openOrganizationModal(content, formGroup?: AbstractControl, controlName?: string) {
|
||||||
|
this.resetOrganizationForm()
|
||||||
|
|
||||||
|
this.modalService.open(content, {
|
||||||
|
ariaLabelledBy: 'modal-organization',
|
||||||
|
beforeDismiss: () => {
|
||||||
|
console.log("validate Organization form")
|
||||||
|
this.newOrganizationForm.markAllAsTouched()
|
||||||
|
this.newOrganizationTypeaheadForm.markAllAsTouched()
|
||||||
|
return this.newOrganizationForm.valid
|
||||||
|
},
|
||||||
|
}).result.then(
|
||||||
|
() => {
|
||||||
|
console.log('Closed without saving');
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
console.log('Closing, saving form');
|
||||||
|
//add this to the list of organization
|
||||||
|
let result = this.newOrganizationForm.getRawValue()
|
||||||
|
result.id = uuidV4();
|
||||||
|
this.addOrganization(result);
|
||||||
|
if(formGroup && controlName){
|
||||||
|
//set this practitioner to the current select box
|
||||||
|
formGroup.get(controlName).setValue(result.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private resetPractitionerForm(){
|
||||||
|
this.newPractitionerTypeaheadForm = new FormGroup({
|
||||||
|
data: new FormControl(null, Validators.required),
|
||||||
|
})
|
||||||
|
this.newPractitionerTypeaheadForm.valueChanges.subscribe(form => {
|
||||||
|
console.log("CHANGE INDIVIDUAL IN MODAL", form)
|
||||||
|
let val = form.data
|
||||||
|
if(val.provider_type){
|
||||||
|
this.newPractitionerForm.get('profession').setValue(val.provider_type)
|
||||||
|
}
|
||||||
|
if(val.identifier){
|
||||||
|
this.newPractitionerForm.get('identifier').setValue( val.identifier);
|
||||||
|
}
|
||||||
|
if(form.data.provider_phone){
|
||||||
|
this.newPractitionerForm.get('phone').setValue( val.provider_phone);
|
||||||
|
}
|
||||||
|
if(val.provider_fax){
|
||||||
|
this.newPractitionerForm.get('fax').setValue(val.provider_fax);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(val.provider_address){
|
||||||
|
let addressGroup = this.newPractitionerForm.get('address')
|
||||||
|
addressGroup.get('line1').setValue(val.provider_address.line1)
|
||||||
|
addressGroup.get('line2').setValue(val.provider_address.line2)
|
||||||
|
addressGroup.get('city').setValue(val.provider_address.city)
|
||||||
|
addressGroup.get('state').setValue(val.provider_address.state)
|
||||||
|
addressGroup.get('zip').setValue(val.provider_address.zip)
|
||||||
|
addressGroup.get('country').setValue(val.provider_address.country)
|
||||||
|
}
|
||||||
|
if(val.text) {
|
||||||
|
this.newPractitionerForm.get('name').setValue( val.text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.newPractitionerForm = new FormGroup({
|
||||||
|
identifier: new FormControl([]),
|
||||||
|
name: new FormControl(null, Validators.required),
|
||||||
|
profession: new FormControl(null, Validators.required),
|
||||||
|
phone: new FormControl(null, Validators.pattern('[- +()0-9]+')),
|
||||||
|
fax: new FormControl(null, Validators.pattern('[- +()0-9]+')),
|
||||||
|
email: new FormControl(null, Validators.email),
|
||||||
|
address: new FormGroup({
|
||||||
|
line1: new FormControl(null),
|
||||||
|
line2: new FormControl(null),
|
||||||
|
city: new FormControl(null),
|
||||||
|
state: new FormControl(null),
|
||||||
|
zip: new FormControl(null),
|
||||||
|
country: new FormControl(null),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetOrganizationForm(){
|
||||||
|
this.newOrganizationTypeaheadForm = new FormGroup({
|
||||||
|
data: new FormControl(null, Validators.required),
|
||||||
|
})
|
||||||
|
this.newOrganizationTypeaheadForm.valueChanges.subscribe(form => {
|
||||||
|
console.log("CHANGE Organization IN MODAL", form)
|
||||||
|
let val = form.data
|
||||||
|
if(val.provider_type) {
|
||||||
|
this.newOrganizationForm.get('type').setValue(val.provider_type)
|
||||||
|
}
|
||||||
|
if(val.identifier){
|
||||||
|
this.newOrganizationForm.get('identifier').setValue(val.identifier)
|
||||||
|
}
|
||||||
|
if(val.provider_phone){
|
||||||
|
this.newOrganizationForm.get('phone').setValue(val.provider_phone)
|
||||||
|
}
|
||||||
|
if(val.provider_fax){
|
||||||
|
this.newOrganizationForm.get('fax').setValue(val.provider_fax)
|
||||||
|
}
|
||||||
|
if(val.provider_address){
|
||||||
|
let addressGroup = this.newOrganizationForm.get('address')
|
||||||
|
addressGroup.get('line1').setValue(val.provider_address.line1)
|
||||||
|
addressGroup.get('line2').setValue(val.provider_address.line2)
|
||||||
|
addressGroup.get('city').setValue(val.provider_address.city)
|
||||||
|
addressGroup.get('state').setValue(val.provider_address.state)
|
||||||
|
addressGroup.get('zip').setValue(val.provider_address.zip)
|
||||||
|
addressGroup.get('country').setValue(val.provider_address.country)
|
||||||
|
}
|
||||||
|
if(val.text) {
|
||||||
|
this.newOrganizationForm.get('name').setValue(val.text)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.newOrganizationForm = new FormGroup({
|
||||||
|
identifier: new FormControl([]),
|
||||||
|
name: new FormControl(null, Validators.required),
|
||||||
|
type: new FormControl(null, Validators.required),
|
||||||
|
phone: new FormControl(null, Validators.pattern('[- +()0-9]+')),
|
||||||
|
fax: new FormControl(null, Validators.pattern('[- +()0-9]+')),
|
||||||
|
email: new FormControl(null, Validators.email),
|
||||||
|
address: new FormGroup({
|
||||||
|
line1: new FormControl(null),
|
||||||
|
line2: new FormControl(null),
|
||||||
|
city: new FormControl(null),
|
||||||
|
state: new FormControl(null),
|
||||||
|
zip: new FormControl(null),
|
||||||
|
country: new FormControl(null),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,395 @@
|
||||||
|
import {
|
||||||
|
ResourceCreate,
|
||||||
|
ResourceCreateCondition, ResourceCreateMedication,
|
||||||
|
ResourceCreateOrganization, ResourceCreatePractitioner,
|
||||||
|
ResourceCreateProcedure
|
||||||
|
} from '../../models/fasten/resource_create';
|
||||||
|
import {
|
||||||
|
Condition,
|
||||||
|
Medication,
|
||||||
|
Procedure,
|
||||||
|
Location as FhirLocation,
|
||||||
|
BundleEntry,
|
||||||
|
Bundle,
|
||||||
|
Organization,
|
||||||
|
Practitioner, MedicationRequest, Patient, Encounter
|
||||||
|
} from 'fhir/r4';
|
||||||
|
import {uuidV4} from '../../../lib/utils/uuid';
|
||||||
|
|
||||||
|
interface ResourceStorage {
|
||||||
|
[resourceType: string]: {
|
||||||
|
[resourceId: string]: Condition | Patient | MedicationRequest | Organization | FhirLocation | Practitioner | Procedure | Encounter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function GenerateR4Bundle(resourceCreate: ResourceCreate): Bundle {
|
||||||
|
let resourceStorage: ResourceStorage = {} //{"resourceType": {"resourceId": resourceData}}
|
||||||
|
resourceStorage = placeholderR4Patient(resourceStorage)
|
||||||
|
resourceStorage = resourceCreateConditionToR4Condition(resourceStorage, resourceCreate.condition)
|
||||||
|
for(let organization of resourceCreate.organizations) {
|
||||||
|
resourceStorage = resourceCreateOrganizationToR4Organization(resourceStorage, organization)
|
||||||
|
}
|
||||||
|
for(let practitioner of resourceCreate.practitioners) {
|
||||||
|
resourceStorage = resourceCreatePractitionerToR4Practitioner(resourceStorage, practitioner)
|
||||||
|
}
|
||||||
|
for(let medication of resourceCreate.medications) {
|
||||||
|
resourceStorage = resourceCreateMedicationToR4MedicationRequest(resourceStorage, medication)
|
||||||
|
}
|
||||||
|
for(let procedure of resourceCreate.procedures) {
|
||||||
|
resourceStorage = resourceCreateProcedureToR4Procedure(resourceStorage, procedure)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("POPULATED RESOURCE STORAGE", resourceStorage)
|
||||||
|
|
||||||
|
let bundle = {
|
||||||
|
resourceType: 'Bundle',
|
||||||
|
type: 'transaction',
|
||||||
|
entry: [],
|
||||||
|
} as Bundle
|
||||||
|
for(let resourceType in resourceStorage) {
|
||||||
|
for(let resourceId in resourceStorage[resourceType]) {
|
||||||
|
let resource = resourceStorage[resourceType][resourceId]
|
||||||
|
bundle.entry.push({
|
||||||
|
fullUrl: `urn:uuid:${resource.id}`,
|
||||||
|
resource: resource,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundle
|
||||||
|
}
|
||||||
|
|
||||||
|
//Private methods
|
||||||
|
|
||||||
|
function placeholderR4Patient(resourceStorage: ResourceStorage): ResourceStorage {
|
||||||
|
resourceStorage['Patient'] = resourceStorage['Patient'] || {}
|
||||||
|
let patientResource = {
|
||||||
|
resourceType: 'Patient',
|
||||||
|
id: uuidV4(),
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
family: 'Placeholder',
|
||||||
|
given: ['Patient'],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
} as Patient
|
||||||
|
resourceStorage['Patient'][patientResource.id] = patientResource
|
||||||
|
return resourceStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// this model is based on FHIR401 Resource Condition - http://hl7.org/fhir/R4/condition.html
|
||||||
|
function resourceCreateConditionToR4Condition(resourceStorage: ResourceStorage, resourceCreateCondition: ResourceCreateCondition): ResourceStorage {
|
||||||
|
resourceStorage['Condition'] = resourceStorage['Condition'] || {}
|
||||||
|
resourceStorage['Encounter'] = resourceStorage['Encounter'] || {}
|
||||||
|
|
||||||
|
let note = []
|
||||||
|
if (resourceCreateCondition.description) {
|
||||||
|
note.push({
|
||||||
|
text: resourceCreateCondition.description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let conditionResource = {
|
||||||
|
subject: {
|
||||||
|
reference: `urn:uuid:${findPatient(resourceStorage).id}` //Patient
|
||||||
|
},
|
||||||
|
resourceType: 'Condition',
|
||||||
|
id: uuidV4(),
|
||||||
|
code: {
|
||||||
|
coding: resourceCreateCondition.data.identifier || [],
|
||||||
|
text: resourceCreateCondition.data.identifier[0].display,
|
||||||
|
},
|
||||||
|
clinicalStatus: {
|
||||||
|
"coding": [
|
||||||
|
{
|
||||||
|
"system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
|
||||||
|
"code": resourceCreateCondition.status,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
onsetDateTime: `${new Date(resourceCreateCondition.started.year, resourceCreateCondition.started.month-1,resourceCreateCondition.started.day).toISOString()}`,
|
||||||
|
abatementDateTime: resourceCreateCondition.stopped ? `${new Date(resourceCreateCondition.stopped.year,resourceCreateCondition.stopped.month-1, resourceCreateCondition.stopped.day).toISOString()}` : null,
|
||||||
|
recordedDate: new Date().toISOString(),
|
||||||
|
note: note
|
||||||
|
} as Condition
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
resourceStorage['Condition'][conditionResource.id] = conditionResource
|
||||||
|
return resourceStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// this model is based on FHIR401 Resource Procedure - http://hl7.org/fhir/R4/procedure.html
|
||||||
|
function resourceCreateProcedureToR4Procedure(resourceStorage: ResourceStorage, resourceCreateProcedure: ResourceCreateProcedure): ResourceStorage {
|
||||||
|
resourceStorage['Procedure'] = resourceStorage['Procedure'] || {}
|
||||||
|
|
||||||
|
|
||||||
|
let note = []
|
||||||
|
if (resourceCreateProcedure.comment) {
|
||||||
|
note.push({
|
||||||
|
text: resourceCreateProcedure.comment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let encounterResource = {
|
||||||
|
resourceType: 'Encounter',
|
||||||
|
id: uuidV4(),
|
||||||
|
status: "finished",
|
||||||
|
subject: {
|
||||||
|
reference: `urn:uuid:${findPatient(resourceStorage).id}` //Patient
|
||||||
|
},
|
||||||
|
participant: [
|
||||||
|
{
|
||||||
|
individual: {
|
||||||
|
reference: `urn:uuid:${resourceCreateProcedure.performer}` //Practitioner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
period: {
|
||||||
|
start: `${new Date(resourceCreateProcedure.whendone.year, resourceCreateProcedure.whendone.month-1,resourceCreateProcedure.whendone.day).toISOString()}`,
|
||||||
|
end: `${new Date(resourceCreateProcedure.whendone.year, resourceCreateProcedure.whendone.month-1,resourceCreateProcedure.whendone.day).toISOString()}`,
|
||||||
|
},
|
||||||
|
reasonReference: [
|
||||||
|
{
|
||||||
|
reference: `urn:uuid:${findCondition(resourceStorage).id}` //Condition
|
||||||
|
}
|
||||||
|
],
|
||||||
|
serviceProvider: {
|
||||||
|
reference: `urn:uuid:${resourceCreateProcedure.location}` //Organization
|
||||||
|
}
|
||||||
|
} as Encounter
|
||||||
|
resourceStorage['Encounter'][encounterResource.id] = encounterResource
|
||||||
|
|
||||||
|
let procedureResource = {
|
||||||
|
subject: {
|
||||||
|
reference: `urn:uuid:${findPatient(resourceStorage).id}` //Patient
|
||||||
|
},
|
||||||
|
status: "completed",
|
||||||
|
resourceType: 'Procedure',
|
||||||
|
|
||||||
|
id: uuidV4(),
|
||||||
|
code: {
|
||||||
|
coding: resourceCreateProcedure.data.identifier || [],
|
||||||
|
text: resourceCreateProcedure.data.identifier[0].display,
|
||||||
|
},
|
||||||
|
performedDateTime: `${new Date(resourceCreateProcedure.whendone.year, resourceCreateProcedure.whendone.month-1,resourceCreateProcedure.whendone.day).toISOString()}`,
|
||||||
|
encounter: {
|
||||||
|
reference: `urn:uuid:${encounterResource.id}` //Encounter
|
||||||
|
},
|
||||||
|
reasonReference: [
|
||||||
|
{
|
||||||
|
reference: `urn:uuid:${findCondition(resourceStorage).id}` //Condition
|
||||||
|
}
|
||||||
|
],
|
||||||
|
performer: [
|
||||||
|
{
|
||||||
|
actor: {
|
||||||
|
reference: `urn:uuid:${resourceCreateProcedure.performer}` //Practitioner
|
||||||
|
},
|
||||||
|
onBehalfOf: {
|
||||||
|
reference: `urn:uuid:${resourceCreateProcedure.location}` //Organization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
note: note,
|
||||||
|
} as Procedure
|
||||||
|
resourceStorage['Procedure'][procedureResource.id] = procedureResource
|
||||||
|
|
||||||
|
return resourceStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// this model is based on FHIR401 Resource Organization - http://hl7.org/fhir/R4/organization.html
|
||||||
|
function resourceCreateOrganizationToR4Organization(resourceStorage: ResourceStorage, resourceCreateOrganization: ResourceCreateOrganization): ResourceStorage {
|
||||||
|
resourceStorage['Organization'] = resourceStorage['Organization'] || {}
|
||||||
|
|
||||||
|
let telecom = []
|
||||||
|
if (resourceCreateOrganization.phone) {
|
||||||
|
telecom.push({
|
||||||
|
system: 'phone',
|
||||||
|
value: resourceCreateOrganization.phone,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (resourceCreateOrganization.fax) {
|
||||||
|
telecom.push({
|
||||||
|
system: 'fax',
|
||||||
|
value: resourceCreateOrganization.fax,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (resourceCreateOrganization.email) {
|
||||||
|
telecom.push({
|
||||||
|
system: 'email',
|
||||||
|
value: resourceCreateOrganization.email,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let organizationResource = {
|
||||||
|
resourceType: 'Organization',
|
||||||
|
id: resourceCreateOrganization.id,
|
||||||
|
name: resourceCreateOrganization.name,
|
||||||
|
identifier: resourceCreateOrganization.identifier || [],
|
||||||
|
type: [
|
||||||
|
{
|
||||||
|
coding: resourceCreateOrganization.type.identifier || [],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
address: [
|
||||||
|
{
|
||||||
|
line: [resourceCreateOrganization.address.line1, resourceCreateOrganization.address.line2],
|
||||||
|
city: resourceCreateOrganization.address.city,
|
||||||
|
state: resourceCreateOrganization.address.state,
|
||||||
|
postalCode: resourceCreateOrganization.address.zip,
|
||||||
|
country: resourceCreateOrganization.address.country,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
telecom: telecom,
|
||||||
|
active: true,
|
||||||
|
} as Organization
|
||||||
|
|
||||||
|
resourceStorage['Organization'][organizationResource.id] = organizationResource
|
||||||
|
return resourceStorage
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// this model is based on FHIR401 Resource Practitioner - http://hl7.org/fhir/R4/practitioner.html
|
||||||
|
function resourceCreatePractitionerToR4Practitioner(resourceStorage: ResourceStorage, resourceCreatePractitioner: ResourceCreatePractitioner): ResourceStorage {
|
||||||
|
resourceStorage['Practitioner'] = resourceStorage['Practitioner'] || {}
|
||||||
|
let telecom = []
|
||||||
|
if (resourceCreatePractitioner.phone) {
|
||||||
|
telecom.push({
|
||||||
|
system: 'phone',
|
||||||
|
value: resourceCreatePractitioner.phone,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (resourceCreatePractitioner.fax) {
|
||||||
|
telecom.push({
|
||||||
|
system: 'fax',
|
||||||
|
value: resourceCreatePractitioner.fax,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (resourceCreatePractitioner.email) {
|
||||||
|
telecom.push({
|
||||||
|
system: 'email',
|
||||||
|
value: resourceCreatePractitioner.email,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let qualification = []
|
||||||
|
if(resourceCreatePractitioner.profession){
|
||||||
|
qualification.push({
|
||||||
|
code: {
|
||||||
|
coding: resourceCreatePractitioner.profession.identifier || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceCreatePractitioner.name.split(" ")
|
||||||
|
|
||||||
|
let practitionerResource = {
|
||||||
|
resourceType: 'Practitioner',
|
||||||
|
id: resourceCreatePractitioner.id,
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
text: resourceCreatePractitioner.name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
identifier: resourceCreatePractitioner.identifier || [],
|
||||||
|
address: [
|
||||||
|
{
|
||||||
|
line: [resourceCreatePractitioner.address.line1, resourceCreatePractitioner.address.line2],
|
||||||
|
city: resourceCreatePractitioner.address.city,
|
||||||
|
state: resourceCreatePractitioner.address.state,
|
||||||
|
postalCode: resourceCreatePractitioner.address.zip,
|
||||||
|
country: resourceCreatePractitioner.address.country,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
telecom: telecom,
|
||||||
|
active: true,
|
||||||
|
qualification: qualification
|
||||||
|
} as Practitioner
|
||||||
|
|
||||||
|
resourceStorage['Practitioner'][practitionerResource.id] = practitionerResource
|
||||||
|
return resourceStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// this model is based on FHIR401 Resource Medication - http://hl7.org/fhir/R4/medication.html
|
||||||
|
function resourceCreateMedicationToR4MedicationRequest(resourceStorage: ResourceStorage, resourceCreateMedication: ResourceCreateMedication): ResourceStorage {
|
||||||
|
resourceStorage['MedicationRequest'] = resourceStorage['MedicationRequest'] || {}
|
||||||
|
|
||||||
|
let encounterResource = {
|
||||||
|
resourceType: 'Encounter',
|
||||||
|
id: uuidV4(),
|
||||||
|
status: "finished",
|
||||||
|
subject: {
|
||||||
|
reference: `urn:uuid:${findPatient(resourceStorage).id}` //Patient
|
||||||
|
},
|
||||||
|
participant: [
|
||||||
|
{
|
||||||
|
individual: {
|
||||||
|
reference: `urn:uuid:${resourceCreateMedication.requester}` //Practitioner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
period: {
|
||||||
|
start: `${new Date(resourceCreateMedication.started.year, resourceCreateMedication.started.month-1,resourceCreateMedication.started.day).toISOString()}`,
|
||||||
|
end: resourceCreateMedication.stopped ? `${new Date(resourceCreateMedication.stopped.year, resourceCreateMedication.stopped.month-1,resourceCreateMedication.stopped.day).toISOString()}` : null,
|
||||||
|
},
|
||||||
|
reasonReference: [
|
||||||
|
{
|
||||||
|
reference: `urn:uuid:${findCondition(resourceStorage).id}` //Condition
|
||||||
|
}
|
||||||
|
],
|
||||||
|
} as Encounter
|
||||||
|
resourceStorage['Encounter'][encounterResource.id] = encounterResource
|
||||||
|
|
||||||
|
let medicationRequestResource = {
|
||||||
|
id: uuidV4(),
|
||||||
|
resourceType: 'MedicationRequest',
|
||||||
|
status: resourceCreateMedication.status,
|
||||||
|
statusReason: {
|
||||||
|
coding: resourceCreateMedication.whystopped.identifier || [],
|
||||||
|
},
|
||||||
|
intent: 'order',
|
||||||
|
medicationCodeableConcept: {
|
||||||
|
coding: resourceCreateMedication.data.identifier || [],
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
reference: `urn:uuid:${findPatient(resourceStorage).id}` //Patient
|
||||||
|
},
|
||||||
|
encounter: {
|
||||||
|
reference: `urn:uuid:${encounterResource.id}` //Encounter
|
||||||
|
},
|
||||||
|
authoredOn: `${new Date(resourceCreateMedication.started.year,resourceCreateMedication.started.month-1,resourceCreateMedication.started.day).toISOString()}`,
|
||||||
|
requester: {
|
||||||
|
reference: `urn:uuid:${resourceCreateMedication.requester}` // Practitioner
|
||||||
|
},
|
||||||
|
reasonReference: [
|
||||||
|
{
|
||||||
|
reference: `urn:uuid:${findCondition(resourceStorage).id}` //Condition
|
||||||
|
},
|
||||||
|
],
|
||||||
|
note: [
|
||||||
|
{
|
||||||
|
text: resourceCreateMedication.instructions,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dispenseRequest: {
|
||||||
|
validityPeriod: {
|
||||||
|
start: `${new Date(resourceCreateMedication.started.year,resourceCreateMedication.started.month-1,resourceCreateMedication.started.day).toISOString()}`,
|
||||||
|
end: resourceCreateMedication.stopped ? `${new Date(resourceCreateMedication.stopped.year,resourceCreateMedication.stopped.month-1,resourceCreateMedication.stopped.day).toISOString()}` : null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as MedicationRequest
|
||||||
|
resourceStorage['MedicationRequest'][medicationRequestResource.id] = medicationRequestResource
|
||||||
|
|
||||||
|
return resourceStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
function findCondition(resourceStorage: ResourceStorage): Condition {
|
||||||
|
let [conditionId] = Object.keys(resourceStorage['Condition'])
|
||||||
|
return resourceStorage['Condition'][conditionId] as Condition
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPatient(resourceStorage: ResourceStorage): Patient {
|
||||||
|
let [patientId] = Object.keys(resourceStorage['Patient'])
|
||||||
|
return resourceStorage['Patient'][patientId] as Patient
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import * as Oauth from '@panva/oauth4webapi';
|
||||||
import {SourceState} from '../models/fasten/source-state';
|
import {SourceState} from '../models/fasten/source-state';
|
||||||
import * as jose from 'jose';
|
import * as jose from 'jose';
|
||||||
import {UserRegisteredClaims} from '../models/fasten/user-registered-claims';
|
import {UserRegisteredClaims} from '../models/fasten/user-registered-claims';
|
||||||
|
import {uuidV4} from '../../lib/utils/uuid';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -22,7 +23,7 @@ export class AuthService {
|
||||||
//Third-party JWT auth, used by Fasten Cloud
|
//Third-party JWT auth, used by Fasten Cloud
|
||||||
public async IdpConnect(idp_type: string) {
|
public async IdpConnect(idp_type: string) {
|
||||||
|
|
||||||
const state = this.uuidV4()
|
const state = uuidV4()
|
||||||
let sourceStateInfo = new SourceState()
|
let sourceStateInfo = new SourceState()
|
||||||
sourceStateInfo.state = state
|
sourceStateInfo.state = state
|
||||||
sourceStateInfo.source_type = idp_type
|
sourceStateInfo.source_type = idp_type
|
||||||
|
@ -192,11 +193,4 @@ export class AuthService {
|
||||||
private setAuthToken(token: string) {
|
private setAuthToken(token: string) {
|
||||||
localStorage.setItem(this.FASTEN_JWT_LOCALSTORAGE_KEY, token)
|
localStorage.setItem(this.FASTEN_JWT_LOCALSTORAGE_KEY, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
private uuidV4(){
|
|
||||||
// @ts-ignore
|
|
||||||
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
|
||||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {LighthouseSourceMetadata} from '../models/lighthouse/lighthouse-source-m
|
||||||
import * as Oauth from '@panva/oauth4webapi';
|
import * as Oauth from '@panva/oauth4webapi';
|
||||||
import {SourceState} from '../models/fasten/source-state';
|
import {SourceState} from '../models/fasten/source-state';
|
||||||
import {MetadataSource} from '../models/fasten/metadata-source';
|
import {MetadataSource} from '../models/fasten/metadata-source';
|
||||||
|
import {uuidV4} from '../../lib/utils/uuid';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -43,7 +44,7 @@ export class LighthouseService {
|
||||||
|
|
||||||
|
|
||||||
async generateSourceAuthorizeUrl(sourceType: string, lighthouseSource: LighthouseSourceMetadata): Promise<URL> {
|
async generateSourceAuthorizeUrl(sourceType: string, lighthouseSource: LighthouseSourceMetadata): Promise<URL> {
|
||||||
const state = this.uuidV4()
|
const state = uuidV4()
|
||||||
let sourceStateInfo = new SourceState()
|
let sourceStateInfo = new SourceState()
|
||||||
sourceStateInfo.state = state
|
sourceStateInfo.state = state
|
||||||
sourceStateInfo.source_type = sourceType
|
sourceStateInfo.source_type = sourceType
|
||||||
|
@ -182,10 +183,4 @@ export class LighthouseService {
|
||||||
return parts.join(separator);
|
return parts.join(separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private uuidV4(){
|
|
||||||
// @ts-ignore
|
|
||||||
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
|
||||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NlmClinicalTableSearchService } from './nlm-clinical-table-search.service';
|
||||||
|
|
||||||
|
describe('NlmClinicalTableSearchService', () => {
|
||||||
|
let service: NlmClinicalTableSearchService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(NlmClinicalTableSearchService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,11 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adds a divider between select options
|
||||||
|
select > optgroup > .divider {
|
||||||
|
font-size: 1px;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
//disable card
|
//disable card
|
||||||
|
|
||||||
|
@ -31,6 +36,18 @@
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Form Validation
|
||||||
|
.form-control.ng-invalid.ng-touched {
|
||||||
|
border-color: #dc3545;
|
||||||
|
padding-right: calc(1.5em + .75rem) !important;
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right calc(.375em + .1875rem) center;
|
||||||
|
background-size: calc(.75em + .375rem) calc(.75em + .375rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Fhir Resource Cards
|
// Fhir Resource Cards
|
||||||
|
|
||||||
.card-fhir-resource-popover {
|
.card-fhir-resource-popover {
|
||||||
|
|
|
@ -4,4 +4,5 @@ export interface CodingModel {
|
||||||
system?: string
|
system?: string
|
||||||
value?: any
|
value?: any
|
||||||
unit?: string
|
unit?: string
|
||||||
|
type?: any
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ export class ConditionModel extends FastenDisplayModel {
|
||||||
clinical_status: string | undefined
|
clinical_status: string | undefined
|
||||||
date_recorded: string | undefined
|
date_recorded: string | undefined
|
||||||
onset_datetime: string | undefined
|
onset_datetime: string | undefined
|
||||||
|
abatement_datetime: string | undefined
|
||||||
|
note: string | undefined
|
||||||
|
|
||||||
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
|
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
|
||||||
super(fastenOptions)
|
super(fastenOptions)
|
||||||
|
@ -32,7 +34,11 @@ export class ConditionModel extends FastenDisplayModel {
|
||||||
this.severity_text =
|
this.severity_text =
|
||||||
_.get(fhirResource, 'severity.coding.0.display') ||
|
_.get(fhirResource, 'severity.coding.0.display') ||
|
||||||
_.get(fhirResource, 'severity.text');
|
_.get(fhirResource, 'severity.text');
|
||||||
this.onset_datetime = _.get(fhirResource, 'onsetDateTime');
|
this.onset_datetime = _.get(fhirResource, 'onsetDateTime') ||
|
||||||
|
_.get(fhirResource, 'onsetPeriod.start') ||
|
||||||
|
_.get(fhirResource, 'assertedDate');
|
||||||
|
this.abatement_datetime = _.get(fhirResource, 'abatementDateTime') ||
|
||||||
|
_.get(fhirResource, 'abatementPeriod.end');
|
||||||
this.has_asserter = _.has(fhirResource, 'asserter');
|
this.has_asserter = _.has(fhirResource, 'asserter');
|
||||||
this.asserter = _.get(fhirResource, 'asserter');
|
this.asserter = _.get(fhirResource, 'asserter');
|
||||||
this.has_body_site = !!_.get(fhirResource, 'bodySite.0.coding.0.display');
|
this.has_body_site = !!_.get(fhirResource, 'bodySite.0.coding.0.display');
|
||||||
|
@ -54,6 +60,7 @@ export class ConditionModel extends FastenDisplayModel {
|
||||||
r4DTO(fhirResource:any){
|
r4DTO(fhirResource:any){
|
||||||
this.clinical_status = _.get(fhirResource, 'clinicalStatus.coding.0.code');
|
this.clinical_status = _.get(fhirResource, 'clinicalStatus.coding.0.code');
|
||||||
this.date_recorded = _.get(fhirResource, 'recordedDate');
|
this.date_recorded = _.get(fhirResource, 'recordedDate');
|
||||||
|
this.note = _.get(fhirResource, 'note.0.text');
|
||||||
};
|
};
|
||||||
|
|
||||||
resourceDTO(fhirResource:any, fhirVersion:fhirVersions){
|
resourceDTO(fhirResource:any, fhirVersion:fhirVersions){
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {FastenOptions} from '../fasten/fasten-options';
|
||||||
|
|
||||||
export class PractitionerModel extends FastenDisplayModel {
|
export class PractitionerModel extends FastenDisplayModel {
|
||||||
|
|
||||||
identifier: string|undefined
|
identifier: CodingModel[]|undefined
|
||||||
name: any|undefined
|
name: any|undefined
|
||||||
gender: string|undefined
|
gender: string|undefined
|
||||||
status: string|undefined
|
status: string|undefined
|
||||||
|
@ -20,6 +20,7 @@ export class PractitionerModel extends FastenDisplayModel {
|
||||||
telecom: { system?: string, value?: string, use?: string }[]|undefined
|
telecom: { system?: string, value?: string, use?: string }[]|undefined
|
||||||
address: string|undefined
|
address: string|undefined
|
||||||
birthdate: string|undefined
|
birthdate: string|undefined
|
||||||
|
qualification: { code: string, system: string }[]|undefined
|
||||||
|
|
||||||
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
|
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
|
||||||
super(fastenOptions)
|
super(fastenOptions)
|
||||||
|
@ -30,7 +31,7 @@ export class PractitionerModel extends FastenDisplayModel {
|
||||||
|
|
||||||
commonDTO(fhirResource:any){
|
commonDTO(fhirResource:any){
|
||||||
const id = _.get(fhirResource, 'id', '');
|
const id = _.get(fhirResource, 'id', '');
|
||||||
this.identifier = _.get(fhirResource, 'identifier', '');
|
this.identifier = _.get(fhirResource, 'identifier');
|
||||||
this.gender = _.get(fhirResource, 'gender', '');
|
this.gender = _.get(fhirResource, 'gender', '');
|
||||||
this.status = _.get(fhirResource, 'active') === true ? 'active' : '';
|
this.status = _.get(fhirResource, 'active') === true ? 'active' : '';
|
||||||
this.is_contact_data = _.has(fhirResource, 'contact[0]');
|
this.is_contact_data = _.has(fhirResource, 'contact[0]');
|
||||||
|
@ -39,6 +40,7 @@ export class PractitionerModel extends FastenDisplayModel {
|
||||||
name: _.get(fhirResource, 'contact[0].name'),
|
name: _.get(fhirResource, 'contact[0].name'),
|
||||||
relationship: _.get(fhirResource, 'contact[0].relationship[0].text'),
|
relationship: _.get(fhirResource, 'contact[0].relationship[0].text'),
|
||||||
};
|
};
|
||||||
|
this.qualification = _.get(fhirResource, 'qualification[0].code.coding');
|
||||||
};
|
};
|
||||||
|
|
||||||
dstu2DTO(fhirResource:any){
|
dstu2DTO(fhirResource:any){
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
export function uuidV4(){
|
||||||
|
// @ts-ignore
|
||||||
|
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
||||||
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||||
|
);
|
||||||
|
}
|
|
@ -1760,6 +1760,11 @@
|
||||||
"@types/qs" "*"
|
"@types/qs" "*"
|
||||||
"@types/serve-static" "*"
|
"@types/serve-static" "*"
|
||||||
|
|
||||||
|
"@types/fhir@^0.0.35":
|
||||||
|
version "0.0.35"
|
||||||
|
resolved "https://registry.npmjs.org/@types/fhir/-/fhir-0.0.35.tgz#e598b99e6468fee556f0e78d398c13ad2571b1eb"
|
||||||
|
integrity sha512-y+VI3Y48xzZBM8AjXPq67EWbiY9VN4Cx5KzN8EplS0Zju8D2KahLXoK6P/PWlKyNTKvJBwemkHzJIocczLRkhQ==
|
||||||
|
|
||||||
"@types/http-proxy@^1.17.8":
|
"@types/http-proxy@^1.17.8":
|
||||||
version "1.17.9"
|
version "1.17.9"
|
||||||
resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a"
|
resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a"
|
||||||
|
|
Loading…
Reference in New Issue