Unified report viewer with filter
This commit is contained in:
parent
0fe07e59af
commit
6132ce2f15
@ -66,11 +66,6 @@
|
|||||||
{ path: 'visits', component: RoutePatientVisits },
|
{ path: 'visits', component: RoutePatientVisits },
|
||||||
{ path: 'orders', component: RoutePatientOrders },
|
{ path: 'orders', component: RoutePatientOrders },
|
||||||
{ path: 'reports', component: RoutePatientReports },
|
{ path: 'reports', component: RoutePatientReports },
|
||||||
{ path: 'reports/bloodbank', component: RoutePatientReports, props: { report_name: 'Blood Bank' } },
|
|
||||||
{ path: 'reports/microbiology', component: RoutePatientReports, props: { report_name: 'Microbiology' } },
|
|
||||||
{ path: 'reports/pathology', component: RoutePatientReports, props: { report_name: 'Pathology' } },
|
|
||||||
{ path: 'reports/radiology', component: RoutePatientReports, props: { report_name: 'Radiology' } },
|
|
||||||
{ path: 'reports/notes', component: RoutePatientReports, props: { report_name: 'Notes' } },
|
|
||||||
{ path: 'document', component: RoutePatientDocuments },
|
{ path: 'document', component: RoutePatientDocuments },
|
||||||
{ path: 'document/:tiu_da', component: RoutePatientDocuments },
|
{ path: 'document/:tiu_da', component: RoutePatientDocuments },
|
||||||
] },
|
] },
|
||||||
|
@ -1,145 +1,405 @@
|
|||||||
<template>
|
<template>
|
||||||
<Subtitle :value="selection ? selection.name : 'Reports'" />
|
<Subtitle value="Search" />
|
||||||
<Subtitle :value="patient_info.name" />
|
<Subtitle :value="patient_info.name" />
|
||||||
<div class="card mb-3 shadow">
|
<div class="filter card mb-3 shadow" :class="{ 'list-wide': !selection }">
|
||||||
<ul class="card-header nav nav-pills nav-fill">
|
|
||||||
<li v-for="report in reports" class="nav-item" @click="selection = report">
|
|
||||||
<router-link class="nav-link" :to="'/patient/' + patient_dfn + '/reports/' + report.name.toLowerCase().replace(/\s+/g, '') + (sensitive ? '?viewsensitive' : '')">{{report.name}}</router-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
<li class="list-group-item d-flex justify-content-around align-items-center">
|
<li class="list-group-item">
|
||||||
<DateRangePicker range="2Y" direction="-1" v-model:date="date_end" v-model:date_end="date_begin" />
|
<div class="input-group">
|
||||||
<div class="limit input-group">
|
<span class="input-group-text">🔎</span>
|
||||||
<span class="input-group-text">Limit</span>
|
<input type="text" class="form-control" placeholder="Filter" v-model="x_query" />
|
||||||
<input type="number" step="1" class="form-control" v-model="limit" />
|
<button v-if="x_query" class="btn btn-outline-secondary" @click="x_query = ''">❌</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="(selection) && (resultset) && (resultset.length)" class="list-group-item"><ViewReport :resultset="resultset" :table="selection.table" :detail="selection.detail" /></li>
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button v-for="report in reports" class="btn" :class="{ 'btn-primary': report.enabled, 'btn-outline-primary': !report.enabled }" @click="toggle(report)">{{report.name}}</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn" :class="{ 'btn-success': unify, 'btn-outline-success': !unify }" @click="unify = !unify">Unify</button>
|
||||||
|
<DateRangePicker range="1M" direction="-1" v-model:date="date_end" v-model:date_end="date_begin" />
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div v-if="(selection) && (resultset) && (resultset.length)" class="card-footer">{{resultset.length}} record{{resultset.length == 1 ? '' : 's'}} loaded<template v-if="resultset.length == limit"> (may be truncated)</template></div>
|
</div>
|
||||||
|
<div v-if="resultset.length > 0" class="row">
|
||||||
|
<div class="selector col-12" :class="{ 'col-xl-4': selection }">
|
||||||
|
<div class="card mb-3 shadow">
|
||||||
|
<div class="card-header"><template v-if="resultset.length > 0"><template v-if="resultset.length > rs_filtered.length">{{rs_filtered.length}} of </template>{{resultset.length}}</template><template v-else-if="is_loading">Loading</template><template v-else>No</template> record{{resultset.length == 1 ? '' : 's'}}</div>
|
||||||
|
<ul class="scroller list-group list-group-flush" :class="{ 'list-skinny': selection }" ref="scroller">
|
||||||
|
<li v-for="item in rs_filtered" :key="item" class="record list-group-item" :class="{ 'active': (selection) && (selection.id == item.id) }" @click="selection = item"><span class="badge emblem" :class="[item.emblem]" /> <span class="datetime date">{{datestring(item.time)}}</span> <span class="datetime time">{{timestring(item.time)}}</span> • <span class="title"><span v-for="title in item.title">{{title}}</span></span><template v-if="item.snippets"><div v-for="snippet in item.snippets" class="snippet" v-html="snippet" /></template></li>
|
||||||
|
<li class="bottom list-group-item" ref="bottom" />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="selection" class="col-12 col-xl-8">
|
||||||
|
<div class="card mb-3 shadow">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<span>{{selection.title.join(' - ')}}</span>
|
||||||
|
<span class="close" @click="selection = null">❌</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail card-body" v-html="selection.highlight || selection.detail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul.scroller span.highlight, div.detail span.highlight {
|
||||||
|
background-color: #ff0;
|
||||||
|
}
|
||||||
|
li.record.active span.highlight {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.router-link-exact-active {
|
div.filter.list-wide {
|
||||||
|
position: sticky;
|
||||||
|
top: 3.65rem;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
div.selector {
|
||||||
|
position: sticky;
|
||||||
|
top: 1.15rem;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
ul.scroller.list-skinny {
|
||||||
|
max-height: 25vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
li.record {
|
||||||
|
cursor: default;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
}
|
||||||
|
li.record:nth-child(even) {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
li.record.active {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #0d6efd;
|
background-color: #0d6efd;
|
||||||
}
|
}
|
||||||
div.limit {
|
li.bottom {
|
||||||
width: 20rem;
|
padding: 0;
|
||||||
}
|
}
|
||||||
div.card-footer {
|
span.badge.emblem:empty {
|
||||||
text-align: center;
|
display: inline-block;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
span.badge.emblem-notes {
|
||||||
|
background-color: var(--bs-purple);
|
||||||
|
}
|
||||||
|
span.badge.emblem-notes::after {
|
||||||
|
content: 'N';
|
||||||
|
}
|
||||||
|
span.badge.emblem-labs {
|
||||||
|
background-color: var(--bs-pink);
|
||||||
|
}
|
||||||
|
span.badge.emblem-labs::after {
|
||||||
|
content: 'L';
|
||||||
|
}
|
||||||
|
span.badge.emblem-microbiology {
|
||||||
|
background-color: var(--bs-orange);
|
||||||
|
}
|
||||||
|
span.badge.emblem-microbiology::after {
|
||||||
|
content: 'M';
|
||||||
|
}
|
||||||
|
span.badge.emblem-bloodbank {
|
||||||
|
background-color: var(--bs-red);
|
||||||
|
}
|
||||||
|
span.badge.emblem-bloodbank::after {
|
||||||
|
content: 'B';
|
||||||
|
}
|
||||||
|
span.badge.emblem-pathology {
|
||||||
|
background-color: var(--bs-yellow);
|
||||||
|
}
|
||||||
|
span.badge.emblem-pathology::after {
|
||||||
|
content: 'P';
|
||||||
|
}
|
||||||
|
span.badge.emblem-radiology {
|
||||||
|
background-color: var(--bs-green);
|
||||||
|
}
|
||||||
|
span.badge.emblem-radiology::after {
|
||||||
|
content: 'R';
|
||||||
|
}
|
||||||
|
span.datetime, span.title span:first-child {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
span.title span:not(:first-child)::before {
|
||||||
|
content: ' - ';
|
||||||
|
}
|
||||||
|
ul.scroller.list-skinny span.datetime.time {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
div.snippet {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
span.close {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
div.detail {
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
div.selector {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
ul.scroller.list-skinny {
|
||||||
|
max-height: 75vh;
|
||||||
|
}
|
||||||
|
div.detail {
|
||||||
|
max-height: 75vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { debounce, strftime_vista } from './util.mjs';
|
import { uniq, debounce, strftime_vista } from './util.mjs';
|
||||||
|
|
||||||
import Subtitle from './Subtitle.vue';
|
import Subtitle from './Subtitle.vue';
|
||||||
import DateRangePicker from './DateRangePicker.vue';
|
import DateRangePicker from './DateRangePicker.vue';
|
||||||
import ViewReport from './ViewReport.vue';
|
|
||||||
|
const SZ_WINDOW = 100;
|
||||||
|
const SZ_RANGE = 40000;
|
||||||
|
|
||||||
const reports = [
|
const reports = [
|
||||||
{
|
{
|
||||||
name: 'Blood Bank',
|
name: 'Notes',
|
||||||
rpt_id: '2:BLOOD BANK REPORT~;;0',
|
rpt_id: 'OR_PN:PROGRESS NOTES~TIUPRG;ORDV04;15;',
|
||||||
detail: null,
|
map(x) {
|
||||||
table: []
|
var time = new Date(x[3]);
|
||||||
|
return {
|
||||||
|
time,
|
||||||
|
id: 'OR_PN:' + time.getTime() + ':' + x[2],
|
||||||
|
emblem: 'emblem-notes',
|
||||||
|
title: [x[4], x[5], '#' + x[2]],
|
||||||
|
detail: escape_html(x[6])
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Labs',
|
||||||
|
rpt_id: 'OR_OV_R:LAB OVERVIEW (COLLECTED SPECIMENS)~OV;ORDV02C;32;',
|
||||||
|
map(x) {
|
||||||
|
var time = new Date(x[2]);
|
||||||
|
return {
|
||||||
|
time,
|
||||||
|
id: 'OR_OV_R:' + time.getTime() + ':' + x[12],
|
||||||
|
emblem: 'emblem-labs',
|
||||||
|
title: [x[3], x[6], x[8], x[10], '#' + x[12]],
|
||||||
|
detail: escape_html(x[15])
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Microbiology',
|
name: 'Microbiology',
|
||||||
rpt_id: 'OR_MIC:MICROBIOLOGY~MI;ORDV05;38;',
|
rpt_id: 'OR_MIC:MICROBIOLOGY~MI;ORDV05;38;',
|
||||||
detail: 7,
|
map(x) {
|
||||||
table: [
|
var time = new Date(x[2]);
|
||||||
{ subscript: 2, title: 'Collection Date/Time' },
|
return {
|
||||||
{ subscript: 3, title: 'Test Name' },
|
time,
|
||||||
{ subscript: 4, title: 'Sample' },
|
id: 'OR_MIC:' + time.getTime() + ':' + x[6],
|
||||||
{ subscript: 5, title: 'Specimen' },
|
emblem: 'emblem-microbiology',
|
||||||
{ subscript: 6, title: 'Accession #' },
|
title: [x[3], x[4], x[5], '#' + x[6]],
|
||||||
{ subscript: 8, title: '[+]' },
|
detail: escape_html(x[7])
|
||||||
]
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Blood Bank',
|
||||||
|
rpt_id: '2:BLOOD BANK REPORT~;;0',
|
||||||
|
singleton: true,
|
||||||
|
map(x) {
|
||||||
|
var time = new Date();
|
||||||
|
return {
|
||||||
|
time,
|
||||||
|
id: 'BB',
|
||||||
|
emblem: 'emblem-bloodbank',
|
||||||
|
title: ['BLOOD BANK'],
|
||||||
|
detail: escape_html(x)
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Pathology',
|
name: 'Pathology',
|
||||||
rpt_id: 'OR_APR:ANATOMIC PATHOLOGY~SP;ORDV02A;0;',
|
rpt_id: 'OR_APR:ANATOMIC PATHOLOGY~SP;ORDV02A;0;',
|
||||||
detail: 5,
|
map(x) {
|
||||||
table: [
|
var time = new Date(x[2]);
|
||||||
{ subscript: 2, title: 'Collection Date/Time' },
|
return {
|
||||||
{ subscript: 3, title: 'Specimen' },
|
time,
|
||||||
{ subscript: 4, title: 'Accession #' },
|
id: 'OR_APR:' + time.getTime() + ':' + x[4],
|
||||||
{ subscript: 6, title: '[+]' },
|
emblem: 'emblem-pathology',
|
||||||
//{ subscript: 7, title: '#7' },
|
title: [x[3], '#' + x[4]],
|
||||||
//{ subscript: 8, title: '#8' },
|
detail: escape_html(x[5])
|
||||||
]
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Radiology',
|
name: 'Radiology',
|
||||||
rpt_id: 'OR_R18:IMAGING~RIM;ORDV08;0;',
|
rpt_id: 'OR_R18:IMAGING~RIM;ORDV08;0;',
|
||||||
detail: 6,
|
map(x) {
|
||||||
table: [
|
var time = new Date(x[2]);
|
||||||
{ subscript: 2, title: 'Procedure Date/Time' },
|
return {
|
||||||
{ subscript: 3, title: 'Procedure Name' },
|
time,
|
||||||
{ subscript: 4, title: 'Report Status' },
|
id: 'OR_R18:' + time.getTime() + ':' + x[9],
|
||||||
{ subscript: 5, title: 'Case #' },
|
emblem: 'emblem-radiology',
|
||||||
{ subscript: 7, title: '[+]' },
|
title: [x[3], x[4], x[5], '#' + x[9]],
|
||||||
//{ subscript: 8, title: '#8' },
|
detail: escape_html(x[6])
|
||||||
//{ subscript: 9, title: '#9' },
|
};
|
||||||
]
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Notes',
|
|
||||||
rpt_id: 'OR_PN:PROGRESS NOTES~TIUPRG;ORDV04;15;',
|
|
||||||
detail: 6,
|
|
||||||
table: [
|
|
||||||
{ subscript: 3, title: 'Record Date/Time' },
|
|
||||||
{ subscript: 4, title: 'Type' },
|
|
||||||
{ subscript: 5, title: 'Author' },
|
|
||||||
{ subscript: 7, title: '[+]' },
|
|
||||||
//{ subscript: 8, title: '#8' },
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const escape_div = document.createElement('div');
|
||||||
|
function escape_html(s) {
|
||||||
|
escape_div.textContent = s;
|
||||||
|
return escape_div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function snippets(text, regex, replacement) {
|
||||||
|
var res = [], context = new RegExp('(?:\\S+\\s+){0,3}\\S*(' + regex.source + ')\\S*(?:\\s+\\S+){0,3}', regex.flags), match;
|
||||||
|
if(context.global) while((match = context.exec(text)) !== null) res.push(match[0].replace(regex, replacement).replace(/\s+/g, ' ').replace(/([\W_])\1{2,}/g, '$1$1'));
|
||||||
|
else if((match = context.exec(text)) !== null) res.push(match[0].replace(regex, replacement).replace(/\s+/g, ' ').replace(/([\W_])\1{2,}/g, '$1$1'));
|
||||||
|
return uniq(res);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Subtitle, DateRangePicker, ViewReport
|
Subtitle, DateRangePicker
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
client: Object,
|
client: Object,
|
||||||
sensitive: Boolean,
|
sensitive: Boolean,
|
||||||
patient_dfn: String,
|
patient_dfn: String,
|
||||||
patient_info: Object,
|
patient_info: Object
|
||||||
report_name: String
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
return {
|
return {
|
||||||
date_begin: now,
|
dfn: null,
|
||||||
|
is_loading: false,
|
||||||
date_end: now,
|
date_end: now,
|
||||||
limit: 100,
|
date_begin: now,
|
||||||
|
query: '',
|
||||||
|
x_query: '',
|
||||||
|
unify: false,
|
||||||
reports,
|
reports,
|
||||||
selection: null,
|
resultsets: {},
|
||||||
resultset: []
|
selection: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
resultset() {
|
||||||
|
return this.reports.map((x, i) => x.enabled ? this.resultsets[i] : null).filter(x => x).reduce((acc, val) => (Array.prototype.push.apply(acc, val), acc), []).sort((a, b) => b.time - a.time);
|
||||||
|
},
|
||||||
|
rs_filtered() {
|
||||||
|
var query = this.query.replace(/^\s+|\s+$/g, '');
|
||||||
|
if(query.length > 0) {
|
||||||
|
if(query.startsWith('"')) {
|
||||||
|
query = query.substring(1, query.length - ((query.length > 1) && (query.endsWith('"')) ? 1 : 0));
|
||||||
|
if(query.length > 0) {
|
||||||
|
query = new RegExp(query.replace(/\s+/g, ' ').split(' ').map(x => x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\s*'), 'gims');
|
||||||
|
return this.resultset.filter(x => (x.detail) && (query.test(x.detail))).map(x => Object.assign({ snippets: snippets(x.detail, query, '<span class="highlight">$&</span>'), highlight: x.detail.replace(query, '<span class="highlight">$&</span>') }, x));
|
||||||
|
}
|
||||||
|
} else if(query.startsWith('/')) {
|
||||||
|
if(query.length > 1) {
|
||||||
|
var m = /^\/(.*)\/([a-z]*)$/.exec(query);
|
||||||
|
query = m ? new RegExp(m[1], m[2]) : new RegExp(query.substring(1), 'gims');
|
||||||
|
return this.resultset.filter(x => (x.detail) && (query.test(x.detail))).map(x => Object.assign({ snippets: snippets(x.detail, query, '<span class="highlight">$&</span>'), highlight: x.detail.replace(query, '<span class="highlight">$&</span>') }, x));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query = new RegExp(query.replace(/\s+/g, ' ').split(' ').map(x => '\\b' + x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\S*(?:\\s+\\S+){0,5}\\s+'), 'gims');
|
||||||
|
return this.resultset.filter(x => (x.detail) && (query.test(x.detail))).map(x => Object.assign({ snippets: snippets(x.detail, query, '<span class="highlight">$&</span>'), highlight: x.detail.replace(query, '<span class="highlight">$&</span>') }, x));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.resultset;
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route: {
|
rs_filtered(value) {
|
||||||
async handler(value) {
|
if((value) && (this.selection)) {
|
||||||
await this.$nextTick();
|
var id = this.selection.id;
|
||||||
this.selection = this.report_name ? reports.find(x => x.name == this.report_name) : null;
|
for(var i = 0; i < value.length; ++i) if(value[i].id == id) return this.selection = value[i];
|
||||||
}, immediate: true
|
this.selection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
strftime_vista,
|
||||||
|
datestring(date) {
|
||||||
|
return date.toLocaleDateString('sv-SE');
|
||||||
|
},
|
||||||
|
timestring(date) {
|
||||||
|
return date.toLocaleTimeString('en-GB').substring(0, 5);
|
||||||
|
},
|
||||||
|
toggle(report) {
|
||||||
|
if(report.enabled) report.enabled = false;
|
||||||
|
else if(this.unify) report.enabled = true;
|
||||||
|
else {
|
||||||
|
var reports = this.reports;
|
||||||
|
for(var i = reports.length - 1; i >= 0; --i) reports[i].enabled = false;
|
||||||
|
report.enabled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async load_more() {
|
||||||
|
try {
|
||||||
|
this.is_loading = true;
|
||||||
|
if((this.client) && (this.patient_dfn)) {
|
||||||
|
if(this.dfn != this.patient_dfn) {
|
||||||
|
this.dfn = this.patient_dfn;
|
||||||
|
this.resultsets = {};
|
||||||
|
}
|
||||||
|
var dfn = this.patient_dfn, resultsets = this.resultsets, resultset, reports = this.reports, report, alpha = strftime_vista(this.date_begin).toFixed(4), omega = strftime_vista(this.date_end).toFixed(4);
|
||||||
|
for(var i = 0; i < reports.length; ++i) if(((report = reports[i]).enabled) && (!((resultset = resultsets[i]) && (alpha == resultset.alpha) && (omega == resultset.omega)))) {
|
||||||
|
var data = [], batch, idmap = {}, omega0 = omega;
|
||||||
|
do {
|
||||||
|
batch = await this.client.ORWRP_REPORT_TEXT(dfn, report.rpt_id + (report.rpt_id.endsWith(';') ? SZ_WINDOW : ''), '', SZ_RANGE, '', alpha, omega0);
|
||||||
|
if(report.singleton) data = [report.map(batch[0].join('\n'))];
|
||||||
|
else if((batch = batch.map(item => {
|
||||||
|
var res = [], line, brk, sub;
|
||||||
|
for(var i = 0; i < item.length; ++i) {
|
||||||
|
brk = (line = item[i]).indexOf('^');
|
||||||
|
if(brk >= 0) {
|
||||||
|
if(res[sub = line.substring(0, brk)]) res[sub].push(line.substring(brk + 1));
|
||||||
|
else res[sub] = [line.substring(brk + 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(var k in res) if(res[k]) res[k] = res[k].join('\n');
|
||||||
|
res = report.map(res);
|
||||||
|
return idmap[res.id] ? console.warn('Duplicate record', res) : res;
|
||||||
|
}).filter(x => x)).length > 0) {
|
||||||
|
Array.prototype.push.apply(data, batch.sort((a, b) => b.time - a.time));
|
||||||
|
batch.map(x => x.id).reduce((acc, val) => (acc[val] = true, acc), idmap);
|
||||||
|
omega0 = strftime_vista(data[data.length - 1].time);
|
||||||
|
};
|
||||||
|
} while(batch.length >= SZ_WINDOW);
|
||||||
|
data.alpha = alpha;
|
||||||
|
data.omega = omega;
|
||||||
|
resultsets[i] = data;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.dfn = null;
|
||||||
|
this.resultsets = {};
|
||||||
|
}
|
||||||
|
} catch(ex) {
|
||||||
|
console.warn(ex);
|
||||||
|
} finally {
|
||||||
|
this.is_loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => (this.client, this.patient_dfn, this.selection, this.date_begin, this.date_end, this.limit, {}),
|
() => (this.client, this.patient_dfn, this.reports.map(x => x.enabled), this.date_begin, this.date_end, {}),
|
||||||
debounce(async () => {
|
debounce(() => this.load_more(), 500),
|
||||||
var limit = Math.floor(Math.abs(this.limit));
|
{ immediate: true }
|
||||||
this.resultset = [];
|
);
|
||||||
if((this.client) && (this.patient_dfn) && (this.selection)) this.resultset = await this.client.ORWRP_REPORT_TEXT(this.patient_dfn, this.selection.rpt_id + (this.selection.rpt_id.endsWith(';') ? Math.round(this.limit) : ''), '', Math.round(this.limit), '', strftime_vista(this.date_begin), strftime_vista(this.date_end));
|
this.$watch(
|
||||||
}, 500),
|
() => this.x_query,
|
||||||
|
debounce(value => this.query = value, 500),
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="(resultset) && (resultset.length == 1) && (!resultset[0][0].startsWith('1^'))" class="detail">{{resultset[0].join('\r\n')}}</div>
|
|
||||||
<div v-else-if="(resultset_calculated) && (resultset_calculated.length)" class="accordion">
|
|
||||||
<div v-for="item in resultset_calculated" class="accordion-item" :key="item">
|
|
||||||
<h2 class="accordion-header">
|
|
||||||
<button type="button" class="accordion-button report-row" :class="{ collapsed: !show[item.hash] }" @click="show[item.hash] = !show[item.hash]">
|
|
||||||
<span v-for="entry in table" class="report-col">{{item[entry.subscript]}}</span>
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div class="accordion-collapse collapse" :class="{ show: show[item.hash] }">
|
|
||||||
<div class="detail accordion-body">{{item[detail]}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.report-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.report-col {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.detail {
|
|
||||||
font-family: monospace;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { strHashJenkins } from './util.mjs';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
client: Object,
|
|
||||||
resultset: Array,
|
|
||||||
table: Array,
|
|
||||||
detail: Number
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
show: {}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
resultset_calculated() {
|
|
||||||
return this.resultset ? this.resultset.map(item => {
|
|
||||||
var res = [], line, brk, sub;
|
|
||||||
for(var i = 0; i < item.length; ++i) {
|
|
||||||
brk = (line = item[i]).indexOf('^');
|
|
||||||
if(brk >= 0) {
|
|
||||||
if(res[sub = line.substring(0, brk)]) res[sub].push(line.substring(brk + 1));
|
|
||||||
else res[sub] = [line.substring(brk + 1)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(var k in res) if(res[k]) res[k] = res[k].join('\r\n');
|
|
||||||
res.hash = strHashJenkins(item.join(''));
|
|
||||||
return res;
|
|
||||||
}) : [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
Loading…
Reference in New Issue
Block a user