TIU-based document view
This commit is contained in:
parent
71db3a6186
commit
7a4e36a0c0
@ -22,6 +22,7 @@
|
|||||||
import RoutePatientVisits from './RoutePatientVisits.vue';
|
import RoutePatientVisits from './RoutePatientVisits.vue';
|
||||||
import RoutePatientOrders from './RoutePatientOrders.vue';
|
import RoutePatientOrders from './RoutePatientOrders.vue';
|
||||||
import RoutePatientReports from './RoutePatientReports.vue';
|
import RoutePatientReports from './RoutePatientReports.vue';
|
||||||
|
import RoutePatientDocuments from './RoutePatientDocuments.vue';
|
||||||
import RoutePlanner from './RoutePlanner.vue';
|
import RoutePlanner from './RoutePlanner.vue';
|
||||||
import RouteRecall from './RouteRecall.vue';
|
import RouteRecall from './RouteRecall.vue';
|
||||||
import RouteInbox from './RouteInbox.vue';
|
import RouteInbox from './RouteInbox.vue';
|
||||||
@ -70,6 +71,8 @@
|
|||||||
{ path: 'reports/pathology', component: RoutePatientReports, props: { report_name: 'Pathology' } },
|
{ path: 'reports/pathology', component: RoutePatientReports, props: { report_name: 'Pathology' } },
|
||||||
{ path: 'reports/radiology', component: RoutePatientReports, props: { report_name: 'Radiology' } },
|
{ path: 'reports/radiology', component: RoutePatientReports, props: { report_name: 'Radiology' } },
|
||||||
{ path: 'reports/notes', component: RoutePatientReports, props: { report_name: 'Notes' } },
|
{ path: 'reports/notes', component: RoutePatientReports, props: { report_name: 'Notes' } },
|
||||||
|
{ path: 'document', component: RoutePatientDocuments },
|
||||||
|
{ path: 'document/:tiu_da', component: RoutePatientDocuments },
|
||||||
] },
|
] },
|
||||||
{ path: '/planner', component: RoutePlanner },
|
{ path: '/planner', component: RoutePlanner },
|
||||||
{ path: '/recall', component: RouteRecall },
|
{ path: '/recall', component: RouteRecall },
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
{ name: 'Visits', href: '/patient/' + this.patient_dfn + '/visits' + (this.sensitive && '?viewsensitive' || '') },
|
{ name: 'Visits', href: '/patient/' + this.patient_dfn + '/visits' + (this.sensitive && '?viewsensitive' || '') },
|
||||||
{ name: 'Orders', href: '/patient/' + this.patient_dfn + '/orders' + (this.sensitive && '?viewsensitive' || '') },
|
{ name: 'Orders', href: '/patient/' + this.patient_dfn + '/orders' + (this.sensitive && '?viewsensitive' || '') },
|
||||||
{ name: 'Reports', href: '/patient/' + this.patient_dfn + '/reports' + (this.sensitive && '?viewsensitive' || '') },
|
{ name: 'Reports', href: '/patient/' + this.patient_dfn + '/reports' + (this.sensitive && '?viewsensitive' || '') },
|
||||||
|
{ name: 'Documents', href: '/patient/' + this.patient_dfn + '/document' + (this.sensitive && '?viewsensitive' || '') },
|
||||||
]
|
]
|
||||||
} : null;
|
} : null;
|
||||||
}
|
}
|
||||||
|
199
htdocs/RoutePatientDocuments.vue
Normal file
199
htdocs/RoutePatientDocuments.vue
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<Subtitle value="Documents" />
|
||||||
|
<Subtitle :value="patient_info.name" />
|
||||||
|
<div class="row">
|
||||||
|
<div class="selector col-12 col-xl-4">
|
||||||
|
<div class="card mb-3 shadow">
|
||||||
|
<div class="card-header"><template v-if="resultset.length > 0">{{resultset.length}}<template v-if="has_more">+</template></template><template v-else>No</template> record{{resultset.length == 1 ? '' : 's'}}</div>
|
||||||
|
<ul class="scroller list-group list-group-flush" ref="scroller">
|
||||||
|
<router-link v-for="item in resultset" :to="'/patient/' + patient_dfn + '/document/' + item.IEN + (sensitive ? '?viewsensitive' : '')" replace custom v-slot="{ navigate, href }">
|
||||||
|
<li :key="item" class="record" :class="{ 'active': selection == item.IEN }" :title="datetimestring(strptime_vista(item.time)) + '\n' + item.title + '\n' + item.location + '\n' + item.author.byline" @click="navigate">
|
||||||
|
<div class="row">
|
||||||
|
<div class="cell col-4"><router-link :to="href" replace>{{datestring(strptime_vista(item.time))}}</router-link></div>
|
||||||
|
<div class="cell col-8">{{item.title}}</div>
|
||||||
|
<div class="cell secondary col-7 col-lg-4 col-xl-7">{{item.location}}</div>
|
||||||
|
<div class="cell secondary col-5 col-lg-8 col-xl-5">{{item.author.byline}}</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</router-link>
|
||||||
|
<li ref="bottom" />
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="selection_text" class="col-12 col-xl-8">
|
||||||
|
<div class="card mb-3 shadow">
|
||||||
|
<div class="card-header">{{doctitle(selection_text) || 'Document'}}</div>
|
||||||
|
<div class="detail card-body">{{selection_text}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
div.selector {
|
||||||
|
position: sticky;
|
||||||
|
top: 1.15rem;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
ul.scroller {
|
||||||
|
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 a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
li.record.active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #0d6efd;
|
||||||
|
}
|
||||||
|
div.cell {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
div.detail {
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
div.selector {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
ul.scroller {
|
||||||
|
max-height: 75vh;
|
||||||
|
}
|
||||||
|
div.cell.secondary {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
div.detail {
|
||||||
|
max-height: 75vh;
|
||||||
|
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_text: null,
|
||||||
|
observer_bottom: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$route.params.tiu_da': {
|
||||||
|
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');
|
||||||
|
},
|
||||||
|
doctitle(doc) {
|
||||||
|
if(doc) {
|
||||||
|
var brk = doc.indexOf('\r\n');
|
||||||
|
if(brk >= 0) {
|
||||||
|
doc = doc.substring(0, brk);
|
||||||
|
brk = doc.indexOf(': ');
|
||||||
|
if(brk >= 0) return doc.substring(brk + 2).replace(/^\s+|\s+$/g, '');
|
||||||
|
else return doc.replace(/^\s+|\s+$/g, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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.has_more = '';
|
||||||
|
this.resultset = [];
|
||||||
|
}
|
||||||
|
var res = await client.TIU_DOCUMENTS_BY_CONTEXT(3, 1, this.patient_dfn, -1, -1, 0, SZ_WINDOW, 'D', 1, 0, 1, this.has_more);
|
||||||
|
if((res) && (res.length > 0)) {
|
||||||
|
res = res.slice();
|
||||||
|
var last = res[res.length - 1];
|
||||||
|
if((last.title == 'SHOW MORE') && (!last.author) && (!last.visit)) {
|
||||||
|
this.has_more = last.IEN;
|
||||||
|
res.splice(res.length - 1, 1);
|
||||||
|
}
|
||||||
|
if(this.resultset.length > 0) Array.prototype.push.apply(this.resultset, res);
|
||||||
|
else this.resultset = res;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.dfn = null;
|
||||||
|
this.has_more = '';
|
||||||
|
this.resultset = [];
|
||||||
|
}
|
||||||
|
} catch(ex) {
|
||||||
|
console.warn(ex);
|
||||||
|
} finally {
|
||||||
|
this.is_loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handle_bottom([entry]) {
|
||||||
|
if((entry.isIntersecting) && (this.has_more) && (!this.is_loading)) this.load_more();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$watch(
|
||||||
|
() => (this.client, this.patient_dfn, {}),
|
||||||
|
debounce(this.load_more, 500),
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
this.$watch(
|
||||||
|
() => (this.client, this.selection, {}),
|
||||||
|
debounce(async function() {
|
||||||
|
try {
|
||||||
|
this.selection_text = (this.client) && (this.selection) ? await this.client.TIU_GET_RECORD_TEXT(this.selection) : null;
|
||||||
|
} catch(ex) {
|
||||||
|
this.selection_text = null;
|
||||||
|
console.warn(ex);
|
||||||
|
}
|
||||||
|
}, 500),
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.observer_bottom = new IntersectionObserver(this.handle_bottom, { root: this.$refs.scroller, rootMargin: '25%' });
|
||||||
|
this.observer_bottom.observe(this.$refs.bottom);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
if(this.observer_bottom) this.observer_bottom.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -186,6 +186,12 @@ export const d_parse_multireport = data => {
|
|||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const d_parse_tiudocumentlist = data => d_split(data, '^', 'IEN', 'title', 'time', 'patient', 'author', 'location', 'status', 'visit').map(row => {
|
||||||
|
row.author = row.author ? d_split1(row.author, ';', 'IEN', 'byline', 'name') : null;
|
||||||
|
row.visit = row.visit ? d_split1(row.visit, ';', 'date', 'time') : null;
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
|
||||||
export function memoized(fn) {
|
export function memoized(fn) {
|
||||||
var cache = {};
|
var cache = {};
|
||||||
return async function(...args) {
|
return async function(...args) {
|
||||||
@ -300,6 +306,8 @@ export function Client(cid, secret) {
|
|||||||
this.TIU_TEMPLATE_DELETE = aflow((...args) => this.call({ method: 'TIU_TEMPLATE_DELETE', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), d_log, d_unwrap);
|
this.TIU_TEMPLATE_DELETE = aflow((...args) => this.call({ method: 'TIU_TEMPLATE_DELETE', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), d_log, d_unwrap);
|
||||||
this.TIU_TEMPLATE_LOCK = aflow((...args) => this.call({ method: 'TIU_TEMPLATE_LOCK', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), d_log, d_unwrap);
|
this.TIU_TEMPLATE_LOCK = aflow((...args) => this.call({ method: 'TIU_TEMPLATE_LOCK', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), d_log, d_unwrap);
|
||||||
this.TIU_TEMPLATE_UNLOCK = aflow((...args) => this.call({ method: 'TIU_TEMPLATE_UNLOCK', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), d_log, d_unwrap);
|
this.TIU_TEMPLATE_UNLOCK = aflow((...args) => this.call({ method: 'TIU_TEMPLATE_UNLOCK', context: ['OR CPRS GUI CHART'], ttl: 0, stale: false }, ...args), d_log, d_unwrap);
|
||||||
|
this.TIU_DOCUMENTS_BY_CONTEXT = aflow((...args) => this.call({ method: 'TIU_DOCUMENTS_BY_CONTEXT', context: ['OR CPRS GUI CHART'], ttl: 60, stale: false }, ...args), d_log, d_unwrap, d_parse_array, d_parse_tiudocumentlist);
|
||||||
|
this.TIU_GET_RECORD_TEXT = aflow((...args) => this.call({ method: 'TIU_GET_RECORD_TEXT', context: ['OR CPRS GUI CHART'], ttl: 60, stale: false }, ...args), d_log, d_unwrap, d_parse_text);
|
||||||
|
|
||||||
this.ORWCV_VST = memoized(aflow((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWCV_VST', ...args), d_log, d_unwrap, f_split('^', 'apptinfo', 'datetime', 'location', 'status')));
|
this.ORWCV_VST = memoized(aflow((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWCV_VST', ...args), d_log, d_unwrap, f_split('^', 'apptinfo', 'datetime', 'location', 'status')));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user