@kenyaemr/esm-appointments-app 7.0.3-pre.89 → 8.0.0
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 +23 -22
- package/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/23.js +2 -0
- package/dist/23.js.map +1 -0
- package/dist/265.js +1 -1
- package/dist/271.js +1 -1
- package/dist/319.js +1 -1
- package/dist/460.js +1 -1
- package/dist/529.js +1 -1
- package/dist/574.js +1 -1
- package/dist/581.js +1 -0
- package/dist/581.js.map +1 -0
- package/dist/644.js +1 -1
- package/dist/646.js +2 -0
- package/dist/646.js.map +1 -0
- package/dist/757.js +1 -1
- package/dist/788.js +1 -1
- package/dist/807.js +1 -1
- package/dist/833.js +1 -1
- package/dist/85.js +1 -0
- package/dist/85.js.map +1 -0
- package/dist/89.js +1 -0
- package/dist/89.js.map +1 -0
- package/dist/kenyaemr-esm-appointments-app.js +1 -1
- package/dist/kenyaemr-esm-appointments-app.js.buildmanifest.json +167 -143
- package/dist/kenyaemr-esm-appointments-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -3
- package/src/admin/appointment-services/appointment-services.scss +5 -5
- package/src/appointments/appointment-tabs.scss +6 -7
- package/src/appointments/appointment-tabs.test.tsx +4 -11
- package/src/appointments/common-components/appointments-actions.test.tsx +121 -74
- package/src/appointments/common-components/appointments-table.scss +6 -6
- package/src/appointments/common-components/appointments-table.test.tsx +30 -32
- package/src/appointments/common-components/end-appointment.test.tsx +20 -19
- package/src/appointments/details/appointment-details.component.tsx +1 -1
- package/src/appointments/details/appointment-details.scss +13 -13
- package/src/appointments/details/appointment-details.test.tsx +49 -48
- package/src/appointments/scheduled/scheduled-appointments.component.tsx +1 -1
- package/src/appointments/scheduled/scheduled-appointments.scss +3 -1
- package/src/appointments/unscheduled/unscheduled-appointments.test.tsx +35 -37
- package/src/appointments.component.tsx +0 -1
- package/src/calendar/appointments-calendar-view-view.scss +3 -4
- package/src/calendar/appointments-calendar-view.test.tsx +1 -5
- package/src/calendar/header/calendar-header.scss +4 -4
- package/src/calendar/monthly/days-of-week.scss +1 -1
- package/src/calendar/monthly/monthly-view-workload.scss +10 -10
- package/src/config-schema.ts +88 -90
- package/src/empty-state/empty-state.scss +4 -4
- package/src/form/appointments-form.component.tsx +58 -26
- package/src/form/appointments-form.resource.ts +2 -2
- package/src/form/appointments-form.scss +11 -11
- package/src/form/appointments-form.test.tsx +42 -43
- package/src/header/appointments-header.scss +12 -12
- package/src/home/home-appointments.scss +0 -1
- package/src/homepage-tile/appointments-tile.component.tsx +23 -0
- package/src/homepage-tile/appointments-tile.scss +39 -0
- package/src/homepage-tile/appointments.resource.ts +15 -0
- package/src/hooks/useClinicalMetrics.ts +1 -1
- package/src/hooks/useDefaultLocation.ts +1 -1
- package/src/hooks/usePatientAppointmentHistory.ts +1 -1
- package/src/hooks/useProviders.ts +1 -1
- package/src/index.ts +5 -0
- package/src/metrics/appointments-metrics.scss +0 -1
- package/src/metrics/appointments-metrics.test.tsx +25 -31
- package/src/metrics/metrics-card.component.tsx +6 -33
- package/src/metrics/metrics-card.scss +8 -8
- package/src/metrics/metrics-header.scss +1 -1
- package/src/past-visit/past-visit.component.tsx +1 -1
- package/src/past-visit/past-visit.resource.ts +1 -1
- package/src/past-visit/past-visit.scss +19 -14
- package/src/patient-appointments/patient-appointments-action-menu.scss +6 -0
- package/src/patient-appointments/patient-appointments-base.component.tsx +6 -6
- package/src/patient-appointments/patient-appointments-base.scss +24 -29
- package/src/patient-appointments/patient-appointments-base.test.tsx +13 -11
- package/src/patient-appointments/patient-appointments-header.scss +4 -5
- package/src/patient-appointments/patient-appointments-overview.component.tsx +2 -2
- package/src/patient-appointments/patient-appointments-overview.scss +0 -1
- package/src/patient-appointments/patient-appointments.resource.ts +1 -1
- package/src/patient-appointments/patient-upcoming-appointments-card.component.tsx +3 -3
- package/src/patient-appointments/patient-upcoming-appointments-card.scss +11 -10
- package/src/patient-search/patient-search.scss +15 -14
- package/src/routes.json +5 -0
- package/src/types/index.ts +4 -0
- package/src/workload/monthly-view-workload/monthly-workload.scss +21 -6
- package/src/workload/monthly-view-workload/monthlyWorkCard.tsx +2 -2
- package/src/workload/workload.scss +3 -3
- package/translations/am.json +2 -0
- package/translations/ar.json +2 -0
- package/translations/en.json +2 -0
- package/translations/es.json +2 -0
- package/translations/fr.json +30 -28
- package/translations/he.json +2 -0
- package/translations/km.json +2 -0
- package/translations/zh.json +2 -0
- package/translations/zh_CN.json +2 -0
- package/dist/224.js +0 -1
- package/dist/224.js.map +0 -1
- package/dist/445.js +0 -2
- package/dist/445.js.map +0 -1
- package/dist/857.js +0 -2
- package/dist/857.js.map +0 -1
- package/dist/904.js +0 -1
- package/dist/904.js.map +0 -1
- package/src/root.scss +0 -50
- /package/dist/{857.js.LICENSE.txt → 23.js.LICENSE.txt} +0 -0
- /package/dist/{445.js.LICENSE.txt → 646.js.LICENSE.txt} +0 -0
- /package/src/helpers/{time.tsx → time.ts} +0 -0
- /package/src/patient-appointments/{patient-appointments-table.tsx → patient-appointments-table.component.tsx} +0 -0
package/src/config-schema.ts
CHANGED
|
@@ -2,94 +2,31 @@ import { Type, restBaseUrl, validators } from '@openmrs/esm-framework';
|
|
|
2
2
|
import { spaHomePage } from './constants';
|
|
3
3
|
|
|
4
4
|
export const configSchema = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
_default: 'c61ce16f-272a-41e7-9924-4c555d0932c5',
|
|
10
|
-
},
|
|
5
|
+
allowAllDayAppointments: {
|
|
6
|
+
_type: Type.Boolean,
|
|
7
|
+
_description: 'Whether to allow scheduling of all-day appointments (vs appointments with start time and end time)',
|
|
8
|
+
_default: false,
|
|
11
9
|
},
|
|
12
|
-
|
|
13
|
-
_type: Type.
|
|
14
|
-
_description: 'Configurable
|
|
15
|
-
_default:
|
|
10
|
+
appointmentsBaseUrl: {
|
|
11
|
+
_type: Type.String,
|
|
12
|
+
_description: 'Configurable alternative URL for the Appointments UI. Eg, the Bahmni Appointments UI URL',
|
|
13
|
+
_default: `${spaHomePage}`,
|
|
16
14
|
},
|
|
17
15
|
appointmentStatuses: {
|
|
18
16
|
_type: Type.Array,
|
|
19
17
|
_description: 'Configurable appointment status (status of appointments)',
|
|
20
18
|
_default: ['Requested', 'Scheduled', 'CheckedIn', 'Completed', 'Cancelled', 'Missed'],
|
|
21
19
|
},
|
|
22
|
-
|
|
20
|
+
appointmentTypes: {
|
|
23
21
|
_type: Type.Array,
|
|
24
|
-
_description: 'Configurable
|
|
25
|
-
_default: ['
|
|
26
|
-
},
|
|
27
|
-
useBahmniAppointmentsUI: {
|
|
28
|
-
_type: Type.Boolean,
|
|
29
|
-
_description: 'Open links in Bahmni Appointments UI instead of O3 UI',
|
|
30
|
-
_default: false,
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
useFullViewPrivilege: {
|
|
34
|
-
_type: Type.Boolean,
|
|
35
|
-
_description: "If set to 'false', will always display the full view, disregarding any privilege",
|
|
36
|
-
_default: false,
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
fullViewPrivilege: {
|
|
40
|
-
_type: Type.String,
|
|
41
|
-
_description: 'Name of the privilege to display the full view of the Appointments dashboard widget.',
|
|
42
|
-
_default: "Today's Appointments Widget: Display Full View",
|
|
22
|
+
_description: 'Configurable appointment types (types of appointments)',
|
|
23
|
+
_default: ['Scheduled'],
|
|
43
24
|
},
|
|
44
25
|
bahmniAppointmentsUiBaseUrl: {
|
|
45
26
|
_type: Type.String,
|
|
46
27
|
_description: 'Configurable base URL that points to the Bahmni Appointments UI',
|
|
47
28
|
_default: '/appointments',
|
|
48
29
|
},
|
|
49
|
-
appointmentsBaseUrl: {
|
|
50
|
-
_type: Type.String,
|
|
51
|
-
_description: 'Configurable alternative URL for the Appointments UI. Eg, the Bahmni Appointments UI URL',
|
|
52
|
-
_default: `${spaHomePage}`,
|
|
53
|
-
},
|
|
54
|
-
hiddenFormFields: {
|
|
55
|
-
_type: Type.Array,
|
|
56
|
-
_description: 'Array of form controls to be hidden on form load',
|
|
57
|
-
_default: [],
|
|
58
|
-
},
|
|
59
|
-
showServiceQueueFields: {
|
|
60
|
-
_type: Type.Boolean,
|
|
61
|
-
_description: 'Whether start visit form should display service queue fields`',
|
|
62
|
-
_default: false,
|
|
63
|
-
},
|
|
64
|
-
defaultFacilityUrl: {
|
|
65
|
-
_type: Type.String,
|
|
66
|
-
_default: `${restBaseUrl}/kenyaemr/default-facility`,
|
|
67
|
-
_description: 'Custom URL to load default facility if it is not in the session',
|
|
68
|
-
},
|
|
69
|
-
customPatientChartUrl: {
|
|
70
|
-
_type: Type.String,
|
|
71
|
-
_description: `Template URL that will be used when clicking on the patient name in the queues table.
|
|
72
|
-
Available argument: patientUuid, openmrsSpaBase, openBase
|
|
73
|
-
(openmrsSpaBase and openBase are available to any <ConfigurableLink>)`,
|
|
74
|
-
_default: '${openmrsSpaBase}/patient/${patientUuid}/chart',
|
|
75
|
-
_validators: [validators.isUrlWithTemplateParameters(['patientUuid'])],
|
|
76
|
-
},
|
|
77
|
-
patientIdentifierType: {
|
|
78
|
-
_type: Type.String,
|
|
79
|
-
_description: 'The name of the patient identifier type to be used for the patient identifier field',
|
|
80
|
-
_default: '',
|
|
81
|
-
},
|
|
82
|
-
showUnscheduledAppointmentsTab: {
|
|
83
|
-
_type: Type.Boolean,
|
|
84
|
-
_description:
|
|
85
|
-
'Whether to show the Unscheduled Appointments tab. Note that configuring this to true requires a custom unscheduledAppointment endpoint not currently available',
|
|
86
|
-
_default: false,
|
|
87
|
-
},
|
|
88
|
-
allowAllDayAppointments: {
|
|
89
|
-
_type: Type.Boolean,
|
|
90
|
-
_description: 'Whether to allow scheduling of all-day appointments (vs appointments with start time and end time)',
|
|
91
|
-
_default: false,
|
|
92
|
-
},
|
|
93
30
|
checkInButton: {
|
|
94
31
|
enabled: {
|
|
95
32
|
_type: Type.Boolean,
|
|
@@ -119,26 +56,74 @@ export const configSchema = {
|
|
|
119
56
|
_default: '',
|
|
120
57
|
},
|
|
121
58
|
},
|
|
59
|
+
concepts: {
|
|
60
|
+
visitQueueNumberAttributeUuid: {
|
|
61
|
+
_type: Type.String,
|
|
62
|
+
_description: 'The UUID of the visit attribute that contains the visit queue number.',
|
|
63
|
+
_default: 'c61ce16f-272a-41e7-9924-4c555d0932c5',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
customPatientChartUrl: {
|
|
67
|
+
_type: Type.String,
|
|
68
|
+
_description: `Template URL that will be used when clicking on the patient name in the queues table.
|
|
69
|
+
Available argument: patientUuid, openmrsSpaBase, openBase
|
|
70
|
+
(openmrsSpaBase and openBase are available to any <ConfigurableLink>)`,
|
|
71
|
+
_default: '${openmrsSpaBase}/patient/${patientUuid}/chart',
|
|
72
|
+
_validators: [validators.isUrlWithTemplateParameters(['patientUuid'])],
|
|
73
|
+
},
|
|
74
|
+
daysOfTheWeek: {
|
|
75
|
+
_type: Type.Array,
|
|
76
|
+
_description: 'Configurable days of the week',
|
|
77
|
+
_default: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
|
78
|
+
},
|
|
79
|
+
defaultFacilityUrl: {
|
|
80
|
+
_type: Type.String,
|
|
81
|
+
_default: `${restBaseUrl}/kenyaemr/default-facility`,
|
|
82
|
+
_description: 'Custom URL to load default facility if it is not in the session',
|
|
83
|
+
},
|
|
84
|
+
fullViewPrivilege: {
|
|
85
|
+
_type: Type.String,
|
|
86
|
+
_description: 'Name of the privilege to display the full view of the Appointments dashboard widget.',
|
|
87
|
+
_default: "Today's Appointments Widget: Display Full View",
|
|
88
|
+
},
|
|
89
|
+
hiddenFormFields: {
|
|
90
|
+
_type: Type.Array,
|
|
91
|
+
_description: 'Array of form controls to be hidden on form load',
|
|
92
|
+
_default: [],
|
|
93
|
+
},
|
|
94
|
+
patientIdentifierType: {
|
|
95
|
+
_type: Type.String,
|
|
96
|
+
_description: 'The name of the patient identifier type to be used for the patient identifier field',
|
|
97
|
+
_default: '',
|
|
98
|
+
},
|
|
99
|
+
showServiceQueueFields: {
|
|
100
|
+
_type: Type.Boolean,
|
|
101
|
+
_description: 'Whether start visit form should display service queue fields`',
|
|
102
|
+
_default: false,
|
|
103
|
+
},
|
|
104
|
+
showUnscheduledAppointmentsTab: {
|
|
105
|
+
_type: Type.Boolean,
|
|
106
|
+
_description:
|
|
107
|
+
'Whether to show the Unscheduled Appointments tab. Note that configuring this to true requires a custom unscheduledAppointment endpoint not currently available',
|
|
108
|
+
_default: false,
|
|
109
|
+
},
|
|
110
|
+
useBahmniAppointmentsUI: {
|
|
111
|
+
_type: Type.Boolean,
|
|
112
|
+
_description: 'Open links in Bahmni Appointments UI instead of O3 UI',
|
|
113
|
+
_default: false,
|
|
114
|
+
},
|
|
115
|
+
useFullViewPrivilege: {
|
|
116
|
+
_type: Type.Boolean,
|
|
117
|
+
_description: "If set to 'false', will always display the full view, disregarding any privilege",
|
|
118
|
+
_default: false,
|
|
119
|
+
},
|
|
122
120
|
};
|
|
123
121
|
|
|
124
122
|
export interface ConfigObject {
|
|
125
|
-
concepts: {
|
|
126
|
-
visitQueueNumberAttributeUuid: string;
|
|
127
|
-
};
|
|
128
|
-
appointmentTypes: Array<string>;
|
|
129
|
-
daysOfTheWeek: Array<string>;
|
|
130
|
-
appointmentStatuses: Array<string>;
|
|
131
|
-
useBahmniAppointmentsUI: boolean;
|
|
132
|
-
useFullViewPrivilege: boolean;
|
|
133
|
-
fullViewPrivilege: string;
|
|
134
|
-
appointmentComments: Array<string>;
|
|
135
|
-
hiddenFormFields: Array<string>;
|
|
136
|
-
showServiceQueueFields: boolean;
|
|
137
|
-
defaultFacilityUrl: string;
|
|
138
|
-
customPatientChartUrl: string;
|
|
139
|
-
patientIdentifierType: string;
|
|
140
|
-
showUnscheduledAppointmentsTab: boolean;
|
|
141
123
|
allowAllDayAppointments: boolean;
|
|
124
|
+
appointmentComments: Array<string>;
|
|
125
|
+
appointmentStatuses: Array<string>;
|
|
126
|
+
appointmentTypes: Array<string>;
|
|
142
127
|
checkInButton: {
|
|
143
128
|
enabled: boolean;
|
|
144
129
|
showIfActiveVisit: boolean;
|
|
@@ -148,4 +133,17 @@ export interface ConfigObject {
|
|
|
148
133
|
enabled: boolean;
|
|
149
134
|
customUrl: string;
|
|
150
135
|
};
|
|
136
|
+
concepts: {
|
|
137
|
+
visitQueueNumberAttributeUuid: string;
|
|
138
|
+
};
|
|
139
|
+
customPatientChartUrl: string;
|
|
140
|
+
daysOfTheWeek: Array<string>;
|
|
141
|
+
defaultFacilityUrl: string;
|
|
142
|
+
fullViewPrivilege: string;
|
|
143
|
+
hiddenFormFields: Array<string>;
|
|
144
|
+
patientIdentifierType: string;
|
|
145
|
+
showServiceQueueFields: boolean;
|
|
146
|
+
showUnscheduledAppointmentsTab: boolean;
|
|
147
|
+
useBahmniAppointmentsUI: boolean;
|
|
148
|
+
useFullViewPrivilege: boolean;
|
|
151
149
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
@use '@carbon/layout';
|
|
2
2
|
@use '@carbon/type';
|
|
3
|
-
@
|
|
3
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
4
4
|
|
|
5
5
|
.content {
|
|
6
6
|
@include type.type-style('heading-compact-01');
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
h4:after {
|
|
37
37
|
content: '';
|
|
38
38
|
display: block;
|
|
39
|
-
width:
|
|
39
|
+
width: layout.$spacing-07;
|
|
40
40
|
padding-top: 0.188rem;
|
|
41
41
|
border-bottom: 0.375rem solid var(--brand-03);
|
|
42
42
|
}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
.heading:after {
|
|
46
46
|
content: '';
|
|
47
47
|
display: block;
|
|
48
|
-
width:
|
|
48
|
+
width: layout.$spacing-07;
|
|
49
49
|
padding-top: 0.188rem;
|
|
50
50
|
border-bottom: 0.375rem solid var(--brand-03);
|
|
51
51
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
.header4 {
|
|
59
59
|
@include type.type-style('heading-compact-01');
|
|
60
|
-
margin-bottom:
|
|
60
|
+
margin-bottom: layout.$spacing-05;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// Overriding styles for RTL support
|
|
@@ -43,7 +43,6 @@ import {
|
|
|
43
43
|
useMutateAppointments,
|
|
44
44
|
} from './appointments-form.resource';
|
|
45
45
|
import { useProviders } from '../hooks/useProviders';
|
|
46
|
-
import Workload from '../workload/workload.component';
|
|
47
46
|
import type { Appointment, AppointmentPayload, RecurringPattern } from '../types';
|
|
48
47
|
import { type ConfigObject } from '../config-schema';
|
|
49
48
|
import {
|
|
@@ -54,9 +53,9 @@ import {
|
|
|
54
53
|
moduleName,
|
|
55
54
|
weekDays,
|
|
56
55
|
} from '../constants';
|
|
57
|
-
import styles from './appointments-form.scss';
|
|
58
56
|
import SelectedDateContext from '../hooks/selectedDateContext';
|
|
59
|
-
import
|
|
57
|
+
import Workload from '../workload/workload.component';
|
|
58
|
+
import styles from './appointments-form.scss';
|
|
60
59
|
|
|
61
60
|
const time12HourFormatRegexPattern = '^(1[0-2]|0?[1-9]):[0-5][0-9]$';
|
|
62
61
|
|
|
@@ -139,12 +138,20 @@ const AppointmentsForm: React.FC<AppointmentsFormProps> = ({
|
|
|
139
138
|
.refine((duration) => (isAllDayAppointment ? true : duration > 0), {
|
|
140
139
|
message: translateFrom(moduleName, 'durationErrorMessage', 'Duration should be greater than zero'),
|
|
141
140
|
}),
|
|
142
|
-
location: z.string().refine((value) => value !== ''
|
|
143
|
-
|
|
141
|
+
location: z.string().refine((value) => value !== '', {
|
|
142
|
+
message: translateFrom(moduleName, 'locationRequired', 'Location is required'),
|
|
143
|
+
}),
|
|
144
|
+
provider: z.string().refine((value) => value !== '', {
|
|
145
|
+
message: translateFrom(moduleName, 'providerRequired', 'Provider is required'),
|
|
146
|
+
}),
|
|
144
147
|
appointmentStatus: z.string().optional(),
|
|
145
148
|
appointmentNote: z.string(),
|
|
146
|
-
appointmentType: z.string().refine((value) => value !== ''
|
|
147
|
-
|
|
149
|
+
appointmentType: z.string().refine((value) => value !== '', {
|
|
150
|
+
message: translateFrom(moduleName, 'appointmentTypeRequired', 'Appointment type is required'),
|
|
151
|
+
}),
|
|
152
|
+
selectedService: z.string().refine((value) => value !== '', {
|
|
153
|
+
message: translateFrom(moduleName, 'serviceRequired', 'Service is required'),
|
|
154
|
+
}),
|
|
148
155
|
recurringPatternType: z.enum(['DAY', 'WEEK']),
|
|
149
156
|
recurringPatternPeriod: z.number(),
|
|
150
157
|
recurringPatternDaysOfWeek: z.array(z.string()),
|
|
@@ -179,7 +186,14 @@ const AppointmentsForm: React.FC<AppointmentsFormProps> = ({
|
|
|
179
186
|
? new Date(appointment?.dateAppointmentScheduled)
|
|
180
187
|
: new Date();
|
|
181
188
|
|
|
182
|
-
const {
|
|
189
|
+
const {
|
|
190
|
+
control,
|
|
191
|
+
getValues,
|
|
192
|
+
setValue,
|
|
193
|
+
watch,
|
|
194
|
+
handleSubmit,
|
|
195
|
+
formState: { errors },
|
|
196
|
+
} = useForm<AppointmentFormData>({
|
|
183
197
|
mode: 'all',
|
|
184
198
|
resolver: zodResolver(appointmentsFormSchema),
|
|
185
199
|
defaultValues: {
|
|
@@ -427,15 +441,16 @@ const AppointmentsForm: React.FC<AppointmentsFormProps> = ({
|
|
|
427
441
|
<Controller
|
|
428
442
|
name="location"
|
|
429
443
|
control={control}
|
|
430
|
-
render={({ field: { onChange, value, onBlur, ref } }) => (
|
|
444
|
+
render={({ field: { onChange, value, onBlur, ref }, fieldState }) => (
|
|
431
445
|
<Select
|
|
432
446
|
id="location"
|
|
433
|
-
invalidText="Required"
|
|
434
447
|
labelText={t('selectALocation', 'Select a location')}
|
|
435
448
|
onChange={onChange}
|
|
436
449
|
onBlur={onBlur}
|
|
437
450
|
value={value}
|
|
438
|
-
ref={ref}
|
|
451
|
+
ref={ref}
|
|
452
|
+
invalid={!!fieldState?.error?.message}
|
|
453
|
+
invalidText={fieldState?.error?.message}>
|
|
439
454
|
<SelectItem text={t('chooseLocation', 'Choose a location')} value="" />
|
|
440
455
|
{locations?.length > 0 &&
|
|
441
456
|
locations.map((location) => (
|
|
@@ -454,13 +469,15 @@ const AppointmentsForm: React.FC<AppointmentsFormProps> = ({
|
|
|
454
469
|
<Controller
|
|
455
470
|
name="dateAppointmentScheduled"
|
|
456
471
|
control={control}
|
|
457
|
-
render={({ field: { onChange, value, ref } }) => (
|
|
472
|
+
render={({ field: { onChange, value, ref }, fieldState }) => (
|
|
458
473
|
<DatePicker
|
|
459
474
|
datePickerType="single"
|
|
460
475
|
dateFormat={datePickerFormat}
|
|
461
476
|
value={value}
|
|
462
477
|
maxDate={new Date()}
|
|
463
|
-
onChange={([date]) => onChange(date)}
|
|
478
|
+
onChange={([date]) => onChange(date)}
|
|
479
|
+
invalid={!!fieldState?.error?.message}
|
|
480
|
+
invalidText={fieldState?.error?.message}>
|
|
464
481
|
<DatePickerInput
|
|
465
482
|
id="dateAppointmentScheduledPickerInput"
|
|
466
483
|
labelText={t('dateScheduledDetail', 'Date appointment issued')}
|
|
@@ -479,21 +496,33 @@ const AppointmentsForm: React.FC<AppointmentsFormProps> = ({
|
|
|
479
496
|
<Controller
|
|
480
497
|
name="selectedService"
|
|
481
498
|
control={control}
|
|
482
|
-
render={({ field: { onBlur, onChange, value, ref } }) => (
|
|
499
|
+
render={({ field: { onBlur, onChange, value, ref }, fieldState }) => (
|
|
483
500
|
<Select
|
|
484
501
|
id="service"
|
|
485
|
-
invalidText="Required"
|
|
486
502
|
labelText={t('selectService', 'Select a service')}
|
|
487
503
|
onChange={(event) => {
|
|
504
|
+
if (context === 'creating') {
|
|
505
|
+
setValue(
|
|
506
|
+
'duration',
|
|
507
|
+
services?.find((service) => service.name === event.target.value)?.durationMins,
|
|
508
|
+
);
|
|
509
|
+
} else if (context === 'editing') {
|
|
510
|
+
const previousServiceDuration = services?.find(
|
|
511
|
+
(service) => service.name === getValues('selectedService'),
|
|
512
|
+
)?.durationMins;
|
|
513
|
+
const selectedServiceDuration = services?.find((service) => service.name === event.target.value)
|
|
514
|
+
?.durationMins;
|
|
515
|
+
if (selectedServiceDuration && previousServiceDuration === getValues('duration')) {
|
|
516
|
+
setValue('duration', selectedServiceDuration);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
488
519
|
onChange(event);
|
|
489
|
-
setValue(
|
|
490
|
-
'duration',
|
|
491
|
-
services?.find((service) => service.name === event.target.value)?.durationMins,
|
|
492
|
-
);
|
|
493
520
|
}}
|
|
494
521
|
onBlur={onBlur}
|
|
495
522
|
value={value}
|
|
496
|
-
ref={ref}
|
|
523
|
+
ref={ref}
|
|
524
|
+
invalid={!!fieldState?.error?.message}
|
|
525
|
+
invalidText={fieldState?.error?.message}>
|
|
497
526
|
<SelectItem text={t('chooseService', 'Select service')} value="" />
|
|
498
527
|
{services?.length > 0 &&
|
|
499
528
|
services.map((service) => (
|
|
@@ -513,16 +542,17 @@ const AppointmentsForm: React.FC<AppointmentsFormProps> = ({
|
|
|
513
542
|
<Controller
|
|
514
543
|
name="appointmentType"
|
|
515
544
|
control={control}
|
|
516
|
-
render={({ field: { onBlur, onChange, value, ref } }) => (
|
|
545
|
+
render={({ field: { onBlur, onChange, value, ref }, fieldState }) => (
|
|
517
546
|
<Select
|
|
518
547
|
disabled={!appointmentTypes?.length}
|
|
519
548
|
id="appointmentType"
|
|
520
|
-
invalidText="Required"
|
|
521
549
|
labelText={t('selectAppointmentType', 'Select the type of appointment')}
|
|
522
550
|
onChange={onChange}
|
|
523
551
|
value={value}
|
|
524
552
|
ref={ref}
|
|
525
|
-
onBlur={onBlur}
|
|
553
|
+
onBlur={onBlur}
|
|
554
|
+
invalid={!!fieldState?.error?.message}
|
|
555
|
+
invalidText={fieldState?.error?.message}>
|
|
526
556
|
<SelectItem text={t('chooseAppointmentType', 'Choose appointment type')} value="" />
|
|
527
557
|
{appointmentTypes?.length > 0 &&
|
|
528
558
|
appointmentTypes.map((appointmentType, index) => (
|
|
@@ -740,10 +770,11 @@ const AppointmentsForm: React.FC<AppointmentsFormProps> = ({
|
|
|
740
770
|
<Controller
|
|
741
771
|
name="appointmentStatus"
|
|
742
772
|
control={control}
|
|
743
|
-
render={({ field: { onBlur, onChange, value, ref } }) => (
|
|
773
|
+
render={({ field: { onBlur, onChange, value, ref }, fieldState }) => (
|
|
744
774
|
<Select
|
|
745
775
|
id="appointmentStatus"
|
|
746
|
-
|
|
776
|
+
invalid={!!fieldState?.error?.message}
|
|
777
|
+
invalidText={fieldState?.error?.message}
|
|
747
778
|
labelText={t('selectAppointmentStatus', 'Select status')}
|
|
748
779
|
onChange={onChange}
|
|
749
780
|
value={value}
|
|
@@ -866,7 +897,7 @@ function TimeAndDuration({ isTablet, t, watch, control, services }) {
|
|
|
866
897
|
name="duration"
|
|
867
898
|
control={control}
|
|
868
899
|
defaultValue={defaultDuration}
|
|
869
|
-
render={({ field: { onChange, onBlur, value, ref } }) => (
|
|
900
|
+
render={({ field: { onChange, onBlur, value, ref }, fieldState }) => (
|
|
870
901
|
<NumberInput
|
|
871
902
|
hideSteppers
|
|
872
903
|
disableWheel
|
|
@@ -880,6 +911,7 @@ function TimeAndDuration({ isTablet, t, watch, control, services }) {
|
|
|
880
911
|
onChange={(event) => onChange(Number(event.target.value))}
|
|
881
912
|
value={value}
|
|
882
913
|
ref={ref}
|
|
914
|
+
invalid={fieldState?.error?.message}
|
|
883
915
|
/>
|
|
884
916
|
)}
|
|
885
917
|
/>
|
|
@@ -77,7 +77,7 @@ export function useAppointments(patientUuid: string, startDate: string, abortCon
|
|
|
77
77
|
|
|
78
78
|
return {
|
|
79
79
|
data: data ? { pastAppointments, upcomingAppointments, todaysAppointments } : null,
|
|
80
|
-
|
|
80
|
+
error,
|
|
81
81
|
isLoading,
|
|
82
82
|
isValidating,
|
|
83
83
|
mutate,
|
|
@@ -92,7 +92,7 @@ export function useAppointmentService() {
|
|
|
92
92
|
|
|
93
93
|
return {
|
|
94
94
|
data: data ? data.data : null,
|
|
95
|
-
|
|
95
|
+
error,
|
|
96
96
|
isLoading,
|
|
97
97
|
};
|
|
98
98
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
@use '@carbon/
|
|
2
|
-
@use '@carbon/
|
|
3
|
-
@use '@carbon/
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
4
|
|
|
5
5
|
$openmrs-background-grey: #ededed;
|
|
6
6
|
|
|
7
7
|
.formGroup {
|
|
8
8
|
display: flex;
|
|
9
|
-
margin-bottom:
|
|
10
|
-
padding:
|
|
9
|
+
margin-bottom: layout.$spacing-02;
|
|
10
|
+
padding: layout.$spacing-05;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
:global(.omrs-breakpoint-gt-tablet) .formGroup {
|
|
@@ -29,13 +29,13 @@ $openmrs-background-grey: #ededed;
|
|
|
29
29
|
.heading {
|
|
30
30
|
color: colors.$gray-70;
|
|
31
31
|
@include type.type-style('heading-compact-02');
|
|
32
|
-
margin-bottom:
|
|
32
|
+
margin-bottom: layout.$spacing-03;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.inputContainer {
|
|
36
36
|
display: flex;
|
|
37
37
|
flex-flow: row wrap;
|
|
38
|
-
gap:
|
|
38
|
+
gap: layout.$spacing-05;
|
|
39
39
|
align-items: center;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -48,7 +48,7 @@ $openmrs-background-grey: #ededed;
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
.button {
|
|
51
|
-
height:
|
|
51
|
+
height: layout.$spacing-10;
|
|
52
52
|
display: flex;
|
|
53
53
|
align-content: flex-start;
|
|
54
54
|
align-items: baseline;
|
|
@@ -59,12 +59,12 @@ $openmrs-background-grey: #ededed;
|
|
|
59
59
|
display: flex;
|
|
60
60
|
background-color: $openmrs-background-grey;
|
|
61
61
|
justify-content: center;
|
|
62
|
-
min-height:
|
|
62
|
+
min-height: layout.$spacing-09;
|
|
63
63
|
height: 100vh;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
.tablet {
|
|
67
|
-
padding:
|
|
67
|
+
padding: layout.$spacing-06 layout.$spacing-05;
|
|
68
68
|
background-color: colors.$white;
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -74,5 +74,5 @@ $openmrs-background-grey: #ededed;
|
|
|
74
74
|
|
|
75
75
|
.weekSelect {
|
|
76
76
|
max-inline-size: fit-content;
|
|
77
|
-
padding-top:
|
|
77
|
+
padding-top: layout.$spacing-03;
|
|
78
78
|
}
|
|
@@ -1,34 +1,32 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { screen } from '@testing-library/react';
|
|
3
2
|
import userEvent from '@testing-library/user-event';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
3
|
+
import { screen } from '@testing-library/react';
|
|
4
|
+
import {
|
|
5
|
+
type FetchResponse,
|
|
6
|
+
getDefaultsFromConfigSchema,
|
|
7
|
+
openmrsFetch,
|
|
8
|
+
useConfig,
|
|
9
|
+
useLocations,
|
|
10
|
+
useSession,
|
|
11
|
+
} from '@openmrs/esm-framework';
|
|
12
|
+
import { mockUseAppointmentServiceData, mockSession, mockLocations } from '__mocks__';
|
|
7
13
|
import { mockPatient, renderWithSwr, waitForLoadingToFinish } from 'tools';
|
|
8
14
|
import { saveAppointment } from './appointments-form.resource';
|
|
15
|
+
import { configSchema, type ConfigObject } from '../config-schema';
|
|
9
16
|
import AppointmentForm from './appointments-form.component';
|
|
10
17
|
|
|
11
|
-
const
|
|
18
|
+
const defaultProps = {
|
|
19
|
+
context: 'creating',
|
|
12
20
|
closeWorkspace: jest.fn(),
|
|
13
21
|
patientUuid: mockPatient.id,
|
|
14
22
|
promptBeforeClosing: jest.fn(),
|
|
15
23
|
};
|
|
16
24
|
|
|
17
|
-
const mockCreateAppointment =
|
|
18
|
-
const mockOpenmrsFetch =
|
|
19
|
-
|
|
20
|
-
jest.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
...originalModule,
|
|
25
|
-
useLocations: jest.fn().mockImplementation(() => mockLocations.data.results),
|
|
26
|
-
useSession: jest.fn().mockImplementation(() => mockSession.data),
|
|
27
|
-
useConfig: jest.fn(() => ({
|
|
28
|
-
appointmentTypes: ['Scheduled', 'WalkIn'],
|
|
29
|
-
})),
|
|
30
|
-
};
|
|
31
|
-
});
|
|
25
|
+
const mockCreateAppointment = jest.mocked(saveAppointment);
|
|
26
|
+
const mockOpenmrsFetch = jest.mocked(openmrsFetch);
|
|
27
|
+
const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
|
|
28
|
+
const mockUseLocations = jest.mocked(useLocations);
|
|
29
|
+
const mockUseSession = jest.mocked(useSession);
|
|
32
30
|
|
|
33
31
|
jest.mock('react-hook-form', () => ({
|
|
34
32
|
...jest.requireActual('react-hook-form'),
|
|
@@ -89,20 +87,25 @@ jest.mock('react-hook-form', () => ({
|
|
|
89
87
|
}),
|
|
90
88
|
}));
|
|
91
89
|
|
|
92
|
-
jest.mock('./appointments-form.resource', () => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
...originalModule,
|
|
97
|
-
saveAppointment: jest.fn(),
|
|
98
|
-
};
|
|
99
|
-
});
|
|
90
|
+
jest.mock('./appointments-form.resource', () => ({
|
|
91
|
+
...jest.requireActual('./appointments-form.resource'),
|
|
92
|
+
saveAppointment: jest.fn(),
|
|
93
|
+
}));
|
|
100
94
|
|
|
101
95
|
describe('AppointmentForm', () => {
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
mockUseConfig.mockReturnValue({
|
|
98
|
+
...getDefaultsFromConfigSchema(configSchema),
|
|
99
|
+
appointmentTypes: ['Scheduled', 'WalkIn'],
|
|
100
|
+
});
|
|
101
|
+
mockUseLocations.mockReturnValue(mockLocations.data.results);
|
|
102
|
+
mockUseSession.mockReturnValue(mockSession.data);
|
|
103
|
+
});
|
|
104
|
+
|
|
102
105
|
it('renders the appointments form showing all the relevant fields and values', async () => {
|
|
103
|
-
mockOpenmrsFetch.
|
|
106
|
+
mockOpenmrsFetch.mockResolvedValue(mockUseAppointmentServiceData as unknown as FetchResponse);
|
|
104
107
|
|
|
105
|
-
|
|
108
|
+
renderWithSwr(<AppointmentForm {...defaultProps} />);
|
|
106
109
|
|
|
107
110
|
await waitForLoadingToFinish();
|
|
108
111
|
|
|
@@ -129,25 +132,25 @@ describe('AppointmentForm', () => {
|
|
|
129
132
|
it('closes the form and the workspace when the cancel button is clicked', async () => {
|
|
130
133
|
const user = userEvent.setup();
|
|
131
134
|
|
|
132
|
-
mockOpenmrsFetch.
|
|
135
|
+
mockOpenmrsFetch.mockResolvedValueOnce(mockUseAppointmentServiceData as unknown as FetchResponse);
|
|
133
136
|
|
|
134
|
-
|
|
137
|
+
renderWithSwr(<AppointmentForm {...defaultProps} />);
|
|
135
138
|
|
|
136
139
|
await waitForLoadingToFinish();
|
|
137
140
|
|
|
138
141
|
const cancelButton = screen.getByRole('button', { name: /Discard/i });
|
|
139
142
|
await user.click(cancelButton);
|
|
140
143
|
|
|
141
|
-
expect(
|
|
144
|
+
expect(defaultProps.closeWorkspace).toHaveBeenCalledTimes(1);
|
|
142
145
|
});
|
|
143
146
|
|
|
144
147
|
it('renders a success snackbar upon successfully scheduling an appointment', async () => {
|
|
145
148
|
const user = userEvent.setup();
|
|
146
149
|
|
|
147
|
-
mockOpenmrsFetch.
|
|
148
|
-
mockCreateAppointment.mockResolvedValue({ status: 200, statusText: 'Ok' });
|
|
150
|
+
mockOpenmrsFetch.mockResolvedValue({ data: mockUseAppointmentServiceData } as unknown as FetchResponse);
|
|
151
|
+
mockCreateAppointment.mockResolvedValue({ status: 200, statusText: 'Ok' } as FetchResponse);
|
|
149
152
|
|
|
150
|
-
|
|
153
|
+
renderWithSwr(<AppointmentForm {...defaultProps} />);
|
|
151
154
|
|
|
152
155
|
await waitForLoadingToFinish();
|
|
153
156
|
|
|
@@ -158,7 +161,7 @@ describe('AppointmentForm', () => {
|
|
|
158
161
|
const serviceSelect = screen.getByRole('combobox', { name: /Select a service/i });
|
|
159
162
|
const appointmentTypeSelect = screen.getByRole('combobox', { name: /Select the type of appointment/i });
|
|
160
163
|
|
|
161
|
-
expect(saveButton).
|
|
164
|
+
expect(saveButton).toBeEnabled();
|
|
162
165
|
|
|
163
166
|
await user.clear(dateInput);
|
|
164
167
|
await user.type(dateInput, '4/4/2021');
|
|
@@ -182,10 +185,10 @@ describe('AppointmentForm', () => {
|
|
|
182
185
|
},
|
|
183
186
|
};
|
|
184
187
|
|
|
185
|
-
mockOpenmrsFetch.
|
|
188
|
+
mockOpenmrsFetch.mockResolvedValueOnce({ data: mockUseAppointmentServiceData } as unknown as FetchResponse);
|
|
186
189
|
mockCreateAppointment.mockRejectedValueOnce(error);
|
|
187
190
|
|
|
188
|
-
|
|
191
|
+
renderWithSwr(<AppointmentForm {...defaultProps} />);
|
|
189
192
|
|
|
190
193
|
await waitForLoadingToFinish();
|
|
191
194
|
|
|
@@ -206,7 +209,3 @@ describe('AppointmentForm', () => {
|
|
|
206
209
|
await user.click(saveButton);
|
|
207
210
|
});
|
|
208
211
|
});
|
|
209
|
-
|
|
210
|
-
function renderAppointmentsForm() {
|
|
211
|
-
renderWithSwr(<AppointmentForm {...testProps} />);
|
|
212
|
-
}
|