adding source summary endpoint
added generic resource list component added resource-list outlet directive and component.
This commit is contained in:
parent
48c8873f18
commit
7bced71569
|
@ -22,5 +22,6 @@ type DatabaseRepository interface {
|
|||
|
||||
CreateSource(context.Context, *models.Source) error
|
||||
GetSource(context.Context, string) (*models.Source, error)
|
||||
GetSourceSummary(context.Context, string) (*models.SourceSummary, error)
|
||||
GetSources(context.Context) ([]models.Source, error)
|
||||
}
|
||||
|
|
|
@ -231,6 +231,40 @@ func (sr *sqliteRepository) GetSource(ctx context.Context, sourceId string) (*mo
|
|||
return &sourceCred, results.Error
|
||||
}
|
||||
|
||||
func (sr *sqliteRepository) GetSourceSummary(ctx context.Context, sourceId string) (*models.SourceSummary, error) {
|
||||
sourceUUID, err := uuid.Parse(sourceId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sourceSummary := &models.SourceSummary{}
|
||||
|
||||
source, err := sr.GetSource(ctx, sourceId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sourceSummary.Source = source
|
||||
|
||||
//group by resource type and return counts
|
||||
// SELECT source_resource_type as resource_type, COUNT(*) as count FROM resource_fhirs WHERE source_id = "53c1e930-63af-46c9-b760-8e83cbc1abd9" GROUP BY source_resource_type;
|
||||
|
||||
var results []map[string]interface{}
|
||||
|
||||
sr.gormClient.WithContext(ctx).
|
||||
Model(models.ResourceFhir{}).
|
||||
Select("source_id, source_resource_type as resource_type, count(*) as count").
|
||||
Group("source_resource_type").
|
||||
Where(models.OriginBase{
|
||||
UserID: sr.GetCurrentUser(ctx).ID,
|
||||
SourceID: sourceUUID,
|
||||
}).
|
||||
Scan(&results)
|
||||
|
||||
sourceSummary.ResourceTypeCounts = results
|
||||
|
||||
return sourceSummary, nil
|
||||
}
|
||||
|
||||
func (sr *sqliteRepository) GetSources(ctx context.Context) ([]models.Source, error) {
|
||||
|
||||
var sourceCreds []models.Source
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package models
|
||||
|
||||
type SourceSummary struct {
|
||||
Source *Source `json:"source,omitempty"`
|
||||
ResourceTypeCounts []map[string]interface{} `json:"resource_type_counts,omitempty"`
|
||||
}
|
|
@ -118,6 +118,19 @@ func GetSource(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, gin.H{"success": true, "data": sourceCred})
|
||||
}
|
||||
|
||||
func GetSourceSummary(c *gin.Context) {
|
||||
logger := c.MustGet("LOGGER").(*logrus.Entry)
|
||||
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
|
||||
|
||||
sourceSummary, err := databaseRepo.GetSourceSummary(c, c.Param("sourceId"))
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while retrieving source summary", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": sourceSummary})
|
||||
}
|
||||
|
||||
func ListSource(c *gin.Context) {
|
||||
logger := c.MustGet("LOGGER").(*logrus.Entry)
|
||||
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
|
||||
|
|
|
@ -49,6 +49,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
|
|||
secure.POST("/source/manual", handler.CreateManualSource)
|
||||
secure.GET("/source", handler.ListSource)
|
||||
secure.GET("/source/:sourceId", handler.GetSource)
|
||||
secure.GET("/source/:sourceId/summary", handler.GetSourceSummary)
|
||||
//in debug mode, this endpoint lets us request data directly from the source api
|
||||
secure.GET("/source/raw/:sourceType/*path", handler.RawRequestSource)
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ import { CanActivateAuthGuard } from './services/can-activate.auth-guard';
|
|||
import {FastenApiService} from './services/fasten-api.service';
|
||||
import {Router} from '@angular/router';
|
||||
import { SourceDetailComponent } from './pages/source-detail/source-detail.component';
|
||||
import { ResourceListOutletDirective } from './directive/resource-list-outlet.directive';
|
||||
import {ResourceListComponent} from './components/resource-list/resource-list.component';
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<p class="mg-b-20">A clinical condition, problem, diagnosis, or other event, situation, issue, or clinical concept that has risen to a level of concern.</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered mg-b-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th>Reason</th>
|
||||
<th>Period</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let careplan of careplanList">
|
||||
<td>
|
||||
<b>{{careplan.category}}</b>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<div *ngFor="let reason of careplan.reason">
|
||||
{{reason[0]}}
|
||||
<span> - </span>
|
||||
<span className="text-muted">
|
||||
{{reason[1] || "no data"}}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle">{{careplan.period?.start}} - {{careplan.period?.end}}</td>
|
||||
<td class="align-middle">{{careplan.status}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
|
@ -1,25 +0,0 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {ResourceFhir} from '../../models/fasten/resource_fhir';
|
||||
import {CarePlan} from '../../models/display/care-plan';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-care-plan',
|
||||
templateUrl: './list-care-plan.component.html',
|
||||
styleUrls: ['./list-care-plan.component.scss']
|
||||
})
|
||||
export class ListCarePlanComponent implements OnInit {
|
||||
|
||||
@Input() resourceList: ResourceFhir[] = []
|
||||
careplanList: CarePlan[] = []
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
let _careplanList = this.careplanList
|
||||
this.resourceList.forEach((resource) => {
|
||||
let careplan = new CarePlan(resource.payload)
|
||||
_careplanList.push(careplan)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<p>list-explanation-of-benefit works!</p>
|
|
@ -1,23 +0,0 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListExplanationOfBenefitComponent } from './list-explanation-of-benefit.component';
|
||||
|
||||
describe('ListExplanationOfBenefitComponent', () => {
|
||||
let component: ListExplanationOfBenefitComponent;
|
||||
let fixture: ComponentFixture<ListExplanationOfBenefitComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ListExplanationOfBenefitComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ListExplanationOfBenefitComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,15 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-explanation-of-benefit',
|
||||
templateUrl: './list-explanation-of-benefit.component.html',
|
||||
styleUrls: ['./list-explanation-of-benefit.component.scss']
|
||||
})
|
||||
export class ListExplanationOfBenefitComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-adverse-event',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListAdverseEventComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Event', versions: '*', format: 'code', getter: a => a.event.coding[0] },
|
||||
{ title: 'Date', versions: '*', format: 'date', getter: a => a.date }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-allergy-intolerance',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListAllergyIntoleranceComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Allergy', versions: '*', format: 'code', getter: a => a.code.coding[0] },
|
||||
{ title: 'Date Recorded', versions: '*', format: 'date', getter: a => a.assertedDate || a.recordedDate },
|
||||
{ title: 'Onset', versions: '*', format: 'date', getter: a => a.onsetDateTime },
|
||||
{ title: 'Resolution Age', versions: '*', format: 'date', getter: a => a.extension.resolutionAge }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
import {getPath} from '../../fhir/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-care-plan',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListCarePlanComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Category', versions: '*', format: 'code', getter: c => c.category[0].coding[0] },
|
||||
{ title: 'Reason', versions: '*', getter: c => {
|
||||
return (c.activity || []).map((a, i) => {
|
||||
let reason = getPath(a, "detail.code.coding.0.display") || ""
|
||||
return reason ? [reason, getPath(a, "detail.status") || "no data"] : []
|
||||
})
|
||||
} },
|
||||
{ title: 'Period', versions: '*', format: 'period', getter: c => c.period },
|
||||
{ title: 'Status', versions: '*', getter: a => a.status },
|
||||
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-communication',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListCommunicationComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Reason', versions: '*', format: 'code', getter: c => c.reasonCode[0].coding[0] },
|
||||
{ title: 'Sent', versions: '*', format: 'date', getter: c => c.sent },
|
||||
{ title: 'Received', versions: '*', format: 'date', getter: c => c.received },
|
||||
{ title: 'Status', versions: '*', getter: c => c.status }
|
||||
]
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent, ResourceListComponentInterface} from './list-generic-resource.component';
|
||||
import {FORMATTERS} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-condition',
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-coverage',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListCoverageComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Type', versions: '*', format: 'code', getter: c => c.type.coding[0] },
|
||||
{ title: 'Period', versions: '*', format: 'period', getter: c => c.period }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-device-request',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListDeviceRequestComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Device', versions: '*', format: 'code', getter: d => d.codeCodeableConcept.coding[0] },
|
||||
{ title: 'Author Date', versions: '*', format: 'date', getter: d => d.authoredOn },
|
||||
{ title: 'Do Not Perform', versions: '*', getter: d => d.modifierExtension.doNotPerform },
|
||||
{ title: 'Do Not Perform Reason', versions: '*', format: 'code', getter: s => s.extension.doNotPerformReason.coding[0] }
|
||||
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-document-reference',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListDocumentReferenceComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Date', versions: '*', format: 'date', getter: d => d.date },
|
||||
{ title: 'Content', versions: '*', getter: d => atob(d.content[0].attachment.data) }
|
||||
|
||||
]
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {Component, OnChanges, OnInit} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent, ResourceListComponentInterface} from './list-generic-resource.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-encounter',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListEncounterComponent extends ListGenericResourceComponent {
|
||||
export class ListEncounterComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Encounter', versions: '*', format: 'code', getter: e => e.type[0].coding[0] },
|
||||
{ title: 'Period', versions: '*', format: 'period', getter: e => e.period },
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import {Component, OnChanges, OnInit} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent, ResourceListComponentInterface} from './list-generic-resource.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-explanation-of-benefit',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListExplanationOfBenefitComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = []
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
<div>
|
||||
<h3>INSIDE THE GERNERIC COMPONENT</h3>
|
||||
|
||||
<ngx-datatable
|
||||
#table
|
||||
class="bootstrap"
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import {Component, Input, OnInit, ViewChild} from '@angular/core';
|
||||
import {ChangeDetectorRef, Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild} from '@angular/core';
|
||||
import {DatatableComponent, ColumnMode} from '@swimlane/ngx-datatable';
|
||||
import {ResourceFhir} from '../../models/fasten/resource_fhir';
|
||||
import {FORMATTERS, getPath, obsValue, attributeXTime} from './utils';
|
||||
import {observableToBeFn} from 'rxjs/internal/testing/TestScheduler';
|
||||
|
||||
|
||||
//all Resource list components must implement this Interface
|
||||
export interface ResourceListComponentInterface {
|
||||
resourceList: ResourceFhir[];
|
||||
markForCheck()
|
||||
}
|
||||
|
||||
|
||||
export class GenericColumnDefn {
|
||||
title: string
|
||||
prop?: string
|
||||
|
@ -18,7 +26,7 @@ export class GenericColumnDefn {
|
|||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListGenericResourceComponent implements OnInit {
|
||||
export class ListGenericResourceComponent implements OnInit, ResourceListComponentInterface {
|
||||
|
||||
@Input() resourceList: ResourceFhir[] = []
|
||||
|
||||
|
@ -32,9 +40,19 @@ export class ListGenericResourceComponent implements OnInit {
|
|||
columns = []; //{ prop: 'name' }, { name: 'Company' }, { name: 'Gender' }
|
||||
ColumnMode = ColumnMode;
|
||||
|
||||
constructor() {}
|
||||
constructor(public changeRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log("INIT CHILD")
|
||||
|
||||
this.renderList()
|
||||
}
|
||||
markForCheck(){
|
||||
this.changeRef.markForCheck()
|
||||
}
|
||||
|
||||
renderList(){
|
||||
console.log("GENERIC RESOURCE INIT")
|
||||
this.columns = this.columnDefinitions.map((defn) => {
|
||||
let column = {name: defn.title, prop: defn.title.replace(/[^A-Z0-9]/ig, "_")}
|
||||
return column
|
||||
|
@ -44,10 +62,13 @@ export class ListGenericResourceComponent implements OnInit {
|
|||
let row = {}
|
||||
|
||||
this.columnDefinitions.forEach((defn) => {
|
||||
let resourceProp = defn.getter(resource.payload)
|
||||
let resourceFormatted = defn.format ? FORMATTERS[defn.format](resourceProp) : resourceProp
|
||||
row[defn.title.replace(/[^A-Z0-9]/ig, "_")] = resourceFormatted
|
||||
//TODO: handle defaultValue
|
||||
try{
|
||||
let resourceProp = defn.getter(resource.payload)
|
||||
let resourceFormatted = defn.format ? FORMATTERS[defn.format](resourceProp) : resourceProp
|
||||
row[defn.title.replace(/[^A-Z0-9]/ig, "_")] = resourceFormatted
|
||||
}catch (e){
|
||||
//ignore
|
||||
}
|
||||
})
|
||||
|
||||
console.log("ROW:", row)
|
||||
|
@ -55,6 +76,7 @@ export class ListGenericResourceComponent implements OnInit {
|
|||
return row
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-medication-administration',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListMedicationAdministrationComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Medication', versions: '*', format: 'code', getter: m => m.medicationCodeableConcept.coding[0] },
|
||||
{ title: 'Route', versions: '*', format: 'code', getter: m => m.dosage.route.coding[0] },
|
||||
{ title: 'Effective', versions: '*', getter: m => attributeXTime(m,'effective')},
|
||||
{ title: 'Status', versions: '*', getter: m => m.status},
|
||||
{ title: 'Status Reason', versions: '*', format: 'code', getter: m => m.statusReason[0].coding[0] },
|
||||
{ title: 'Recorded', versions: '*', format: 'date', getter: m => m.extension.recorded }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-medication-dispense',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListMedicationDispenseComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Medication', versions: '*', format: 'code', getter: m => m.medicationCodeableConcept.coding[0] },
|
||||
{ title: 'Handed Over Date', versions: '*', format: 'date', getter: m => m.whenHandedOver}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-nutrition-order',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListNutritionOrderComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Preference', versions: '*', format: 'code', getter: n => n.foodPreferenceModifier[0].coding[0] },
|
||||
{ title: 'Exclusion', versions: '*', format: 'code', getter: n => n.excludeFoodModifier[0].coding[0] },
|
||||
{ title: 'Date', versions: '*', format: 'date', getter: n => n.dateTime },
|
||||
{ title: 'Status', versions: '*', getter: n => n.status }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
|
||||
import {attributeXTime} from './utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-service-request',
|
||||
templateUrl: './list-generic-resource.component.html',
|
||||
styleUrls: ['./list-generic-resource.component.scss']
|
||||
})
|
||||
export class ListServiceRequestComponent extends ListGenericResourceComponent {
|
||||
columnDefinitions: GenericColumnDefn[] = [
|
||||
{ title: 'Service', versions: '*', format: 'code', getter: s => s.code.coding[0] },
|
||||
{ title: 'Author Date', versions: '*', format: 'date', getter: s => s.authoredOn },
|
||||
{ title: 'Status', versions: '*', getter: s => s.status },
|
||||
{ title: 'Reason', versions: '*', format: 'code', getter: s => s.reasonCode[0].coding[0] },
|
||||
{ title: 'ID', versions: '*', getter: s => s.id },
|
||||
{ title: 'Do Not Perform', versions: '*', getter: s => s.doNotPerform },
|
||||
{ title: 'Reason Refused', versions: '*', format: 'code', getter: s => s.extension.reasonRefused.coding[0] }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<h2 class="az-content-title mg-t-40">{{resourceListType}}</h2>
|
||||
|
||||
<ng-template resourceListOutlet></ng-template>
|
|
@ -1,18 +1,18 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListCarePlanComponent } from './list-care-plan.component';
|
||||
import { ResourceListComponent } from './resource-list.component';
|
||||
|
||||
describe('ListCarePlanComponent', () => {
|
||||
let component: ListCarePlanComponent;
|
||||
let fixture: ComponentFixture<ListCarePlanComponent>;
|
||||
describe('ResourceListComponent', () => {
|
||||
let component: ResourceListComponent;
|
||||
let fixture: ComponentFixture<ResourceListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ListCarePlanComponent ]
|
||||
declarations: [ ResourceListComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ListCarePlanComponent);
|
||||
fixture = TestBed.createComponent(ResourceListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
|
@ -0,0 +1,152 @@
|
|||
import {ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges, Type, ViewChild} from '@angular/core';
|
||||
import {ResourceListOutletDirective} from '../../directive/resource-list-outlet.directive';
|
||||
import {FastenApiService} from '../../services/fasten-api.service';
|
||||
import {Source} from '../../models/fasten/source';
|
||||
import {Observable, of} from 'rxjs';
|
||||
import {ResourceFhir} from '../../models/fasten/resource_fhir';
|
||||
import {ListAdverseEventComponent} from '../list-generic-resource/list-adverse-event.component';
|
||||
import {ListCommunicationComponent} from '../list-generic-resource/list-communication.component';
|
||||
import {ListConditionComponent} from '../list-generic-resource/list-condition.component';
|
||||
import {ListCoverageComponent} from '../list-generic-resource/list-coverage.component';
|
||||
import {ListDeviceRequestComponent} from '../list-generic-resource/list-device-request.component';
|
||||
import {ListDocumentReferenceComponent} from '../list-generic-resource/list-document-reference.component';
|
||||
import {ListEncounterComponent} from '../list-generic-resource/list-encounter.component';
|
||||
import {ListImmunizationComponent} from '../list-generic-resource/list-immunization.component';
|
||||
import {ListMedicationComponent} from '../list-generic-resource/list-medication.component';
|
||||
import {ListMedicationAdministrationComponent} from '../list-generic-resource/list-medication-administration.component';
|
||||
import {ListMedicationDispenseComponent} from '../list-generic-resource/list-medication-dispense.component';
|
||||
import {ListMedicationRequestComponent} from '../list-generic-resource/list-medication-request.component';
|
||||
import {ListNutritionOrderComponent} from '../list-generic-resource/list-nutrition-order.component';
|
||||
import {ListObservationComponent} from '../list-generic-resource/list-observation.component';
|
||||
import {ListProcedureComponent} from '../list-generic-resource/list-procedure.component';
|
||||
import {ListServiceRequestComponent} from '../list-generic-resource/list-service-request.component';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {ResponseWrapper} from '../../models/response-wrapper';
|
||||
import {ListGenericResourceComponent, ResourceListComponentInterface} from '../list-generic-resource/list-generic-resource.component';
|
||||
import {ListCarePlanComponent} from '../list-generic-resource/list-care-plan.component';
|
||||
import {ListAllergyIntoleranceComponent} from '../list-generic-resource/list-allergy-intolerance.component';
|
||||
|
||||
@Component({
|
||||
selector: 'source-resource-list',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './resource-list.component.html',
|
||||
styleUrls: ['./resource-list.component.scss']
|
||||
})
|
||||
export class ResourceListComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input() source: Source;
|
||||
@Input() resourceListType: string;
|
||||
resourceListCache: { [name:string]: ResourceFhir[] } = {}
|
||||
|
||||
|
||||
|
||||
//location to dynamically load the resource list
|
||||
@ViewChild(ResourceListOutletDirective, {static: true}) resourceListOutlet!: ResourceListOutletDirective;
|
||||
|
||||
|
||||
constructor(private fastenApi: FastenApiService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadComponent()
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.loadComponent()
|
||||
}
|
||||
|
||||
loadComponent() {
|
||||
//clear the current outlet
|
||||
const viewContainerRef = this.resourceListOutlet.viewContainerRef;
|
||||
viewContainerRef.clear();
|
||||
|
||||
this.getResources().subscribe((resourceList) => {
|
||||
let componentType = this.typeLookup(this.resourceListType)
|
||||
if(componentType != null){
|
||||
console.log("Attempting to create component", this.resourceListType, componentType)
|
||||
const componentRef = viewContainerRef.createComponent<ResourceListComponentInterface>(componentType);
|
||||
componentRef.instance.resourceList = resourceList;
|
||||
componentRef.instance.markForCheck()
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getResources(): Observable<ResourceFhir[]>{
|
||||
|
||||
if(!this.resourceListCache[this.resourceListType]){
|
||||
// this resource type list has not been downloaded yet, do so now
|
||||
|
||||
return this.fastenApi.getResources(this.resourceListType, this.source.id)
|
||||
.pipe(map((resourceList: ResourceFhir[]) => {
|
||||
//cache this response so we can skip the request next time
|
||||
this.resourceListCache[this.resourceListType] = resourceList
|
||||
return resourceList
|
||||
}))
|
||||
} else {
|
||||
return of(this.resourceListCache[this.resourceListType])
|
||||
}
|
||||
}
|
||||
|
||||
typeLookup(resourceType: string): Type<any> {
|
||||
switch(resourceType) {
|
||||
case "AllergyIntolerance": {
|
||||
return ListAllergyIntoleranceComponent;
|
||||
}
|
||||
case "AdverseEvent": {
|
||||
return ListAdverseEventComponent;
|
||||
}
|
||||
case "CarePlan": {
|
||||
return ListCarePlanComponent;
|
||||
}
|
||||
case "Communication": {
|
||||
return ListCommunicationComponent;
|
||||
}
|
||||
case "Condition": {
|
||||
return ListConditionComponent;
|
||||
}
|
||||
case "Coverage": {
|
||||
return ListCoverageComponent;
|
||||
}
|
||||
case "DeviceRequest": {
|
||||
return ListDeviceRequestComponent;
|
||||
}
|
||||
case "DocumentReference": {
|
||||
return ListDocumentReferenceComponent;
|
||||
}
|
||||
case "Encounter": {
|
||||
return ListEncounterComponent;
|
||||
}
|
||||
case "Immunization": {
|
||||
return ListImmunizationComponent;
|
||||
}
|
||||
case "Medication": {
|
||||
return ListMedicationComponent;
|
||||
}
|
||||
case "MedicationAdministration": {
|
||||
return ListMedicationAdministrationComponent;
|
||||
}
|
||||
case "MedicationDispense": {
|
||||
return ListMedicationDispenseComponent;
|
||||
}
|
||||
case "MedicationRequest": {
|
||||
return ListMedicationRequestComponent;
|
||||
}
|
||||
case "NutritionOrder": {
|
||||
return ListNutritionOrderComponent;
|
||||
}
|
||||
case "Observation": {
|
||||
return ListObservationComponent;
|
||||
}
|
||||
case "Procedure": {
|
||||
return ListProcedureComponent;
|
||||
}
|
||||
case "ServiceRequest": {
|
||||
return ListServiceRequestComponent;
|
||||
}
|
||||
default: {
|
||||
console.error("UNKNOWN COMPONENT TYPE", resourceType)
|
||||
return null
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +1,93 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { ComponentsSidebarComponent } from './components-sidebar/components-sidebar.component';
|
||||
import { ListExplanationOfBenefitComponent } from './list-explanation-of-benefit/list-explanation-of-benefit.component';
|
||||
import { ListGenericResourceComponent,} from './list-generic-resource/list-generic-resource.component';
|
||||
import { ListPatientComponent } from './list-patient/list-patient.component';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UtilitiesSidebarComponent } from './utilities-sidebar/utilities-sidebar.component';
|
||||
import { ListPatientComponent } from './list-patient/list-patient.component';
|
||||
import { ListExplanationOfBenefitComponent } from './list-explanation-of-benefit/list-explanation-of-benefit.component';
|
||||
import { ListCarePlanComponent } from './list-care-plan/list-care-plan.component';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import { ListGenericResourceComponent,} from './list-generic-resource/list-generic-resource.component';
|
||||
import {ListAdverseEventComponent} from './list-generic-resource/list-adverse-event.component';
|
||||
import {ListConditionComponent} from './list-generic-resource/list-condition.component'
|
||||
import {ListEncounterComponent} from './list-generic-resource/list-encounter.component'
|
||||
import {ListImmunizationComponent} from './list-generic-resource/list-immunization.component'
|
||||
import {ListMedicationAdministrationComponent} from './list-generic-resource/list-medication-administration.component';
|
||||
import {ListMedicationComponent} from './list-generic-resource/list-medication.component'
|
||||
import {ListMedicationDispenseComponent} from './list-generic-resource/list-medication-dispense.component';
|
||||
import {ListMedicationRequestComponent} from './list-generic-resource/list-medication-request.component'
|
||||
import {ListNutritionOrderComponent} from './list-generic-resource/list-nutrition-order.component';
|
||||
import {ListObservationComponent} from './list-generic-resource/list-observation.component'
|
||||
import {ListProcedureComponent} from './list-generic-resource/list-procedure.component'
|
||||
import {ListImmunizationComponent} from './list-generic-resource/list-immunization.component'
|
||||
import {ListMedicationRequestComponent} from './list-generic-resource/list-medication-request.component'
|
||||
|
||||
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
|
||||
import {ListCommunicationComponent} from './list-generic-resource/list-communication.component';
|
||||
import {ListDeviceRequestComponent} from './list-generic-resource/list-device-request.component';
|
||||
import {ListCoverageComponent} from './list-generic-resource/list-coverage.component';
|
||||
import {ListServiceRequestComponent} from './list-generic-resource/list-service-request.component';
|
||||
import {ListDocumentReferenceComponent} from './list-generic-resource/list-document-reference.component';
|
||||
import { ResourceListComponent } from './resource-list/resource-list.component';
|
||||
import {ResourceListOutletDirective} from '../directive/resource-list-outlet.directive';
|
||||
import {ListCarePlanComponent} from './list-generic-resource/list-care-plan.component';
|
||||
import {ListAllergyIntoleranceComponent} from './list-generic-resource/list-allergy-intolerance.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule,
|
||||
BrowserModule,
|
||||
NgxDatatableModule
|
||||
NgxDatatableModule,
|
||||
],
|
||||
declarations: [
|
||||
ComponentsSidebarComponent,
|
||||
UtilitiesSidebarComponent,
|
||||
ListPatientComponent,
|
||||
ListObservationComponent,
|
||||
ListExplanationOfBenefitComponent,
|
||||
ListImmunizationComponent,
|
||||
ListEncounterComponent,
|
||||
ListAllergyIntoleranceComponent,
|
||||
ListAdverseEventComponent,
|
||||
ListCarePlanComponent,
|
||||
ListGenericResourceComponent,
|
||||
ListMedicationComponent,
|
||||
ListProcedureComponent,
|
||||
ListCommunicationComponent,
|
||||
ListConditionComponent,
|
||||
ListMedicationRequestComponent
|
||||
ListEncounterComponent,
|
||||
ListExplanationOfBenefitComponent,
|
||||
ListGenericResourceComponent,
|
||||
ListImmunizationComponent,
|
||||
ListMedicationAdministrationComponent,
|
||||
ListMedicationComponent,
|
||||
ListMedicationDispenseComponent,
|
||||
ListMedicationRequestComponent,
|
||||
ListNutritionOrderComponent,
|
||||
ListObservationComponent,
|
||||
ListPatientComponent,
|
||||
ListDeviceRequestComponent,
|
||||
ListProcedureComponent,
|
||||
ListCoverageComponent,
|
||||
ListServiceRequestComponent,
|
||||
ListDocumentReferenceComponent,
|
||||
ResourceListComponent,
|
||||
ResourceListOutletDirective,
|
||||
|
||||
|
||||
],
|
||||
exports: [
|
||||
ComponentsSidebarComponent,
|
||||
UtilitiesSidebarComponent,
|
||||
ListPatientComponent,
|
||||
ListExplanationOfBenefitComponent,
|
||||
ListImmunizationComponent,
|
||||
ListEncounterComponent,
|
||||
ListCarePlanComponent,
|
||||
ListGenericResourceComponent,
|
||||
ListMedicationComponent,
|
||||
ListObservationComponent,
|
||||
ListProcedureComponent,
|
||||
ListConditionComponent,
|
||||
ListMedicationRequestComponent
|
||||
|
||||
ComponentsSidebarComponent,
|
||||
ListAllergyIntoleranceComponent,
|
||||
ListAdverseEventComponent,
|
||||
ListCarePlanComponent,
|
||||
ListCommunicationComponent,
|
||||
ListConditionComponent,
|
||||
ListEncounterComponent,
|
||||
ListExplanationOfBenefitComponent,
|
||||
ListGenericResourceComponent,
|
||||
ListImmunizationComponent,
|
||||
ListMedicationAdministrationComponent,
|
||||
ListMedicationComponent,
|
||||
ListMedicationDispenseComponent,
|
||||
ListMedicationRequestComponent,
|
||||
ListNutritionOrderComponent,
|
||||
ListObservationComponent,
|
||||
ListPatientComponent,
|
||||
ListProcedureComponent,
|
||||
ListDeviceRequestComponent,
|
||||
UtilitiesSidebarComponent,
|
||||
ListCoverageComponent,
|
||||
ListServiceRequestComponent,
|
||||
ListDocumentReferenceComponent,
|
||||
ResourceListComponent
|
||||
]
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { ResourceListOutletDirective } from './resource-list-outlet.directive';
|
||||
|
||||
describe('ResourceListOutletDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
const directive = new ResourceListOutletDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
import {Directive, ViewContainerRef} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[resourceListOutlet]'
|
||||
})
|
||||
export class ResourceListOutletDirective {
|
||||
|
||||
constructor(public viewContainerRef: ViewContainerRef) { }
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import {Source} from './source';
|
||||
|
||||
export class ResourceTypeCounts {
|
||||
count: number
|
||||
source_id: string
|
||||
resource_type: string
|
||||
}
|
||||
|
||||
export class SourceSummary {
|
||||
source: Source
|
||||
resource_type_counts: ResourceTypeCounts[]
|
||||
}
|
|
@ -2,8 +2,8 @@
|
|||
<div class="container">
|
||||
<div class="az-content-left">
|
||||
<ul class="list-group">
|
||||
<li *ngFor="let resourceGroup of resourcesGroupedByType | keyvalue" (click)="selectResourceType(resourceGroup.key)" class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{resourceGroup.key}} <span class="badge badge-primary badge-pill">{{resourceGroup.value.length}}</span>
|
||||
<li *ngFor="let resourceGroup of resourceTypeCounts | keyvalue" (click)="selectResourceType(resourceGroup.key)" class="list-group-item d-flex justify-content-between align-items-center">
|
||||
{{resourceGroup.key}} <span class="badge badge-primary badge-pill">{{resourceGroup.value}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -14,16 +14,7 @@
|
|||
<span>Details</span>
|
||||
</div>
|
||||
|
||||
<h2 class="az-content-title mg-t-40">{{selectedResourceType}}</h2>
|
||||
|
||||
<app-list-condition *ngIf="selectedResourceType == 'Condition'" [resourceList]="selectedResources"></app-list-condition>
|
||||
<app-list-care-plan *ngIf="selectedResourceType == 'CarePlan'" [resourceList]="selectedResources"></app-list-care-plan>
|
||||
<app-list-encounter *ngIf="selectedResourceType == 'Encounter'" [resourceList]="selectedResources"></app-list-encounter>
|
||||
<app-list-immunization *ngIf="selectedResourceType == 'Immunization'" [resourceList]="selectedResources"></app-list-immunization>
|
||||
<app-list-medication *ngIf="selectedResourceType == 'Medication'" [resourceList]="selectedResources"></app-list-medication>
|
||||
<app-list-medication-request *ngIf="selectedResourceType == 'MedicationRequest'" [resourceList]="selectedResources"></app-list-medication-request>
|
||||
<app-list-observation *ngIf="selectedResourceType == 'Observation'" [resourceList]="selectedResources"></app-list-observation>
|
||||
<app-list-procedure *ngIf="selectedResourceType == 'Procedure'" [resourceList]="selectedResources"></app-list-procedure>
|
||||
<source-resource-list [source]="selectedSource" [resourceListType]="selectedResourceType"></source-resource-list>
|
||||
|
||||
</div><!-- az-content-body -->
|
||||
</div><!-- container -->
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {Source} from '../../models/fasten/source';
|
||||
import {FastenApiService} from '../../services/fasten-api.service';
|
||||
import {ResourceListOutletDirective} from '../../directive/resource-list-outlet.directive';
|
||||
|
||||
|
||||
@Component({
|
||||
|
@ -13,9 +14,8 @@ export class SourceDetailComponent implements OnInit {
|
|||
|
||||
selectedSource: Source = null
|
||||
selectedResourceType: string = null
|
||||
selectedResources: any[] = []
|
||||
|
||||
resourcesGroupedByType: { [name: string]: any[] } = {}
|
||||
resourceTypeCounts: { [name: string]: number } = {}
|
||||
|
||||
constructor(private fastenApi: FastenApiService, private router: Router, private route: ActivatedRoute) {
|
||||
//check if the current Source was sent over using the router state storage:
|
||||
|
@ -25,26 +25,16 @@ export class SourceDetailComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.selectedSource == null) {
|
||||
//lookup the source by ID.
|
||||
this.fastenApi.getSource(this.route.snapshot.paramMap.get('source_id')).subscribe((source) => {
|
||||
this.selectedSource = source;
|
||||
});
|
||||
}
|
||||
|
||||
this.fastenApi.getResources(undefined, this.route.snapshot.paramMap.get('source_id')).subscribe((resourceList) => {
|
||||
console.log("RESOURCES", resourceList)
|
||||
let _resourcesGroupedByType = this.resourcesGroupedByType
|
||||
resourceList.forEach(resource => {
|
||||
let resourceTypeList = _resourcesGroupedByType[resource.source_resource_type] || [];
|
||||
resourceTypeList.push(resource)
|
||||
_resourcesGroupedByType[resource.source_resource_type] = resourceTypeList
|
||||
})
|
||||
})
|
||||
//always request the source summary
|
||||
this.fastenApi.getSourceSummary(this.route.snapshot.paramMap.get('source_id')).subscribe((sourceSummary) => {
|
||||
this.selectedSource = sourceSummary.source;
|
||||
for(let resourceTypeCount of sourceSummary.resource_type_counts){
|
||||
this.resourceTypeCounts[resourceTypeCount.resource_type] = resourceTypeCount.count
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
selectResourceType(resourceType: string) {
|
||||
this.selectedResourceType = resourceType
|
||||
this.selectedResources = this.resourcesGroupedByType[resourceType]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {ResponseWrapper} from '../models/response-wrapper';
|
|||
import {Source} from '../models/fasten/source';
|
||||
import {User} from '../models/fasten/user';
|
||||
import {ResourceFhir} from '../models/fasten/resource_fhir';
|
||||
import {SourceSummary} from '../models/fasten/source-summary';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -110,6 +111,16 @@ export class FastenApiService {
|
|||
);
|
||||
}
|
||||
|
||||
getSourceSummary(sourceId: string): Observable<SourceSummary> {
|
||||
return this._httpClient.get<any>(`${this.getBasePath()}/api/secure/source/${sourceId}/summary`)
|
||||
.pipe(
|
||||
map((response: ResponseWrapper) => {
|
||||
console.log("SOURCE RESPONSE", response)
|
||||
return response.data as SourceSummary
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getResources(sourceResourceType?: string, sourceID?: string): Observable<ResourceFhir[]> {
|
||||
let queryParams = {}
|
||||
if(sourceResourceType){
|
||||
|
|
Loading…
Reference in New Issue