Better schedule overview

This commit is contained in:
Jiang Yio 2023-05-03 00:25:01 -04:00
parent f64527122e
commit b1038fb577

View File

@ -6,19 +6,27 @@
<ViewResourceLookup :client="client" v-model:selection="selection" /> <ViewResourceLookup :client="client" v-model:selection="selection" />
</div> </div>
</div> </div>
<div class="card mb-3 shadow"> <div class="card mb-3 shadow" v-for="clinic in resultset">
<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>Daily</span> <span>{{clinic.key}}</span>
</div> </div>
<div class="card-body"> <div class="card-body">
<table class="table" style="font-family: monospace;" v-if="appointments_daily.length > 0"> <table class="table">
<thead> <thead>
<tr><th>Date</th><th>Count</th></tr> <tr><th v-for="x in dow">{{x}}</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="row in appointments_daily"> <tr v-for="week in clinic.values">
<td>{{row.key}}</td> <td v-for="day in [0, 1, 2, 3, 4, 5, 6]" class="datebox">
<td>{{row.values.length}}</td> <template v-if="week.values[day]">
<div class="datebox" :style="{ backgroundColor: clinic.max > 0 ? 'rgba(220, 53, 69, ' + week.values[day].length/clinic.max + ')' : null }"><span class="occupancy hidden">#{{week.values[day].length}}</span> {{day > 0 ? week.values[day][0]._START_OBJ.getDate() : week.key.toLocaleDateString('sv-SE')}} <span class="occupancy">#{{week.values[day].length}}</span></div>
<template v-for="appointment in week.values[day]">
<div v-if="appointment._BREAK" class="vacancy" :title="appointment._START_OBJ.toLocaleTimeString('en-GB').substring(0, 5) + '' + appointment._END_OBJ.toLocaleTimeString('en-GB').substring(0, 5)" />
<div v-else :title="appointment._START_OBJ.toLocaleTimeString('en-GB').substring(0, 5) + '' + appointment._END_OBJ.toLocaleTimeString('en-GB').substring(0, 5) + '\n' + appointment.PATIENTNAME + ' ' + appointment.HRN.slice(-4) + '\n' + appointment.NOTE"><span v-if="appointment._CONCURRENCY > 0" class="concurrency hidden">*<template v-if="appointment._CONCURRENCY > 1">{{appointment._CONCURRENCY}}</template></span>{{appointment._START_OBJ.toLocaleTimeString('en-GB').substring(0, 5)}} {{appointment.PATIENTNAME.substring(0, 1)}}{{appointment.HRN.slice(-4)}}<span v-if="appointment._CONCURRENCY > 0" class="concurrency">*<template v-if="appointment._CONCURRENCY > 1">{{appointment._CONCURRENCY}}</template></span></div>
</template>
</template>
<div v-else class="datebox">{{day > 0 ? (new Date(week.key.getTime() + 1000*60*60*24*day)).getDate() : week.key.toLocaleDateString('sv-SE')}}</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -27,13 +35,70 @@
</div> </div>
</template> </template>
<style scoped>
table {
font-family: monospace;
text-align: center;
}
tbody, td {
border: 1px solid #dee2e6;
}
div.datebox {
font-weight: bold;
}
div.vacancy {
width: 2em;
margin: auto;
border-bottom: 0.25em dotted #bbb;
}
span.occupancy {
font-weight: normal;
}
span.concurrency {
font-weight: bold;
}
span.hidden {
visibility: hidden;
}
</style>
<script> <script>
import { groupByArray, strtr_unscramble, strHashHSL, strftime_vista, debounce } from './util.mjs'; import { groupBy, groupByArray, strfdate_vista, debounce } from './util.mjs';
import ViewResourceLookup from './ViewResourceLookup.vue'; import ViewResourceLookup from './ViewResourceLookup.vue';
function dateonly(date) { const C_DAY = 1000*60*60*24;
return new Date(date.getFullYear(), date.getMonth(), date.getDate()); const C_WEEK = C_DAY*7;
function infill_weeks(res, date_begin, date_end) {
for(var i = res.length - 1; i > 0; --i) {
var cur = res[i], prev = res[i - 1];
while(cur.key - prev.key > C_WEEK) res.splice(i, 0, cur = { key: new Date(cur.key.getTime() - C_WEEK), values: [] });
}
var item;
if(res.length < 1) res.push({ key: date_begin, values: [] });
else {
item = res[0];
while(item.key - date_begin >= C_WEEK) res.splice(0, 0, item = { key: new Date(item.key.getTime() - C_WEEK), values: [] });
}
item = res[res.length - 1];
while(date_end - item.key >= C_WEEK) res.push(item = { key: new Date(item.key.getTime() + C_WEEK), values: [] });
return res;
}
function analyze_week(res) {
for(var k in res) if(res.hasOwnProperty(k)) analyze_day(res[k]);
return res;
}
function analyze_day(res) {
for(var i = res.length - 1; i > 0; --i) {
var item = res[i];
item._CONCURRENCY = 0;
for(var j = i - 1; j >= 0; --j) if(item._START_OBJ < res[j]._END_OBJ) item._CONCURRENCY++;
if((item._CONCURRENCY < 1) && (item._START_OBJ > res[i - 1]._END_OBJ)) res.splice(i, 0, { _BREAK: true, _START_OBJ: res[i - 1]._END_OBJ, _END_OBJ: item._START_OBJ });
}
return res;
} }
export default { export default {
@ -44,12 +109,11 @@
client: Object client: Object
}, },
data() { data() {
var today = dateonly(new Date()); var now = new Date();
return { return {
appointments: [], resultset: [],
production: true, date_begin: new Date(now.getFullYear(), now.getMonth(), now.getDate()),
date_begin: new Date(today.getFullYear(), today.getMonth(), today.getDate()), date_end: new Date(now.getFullYear(), now.getMonth() + 1, now.getDate()),
date_end: new Date(today.getFullYear() + 1, today.getMonth(), today.getDate()),
dow: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] dow: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
}; };
}, },
@ -57,37 +121,26 @@
selection: { selection: {
get() { return this.client.remotestate.resources ? (this.client.remotestate.resources.split(',').filter(x => x) || []) : [] }, get() { return this.client.remotestate.resources ? (this.client.remotestate.resources.split(',').filter(x => x) || []) : [] },
set(value) { this.client.remotestate.resources = value.join(','); } set(value) { this.client.remotestate.resources = value.join(','); }
},
appointments_daily() {
return groupByArray(this.appointments, x => x.ApptDateDate);
}
},
watch: {
selection: {
handler(value) { this.$nextTick(() => this.debounced_selection(value)); },
immediate: true
}
},
methods: {
strHashHSL,
strtr_unscramble,
datefmt(date) {
return date ? date.toLocaleDateString('sv-SE') : '';
} }
}, },
created() { created() {
this.debounced_selection = debounce(async function(value) { this.$watch(
this.appointments = this.selection.length > 0 ? (await this.client.SDEC_CLINLET(this.selection.join('|') + '|', strftime_vista(this.date_begin), strftime_vista(this.date_end))) : []; () => (this.client, this.selection, {}),
this.appointments.forEach(x => { debounce(async () => {
var obj = x.ApptDateObj = new Date(x.ApptDate); var date_begin = new Date(this.date_begin.getTime() - C_DAY*this.date_begin.getDay()), weekref = date_begin;
var date = x.ApptDateDate = obj.toLocaleDateString('sv-SE'); var date_end = new Date(this.date_end.getTime() + C_DAY*(6 - this.date_end.getDay()));
//x.ApptDateWeek = obj.getFullYear() + '-' + Math.floor(((obj - new Date(obj.getFullYear(), 0, 1))/(24*60*60*1000) + obj.getDay())/7); var resultset = this.selection.length < 1 ? [] : (await this.client.SDEC_CRSCHED(this.selection.join('|') + '|', strfdate_vista(date_begin), strfdate_vista(date_end) + '@2359')).filter(x => (x.CANCELLED == '0') && (x.NOSHOW == '0')).map(x => {
//x.ApptDateMonth = obj.getFullYear() + '-' + obj.getMonth(); var _START_OBJ = x._START_OBJ = new Date(x.START_TIME);
}); x._START_DATE = _START_OBJ.toLocaleDateString('sv-SE');
}, 500); x._END_OBJ = new Date(x.END_TIME);
}, x._WEEK_DAY = _START_OBJ.getDay();
async mounted() { x._WEEK_NUM = Math.floor((x._START_OBJ - weekref)/(C_WEEK));
this.production = (await this.client.serverinfo()).result.production == '1'; return x;
}).sort((a, b) => a._START_OBJ - b._START_OBJ);
this.resultset = groupByArray(resultset, 'RESOURCENAME').sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(clinic => ({ key: clinic.key, max: Math.max.apply(null, groupByArray(clinic.values, '_START_DATE').map(x => x.values.length)), values: infill_weeks(groupByArray(clinic.values, '_WEEK_NUM').map(week => ({ key: new Date(weekref.getTime() + C_WEEK*week.key), values: analyze_week(groupBy(week.values, '_WEEK_DAY')) })), date_begin, date_end) }));
}, 500),
{ immediate: true }
);
} }
}; };
</script> </script>