2023-05-10 21:55:15 -04:00
|
|
|
<template>
|
|
|
|
<Subtitle value="Documents" />
|
|
|
|
<Subtitle :value="patient_info.name" />
|
|
|
|
<div class="row">
|
2023-08-08 22:08:08 -04:00
|
|
|
<div class="selector col-12" :class="{ 'col-xl-4': selection }">
|
2023-05-10 21:55:15 -04:00
|
|
|
<div class="card mb-3 shadow">
|
2023-08-08 22:08:08 -04:00
|
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
|
|
<span><template v-if="resultset.length > 0">{{resultset.length}}<template v-if="has_more">+</template></template><template v-else-if="is_loading">Loading</template><template v-else>No</template> record{{resultset.length == 1 ? '' : 's'}}</span>
|
|
|
|
<router-link :to="'/patient/' + patient_dfn + '/document/new'">
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" style="width: 1.25em; height: 1.25em; vertical-align: text-bottom;">
|
|
|
|
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
|
|
|
|
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
|
|
|
|
</svg>
|
|
|
|
</router-link>
|
|
|
|
</div>
|
|
|
|
<ul class="scroller list-group list-group-flush" :class="{ 'list-skinny': selection }" ref="scroller">
|
2023-05-16 23:21:15 -04:00
|
|
|
<router-link v-for="item in resultset" :to="'/patient/' + patient_dfn + '/document/' + item.IEN" replace custom v-slot="{ navigate, href }">
|
2023-05-25 06:24:20 -04:00
|
|
|
<li :key="item" class="record list-group-item" :class="{ 'active': selection == item.IEN }" :title="datetimestring(strptime_vista(item.time)) + '\n' + item.title + '\n' + item.location + '\n' + item.author.byline" @click="navigate">
|
2023-05-10 21:55:15 -04:00
|
|
|
<div class="row">
|
|
|
|
<div class="cell col-4"><router-link :to="href" replace>{{datestring(strptime_vista(item.time))}}</router-link></div>
|
2023-08-08 22:08:08 -04:00
|
|
|
<div class="cell col-8"><template v-if="item.status == 'unsigned'">✱</template>{{item.title}}</div>
|
2023-05-10 21:55:15 -04:00
|
|
|
<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>
|
2023-05-25 06:26:32 -04:00
|
|
|
<li class="bottom list-group-item" ref="bottom" />
|
2023-05-10 21:55:15 -04:00
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-08-08 22:08:08 -04:00
|
|
|
<div v-if="selection == 'new'" class="col-12 col-xl-8">
|
|
|
|
<ViewDocNew :client="client" :dfn="patient_dfn" :datetime="datetimestring(new Date())" @cancel="() => $router.replace({ path: '/patient/' + patient_dfn + '/document' })" @submit="doc_create" />
|
|
|
|
</div>
|
|
|
|
<div v-else-if="selection" class="detail col-12 col-xl-8" ref="detail">
|
|
|
|
<ViewDocView :client="client" :dfn="patient_dfn" :ien="selection" @sign="doc_sign_prompt" @delete="doc_delete" @cancel="() => $router.replace({ path: '/patient/' + patient_dfn + '/document' })" />
|
2023-05-10 21:55:15 -04:00
|
|
|
</div>
|
|
|
|
</div>
|
2023-08-08 22:08:08 -04:00
|
|
|
<ModalPromptSignatureCode :client="client" v-model:show="show_signature" @submit="doc_sign" label="Sign Document" />
|
2023-05-10 21:55:15 -04:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
div.selector {
|
|
|
|
position: sticky;
|
|
|
|
top: 1.15rem;
|
|
|
|
z-index: 1;
|
|
|
|
}
|
2023-05-25 06:25:41 -04:00
|
|
|
ul.scroller.list-skinny {
|
2023-05-10 21:55:15 -04:00
|
|
|
max-height: 25vh;
|
|
|
|
overflow-y: auto;
|
|
|
|
}
|
|
|
|
li.record {
|
|
|
|
cursor: default;
|
|
|
|
border-top: 1px solid #dee2e6;
|
|
|
|
padding: 0.25rem 0.75rem;
|
2023-05-29 18:23:01 -04:00
|
|
|
scroll-margin-top: 3.6875rem;
|
2023-05-10 21:55:15 -04:00
|
|
|
}
|
|
|
|
li.record:nth-child(even) {
|
|
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
|
|
}
|
2023-05-29 18:23:01 -04:00
|
|
|
ul.scroller.list-skinny li.record {
|
|
|
|
scroll-margin-top: 0;
|
|
|
|
}
|
2023-05-10 21:55:15 -04:00
|
|
|
li.record a {
|
|
|
|
color: inherit;
|
|
|
|
}
|
|
|
|
li.record.active {
|
|
|
|
color: #fff;
|
|
|
|
background-color: #0d6efd;
|
|
|
|
}
|
2023-05-25 06:26:32 -04:00
|
|
|
li.bottom {
|
|
|
|
overflow-anchor: none;
|
|
|
|
}
|
2023-05-10 21:55:15 -04:00
|
|
|
div.cell {
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
white-space: nowrap;
|
|
|
|
}
|
2023-05-25 06:25:41 -04:00
|
|
|
a.close {
|
|
|
|
cursor: default;
|
|
|
|
text-decoration: none;
|
|
|
|
}
|
2023-08-08 22:08:08 -04:00
|
|
|
div.detail /deep/ .card-body {
|
2023-05-29 18:23:01 -04:00
|
|
|
scroll-margin-top: calc(3.6875rem + 2.5625rem + 25vh);
|
2023-05-10 21:55:15 -04:00
|
|
|
font-family: monospace;
|
|
|
|
white-space: pre-wrap;
|
|
|
|
}
|
|
|
|
@media (min-width: 1200px) {
|
|
|
|
div.selector {
|
|
|
|
position: static;
|
|
|
|
}
|
2023-05-25 06:25:41 -04:00
|
|
|
ul.scroller.list-skinny {
|
2023-05-10 21:55:15 -04:00
|
|
|
max-height: 75vh;
|
|
|
|
}
|
|
|
|
div.cell.secondary {
|
|
|
|
font-size: 0.8em;
|
|
|
|
}
|
2023-08-08 22:08:08 -04:00
|
|
|
div.detail /deep/ .card-body {
|
2023-05-10 21:55:15 -04:00
|
|
|
max-height: 75vh;
|
2023-05-29 18:23:01 -04:00
|
|
|
scroll-margin-top: 0;
|
2023-05-10 21:55:15 -04:00
|
|
|
overflow-y: auto;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
import { debounce, strptime_vista } from './util.mjs';
|
|
|
|
|
|
|
|
import Subtitle from './Subtitle.vue';
|
2023-08-08 22:08:08 -04:00
|
|
|
import ViewDocNew from './ViewDocNew.vue';
|
|
|
|
import ViewDocView from './ViewDocView.vue';
|
|
|
|
import ModalPromptSignatureCode from './ModalPromptSignatureCode.vue';
|
2023-05-10 21:55:15 -04:00
|
|
|
|
|
|
|
const SZ_WINDOW = 100;
|
|
|
|
|
|
|
|
export default {
|
|
|
|
components: {
|
2023-08-08 22:08:08 -04:00
|
|
|
Subtitle, ViewDocNew, ViewDocView, ModalPromptSignatureCode
|
2023-05-10 21:55:15 -04:00
|
|
|
},
|
|
|
|
props: {
|
|
|
|
client: Object,
|
|
|
|
sensitive: Boolean,
|
|
|
|
patient_dfn: String,
|
|
|
|
patient_info: Object
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
dfn: null,
|
|
|
|
has_more: '',
|
|
|
|
is_loading: false,
|
2023-08-08 22:08:08 -04:00
|
|
|
rs_unsigned: [],
|
|
|
|
rs_signed: [],
|
2023-05-10 21:55:15 -04:00
|
|
|
selection: null,
|
2023-08-08 22:08:08 -04:00
|
|
|
show_signature: false,
|
2023-05-25 06:25:41 -04:00
|
|
|
observer_scroller: null,
|
|
|
|
observer_viewport: null
|
2023-05-10 21:55:15 -04:00
|
|
|
};
|
|
|
|
},
|
2023-08-08 22:08:08 -04:00
|
|
|
computed: {
|
|
|
|
resultset() {
|
|
|
|
return this.rs_unsigned.concat(this.rs_signed);
|
|
|
|
}
|
|
|
|
},
|
2023-05-10 21:55:15 -04:00
|
|
|
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');
|
|
|
|
},
|
|
|
|
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 = '';
|
2023-08-08 22:08:08 -04:00
|
|
|
this.rs_signed = [];
|
2023-05-10 21:55:15 -04:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
2023-08-08 22:08:08 -04:00
|
|
|
if(this.rs_signed.length > 0) Array.prototype.push.apply(this.rs_signed, res);
|
|
|
|
else this.rs_signed = res;
|
2023-05-10 21:55:15 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.dfn = null;
|
|
|
|
this.has_more = '';
|
2023-08-08 22:08:08 -04:00
|
|
|
this.rs_signed = [];
|
2023-05-10 21:55:15 -04:00
|
|
|
}
|
|
|
|
} catch(ex) {
|
|
|
|
console.warn(ex);
|
|
|
|
} finally {
|
|
|
|
this.is_loading = false;
|
|
|
|
}
|
2023-08-08 22:08:08 -04:00
|
|
|
},
|
|
|
|
async load_unsigned() {
|
|
|
|
this.rs_unsigned = [];
|
|
|
|
this.rs_unsigned = await client.TIU_DOCUMENTS_BY_CONTEXT(3, 2, this.patient_dfn, 0, 0, 0, 0, 'D', 1, 0, 1, '');
|
|
|
|
},
|
|
|
|
async reload() {
|
|
|
|
this.dfn = null;
|
|
|
|
await client.TIU_DOCUMENTS_BY_CONTEXT_FLUSH(3, 2, this.patient_dfn, 0, 0, 0, 0, 'D', 1, 0, 1, '');
|
|
|
|
await this.load_unsigned();
|
|
|
|
await client.TIU_DOCUMENTS_BY_CONTEXT_FLUSH(3, 1, this.patient_dfn, -1, -1, 0, SZ_WINDOW, 'D', 1, 0, 1, '');
|
|
|
|
await this.load_more();
|
|
|
|
},
|
|
|
|
async doc_create(params) {
|
|
|
|
var vstr = params.location.datetime ? ('' + params.location.IEN + ';' + params.location.datetime + ';A') : ('' + params.location.IEN + ';' + params.datetime + ';E');
|
|
|
|
var res = await this.client.TIU_CREATE_RECORD(this.patient_dfn, params.title, '', '', '', { '".01"': params.title, '"1202"': params.author, '"1301"': params.datetime, '"1205"': params.location.IEN }, vstr, '1');
|
|
|
|
if(res) {
|
|
|
|
this.reload();
|
|
|
|
this.$router.replace({ path: '/patient/' + this.patient_dfn + '/document/' + res, query: { edit: '' } });
|
|
|
|
} else {
|
|
|
|
console.error('Unable to create document', params, res);
|
|
|
|
window.alert('Unable to create document.');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
doc_sign_prompt(ien) {
|
|
|
|
this.show_signature = true;
|
|
|
|
},
|
|
|
|
async doc_sign(code) {
|
|
|
|
var selection = this.selection;
|
|
|
|
if((selection) & (code)) {
|
|
|
|
this.show_signature = false;
|
|
|
|
await this.client.TIU_SIGN_RECORD(selection, ' ' + code + ' ');
|
|
|
|
this.reload();
|
|
|
|
this.selection = null;
|
|
|
|
await this.$nextTick();
|
|
|
|
this.selection = selection;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
async doc_delete(ien) {
|
|
|
|
if(window.confirm('Delete this document?')) {
|
|
|
|
var vstr = await this.client.ORWPCE_NOTEVSTR(ien);
|
|
|
|
if(vstr) await this.client.ORWPCE_DELETE(vstr, this.patient_dfn);
|
|
|
|
await this.client.TIU_DELETE_RECORD(ien);
|
|
|
|
this.reload();
|
|
|
|
if(this.selection == ien) {
|
|
|
|
this.selection = null;
|
|
|
|
this.$router.replace({ path: '/patient/' + this.patient_dfn + '/document' });
|
|
|
|
}
|
|
|
|
}
|
2023-05-10 21:55:15 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
created() {
|
|
|
|
this.$watch(
|
|
|
|
() => (this.client, this.patient_dfn, {}),
|
2023-08-08 22:08:08 -04:00
|
|
|
debounce(() => { this.load_more(); this.load_unsigned(); }, 500),
|
2023-05-10 21:55:15 -04:00
|
|
|
{ immediate: true }
|
|
|
|
);
|
|
|
|
this.$watch(
|
|
|
|
() => (this.client, this.selection, {}),
|
2023-05-16 23:21:32 -04:00
|
|
|
async function() {
|
2023-05-29 18:23:01 -04:00
|
|
|
if(this.$refs.scroller) {
|
2023-08-08 22:08:08 -04:00
|
|
|
if(this.selection) { // scroll to selected item
|
|
|
|
if(this.selection != 'new') {
|
|
|
|
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;
|
|
|
|
}
|
2023-05-29 18:23:01 -04:00
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-16 23:21:32 -04:00
|
|
|
},
|
2023-05-10 21:55:15 -04:00
|
|
|
{ immediate: true }
|
|
|
|
);
|
|
|
|
},
|
|
|
|
mounted() {
|
2023-08-08 22:08:08 -04:00
|
|
|
this.observer_scroller = new IntersectionObserver(([entry]) => { if((entry.isIntersecting) && (this.selection) && (this.has_more) && (!this.is_loading)) this.load_more(); }, { root: this.$refs.scroller, rootMargin: '25%' });
|
2023-05-25 06:25:41 -04:00
|
|
|
this.observer_scroller.observe(this.$refs.bottom);
|
2023-08-08 22:08:08 -04:00
|
|
|
this.observer_viewport = new IntersectionObserver(([entry]) => { if((entry.isIntersecting) && (!this.selection) && (this.has_more) && (!this.is_loading)) this.load_more(); }, { rootMargin: '25%' });
|
2023-05-25 06:25:41 -04:00
|
|
|
this.observer_viewport.observe(this.$refs.bottom);
|
2023-05-10 21:55:15 -04:00
|
|
|
},
|
|
|
|
destroyed() {
|
2023-05-25 06:25:41 -04:00
|
|
|
if(this.observer_viewport) this.observer_viewport.disconnect();
|
|
|
|
if(this.observer_scroller) this.observer_scroller.disconnect();
|
2023-05-10 21:55:15 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
</script>
|