Compare commits
5 Commits
25772419a0
...
f2dcb718c7
Author | SHA1 | Date | |
---|---|---|---|
f2dcb718c7 | |||
426d50d5f7 | |||
7f42be2e64 | |||
9687d9638e | |||
91f2c45e4f |
@ -14,6 +14,7 @@
|
|||||||
import RouteSchedule from './RouteSchedule.vue';
|
import RouteSchedule from './RouteSchedule.vue';
|
||||||
import RoutePatientLookup from './RoutePatientLookup.vue';
|
import RoutePatientLookup from './RoutePatientLookup.vue';
|
||||||
import RoutePatientDetail from './RoutePatientDetail.vue';
|
import RoutePatientDetail from './RoutePatientDetail.vue';
|
||||||
|
import RouteRecall from './RouteRecall.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -42,6 +43,7 @@
|
|||||||
{ path: '/', component: RouteSchedule, props: { client: this.client } },
|
{ path: '/', component: RouteSchedule, props: { client: this.client } },
|
||||||
{ path: '/patient', component: RoutePatientLookup, props: { client: this.client } },
|
{ path: '/patient', component: RoutePatientLookup, props: { client: this.client } },
|
||||||
{ path: '/patient/:id', component: RoutePatientDetail, props: { client: this.client } },
|
{ path: '/patient/:id', component: RoutePatientDetail, props: { client: this.client } },
|
||||||
|
{ path: '/recall', component: RouteRecall, props: { client: this.client } },
|
||||||
].forEach(route => this.$root.$router.addRoute(route));
|
].forEach(route => this.$root.$router.addRoute(route));
|
||||||
await this.$root.$router.replace(this.$route);
|
await this.$root.$router.replace(this.$route);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="navbar navbar-expand-lg bg-dark">
|
<nav class="navbar navbar-expand-lg bg-dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/"><template v-if="user">{{user[2]}}</template><template v-else>nuVistA</template></a>
|
<router-link class="navbar-brand" to="/"><template v-if="user">{{user[2]}}</template><template v-else>nuVistA</template></router-link>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/">Schedule</a>
|
<router-link class="nav-link" to="/">Schedule</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/patient">Patient</a>
|
<router-link class="nav-link" to="/patient">Patient</router-link>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<router-link class="nav-link" to="/recall">Recall</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" v-if="user">
|
<li class="nav-item" v-if="user">
|
||||||
<a class="nav-link disabled">{{user[3]}}</a>
|
<a class="nav-link disabled">{{user[3]}}</a>
|
||||||
|
@ -15,17 +15,17 @@
|
|||||||
<div class="card mb-3 shadow">
|
<div class="card mb-3 shadow">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<span>Data</span>
|
<span>Data</span>
|
||||||
<DateRangePicker range="1M" direction="-1" v-model:date="vitals_date" v-model:date_end="vitals_date_begin" />
|
<DateRangePicker range="1M" direction="-1" v-model:date="report_date" v-model:date_end="report_date_begin" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ViewVitalsLabs :client="client" :dfn="dfn" :date_begin="vitals_date_begin" :date_end="vitals_date" />
|
<ViewVitalsLabs :client="client" :dfn="dfn" :date_begin="report_date_begin" :date_end="report_date" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { uniq, groupByArray, strptime_vista } from './util.mjs';
|
import { strptime_vista } from './util.mjs';
|
||||||
|
|
||||||
import DateRangePicker from './DateRangePicker.vue';
|
import DateRangePicker from './DateRangePicker.vue';
|
||||||
import ViewVitalsLabs from './ViewVitalsLabs.vue';
|
import ViewVitalsLabs from './ViewVitalsLabs.vue';
|
||||||
@ -43,10 +43,8 @@
|
|||||||
return {
|
return {
|
||||||
dfn: null,
|
dfn: null,
|
||||||
info: null,
|
info: null,
|
||||||
vitals_date: now,
|
report_date: now,
|
||||||
vitals_date_begin: now,
|
report_date_begin: now
|
||||||
labs_date: now,
|
|
||||||
labs_date_begin: now
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -23,10 +23,6 @@
|
|||||||
return {
|
return {
|
||||||
selection: null
|
selection: null
|
||||||
};
|
};
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
121
htdocs/RouteRecall.vue
Normal file
121
htdocs/RouteRecall.vue
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="card mb-3 shadow">
|
||||||
|
<div class="card-header">Clinics</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ViewResourceLookup :client="client" v-model:selection="selection" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card mb-3 shadow">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<span>Recall list ({{patients_lost.length + patients_outstanding.length}})</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table" style="font-family: monospace;" v-if="patients_lost && patients_lost.length > 0">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Lost ({{patients_lost.length}})</th><th>Last appt</th><th>Clinic</th><th>Days</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in patients_lost" :style="{ backgroundColor: strHashHSL(row.Clinic, '90%') }">
|
||||||
|
<td v-if="production"><router-link :to="'/patient/$' + row.key">{{row.Name}} ${{row.key}}</router-link></td>
|
||||||
|
<td v-else><router-link :title="strtr_unscramble(row.Name)" :to="'/patient/$' + row.Name.charAt(0) + row.key.slice(-4) + '?name=' + row.Name">{{row.Name}} ${{row.key}}</router-link></td>
|
||||||
|
<td>{{datefmt(row.TimeLast)}} {{dow[row.TimeLast.getDay()]}}</td>
|
||||||
|
<td>{{row.Clinic}}</td>
|
||||||
|
<td>{{Math.round(row.TimeLastDiff/86400000)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="table" style="font-family: monospace;" v-if="patients_outstanding && patients_outstanding.length > 0">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Outstanding ({{patients_outstanding.length}})</th><th>Next appt</th><th>Clinic</th><th>Days</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in patients_outstanding" :style="{ backgroundColor: strHashHSL(row.Clinic, '90%') }">
|
||||||
|
<td v-if="production"><router-link :to="'/patient/$' + row.key">{{row.Name}} ${{row.key}}</router-link></td>
|
||||||
|
<td v-else><router-link :title="strtr_unscramble(row.Name)" :to="'/patient/$' + row.Name.charAt(0) + row.key.slice(-4) + '?name=' + row.Name">{{row.Name}} ${{row.key}}</router-link></td>
|
||||||
|
<td>{{datefmt(row.TimeNext)}} {{dow[row.TimeNext.getDay()]}}</td>
|
||||||
|
<td>{{row.Clinic}}</td>
|
||||||
|
<td>{{Math.round(row.TimeNextDiff/86400000)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import cookie from './cookie.mjs';
|
||||||
|
import { groupByArray, strtr_unscramble, strHashHSL, strftime_vista, debounce } from './util.mjs';
|
||||||
|
|
||||||
|
import ViewResourceLookup from './ViewResourceLookup.vue';
|
||||||
|
|
||||||
|
function dateonly(date) {
|
||||||
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ViewResourceLookup
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
client: Object
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
var resources = cookie.get('vista.resources');
|
||||||
|
var today = dateonly(new Date());
|
||||||
|
return {
|
||||||
|
selection: resources ? (resources.split(',').filter(x => x) || []) : [],
|
||||||
|
patients: [],
|
||||||
|
production: true,
|
||||||
|
date_begin: new Date(today.getFullYear() - 1, today.getMonth(), today.getDate()),
|
||||||
|
date_end: new Date(today.getFullYear() + 1, today.getMonth(), today.getDate()),
|
||||||
|
dow: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
patients_lost() {
|
||||||
|
return this.patients.filter(x => x.TimeLastDiff >= 0).sort((a, b) => b.TimeLastDiff - a.TimeLastDiff);
|
||||||
|
},
|
||||||
|
patients_outstanding() {
|
||||||
|
return this.patients.filter(x => x.TimeNextDiff >= 0).sort((a, b) => b.TimeNextDiff - a.TimeNextDiff);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selection(value) {
|
||||||
|
this.debounced_selection(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
strHashHSL,
|
||||||
|
strtr_unscramble,
|
||||||
|
datefmt(date) {
|
||||||
|
return date ? date.toLocaleDateString('en-CA') : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.debounced_selection = debounce(async function(value) {
|
||||||
|
cookie.set('vista.resources', value.join(','), 7);
|
||||||
|
var patients = this.selection.length > 0 ? groupByArray(await this.client.SDEC_CLINLET(this.selection.join('|') + '|', strftime_vista(this.date_begin), strftime_vista(this.date_end)), x => x.HRN) : [], now = new Date(), group, values, appt;
|
||||||
|
for(var i = patients.length - 1; i >= 0; --i) {
|
||||||
|
group = patients[i];
|
||||||
|
group.Name = group.values[0].Name;
|
||||||
|
group.DOB = group.values[0].DOB;
|
||||||
|
group.values = values = group.values.map(function(x) { return { Time: new Date(x.ApptDate), Clinic: x.Clinic }; }).sort((a, b) => a.Time - b.Time);
|
||||||
|
group.TimeLast = (appt = group.values[group.values.length - 1]).Time;
|
||||||
|
group.TimeLastDiff = now - group.TimeLast;
|
||||||
|
group.Clinic = appt.Clinic;
|
||||||
|
if(group.TimeLastDiff < 0) for(var j = 0; j < values.length; ++j) if(values[j].Time - now > 0) {
|
||||||
|
group.TimeNext = values[j].Time;
|
||||||
|
group.TimeNextDiff = group.TimeNext - now;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.patients = patients.sort((a, b) => a.Time - b.Time);
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.production = (await this.client.serverinfo()).result.production == '1';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -12,7 +12,7 @@
|
|||||||
<DateRangePicker range="1D" direction="+1" v-model:date="date" v-model:date_end="date_end" />
|
<DateRangePicker range="1D" direction="+1" v-model:date="date" v-model:date_end="date_end" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<ViewSchedule :client="client" :selection="selection" :date_begin="datefmt(date)" :date_end="datefmt(new Date(date_end.getTime() - 1))" />
|
<ViewSchedule :client="client" :selection="selection" :date_begin="date" :date_end="new Date(date_end.getTime() - 1)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,12 +48,6 @@
|
|||||||
selection(value, oldvalue) {
|
selection(value, oldvalue) {
|
||||||
cookie.set('vista.resources', value.join(','), 7);
|
cookie.set('vista.resources', value.join(','), 7);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
datefmt(date) {
|
|
||||||
return date ? date.toLocaleDateString('en-CA') : '';
|
|
||||||
//return (new Date(date.getTime() + date.getTimezoneOffset()*60000)).toLocaleDateString('en-CA');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<td>{{row.ApptDate}}</td>
|
<td>{{row.ApptDate}}</td>
|
||||||
<td>{{row.Clinic}}</td>
|
<td>{{row.Clinic}}</td>
|
||||||
<td v-if="production"><router-link :to="'/patient/$' + row.HRN">{{row.Name}} ${{row.HRN}}</router-link></td>
|
<td v-if="production"><router-link :to="'/patient/$' + row.HRN">{{row.Name}} ${{row.HRN}}</router-link></td>
|
||||||
<td v-else><router-link :title="unscramble(row.Name)" :to="'/patient/$' + row.Name.charAt(0) + row.HRN.slice(-4) + '?name=' + row.Name">{{row.Name}} ${{row.HRN}}</router-link></td>
|
<td v-else><router-link :title="strtr_unscramble(row.Name)" :to="'/patient/$' + row.Name.charAt(0) + row.HRN.slice(-4) + '?name=' + row.Name">{{row.Name}} ${{row.HRN}}</router-link></td>
|
||||||
<td>{{row.NOTE}} [{{row.APPT_MADE_BY}} on {{row.DATE_APPT_MADE}}]</td>
|
<td>{{row.NOTE}} [{{row.APPT_MADE_BY}} on {{row.DATE_APPT_MADE}}]</td>
|
||||||
<td><Autocomplete :value="practitioner[row.Name]" @update:value="x => set_practitioner(row.Name, x)" :items="practitioner_list" /></td>
|
<td><Autocomplete :value="practitioner[row.Name]" @update:value="x => set_practitioner(row.Name, x)" :items="practitioner_list" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -18,25 +18,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import cookie from './cookie.mjs';
|
import cookie from './cookie.mjs';
|
||||||
import { uniq, strHashHSL } from './util.mjs';
|
import { uniq, strtr_unscramble, strHashHSL, strfdate_vista, debounce } from './util.mjs';
|
||||||
|
|
||||||
import Autocomplete from './Autocomplete.vue';
|
import Autocomplete from './Autocomplete.vue';
|
||||||
|
|
||||||
function datefm(datestr) {
|
|
||||||
var date = datestr ? new Date(datestr) : new Date();
|
|
||||||
date = new Date(date.getTime() + date.getTimezoneOffset()*60000);
|
|
||||||
return 10000*(date.getFullYear() - 1700) + 100*(date.getMonth() + 1) + date.getDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
function strtr(s, a, b) {
|
|
||||||
var res = '';
|
|
||||||
for(var i = 0; i < s.length; ++i) {
|
|
||||||
var j = a.indexOf(s.charAt(i));
|
|
||||||
res += j >= 0 ? b.charAt(j) : s.charAt(i);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Autocomplete
|
Autocomplete
|
||||||
@ -47,8 +32,8 @@
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: []
|
default: []
|
||||||
},
|
},
|
||||||
date_begin: String,
|
date_begin: Date,
|
||||||
date_end: String
|
date_end: Date
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -66,20 +51,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
async params(value) {
|
params(value) {
|
||||||
this.appointments = value.selection.length > 0 ? (await this.client.SDEC_CLINLET(value.selection.join('|') + '|', datefm(value.date_begin), datefm(value.date_end))).sort((a, b) => (new Date(a.ApptDate)) - (new Date(b.ApptDate))) : [];
|
this.debounced_params(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
strHashHSL,
|
strHashHSL,
|
||||||
unscramble(name) {
|
strtr_unscramble,
|
||||||
return name.length > 0 ? (name.charAt(0) + strtr(name.substring(1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'LKJIHGFEDCBAZYXWVUTSRQPONM')) : name;
|
|
||||||
},
|
|
||||||
set_practitioner(patient, practitioner) {
|
set_practitioner(patient, practitioner) {
|
||||||
this.practitioner[patient] = practitioner;
|
this.practitioner[patient] = practitioner;
|
||||||
cookie.set('vista.practitioner', JSON.stringify(this.practitioner), 1);
|
cookie.set('vista.practitioner', JSON.stringify(this.practitioner), 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
this.debounced_params = debounce(async function(value) { this.appointments = value.selection.length > 0 ? (await this.client.SDEC_CLINLET(value.selection.join('|') + '|', strfdate_vista(value.date_begin), strfdate_vista(value.date_end))).sort((a, b) => (new Date(a.ApptDate)) - (new Date(b.ApptDate))) : []; }, 500);
|
||||||
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
var practitioner = cookie.get('vista.practitioner');
|
var practitioner = cookie.get('vista.practitioner');
|
||||||
if(practitioner) this.practitioner = JSON.parse(practitioner);
|
if(practitioner) this.practitioner = JSON.parse(practitioner);
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { strftime_vista, strptime_vista } from './util.mjs';
|
import { strftime_vista } from './util.mjs';
|
||||||
|
|
||||||
import ViewData from './ViewData.vue';
|
import ViewData from './ViewData.vue';
|
||||||
|
|
||||||
|
@ -35,6 +35,19 @@ export function quantile_sorted(arr_sorted, quantile) {
|
|||||||
return arr_sorted[base + 1] !== undefined ? arr_sorted[base] + rest * (arr_sorted[base + 1] - arr_sorted[base]) : arr_sorted[base];
|
return arr_sorted[base + 1] !== undefined ? arr_sorted[base] + rest * (arr_sorted[base + 1] - arr_sorted[base]) : arr_sorted[base];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function strtr(s, a, b) {
|
||||||
|
var res = '';
|
||||||
|
for(var i = 0; i < s.length; ++i) {
|
||||||
|
var j = a.indexOf(s.charAt(i));
|
||||||
|
res += j >= 0 ? b.charAt(j) : s.charAt(i);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function strtr_unscramble(name) {
|
||||||
|
return name.length > 0 ? (name.charAt(0) + strtr(name.substring(1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'LKJIHGFEDCBAZYXWVUTSRQPONM')) : name;
|
||||||
|
}
|
||||||
|
|
||||||
export function strHashCode(str) {
|
export function strHashCode(str) {
|
||||||
var hash = 0;
|
var hash = 0;
|
||||||
for(var i = 0; i < str.length; ++i) hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
for(var i = 0; i < str.length; ++i) hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
@ -63,6 +76,10 @@ export function strftime_vista(date) {
|
|||||||
return 10000*(date.getFullYear() - 1700) + 100*(date.getMonth() + 1) + date.getDate() + date.getHours()/100 + date.getMinutes()/10000 + date.getSeconds()/1000000 + date.getMilliseconds()/1000000000;
|
return 10000*(date.getFullYear() - 1700) + 100*(date.getMonth() + 1) + date.getDate() + date.getHours()/100 + date.getMinutes()/10000 + date.getSeconds()/1000000 + date.getMilliseconds()/1000000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function strfdate_vista(date) {
|
||||||
|
return 10000*(date.getFullYear() - 1700) + 100*(date.getMonth() + 1) + date.getDate();
|
||||||
|
}
|
||||||
|
|
||||||
export function strptime_vista(s) {
|
export function strptime_vista(s) {
|
||||||
s = +s;
|
s = +s;
|
||||||
var date = Math.floor(s), time = s - date;
|
var date = Math.floor(s), time = s - date;
|
||||||
|
Loading…
Reference in New Issue
Block a user