@openmrs/esm-appointments-app 6.1.0 → 6.1.1-pre.2939
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +3 -3
- package/dist/{368.js → 365.js} +1 -1
- package/dist/365.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-appointments-app.js +1 -1
- package/dist/openmrs-esm-appointments-app.js.buildmanifest.json +9 -9
- package/dist/routes.json +1 -1
- package/package.json +3 -2
- package/src/appointments/common-components/appointments-actions.component.tsx +22 -16
- package/src/appointments/common-components/appointments-actions.test.tsx +92 -24
- package/src/appointments/common-components/appointments-table.test.tsx +2 -0
- package/src/appointments/common-components/checkin-button.component.tsx +22 -13
- package/src/config-schema.ts +32 -0
- package/src/hooks/{useVisits.ts → useTodaysVisits.ts} +5 -8
- package/src/patient-queue/visit-form/visit-form.component.tsx +2 -2
- package/dist/368.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
var _openmrs_esm_appointments_app;(()=>{"use strict";var e,r,t,n,o,a,i,l,s,u,p,f,d,c,h,m,v,g,b={5605:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(255),t.e(426),t.e(268),t.e(325),t.e(
|
|
1
|
+
var _openmrs_esm_appointments_app;(()=>{"use strict";var e,r,t,n,o,a,i,l,s,u,p,f,d,c,h,m,v,g,b={5605:(e,r,t)=>{var n={"./start":()=>Promise.all([t.e(255),t.e(426),t.e(268),t.e(325),t.e(365)]).then((()=>()=>t(9193)))},o=(e,r)=>(t.R=r,r=t.o(n,e)?n[e]():Promise.resolve().then((()=>{throw new Error('Module "'+e+'" does not exist in container.')})),t.R=void 0,r),a=(e,r)=>{if(t.S){var n="default",o=t.S[n];if(o&&o!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[n]=e,t.I(n,r)}};t.d(r,{get:()=>o,init:()=>a})}},y={};function w(e){var r=y[e];if(void 0!==r)return r.exports;var t=y[e]={id:e,loaded:!1,exports:{}};return b[e].call(t.exports,t,t.exports,w),t.loaded=!0,t.exports}w.m=b,w.c=y,w.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return w.d(r,{a:r}),r},w.d=(e,r)=>{for(var t in r)w.o(r,t)&&!w.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},w.f={},w.e=e=>Promise.all(Object.keys(w.f).reduce(((r,t)=>(w.f[t](e,r),r)),[])),w.u=e=>e+".js",w.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),w.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="@openmrs/esm-appointments-app:",w.l=(t,n,o,a)=>{if(e[t])e[t].push(n);else{var i,l;if(void 0!==o)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var p=s[u];if(p.getAttribute("src")==t||p.getAttribute("data-webpack")==r+o){i=p;break}}i||(l=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,w.nc&&i.setAttribute("nonce",w.nc),i.setAttribute("data-webpack",r+o),i.src=t),e[t]=[n];var f=(r,n)=>{i.onerror=i.onload=null,clearTimeout(d);var o=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach((e=>e(n))),r)return r(n)},d=setTimeout(f.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=f.bind(null,i.onerror),i.onload=f.bind(null,i.onload),l&&document.head.appendChild(i)}},w.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},w.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{w.S={};var e={},r={};w.I=(t,n)=>{n||(n=[]);var o=r[t];if(o||(o=r[t]={}),!(n.indexOf(o)>=0)){if(n.push(o),e[t])return e[t];w.o(w.S,t)||(w.S[t]={});var a=w.S[t],i="@openmrs/esm-appointments-app",l=(e,r,t,n)=>{var o=a[e]=a[e]||{},l=o[r];(!l||!l.loaded&&(!n!=!l.eager?n:i>l.from))&&(o[r]={get:t,from:i,eager:!!n})},s=[];return"default"===t&&(l("@openmrs/esm-framework","5.4.1-pre.1638",(()=>Promise.all([w.e(130),w.e(268)]).then((()=>()=>w(130))))),l("react-i18next","11.18.6",(()=>Promise.all([w.e(588),w.e(268)]).then((()=>()=>w(6588))))),l("react-router-dom","6.4.3",(()=>Promise.all([w.e(591),w.e(268)]).then((()=>()=>w(6591))))),l("react","18.2.0",(()=>w.e(784).then((()=>()=>w(2784))))),l("swr/_internal","2.0.1",(()=>Promise.all([w.e(729),w.e(268)]).then((()=>()=>w(4729))))),l("swr/immutable","2.0.1",(()=>Promise.all([w.e(255),w.e(268),w.e(325),w.e(152)]).then((()=>()=>w(2303)))))),e[t]=s.length?Promise.all(s).then((()=>e[t]=1)):1}}})(),(()=>{var e;w.g.importScripts&&(e=w.g.location+"");var r=w.g.document;if(!e&&r&&(r.currentScript&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var n=t.length-1;n>-1&&!e;)e=t[n--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),w.p=e})(),t=e=>{var r=e=>e.split(".").map((e=>+e==e?+e:e)),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),n=t[1]?r(t[1]):[];return t[2]&&(n.length++,n.push.apply(n,r(t[2]))),t[3]&&(n.push([]),n.push.apply(n,r(t[3]))),n},n=(e,r)=>{e=t(e),r=t(r);for(var n=0;;){if(n>=e.length)return n<r.length&&"u"!=(typeof r[n])[0];var o=e[n],a=(typeof o)[0];if(n>=r.length)return"u"==a;var i=r[n],l=(typeof i)[0];if(a!=l)return"o"==a&&"n"==l||"s"==l||"u"==a;if("o"!=a&&"u"!=a&&o!=i)return o<i;n++}},o=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var n=1,a=1;a<e.length;a++)n--,t+="u"==(typeof(l=e[a]))[0]?"-":(n>0?".":"")+(n=2,l);return t}var i=[];for(a=1;a<e.length;a++){var l=e[a];i.push(0===l?"not("+s()+")":1===l?"("+s()+" || "+s()+")":2===l?i.pop()+" "+i.pop():o(l))}return s();function s(){return i.pop().replace(/^\((.+)\)$/,"$1")}},a=(e,r)=>{if(0 in e){r=t(r);var n=e[0],o=n<0;o&&(n=-n-1);for(var i=0,l=1,s=!0;;l++,i++){var u,p,f=l<e.length?(typeof e[l])[0]:"";if(i>=r.length||"o"==(p=(typeof(u=r[i]))[0]))return!s||("u"==f?l>n&&!o:""==f!=o);if("u"==p){if(!s||"u"!=f)return!1}else if(s)if(f==p)if(l<=n){if(u!=e[l])return!1}else{if(o?u>e[l]:u<e[l])return!1;u!=e[l]&&(s=!1)}else if("s"!=f&&"n"!=f){if(o||l<=n)return!1;s=!1,l--}else{if(l<=n||p<f!=o)return!1;s=!1}else"s"!=f&&"n"!=f&&(s=!1,l--)}}var d=[],c=d.pop.bind(d);for(i=1;i<e.length;i++){var h=e[i];d.push(1==h?c()|c():2==h?c()&c():h?a(h,r):!c())}return!!c()},i=(e,r)=>{var t=e[r];return Object.keys(t).reduce(((e,r)=>!e||!t[e].loaded&&n(e,r)?r:e),0)},l=(e,r,t,n)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+o(n)+")",s=(e,r,t,n)=>{var o=i(e,t);return f(e[t][o])},u=(e,r,t,n)=>{var o=i(e,t);return a(n,o)||p(l(e,t,o,n)),f(e[t][o])},p=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},f=e=>(e.loaded=1,e.get()),c=(d=e=>function(r,t,n,o){var a=w.I(r);return a&&a.then?a.then(e.bind(e,r,w.S[r],t,n,o)):e(r,w.S[r],t,n,o)})(((e,r,t,n)=>r&&w.o(r,t)?s(r,0,t):n())),h=d(((e,r,t,n,o)=>r&&w.o(r,t)?u(r,0,t,n):o())),m={},v={268:()=>h("default","react",[1,18],(()=>w.e(784).then((()=>()=>w(2784))))),3325:()=>h("default","swr/_internal",[1,2],(()=>w.e(729).then((()=>()=>w(4729))))),2870:()=>h("default","@openmrs/esm-framework",[1,5],(()=>w.e(130).then((()=>()=>w(130))))),4924:()=>h("default","react-i18next",[1,11],(()=>w.e(588).then((()=>()=>w(6588))))),6081:()=>c("default","@openmrs/esm-framework/src/internal",(()=>w.e(130).then((()=>()=>w(130))))),8744:()=>h("default","react-router-dom",[1,6],(()=>w.e(591).then((()=>()=>w(6591))))),9198:()=>h("default","swr/immutable",[1,2],(()=>w.e(303).then((()=>()=>w(2303)))))},g={268:[268],325:[3325],365:[2870,4924,6081,8744,9198]},w.f.consumes=(e,r)=>{w.o(g,e)&&g[e].forEach((e=>{if(w.o(m,e))return r.push(m[e]);var t=r=>{m[e]=0,w.m[e]=t=>{delete w.c[e],t.exports=r()}},n=r=>{delete m[e],w.m[e]=t=>{throw delete w.c[e],r}};try{var o=v[e]();o.then?r.push(m[e]=o.then(t).catch(n)):t(o)}catch(e){n(e)}}))},(()=>{var e={529:0};w.f.j=(r,t)=>{var n=w.o(e,r)?e[r]:void 0;if(0!==n)if(n)t.push(n[2]);else if(/^(268|325)$/.test(r))e[r]=0;else{var o=new Promise(((t,o)=>n=e[r]=[t,o]));t.push(n[2]=o);var a=w.p+w.u(r),i=new Error;w.l(a,(t=>{if(w.o(e,r)&&(0!==(n=e[r])&&(e[r]=void 0),n)){var o=t&&("load"===t.type?"missing":t.type),a=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+o+": "+a+")",i.name="ChunkLoadError",i.type=o,i.request=a,n[1](i)}}),"chunk-"+r,r)}};var r=(r,t)=>{var n,o,[a,i,l]=t,s=0;if(a.some((r=>0!==e[r]))){for(n in i)w.o(i,n)&&(w.m[n]=i[n]);l&&l(w)}for(r&&r(t);s<a.length;s++)o=a[s],w.o(e,o)&&e[o]&&e[o][0](),e[o]=0},t=globalThis.webpackChunk_openmrs_esm_appointments_app=globalThis.webpackChunk_openmrs_esm_appointments_app||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),w.nc=void 0;var _=w(5605);_openmrs_esm_appointments_app=_})();
|
|
@@ -56,10 +56,10 @@
|
|
|
56
56
|
"initial": true,
|
|
57
57
|
"entry": true,
|
|
58
58
|
"recorded": false,
|
|
59
|
-
"size":
|
|
59
|
+
"size": 4893037,
|
|
60
60
|
"sizes": {
|
|
61
61
|
"consume-shared": 294,
|
|
62
|
-
"javascript":
|
|
62
|
+
"javascript": 4871895,
|
|
63
63
|
"share-init": 252,
|
|
64
64
|
"runtime": 20596
|
|
65
65
|
},
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"auxiliaryFiles": [
|
|
77
77
|
"main.js.map"
|
|
78
78
|
],
|
|
79
|
-
"hash": "
|
|
79
|
+
"hash": "0916d621310f1832",
|
|
80
80
|
"childrenByOrder": {}
|
|
81
81
|
},
|
|
82
82
|
{
|
|
@@ -221,9 +221,9 @@
|
|
|
221
221
|
"initial": false,
|
|
222
222
|
"entry": false,
|
|
223
223
|
"recorded": false,
|
|
224
|
-
"size":
|
|
224
|
+
"size": 428610,
|
|
225
225
|
"sizes": {
|
|
226
|
-
"javascript":
|
|
226
|
+
"javascript": 428400,
|
|
227
227
|
"consume-shared": 210
|
|
228
228
|
},
|
|
229
229
|
"names": [],
|
|
@@ -232,12 +232,12 @@
|
|
|
232
232
|
"@openmrs/esm-appointments-app"
|
|
233
233
|
],
|
|
234
234
|
"files": [
|
|
235
|
-
"
|
|
235
|
+
"365.js"
|
|
236
236
|
],
|
|
237
237
|
"auxiliaryFiles": [
|
|
238
|
-
"
|
|
238
|
+
"365.js.map"
|
|
239
239
|
],
|
|
240
|
-
"hash": "
|
|
240
|
+
"hash": "36e327dd20f8c8e7",
|
|
241
241
|
"childrenByOrder": {}
|
|
242
242
|
},
|
|
243
243
|
{
|
|
@@ -336,7 +336,7 @@
|
|
|
336
336
|
"auxiliaryFiles": [
|
|
337
337
|
"openmrs-esm-appointments-app.js.map"
|
|
338
338
|
],
|
|
339
|
-
"hash": "
|
|
339
|
+
"hash": "230faea83d7118cc",
|
|
340
340
|
"childrenByOrder": {}
|
|
341
341
|
},
|
|
342
342
|
{
|
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.2.0"},"extensions":[{"name":"home-appointments","slot":"homepage-widgets-slot","component":"homeAppointments","order":1},{"name":"clinical-appointments-dashboard-link","slot":"homepage-dashboard-slot","component":"appointmentsDashboardLink","meta":{"name":"appointments","slot":"clinical-appointments-dashboard-slot","title":"Appointments"}},{"component":"root","name":"clinical-appointments-dashboard","slot":"clinical-appointments-dashboard-slot"},{"name":"appointments-calendar-dashboard-link","slot":"calendar-dashboard-slot","component":"appointmentsCalendarDashboardLink"},{"name":"check-in-appointment-modal","slot":"todays-appointment-slot","component":"checkInModal"},{"name":"todays-appointments-dashboard","slot":"todays-appointment-slot","component":"homeAppointments"},{"name":"expected-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"checked-in-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"completed-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"missed-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"cancelled-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"early-appointments-panel","component":"earlyAppointments"},{"name":"appointments-form-workspace","component":"appointmentsFormWorkspace","meta":{"title":{"key":"createNewAppointment","default":"Create new appointment"}}}],"version":"6.1.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.2.0"},"extensions":[{"name":"home-appointments","slot":"homepage-widgets-slot","component":"homeAppointments","order":1},{"name":"clinical-appointments-dashboard-link","slot":"homepage-dashboard-slot","component":"appointmentsDashboardLink","meta":{"name":"appointments","slot":"clinical-appointments-dashboard-slot","title":"Appointments"}},{"component":"root","name":"clinical-appointments-dashboard","slot":"clinical-appointments-dashboard-slot"},{"name":"appointments-calendar-dashboard-link","slot":"calendar-dashboard-slot","component":"appointmentsCalendarDashboardLink"},{"name":"check-in-appointment-modal","slot":"todays-appointment-slot","component":"checkInModal"},{"name":"todays-appointments-dashboard","slot":"todays-appointment-slot","component":"homeAppointments"},{"name":"expected-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"checked-in-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"completed-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"missed-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"cancelled-appointments-panel","slot":"scheduled-appointments-panels-slot","component":"appointmentsByStatus"},{"name":"early-appointments-panel","component":"earlyAppointments"},{"name":"appointments-form-workspace","component":"appointmentsFormWorkspace","meta":{"title":{"key":"createNewAppointment","default":"Create new appointment"}}}],"version":"6.1.1-pre.2939"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-appointments-app",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.1-pre.2939",
|
|
4
4
|
"description": "Appointments front-end module for the OpenMRS SPA",
|
|
5
5
|
"browser": "dist/openmrs-esm-appointments-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -51,5 +51,6 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"webpack": "^5.74.0"
|
|
54
|
-
}
|
|
54
|
+
},
|
|
55
|
+
"stableVersion": "6.1.0"
|
|
55
56
|
}
|
|
@@ -7,10 +7,11 @@ import { TaskComplete } from '@carbon/react/icons';
|
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
import { closeOverlay, launchOverlay } from '../../hooks/useOverlay';
|
|
9
9
|
import { type Appointment } from '../../types';
|
|
10
|
-
import { showModal } from '@openmrs/esm-framework';
|
|
11
|
-
import {
|
|
10
|
+
import { navigate, showModal, useConfig } from '@openmrs/esm-framework';
|
|
11
|
+
import { useTodaysVisits } from '../../hooks/useTodaysVisits';
|
|
12
12
|
import AppointmentForm from '../../form/appointments-form.component';
|
|
13
13
|
import CheckInButton from './checkin-button.component';
|
|
14
|
+
import { type ConfigObject } from '../../config-schema';
|
|
14
15
|
|
|
15
16
|
dayjs.extend(utc);
|
|
16
17
|
dayjs.extend(isToday);
|
|
@@ -22,24 +23,29 @@ interface AppointmentActionsProps {
|
|
|
22
23
|
|
|
23
24
|
const AppointmentActions: React.FC<AppointmentActionsProps> = ({ appointment }) => {
|
|
24
25
|
const { t } = useTranslation();
|
|
25
|
-
const {
|
|
26
|
+
const { checkInButton, checkOutButton } = useConfig<ConfigObject>();
|
|
27
|
+
const { visits, mutateVisit } = useTodaysVisits();
|
|
26
28
|
const patientUuid = appointment.patient.uuid;
|
|
27
29
|
const visitDate = dayjs(appointment.startDateTime);
|
|
28
30
|
const isFutureAppointment = visitDate.isAfter(dayjs());
|
|
29
31
|
const isTodayAppointment = visitDate.isToday();
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
+
const hasActiveVisitToday = visits?.some((visit) => visit?.patient?.uuid === patientUuid && visit?.startDatetime);
|
|
33
|
+
const hasCheckedOutToday = visits?.some(
|
|
32
34
|
(visit) => visit?.patient?.uuid === patientUuid && visit?.startDatetime && visit?.stopDatetime,
|
|
33
35
|
);
|
|
34
36
|
|
|
35
37
|
const handleCheckout = () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
if (checkOutButton.customUrl) {
|
|
39
|
+
navigate({ to: checkOutButton.customUrl, templateParams: { patientUuid, appointmentUuid: appointment.uuid } });
|
|
40
|
+
} else {
|
|
41
|
+
const dispose = showModal('end-visit-dialog', {
|
|
42
|
+
closeModal: () => {
|
|
43
|
+
mutateVisit();
|
|
44
|
+
dispose();
|
|
45
|
+
},
|
|
46
|
+
patientUuid,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
43
49
|
};
|
|
44
50
|
|
|
45
51
|
/**
|
|
@@ -50,19 +56,19 @@ const AppointmentActions: React.FC<AppointmentActionsProps> = ({ appointment })
|
|
|
50
56
|
const checkedOutText = t('checkedOut', 'Checked out');
|
|
51
57
|
|
|
52
58
|
switch (true) {
|
|
53
|
-
case
|
|
59
|
+
case hasCheckedOutToday && isTodayAppointment:
|
|
54
60
|
return (
|
|
55
61
|
<Button size="sm" kind="ghost" renderIcon={TaskComplete} iconDescription="Add">
|
|
56
62
|
{checkedOutText}
|
|
57
63
|
</Button>
|
|
58
64
|
);
|
|
59
|
-
case
|
|
65
|
+
case checkOutButton.enabled && hasActiveVisitToday && isTodayAppointment:
|
|
60
66
|
return (
|
|
61
67
|
<Button onClick={handleCheckout} size="sm" kind="danger--tertiary">
|
|
62
68
|
{t('checkOut', 'Check out')}
|
|
63
69
|
</Button>
|
|
64
70
|
);
|
|
65
|
-
case isTodayAppointment: {
|
|
71
|
+
case checkInButton.enabled && !hasActiveVisitToday && isTodayAppointment: {
|
|
66
72
|
return <CheckInButton patientUuid={patientUuid} appointment={appointment} />;
|
|
67
73
|
}
|
|
68
74
|
|
|
@@ -74,7 +80,7 @@ const AppointmentActions: React.FC<AppointmentActionsProps> = ({ appointment })
|
|
|
74
80
|
return (
|
|
75
81
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
76
82
|
{renderVisitStatus()}
|
|
77
|
-
{isFutureAppointment || (isTodayAppointment && (!handleCheckout || !
|
|
83
|
+
{isFutureAppointment || (isTodayAppointment && (!handleCheckout || !hasActiveVisitToday)) ? (
|
|
78
84
|
<OverflowMenu aria-label="Actions" iconDescription={t('actions', 'Actions')} size="sm" flipped>
|
|
79
85
|
<OverflowMenuItem
|
|
80
86
|
itemText={t('editAppointments', 'Edit Appointment')}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { render
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import AppointmentActions from './appointments-actions.component';
|
|
4
|
-
import {
|
|
4
|
+
import { useTodaysVisits } from '../../hooks/useTodaysVisits';
|
|
5
5
|
import { type Appointment } from '../../types';
|
|
6
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
6
7
|
|
|
7
8
|
const appointment: Appointment = {
|
|
8
9
|
uuid: '7cd38a6d-377e-491b-8284-b04cf8b8c6d8',
|
|
@@ -14,8 +15,6 @@ const appointment: Appointment = {
|
|
|
14
15
|
uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e',
|
|
15
16
|
gender: 'M',
|
|
16
17
|
age: '35',
|
|
17
|
-
birthDate: '1986-04-03T00:00:00.000+0000',
|
|
18
|
-
phoneNumber: '0700000000',
|
|
19
18
|
},
|
|
20
19
|
service: {
|
|
21
20
|
appointmentServiceId: 1,
|
|
@@ -49,12 +48,20 @@ const appointment: Appointment = {
|
|
|
49
48
|
extensions: [],
|
|
50
49
|
};
|
|
51
50
|
|
|
52
|
-
jest.mock('../../hooks/
|
|
53
|
-
const originalModule = jest.requireActual('../../hooks/
|
|
51
|
+
jest.mock('../../hooks/useTodaysVisits', () => {
|
|
52
|
+
const originalModule = jest.requireActual('../../hooks/useTodaysVisits');
|
|
54
53
|
|
|
55
54
|
return {
|
|
56
55
|
...originalModule,
|
|
57
|
-
|
|
56
|
+
useTodaysVisits: jest.fn(),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
61
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
62
|
+
return {
|
|
63
|
+
...originalModule,
|
|
64
|
+
useConfig: jest.fn(),
|
|
58
65
|
};
|
|
59
66
|
});
|
|
60
67
|
|
|
@@ -66,22 +73,44 @@ describe('AppointmentActions', () => {
|
|
|
66
73
|
mutate: () => {},
|
|
67
74
|
};
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
jest.
|
|
71
|
-
|
|
72
|
-
const currentDateTime = new Date();
|
|
73
|
-
currentDateTime.setHours(12);
|
|
74
|
-
currentDateTime.setMinutes(0);
|
|
76
|
+
afterAll(() => {
|
|
77
|
+
jest.useRealTimers();
|
|
78
|
+
});
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
it('renders the check in button when appointment is today and the patient has not checked in and check in button enabled', () => {
|
|
81
|
+
useConfig.mockImplementation(() => ({
|
|
82
|
+
checkInButton: { enabled: true },
|
|
83
|
+
checkOutButton: { enabled: true },
|
|
84
|
+
}));
|
|
85
|
+
useTodaysVisits.mockImplementation(() => ({
|
|
86
|
+
visits: [],
|
|
87
|
+
}));
|
|
88
|
+
const props = { ...defaultProps };
|
|
89
|
+
const { getByText } = render(<AppointmentActions {...props} />);
|
|
90
|
+
const button = getByText('Check In');
|
|
91
|
+
expect(button).toBeInTheDocument();
|
|
77
92
|
});
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
it('does not renders the check in button when appointment is today and the patient has not checked in but the check-in button is disabled', () => {
|
|
95
|
+
useConfig.mockImplementation(() => ({
|
|
96
|
+
checkInButton: { enabled: false },
|
|
97
|
+
checkOutButton: { enabled: true },
|
|
98
|
+
}));
|
|
99
|
+
useTodaysVisits.mockImplementation(() => ({
|
|
100
|
+
visits: [],
|
|
101
|
+
}));
|
|
102
|
+
const props = { ...defaultProps };
|
|
103
|
+
const { queryByText } = render(<AppointmentActions {...props} />);
|
|
104
|
+
const button = queryByText('Check In');
|
|
105
|
+
expect(button).not.toBeInTheDocument();
|
|
81
106
|
});
|
|
82
107
|
|
|
83
|
-
it('renders the
|
|
84
|
-
|
|
108
|
+
it('renders the checked out button when the patient has checked out', () => {
|
|
109
|
+
useConfig.mockImplementation(() => ({
|
|
110
|
+
checkInButton: { enabled: true },
|
|
111
|
+
checkOutButton: { enabled: true },
|
|
112
|
+
}));
|
|
113
|
+
useTodaysVisits.mockImplementation(() => ({
|
|
85
114
|
visits: [
|
|
86
115
|
{
|
|
87
116
|
patient: { uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e' },
|
|
@@ -96,8 +125,12 @@ describe('AppointmentActions', () => {
|
|
|
96
125
|
expect(button).toBeInTheDocument();
|
|
97
126
|
});
|
|
98
127
|
|
|
99
|
-
it('renders the
|
|
100
|
-
|
|
128
|
+
it('renders the check out button when the patient has an active visit and today is the appointment date and the check out button enabled', () => {
|
|
129
|
+
useConfig.mockImplementation(() => ({
|
|
130
|
+
checkInButton: { enabled: true },
|
|
131
|
+
checkOutButton: { enabled: true },
|
|
132
|
+
}));
|
|
133
|
+
useTodaysVisits.mockImplementation(() => ({
|
|
101
134
|
visits: [
|
|
102
135
|
{
|
|
103
136
|
patient: { uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e' },
|
|
@@ -112,17 +145,52 @@ describe('AppointmentActions', () => {
|
|
|
112
145
|
expect(button).toBeInTheDocument();
|
|
113
146
|
});
|
|
114
147
|
|
|
115
|
-
it('
|
|
148
|
+
it('does not render check out button when the patient has an active visit and today is the appointment date but the check out button is disabled', () => {
|
|
149
|
+
useConfig.mockImplementation(() => ({
|
|
150
|
+
checkInButton: { enabled: true },
|
|
151
|
+
checkOutButton: { enabled: false },
|
|
152
|
+
}));
|
|
153
|
+
useTodaysVisits.mockImplementation(() => ({
|
|
154
|
+
visits: [
|
|
155
|
+
{
|
|
156
|
+
patient: { uuid: '8673ee4f-e2ab-4077-ba55-4980f408773e' },
|
|
157
|
+
startDatetime: new Date().toISOString(),
|
|
158
|
+
stopDatetime: null,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
}));
|
|
162
|
+
const props = { ...defaultProps, scheduleType: 'Scheduled' };
|
|
163
|
+
const { queryByText } = render(<AppointmentActions {...props} />);
|
|
164
|
+
const button = queryByText('Check out');
|
|
165
|
+
expect(button).not.toBeInTheDocument();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// commenting these tests out as this functionality is not implemented yet so not sure how they would have ever passed?
|
|
169
|
+
/*it('renders the correct button when today is the appointment date and the schedule type is pending', () => {
|
|
170
|
+
useConfig.mockImplementation(() => ({
|
|
171
|
+
checkInButton: { enabled: true },
|
|
172
|
+
checkOutButton: { enabled: true },
|
|
173
|
+
}));
|
|
174
|
+
useTodaysVisits.mockImplementation(() => ({
|
|
175
|
+
visits: [],
|
|
176
|
+
}));
|
|
116
177
|
const props = { ...defaultProps, scheduleType: 'Pending' };
|
|
117
178
|
render(<AppointmentActions {...props} />);
|
|
118
|
-
const button = screen.getByRole('button', { name: /
|
|
179
|
+
const button = screen.getByRole('button', { name: /Checked out/i });
|
|
119
180
|
expect(button).toBeInTheDocument();
|
|
120
181
|
});
|
|
121
182
|
|
|
122
183
|
it('renders the correct button when today is the appointment date and the schedule type is not pending', () => {
|
|
184
|
+
useConfig.mockImplementation(() => ({
|
|
185
|
+
checkInButton: { enabled: true },
|
|
186
|
+
checkOutButton: { enabled: true },
|
|
187
|
+
}));
|
|
188
|
+
useTodaysVisits.mockImplementation(() => ({
|
|
189
|
+
visits: [],
|
|
190
|
+
}));
|
|
123
191
|
const props = { ...defaultProps, scheduleType: 'Confirmed' };
|
|
124
192
|
render(<AppointmentActions {...props} />);
|
|
125
|
-
const button = screen.getByRole('button', { name: /
|
|
193
|
+
const button = screen.getByRole('button', { name: /Checked out/i });
|
|
126
194
|
expect(button).toBeInTheDocument();
|
|
127
|
-
})
|
|
195
|
+
});*/
|
|
128
196
|
});
|
|
@@ -7,6 +7,8 @@ import { type Appointment } from '../../types';
|
|
|
7
7
|
import dayjs from 'dayjs';
|
|
8
8
|
import isToday from 'dayjs/plugin/isToday';
|
|
9
9
|
import utc from 'dayjs/plugin/utc';
|
|
10
|
+
import { navigate, useConfig } from '@openmrs/esm-framework';
|
|
11
|
+
import { type ConfigObject } from '../../config-schema';
|
|
10
12
|
dayjs.extend(utc);
|
|
11
13
|
dayjs.extend(isToday);
|
|
12
14
|
|
|
@@ -16,22 +18,29 @@ interface CheckInButtonProps {
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const CheckInButton: React.FC<CheckInButtonProps> = ({ appointment, patientUuid }) => {
|
|
21
|
+
const { checkInButton } = useConfig<ConfigObject>();
|
|
19
22
|
const { t } = useTranslation();
|
|
20
23
|
return (
|
|
21
24
|
<>
|
|
22
|
-
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
{checkInButton.enabled &&
|
|
26
|
+
(dayjs(appointment.startDateTime).isAfter(dayjs()) || dayjs(appointment.startDateTime).isToday()) && (
|
|
27
|
+
<Button
|
|
28
|
+
size="sm"
|
|
29
|
+
kind="tertiary"
|
|
30
|
+
onClick={() =>
|
|
31
|
+
checkInButton.customUrl
|
|
32
|
+
? navigate({
|
|
33
|
+
to: checkInButton.customUrl,
|
|
34
|
+
templateParams: { patientUuid: appointment.patient.uuid, appointmentUuid: appointment.uuid },
|
|
35
|
+
})
|
|
36
|
+
: launchOverlay(
|
|
37
|
+
t('patientSearch', 'Patient search'),
|
|
38
|
+
<VisitForm patientUuid={patientUuid} appointment={appointment} />,
|
|
39
|
+
)
|
|
40
|
+
}>
|
|
41
|
+
{t('checkIn', 'Check In')}
|
|
42
|
+
</Button>
|
|
43
|
+
)}
|
|
35
44
|
</>
|
|
36
45
|
);
|
|
37
46
|
};
|
package/src/config-schema.ts
CHANGED
|
@@ -90,6 +90,30 @@ export const configSchema = {
|
|
|
90
90
|
_description: 'Whether to allow scheduling of all-day appointments (vs appointments with start time and end time)',
|
|
91
91
|
_default: true,
|
|
92
92
|
},
|
|
93
|
+
checkInButton: {
|
|
94
|
+
enabled: {
|
|
95
|
+
_type: Type.Boolean,
|
|
96
|
+
_description: 'Whether the check-in button on the "Appointments" list should be enabled',
|
|
97
|
+
_default: true,
|
|
98
|
+
},
|
|
99
|
+
customUrl: {
|
|
100
|
+
_type: Type.String,
|
|
101
|
+
_description: 'Custom URL to open when clicking the check-in button (instead of thes start visit form)',
|
|
102
|
+
_default: '',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
checkOutButton: {
|
|
106
|
+
enabled: {
|
|
107
|
+
_type: Type.Boolean,
|
|
108
|
+
_description: 'Whether the check-out button on the "Appointments" list should be disabled',
|
|
109
|
+
_default: true,
|
|
110
|
+
},
|
|
111
|
+
customUrl: {
|
|
112
|
+
_type: Type.String,
|
|
113
|
+
_description: 'Custom URL to open when clicking the check-out button',
|
|
114
|
+
_default: '',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
93
117
|
};
|
|
94
118
|
|
|
95
119
|
export interface ConfigObject {
|
|
@@ -110,4 +134,12 @@ export interface ConfigObject {
|
|
|
110
134
|
patientIdentifierType: string;
|
|
111
135
|
showUnscheduledAppointmentsTab: boolean;
|
|
112
136
|
allowAllDayAppointments: boolean;
|
|
137
|
+
checkInButton: {
|
|
138
|
+
enabled: boolean;
|
|
139
|
+
customUrl: string;
|
|
140
|
+
};
|
|
141
|
+
checkOutButton: {
|
|
142
|
+
enabled: boolean;
|
|
143
|
+
customUrl: string;
|
|
144
|
+
};
|
|
113
145
|
}
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import useSWR from 'swr';
|
|
2
2
|
import { useSession, type Visit, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
3
3
|
import dayjs from 'dayjs';
|
|
4
|
-
import { useContext } from 'react';
|
|
5
|
-
import SelectedDateContext from './selectedDateContext';
|
|
6
4
|
|
|
7
5
|
/**
|
|
8
6
|
* Custom hook to fetch visits from the OpenMRS REST API.
|
|
9
|
-
* This fetches all visits that started on
|
|
7
|
+
* This fetches all visits that started on today
|
|
10
8
|
* @returns An object containing the visits, isLoading flag, and error message.
|
|
11
9
|
*/
|
|
12
|
-
export const
|
|
13
|
-
const { selectedDate } = useContext(SelectedDateContext);
|
|
10
|
+
export const useTodaysVisits = () => {
|
|
14
11
|
const session = useSession();
|
|
15
|
-
const visitsUrl = `${restBaseUrl}/visit?includeInactive=true&includeParentLocations=true&v=custom:(uuid,patient:(uuid,identifiers:(identifier,uuid),person:(age,display,gender,uuid)),visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,stopDatetime)&fromStartDate=${dayjs(
|
|
16
|
-
|
|
17
|
-
)
|
|
12
|
+
const visitsUrl = `${restBaseUrl}/visit?includeInactive=true&includeParentLocations=true&v=custom:(uuid,patient:(uuid,identifiers:(identifier,uuid),person:(age,display,gender,uuid)),visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,stopDatetime)&fromStartDate=${dayjs().format(
|
|
13
|
+
'YYYY-MM-DD',
|
|
14
|
+
)}&location=${session?.sessionLocation?.uuid}`;
|
|
18
15
|
const { data, error, isLoading, mutate } = useSWR<{ data: { results: Visit[] } }>(visitsUrl, openmrsFetch);
|
|
19
16
|
const visits = data?.data?.results ?? [];
|
|
20
17
|
|
|
@@ -39,7 +39,7 @@ import { closeOverlay } from '../../hooks/useOverlay';
|
|
|
39
39
|
import { saveQueueEntry } from './queue.resource';
|
|
40
40
|
import { type Appointment } from '../../types';
|
|
41
41
|
import { useDefaultLoginLocation } from '../../hooks/useDefaultLocation';
|
|
42
|
-
import {
|
|
42
|
+
import { useTodaysVisits } from '../../hooks/useTodaysVisits';
|
|
43
43
|
import styles from './visit-form.scss';
|
|
44
44
|
import { appointmentLocationTagName } from '../../constants';
|
|
45
45
|
import SelectedDateContext from '../../hooks/selectedDateContext';
|
|
@@ -66,7 +66,7 @@ const VisitForm: React.FC<VisitFormProps> = ({ patientUuid, appointment }) => {
|
|
|
66
66
|
const state = useMemo(() => ({ patientUuid }), [patientUuid]);
|
|
67
67
|
const allVisitTypes = useVisitTypes();
|
|
68
68
|
const { mutateAppointments } = useMutateAppointments();
|
|
69
|
-
const { mutateVisit } =
|
|
69
|
+
const { mutateVisit } = useTodaysVisits();
|
|
70
70
|
const { isLoading, patient } = usePatient(patientUuid);
|
|
71
71
|
const config = useConfig();
|
|
72
72
|
const visitQueueNumberAttributeUuid = config.concepts.visitQueueNumberAttributeUuid;
|