229 lines
7.7 KiB
Vue
229 lines
7.7 KiB
Vue
|
<template>
|
||
|
<Subtitle value="Imaging" />
|
||
|
<Subtitle :value="patient_info.name" />
|
||
|
<div class="row">
|
||
|
<div class="selector col-12" :class="{ 'col-xl-4': selection_data }">
|
||
|
<div class="card mb-3 shadow">
|
||
|
<div class="card-header">{{resultset.length > 0 ? resultset.length : 'No'}} record{{resultset.length == 1 ? '' : 's'}}</div>
|
||
|
<ul class="scroller list-group list-group-flush" :class="{ 'list-skinny': selection_data }" ref="scroller">
|
||
|
<router-link v-for="item in resultset" :to="'/patient/' + patient_dfn + '/imaging/' + item.Info.IEN" replace custom v-slot="{ navigate, href }">
|
||
|
<li :key="item" class="record list-group-item" :class="{ 'active': selection == item.Info.IEN }" :title="'Site: ' + item['Site'] + '\nNote Title: ' + item['Note Title~~W0'] + '\nProc DT: ' + item['Proc DT~S1'] + '\nProcedure: ' + item['Procedure'] + '\n# Img: ' + item['# Img~S2'] + '\nShort Desc: ' + item['Short Desc'] + '\nPkg: ' + item['Pkg'] + '\nClass: ' + item['Class'] + '\nType: ' + item['Type'] + '\nSpecialty: ' + item['Specialty'] + '\nOrigin: ' + item['Origin'] + '\nCap Dt: ' + item['Cap Dt~S1~W0'] + '\nCap by: ' + item['Cap by~~W0'] + '\nImage ID: ' + item['Image ID~S2~W0'] + '\nCreation Date: ' + item.Info['Document Date']" @click="navigate">
|
||
|
<div class="row">
|
||
|
<div class="cell col-4"><router-link :to="href" replace>{{item['Proc DT~S1']}}</router-link></div>
|
||
|
<div class="cell col-7">{{doctitledesc(item)}}</div>
|
||
|
<div class="cell col-1">#{{item['# Img~S2']}}</div>
|
||
|
<div class="cell col-3">{{doctypespec(item)}}</div>
|
||
|
<div class="cell col-3">{{docclspkgproc(item)}}</div>
|
||
|
<div class="cell col-3">{{item['Site']}} {{item['Origin']}}</div>
|
||
|
</div>
|
||
|
</li>
|
||
|
</router-link>
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div v-if="selection_data" 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>{{doctitledesc(selection_data) || 'Image'}} #{{selection}}</span>
|
||
|
<router-link class="close" :to="'/patient/' + patient_dfn + '/imaging'" replace>❌</router-link>
|
||
|
</div>
|
||
|
<div class="detail card-body" ref="detail">
|
||
|
<p v-if="selection_info">{{selection_info}}</p>
|
||
|
<ul v-if="(selection_images) && (selection_images.length > 0)" class="list-group list-group-flush">
|
||
|
<li v-for="info in selection_images" class="list-group-item"><router-link :to="'/v1/vista/' + client.cid + '/imaging/' + info['Image Path'].replace(/\\/g, '/').replace(/^\/+/, '') + '?view'" target="_blank">{{info['Short Desc']}}</router-link></li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<style scoped>
|
||
|
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;
|
||
|
scroll-margin-top: 3.6875rem;
|
||
|
}
|
||
|
li.record:nth-child(even) {
|
||
|
background-color: rgba(0, 0, 0, 0.05);
|
||
|
}
|
||
|
ul.scroller.list-skinny li.record {
|
||
|
scroll-margin-top: 0;
|
||
|
}
|
||
|
li.record a {
|
||
|
color: inherit;
|
||
|
}
|
||
|
li.record.active {
|
||
|
color: #fff;
|
||
|
background-color: #0d6efd;
|
||
|
}
|
||
|
li.bottom {
|
||
|
overflow-anchor: none;
|
||
|
}
|
||
|
div.cell {
|
||
|
overflow: hidden;
|
||
|
text-overflow: ellipsis;
|
||
|
white-space: nowrap;
|
||
|
}
|
||
|
a.close {
|
||
|
cursor: default;
|
||
|
text-decoration: none;
|
||
|
}
|
||
|
div.detail {
|
||
|
scroll-margin-top: calc(3.6875rem + 2.5625rem + 25vh);
|
||
|
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;
|
||
|
scroll-margin-top: 0;
|
||
|
overflow-y: auto;
|
||
|
}
|
||
|
}
|
||
|
</style>
|
||
|
|
||
|
<script>
|
||
|
import { debounce, strptime_vista } from './util.mjs';
|
||
|
|
||
|
import Subtitle from './Subtitle.vue';
|
||
|
|
||
|
const SZ_WINDOW = 100;
|
||
|
|
||
|
export default {
|
||
|
components: {
|
||
|
Subtitle
|
||
|
},
|
||
|
props: {
|
||
|
client: Object,
|
||
|
sensitive: Boolean,
|
||
|
patient_dfn: String,
|
||
|
patient_info: Object
|
||
|
},
|
||
|
data() {
|
||
|
return {
|
||
|
dfn: null,
|
||
|
has_more: '',
|
||
|
is_loading: false,
|
||
|
resultset: [],
|
||
|
selection: null,
|
||
|
selection_info: null,
|
||
|
selection_images: null
|
||
|
};
|
||
|
},
|
||
|
computed: {
|
||
|
selection_data() {
|
||
|
var selection = this.selection, resultset = this.resultset;
|
||
|
if((selection) && (resultset) && (resultset.length > 0)) return resultset.find(item => item['Image ID~S2~W0'] == selection);
|
||
|
}
|
||
|
},
|
||
|
watch: {
|
||
|
'$route.params.ien': {
|
||
|
async handler(value) {
|
||
|
this.selection = value;
|
||
|
}, immediate: true
|
||
|
}
|
||
|
},
|
||
|
methods: {
|
||
|
strptime_vista,
|
||
|
datestring(date) {
|
||
|
return date.toLocaleDateString('sv-SE');
|
||
|
},
|
||
|
datetimestring(date) {
|
||
|
return date.toLocaleDateString('sv-SE') + ' ' + date.toLocaleTimeString('en-GB');
|
||
|
},
|
||
|
docclspkgproc(item) {
|
||
|
var res = [], cls = item['Class'], pkg = item['Pkg'], proc = item['Procedure'];
|
||
|
if(cls) res.push(cls);
|
||
|
if((pkg) && (pkg != 'NONE') && (pkg != cls)) res.push(pkg);
|
||
|
if((proc) && (proc != cls) && (proc != pkg)) res.push(proc);
|
||
|
return res.join(' • ');
|
||
|
},
|
||
|
doctypespec(item) {
|
||
|
var res = [], type = item['Type'], spec = item['Specialty'];
|
||
|
if(type) res.push(type);
|
||
|
if(spec) res.push(spec);
|
||
|
return res.join(' • ');
|
||
|
},
|
||
|
doctitledesc(item) {
|
||
|
var title = item['Note Title~~W0'].replace(/^\s+|\s+$/g, ''), desc = item['Short Desc'].replace(/^\s+|\s+$/g, '');
|
||
|
if(title) {
|
||
|
if((desc == title) || (desc == '+' + title)) return title;
|
||
|
else return title + ' • ' + desc;
|
||
|
} else return desc;
|
||
|
},
|
||
|
doctitle(doc) {
|
||
|
if(doc) {
|
||
|
var m = doc.match(/^Orderable Item:\s*(.*)$/m);
|
||
|
if(m) return m[1];
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
created() {
|
||
|
this.$watch(
|
||
|
() => (this.client, this.patient_dfn, {}),
|
||
|
debounce(async () => {
|
||
|
if((this.client) && (this.patient_dfn)) this.resultset = await this.client.MAG4_IMAGE_LIST('E', '', '', '', ['IXCLASS^^CLIN^CLIN/ADMIN^ADMIN/CLIN', 'IDFN^^' + this.patient_dfn]);
|
||
|
else this.resultset = [];
|
||
|
}, 500),
|
||
|
{ immediate: true }
|
||
|
);
|
||
|
this.$watch(
|
||
|
() => (this.client, this.selection, {}),
|
||
|
async function() {
|
||
|
try {
|
||
|
this.selection_info = (this.client) && (this.selection) ? await this.client.MAG4_GET_IMAGE_INFO(this.selection) : null;
|
||
|
} catch(ex) {
|
||
|
this.selection_info = null;
|
||
|
console.warn(ex);
|
||
|
}
|
||
|
try {
|
||
|
this.selection_images = (this.client) && (this.selection) && (this.selection_data) ? (this.selection_data['# Img~S2'] == '1' ? [await this.client.MAGG_IMAGE_INFO(this.selection, '1')] : await this.client.MAGG_GROUP_IMAGES(this.selection, '1')) : null;
|
||
|
} catch(ex) {
|
||
|
this.selection_images = null;
|
||
|
console.warn(ex);
|
||
|
}
|
||
|
if(this.$refs.scroller) {
|
||
|
if(this.selection_data) { // scroll to selected item
|
||
|
await this.$nextTick();
|
||
|
var active = this.$refs.scroller.querySelectorAll(':scope > .active');
|
||
|
if(active.length > 0) (Element.prototype.scrollIntoViewIfNeeded || Element.prototype.scrollIntoView).call(active[0]);
|
||
|
if(this.$refs.detail) { // scroll to top of detail panel
|
||
|
this.$refs.detail.scrollIntoView();
|
||
|
this.$refs.detail.scrollTop = 0;
|
||
|
}
|
||
|
} else { // scroll to topmost item
|
||
|
var offset = this.$refs.scroller.getBoundingClientRect().top;
|
||
|
for(var children = this.$refs.scroller.children, count = children.length, i = 0; i < count; ++i) if(children[i].getBoundingClientRect().top >= offset) {
|
||
|
await this.$nextTick();
|
||
|
var behavior = document.documentElement.style.scrollBehavior;
|
||
|
document.documentElement.style.scrollBehavior = 'auto'; // inhibit Bootstrap smooth scrolling
|
||
|
children[i].scrollIntoView();
|
||
|
document.documentElement.style.scrollBehavior = behavior;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
{ immediate: true }
|
||
|
);
|
||
|
}
|
||
|
};
|
||
|
</script>
|