@openmrs/esm-appointments-app 9.2.1-pre.7296 → 9.2.1-pre.7315
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 +8 -8
- package/dist/1431.js +1 -1
- package/dist/1431.js.map +1 -1
- package/dist/1559.js +1 -1
- package/dist/1559.js.map +1 -1
- package/dist/2265.js +1 -0
- package/dist/2265.js.map +1 -0
- package/dist/{5160.js → 4036.js} +1 -1
- package/dist/{5160.js.map → 4036.js.map} +1 -1
- package/dist/449.js +1 -1
- package/dist/449.js.map +1 -1
- package/dist/4515.js +1 -0
- package/dist/4515.js.map +1 -0
- package/dist/4745.js +1 -1
- package/dist/4745.js.map +1 -1
- package/dist/4889.js +1 -1
- package/dist/4889.js.map +1 -1
- package/dist/525.js +1 -1
- package/dist/525.js.map +1 -1
- package/dist/5666.js +1 -1
- package/dist/5666.js.map +1 -1
- package/dist/5755.js +1 -1
- package/dist/5755.js.map +1 -1
- package/dist/592.js +1 -1
- package/dist/592.js.map +1 -1
- package/dist/6467.js +1 -1
- package/dist/6467.js.map +1 -1
- package/dist/6886.js +1 -1
- package/dist/6886.js.map +1 -1
- package/dist/{7565.js → 7294.js} +1 -1
- package/dist/7294.js.map +1 -0
- package/dist/9712.js +1 -1
- package/dist/9712.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-appointments-app.js.buildmanifest.json +121 -120
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/appointments/common-components/appointments-table.component.tsx +3 -5
- package/src/constants.ts +1 -0
- package/src/form/appointments-form.resource.ts +1 -0
- package/src/form/appointments-form.test.tsx +136 -117
- package/src/form/appointments-form.workspace.tsx +43 -50
- package/src/form/exported-appointments-form.workspace.tsx +24 -0
- package/src/helpers/functions.ts +72 -25
- package/src/index.ts +5 -3
- package/src/metrics/metrics-cards/highest-volume-service.extension.tsx +1 -1
- package/src/metrics/metrics-cards/metrics-card.component.tsx +1 -1
- package/src/metrics/metrics-header.component.tsx +9 -24
- package/src/patient-appointments/patient-appointments-action-menu.component.tsx +1 -5
- package/src/patient-appointments/patient-appointments-detailed-summary.extension.tsx +176 -15
- package/src/patient-appointments/{patient-appointments-base.test.tsx → patient-appointments-detailed-summary.test.tsx} +14 -22
- package/src/patient-appointments/patient-appointments-overview.component.tsx +15 -18
- package/src/routes.json +18 -14
- package/dist/3092.js +0 -1
- package/dist/3092.js.map +0 -1
- package/dist/7026.js +0 -1
- package/dist/7026.js.map +0 -1
- package/dist/7565.js.map +0 -1
- package/src/hooks/patient-appointment-context.ts +0 -18
- package/src/patient-appointments/patient-appointments-base.component.tsx +0 -178
- package/src/patient-search/patient-search.component.tsx +0 -33
- package/src/patient-search/patient-search.scss +0 -24
- /package/src/patient-appointments/{patient-appointments-base.scss → patient-appointments-detailed-summary.scss} +0 -0
|
@@ -34,8 +34,8 @@ import {
|
|
|
34
34
|
usePatient,
|
|
35
35
|
useSession,
|
|
36
36
|
Workspace2,
|
|
37
|
-
type FetchResponse,
|
|
38
37
|
type Workspace2DefinitionProps,
|
|
38
|
+
type FetchResponse,
|
|
39
39
|
} from '@openmrs/esm-framework';
|
|
40
40
|
import { z } from 'zod';
|
|
41
41
|
import { type ConfigObject } from '../config-schema';
|
|
@@ -53,11 +53,10 @@ import { useProviders } from '../hooks/useProviders';
|
|
|
53
53
|
import Workload from '../workload/workload.component';
|
|
54
54
|
import styles from './appointments-form.scss';
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
interface AppointmentsFormProps {
|
|
57
57
|
appointment?: Appointment;
|
|
58
58
|
recurringPattern?: RecurringPattern;
|
|
59
|
-
patientUuid
|
|
60
|
-
context: 'creating' | 'editing';
|
|
59
|
+
patientUuid: string;
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
const time12HourFormatRegexPattern = '^(1[0-2]|0?[1-9]):[0-5][0-9]$';
|
|
@@ -65,8 +64,11 @@ const time12HourFormatRegex = /^(1[0-2]|0?[1-9]):[0-5][0-9]$/;
|
|
|
65
64
|
|
|
66
65
|
const isValidTime = (timeStr: string) => time12HourFormatRegex.test(timeStr);
|
|
67
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Workspace used to create or edit an appointment within the appointments app
|
|
69
|
+
*/
|
|
68
70
|
const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps>> = ({
|
|
69
|
-
workspaceProps: { appointment, recurringPattern, patientUuid
|
|
71
|
+
workspaceProps: { appointment, recurringPattern, patientUuid },
|
|
70
72
|
closeWorkspace,
|
|
71
73
|
}) => {
|
|
72
74
|
const { patient } = usePatient(patientUuid);
|
|
@@ -262,7 +264,7 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
262
264
|
|
|
263
265
|
useEffect(() => setValue('formIsRecurringAppointment', isRecurringAppointment), [isRecurringAppointment, setValue]);
|
|
264
266
|
|
|
265
|
-
//
|
|
267
|
+
// Retrieve ref callback for appointmentDateTime (startDate & recurringPatternEndDate)
|
|
266
268
|
const {
|
|
267
269
|
field: { ref: startDateRef },
|
|
268
270
|
} = useController({ name: 'appointmentDateTime.startDate', control });
|
|
@@ -281,7 +283,7 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
281
283
|
useEffect(() => {
|
|
282
284
|
if (isSuccessful) {
|
|
283
285
|
reset();
|
|
284
|
-
closeWorkspace({
|
|
286
|
+
closeWorkspace({ discardUnsavedChanges: true, closeWindow: true });
|
|
285
287
|
}
|
|
286
288
|
}, [isSuccessful, reset, closeWorkspace]);
|
|
287
289
|
|
|
@@ -290,7 +292,7 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
290
292
|
setValue('appointmentDateTime', { ...appointmentDate, startDate: date });
|
|
291
293
|
};
|
|
292
294
|
|
|
293
|
-
const handleSelectChange = (e
|
|
295
|
+
const handleSelectChange = (e) => {
|
|
294
296
|
setValue(
|
|
295
297
|
'selectedDaysOfWeekText',
|
|
296
298
|
(() => {
|
|
@@ -307,8 +309,8 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
307
309
|
);
|
|
308
310
|
setValue(
|
|
309
311
|
'recurringPatternDaysOfWeek',
|
|
310
|
-
e.selectedItems.map((s
|
|
311
|
-
return
|
|
312
|
+
e.selectedItems.map((s) => {
|
|
313
|
+
return s.id;
|
|
312
314
|
}),
|
|
313
315
|
);
|
|
314
316
|
};
|
|
@@ -326,23 +328,21 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
326
328
|
}
|
|
327
329
|
})();
|
|
328
330
|
|
|
331
|
+
const isEditing = Boolean(appointment);
|
|
332
|
+
|
|
329
333
|
// Same for creating and editing
|
|
330
334
|
const handleSaveAppointment = async (data: AppointmentFormData) => {
|
|
331
335
|
setIsSubmitting(true);
|
|
332
336
|
// Construct appointment payload
|
|
333
337
|
const appointmentPayload = constructAppointmentPayload(data);
|
|
334
338
|
|
|
335
|
-
//
|
|
339
|
+
// Check if a duplicate response occurs
|
|
336
340
|
const response: FetchResponse = await checkAppointmentConflict(appointmentPayload);
|
|
337
341
|
let errorMessage = t('appointmentConflict', 'Appointment conflict');
|
|
338
342
|
if (response?.data?.hasOwnProperty('SERVICE_UNAVAILABLE')) {
|
|
339
343
|
errorMessage = t('serviceUnavailable', 'Appointment time is outside of service hours');
|
|
340
344
|
} else if (response?.data?.hasOwnProperty('PATIENT_DOUBLE_BOOKING')) {
|
|
341
|
-
|
|
342
|
-
errorMessage = t('patientDoubleBooking', 'Patient already booked for an appointment at this time');
|
|
343
|
-
} else {
|
|
344
|
-
errorMessage = null;
|
|
345
|
-
}
|
|
345
|
+
errorMessage = t('patientDoubleBooking', 'Patient already booked for an appointment at this time');
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
if (response.status === 200 && errorMessage) {
|
|
@@ -376,19 +376,17 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
376
376
|
isLowContrast: true,
|
|
377
377
|
kind: 'success',
|
|
378
378
|
subtitle: t('appointmentNowVisible', 'It is now visible on the Appointments page'),
|
|
379
|
-
title:
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
: t('appointmentScheduled', 'Appointment scheduled'),
|
|
379
|
+
title: isEditing
|
|
380
|
+
? t('appointmentEdited', 'Appointment edited')
|
|
381
|
+
: t('appointmentScheduled', 'Appointment scheduled'),
|
|
383
382
|
});
|
|
384
383
|
}
|
|
385
384
|
if (status === 204) {
|
|
386
385
|
setIsSubmitting(false);
|
|
387
386
|
showSnackbar({
|
|
388
|
-
title:
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
: t('appointmentFormError', 'Error scheduling appointment'),
|
|
387
|
+
title: isEditing
|
|
388
|
+
? t('appointmentEditError', 'Error editing appointment')
|
|
389
|
+
: t('appointmentFormError', 'Error scheduling appointment'),
|
|
392
390
|
kind: 'error',
|
|
393
391
|
isLowContrast: false,
|
|
394
392
|
subtitle: t('noContent', 'No Content'),
|
|
@@ -398,10 +396,9 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
398
396
|
(error) => {
|
|
399
397
|
setIsSubmitting(false);
|
|
400
398
|
showSnackbar({
|
|
401
|
-
title:
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
: t('appointmentFormError', 'Error scheduling appointment'),
|
|
399
|
+
title: isEditing
|
|
400
|
+
? t('appointmentEditError', 'Error editing appointment')
|
|
401
|
+
: t('appointmentFormError', 'Error scheduling appointment'),
|
|
405
402
|
kind: 'error',
|
|
406
403
|
isLowContrast: false,
|
|
407
404
|
subtitle: error?.message,
|
|
@@ -445,7 +442,7 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
445
442
|
providers: [{ uuid: provider }],
|
|
446
443
|
patientUuid: patientUuid,
|
|
447
444
|
comments: appointmentNote,
|
|
448
|
-
uuid:
|
|
445
|
+
uuid: isEditing ? appointment.uuid : undefined,
|
|
449
446
|
dateAppointmentScheduled: dayjs(dateAppointmentScheduled).format(),
|
|
450
447
|
};
|
|
451
448
|
};
|
|
@@ -469,19 +466,18 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
469
466
|
};
|
|
470
467
|
};
|
|
471
468
|
|
|
472
|
-
if (isLoading)
|
|
469
|
+
if (isLoading) {
|
|
473
470
|
return (
|
|
474
471
|
<InlineLoading className={styles.loader} description={`${t('loading', 'Loading')} ...`} role="progressbar" />
|
|
475
472
|
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const title = isEditing
|
|
476
|
+
? t('editAppointment', 'Edit appointment')
|
|
477
|
+
: t('createNewAppointment', 'Create new appointment');
|
|
476
478
|
|
|
477
479
|
return (
|
|
478
|
-
<Workspace2
|
|
479
|
-
title={
|
|
480
|
-
context === 'editing'
|
|
481
|
-
? t('editAppointment', 'Edit appointment')
|
|
482
|
-
: t('createNewAppointment', 'Create new appointment')
|
|
483
|
-
}
|
|
484
|
-
hasUnsavedChanges={isDirty}>
|
|
480
|
+
<Workspace2 title={title} hasUnsavedChanges={isDirty}>
|
|
485
481
|
<Form onSubmit={handleSubmit(handleSaveAppointment)}>
|
|
486
482
|
{patient && (
|
|
487
483
|
<ExtensionSlot
|
|
@@ -534,12 +530,12 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
534
530
|
labelText={t('selectService', 'Select a service')}
|
|
535
531
|
onBlur={onBlur}
|
|
536
532
|
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
537
|
-
if (
|
|
533
|
+
if (!isEditing) {
|
|
538
534
|
setValue(
|
|
539
535
|
'duration',
|
|
540
536
|
services?.find((service) => service.name === event.target.value)?.durationMins,
|
|
541
537
|
);
|
|
542
|
-
} else
|
|
538
|
+
} else {
|
|
543
539
|
const previousServiceDuration = services?.find(
|
|
544
540
|
(service) => service.name === getValues('selectedService'),
|
|
545
541
|
)?.durationMins;
|
|
@@ -677,7 +673,7 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
677
673
|
invalidText={t('invalidNumber', 'Number is not valid')}
|
|
678
674
|
value={value}
|
|
679
675
|
onBlur={onBlur}
|
|
680
|
-
onChange={(e
|
|
676
|
+
onChange={(e) => {
|
|
681
677
|
onChange(Number(e.target.value));
|
|
682
678
|
}}
|
|
683
679
|
/>
|
|
@@ -693,7 +689,7 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
693
689
|
<RadioButtonGroup
|
|
694
690
|
legendText={t('period', 'Period')}
|
|
695
691
|
name="radio-button-group"
|
|
696
|
-
onChange={(type
|
|
692
|
+
onChange={(type) => onChange(type)}
|
|
697
693
|
valueSelected={value}>
|
|
698
694
|
<RadioButton labelText={t('day', 'Day')} value="DAY" id="radioDay" />
|
|
699
695
|
<RadioButton labelText={t('week', 'Week')} value="WEEK" id="radioWeek" />
|
|
@@ -716,15 +712,15 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
716
712
|
getValues('recurringPatternDaysOfWeek').includes(i.id),
|
|
717
713
|
)}
|
|
718
714
|
items={weekDays}
|
|
719
|
-
itemToString={(item
|
|
715
|
+
itemToString={(item) => (item ? t(item.labelCode, item.label) : '')}
|
|
720
716
|
label={getValues('selectedDaysOfWeekText')}
|
|
721
|
-
onChange={(e
|
|
717
|
+
onChange={(e) => {
|
|
722
718
|
onChange(e);
|
|
723
719
|
handleSelectChange(e);
|
|
724
720
|
}}
|
|
725
721
|
selectionFeedback="top-after-reopen"
|
|
726
|
-
sortItems={(items
|
|
727
|
-
return items.sort((a
|
|
722
|
+
sortItems={(items) => {
|
|
723
|
+
return items.sort((a, b) => a.order > b.order);
|
|
728
724
|
}}
|
|
729
725
|
/>
|
|
730
726
|
)}
|
|
@@ -795,7 +791,7 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
795
791
|
</FormGroup>
|
|
796
792
|
)}
|
|
797
793
|
|
|
798
|
-
{
|
|
794
|
+
{isEditing ? (
|
|
799
795
|
<FormGroup className={styles.formGroup} legendText={t('appointmentStatus', 'Appointment Status')}>
|
|
800
796
|
<ResponsiveWrapper>
|
|
801
797
|
<Controller
|
|
@@ -902,10 +898,7 @@ const AppointmentsForm: React.FC<Workspace2DefinitionProps<AppointmentsFormProps
|
|
|
902
898
|
</FormGroup>
|
|
903
899
|
</Stack>
|
|
904
900
|
<ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
|
|
905
|
-
<Button
|
|
906
|
-
className={styles.button}
|
|
907
|
-
onClick={() => closeWorkspace({ discardUnsavedChanges: false })}
|
|
908
|
-
kind="secondary">
|
|
901
|
+
<Button className={styles.button} onClick={closeWorkspace} kind="secondary">
|
|
909
902
|
{t('discard', 'Discard')}
|
|
910
903
|
</Button>
|
|
911
904
|
<Button className={styles.button} disabled={isSubmitting} type="submit">
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Workspace2DefinitionProps } from '@openmrs/esm-framework';
|
|
3
|
+
import type { Appointment, RecurringPattern } from '../types';
|
|
4
|
+
import AppointmentsForm from './appointments-form.workspace';
|
|
5
|
+
|
|
6
|
+
interface ExportedAppointmentsFormProps {
|
|
7
|
+
appointment?: Appointment;
|
|
8
|
+
recurringPattern?: RecurringPattern;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ExportedAppointmentsFormGroupProps {
|
|
12
|
+
patientUuid: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Workspace used to create or edit an appointment in the patient chart (or app with compatible workspaceGroup)
|
|
17
|
+
*/
|
|
18
|
+
const ExportedAppointmentsForm: React.FC<
|
|
19
|
+
Workspace2DefinitionProps<ExportedAppointmentsFormProps, {}, ExportedAppointmentsFormGroupProps>
|
|
20
|
+
> = ({ workspaceProps: { appointment, recurringPattern }, groupProps: { patientUuid }, ...rest }) => {
|
|
21
|
+
return <AppointmentsForm workspaceProps={{ appointment, recurringPattern, patientUuid }} groupProps={{}} {...rest} />;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default ExportedAppointmentsForm;
|
package/src/helpers/functions.ts
CHANGED
|
@@ -1,41 +1,65 @@
|
|
|
1
1
|
import dayjs, { type Dayjs } from 'dayjs';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { type TFunction } from 'i18next';
|
|
3
|
+
import { launchWorkspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework';
|
|
4
|
+
import { type AppointmentSummary, type AppointmentCountMap } from '../types';
|
|
5
|
+
import { appointmentsFormWorkspace } from '../constants';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
interface FlattenedAppointmentSummary {
|
|
8
|
+
serviceName: string;
|
|
9
|
+
countMap: AppointmentCountMap[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ServiceLoadSummary {
|
|
13
|
+
serviceName: string;
|
|
14
|
+
count: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const getHighestAppointmentServiceLoad = (
|
|
18
|
+
appointmentSummary: FlattenedAppointmentSummary[] = [],
|
|
19
|
+
): ServiceLoadSummary | undefined => {
|
|
20
|
+
const groupedAppointments = appointmentSummary.map(({ countMap, serviceName }) => ({
|
|
8
21
|
serviceName: serviceName,
|
|
9
|
-
count: countMap.reduce((
|
|
22
|
+
count: countMap.reduce((accumulator, currentValue) => accumulator + currentValue.allAppointmentsCount, 0),
|
|
10
23
|
}));
|
|
24
|
+
if (groupedAppointments.length === 0) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
11
27
|
return groupedAppointments.find((summary) => summary.count === Math.max(...groupedAppointments.map((x) => x.count)));
|
|
12
28
|
};
|
|
13
29
|
|
|
14
|
-
export const flattenAppointmentSummary = (
|
|
15
|
-
|
|
30
|
+
export const flattenAppointmentSummary = (
|
|
31
|
+
appointmentToTransform: AppointmentSummary[],
|
|
32
|
+
): FlattenedAppointmentSummary[] =>
|
|
33
|
+
appointmentToTransform.flatMap((el) => ({
|
|
16
34
|
serviceName: el.appointmentService.name,
|
|
17
|
-
countMap: Object.entries(el.appointmentCountMap).flatMap((
|
|
35
|
+
countMap: Object.entries(el.appointmentCountMap).flatMap(([, countMap]) => countMap),
|
|
18
36
|
}));
|
|
19
37
|
|
|
20
38
|
export const getServiceCountByAppointmentType = (
|
|
21
|
-
appointmentSummary:
|
|
22
|
-
appointmentType:
|
|
23
|
-
) => {
|
|
39
|
+
appointmentSummary: AppointmentSummary[],
|
|
40
|
+
appointmentType: 'allAppointmentsCount' | 'missedAppointmentsCount',
|
|
41
|
+
): number => {
|
|
24
42
|
return appointmentSummary
|
|
25
|
-
.map((el) =>
|
|
43
|
+
.map((el) =>
|
|
44
|
+
Object.values(el.appointmentCountMap).map((countMap) => {
|
|
45
|
+
const value = countMap[appointmentType];
|
|
46
|
+
if (typeof value === 'number') {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
26
52
|
.flat(1)
|
|
27
53
|
.reduce((count, val) => count + val, 0);
|
|
28
54
|
};
|
|
29
55
|
|
|
30
|
-
export const formatAMPM = (date) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const strTime = hours + ':' + minutes + ' ' + ampm;
|
|
38
|
-
return strTime;
|
|
56
|
+
export const formatAMPM = (date: Date): string => {
|
|
57
|
+
const hours24 = date.getHours();
|
|
58
|
+
const minutes = date.getMinutes();
|
|
59
|
+
const ampm = hours24 >= 12 ? 'PM' : 'AM';
|
|
60
|
+
const hours12 = hours24 % 12 || 12; // Convert 0 to 12
|
|
61
|
+
const minutesStr = minutes < 10 ? `0${minutes}` : minutes.toString();
|
|
62
|
+
return `${hours12}:${minutesStr} ${ampm}`;
|
|
39
63
|
};
|
|
40
64
|
|
|
41
65
|
export const isSameMonth = (cellDate: Dayjs, currentDate: Dayjs) => {
|
|
@@ -51,7 +75,7 @@ export const monthDays = (currentDate: Dayjs) => {
|
|
|
51
75
|
let days: Dayjs[] = [];
|
|
52
76
|
|
|
53
77
|
for (let i = lastMonth.daysInMonth() - monthStart.day() + 1; i <= lastMonth.daysInMonth(); i++) {
|
|
54
|
-
days.push(
|
|
78
|
+
days.push(lastMonth.date(i));
|
|
55
79
|
}
|
|
56
80
|
|
|
57
81
|
for (let i = 1; i <= monthDays; i++) {
|
|
@@ -61,12 +85,12 @@ export const monthDays = (currentDate: Dayjs) => {
|
|
|
61
85
|
const dayLen = days.length > 30 ? 7 : 14;
|
|
62
86
|
|
|
63
87
|
for (let i = 1; i < dayLen - monthEnd.day(); i++) {
|
|
64
|
-
days.push(
|
|
88
|
+
days.push(nextMonth.date(i));
|
|
65
89
|
}
|
|
66
90
|
return days;
|
|
67
91
|
};
|
|
68
92
|
|
|
69
|
-
export const getGender = (gender, t) => {
|
|
93
|
+
export const getGender = (gender: string | undefined, t: TFunction<'translation', undefined>): string => {
|
|
70
94
|
switch (gender) {
|
|
71
95
|
case 'M':
|
|
72
96
|
return t('male', 'Male');
|
|
@@ -80,3 +104,26 @@ export const getGender = (gender, t) => {
|
|
|
80
104
|
return gender;
|
|
81
105
|
}
|
|
82
106
|
};
|
|
107
|
+
|
|
108
|
+
export const launchCreateAppointmentForm = (t: TFunction<'translation', undefined>) => {
|
|
109
|
+
launchWorkspace2(
|
|
110
|
+
'appointments-patient-search-workspace',
|
|
111
|
+
{
|
|
112
|
+
initialQuery: '',
|
|
113
|
+
workspaceTitle: t('createNewAppointment', 'Create new appointment'),
|
|
114
|
+
onPatientSelected(
|
|
115
|
+
patientUuid: string,
|
|
116
|
+
patient: fhir.Patient,
|
|
117
|
+
launchChildWorkspace: Workspace2DefinitionProps['launchChildWorkspace'],
|
|
118
|
+
closeWorkspace: Workspace2DefinitionProps['closeWorkspace'],
|
|
119
|
+
) {
|
|
120
|
+
launchChildWorkspace(appointmentsFormWorkspace, {
|
|
121
|
+
patientUuid: patient.id,
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
startVisitWorkspaceName: 'appointments-patient-search-start-visit-workspace',
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -92,8 +92,6 @@ export const metricsCardProvidersBooked = getAsyncLifecycle(
|
|
|
92
92
|
options,
|
|
93
93
|
);
|
|
94
94
|
|
|
95
|
-
export const searchPatient = getAsyncLifecycle(() => import('./patient-search/patient-search.component'), options);
|
|
96
|
-
|
|
97
95
|
// t('Appointments', 'Appointments')
|
|
98
96
|
export const patientAppointmentsSummaryDashboardLink = getAsyncLifecycle(async () => {
|
|
99
97
|
const commonLib = await import('@openmrs/esm-patient-common-lib');
|
|
@@ -120,9 +118,13 @@ export const cancelAppointmentModal = getAsyncLifecycle(
|
|
|
120
118
|
options,
|
|
121
119
|
);
|
|
122
120
|
|
|
123
|
-
// t('createNewAppointment', 'Create new appointment')
|
|
124
121
|
export const appointmentsFormWorkspace = getAsyncLifecycle(() => import('./form/appointments-form.workspace'), options);
|
|
125
122
|
|
|
123
|
+
export const exportedAppointmentsFormWorkspace = getAsyncLifecycle(
|
|
124
|
+
() => import('./form/exported-appointments-form.workspace'),
|
|
125
|
+
options,
|
|
126
|
+
);
|
|
127
|
+
|
|
126
128
|
export const endAppointmentModal = getAsyncLifecycle(
|
|
127
129
|
() => import('./appointments/common-components/end-appointment.modal'),
|
|
128
130
|
options,
|
|
@@ -19,7 +19,7 @@ export default function HighestVolumeServiceExtension() {
|
|
|
19
19
|
return (
|
|
20
20
|
<MetricsCard
|
|
21
21
|
headerLabel={t('highestServiceVolume', 'Highest volume service: {{time}}', { time: formattedStartDate })}
|
|
22
|
-
label={highestServiceLoad
|
|
22
|
+
label={highestServiceLoad ? t(highestServiceLoad.serviceName) : t('serviceName', 'Service name')}
|
|
23
23
|
value={highestServiceLoad?.count ?? '--'}
|
|
24
24
|
/>
|
|
25
25
|
);
|
|
@@ -4,10 +4,11 @@ import isToday from 'dayjs/plugin/isToday';
|
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { Calendar, Hospital } from '@carbon/react/icons';
|
|
6
6
|
import { Button } from '@carbon/react';
|
|
7
|
-
import {
|
|
7
|
+
import { isDesktop, navigate, useLayoutType } from '@openmrs/esm-framework';
|
|
8
8
|
import { spaHomePage } from '../constants';
|
|
9
9
|
import { useAppointmentsStore } from '../store';
|
|
10
10
|
import styles from './metrics-header.scss';
|
|
11
|
+
import { launchCreateAppointmentForm } from '../helpers';
|
|
11
12
|
|
|
12
13
|
dayjs.extend(isToday);
|
|
13
14
|
|
|
@@ -17,16 +18,6 @@ const MetricsHeader: React.FC = () => {
|
|
|
17
18
|
const layout = useLayoutType();
|
|
18
19
|
const responsiveSize = isDesktop(layout) ? 'sm' : 'md';
|
|
19
20
|
|
|
20
|
-
const launchCreateAppointmentForm = (patientUuid) => {
|
|
21
|
-
const props = {
|
|
22
|
-
patientUuid: patientUuid,
|
|
23
|
-
context: 'creating',
|
|
24
|
-
mutate: () => {}, // TODO get this to mutate properly
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
launchWorkspace2('appointments-form-workspace', { ...props });
|
|
28
|
-
};
|
|
29
|
-
|
|
30
21
|
return (
|
|
31
22
|
<div className={styles.metricsContainer}>
|
|
32
23
|
<div className={styles.metricsContent}>
|
|
@@ -39,19 +30,13 @@ const MetricsHeader: React.FC = () => {
|
|
|
39
30
|
}>
|
|
40
31
|
{t('appointmentsCalendar', 'Appointments calendar')}
|
|
41
32
|
</Button>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
kind: 'primary',
|
|
50
|
-
renderIcon: (props) => <Hospital size={32} {...props} />,
|
|
51
|
-
size: responsiveSize,
|
|
52
|
-
},
|
|
53
|
-
}}
|
|
54
|
-
/>
|
|
33
|
+
<Button
|
|
34
|
+
kind="primary"
|
|
35
|
+
renderIcon={(props) => <Hospital size={32} {...props} />}
|
|
36
|
+
size={responsiveSize}
|
|
37
|
+
onClick={() => launchCreateAppointmentForm(t)}>
|
|
38
|
+
{t('createNewAppointment', 'Create new appointment')}
|
|
39
|
+
</Button>
|
|
55
40
|
</div>
|
|
56
41
|
</div>
|
|
57
42
|
);
|
|
@@ -15,11 +15,7 @@ export const PatientAppointmentsActionMenu = ({ appointment, patientUuid }: appo
|
|
|
15
15
|
const isTablet = useLayoutType() === 'tablet';
|
|
16
16
|
|
|
17
17
|
const handleLaunchEditAppointmentForm = () => {
|
|
18
|
-
launchWorkspace2('appointments-form-workspace', {
|
|
19
|
-
appointment,
|
|
20
|
-
context: 'editing',
|
|
21
|
-
patientUuid,
|
|
22
|
-
});
|
|
18
|
+
launchWorkspace2('appointments-form-workspace', { appointment, patientUuid });
|
|
23
19
|
};
|
|
24
20
|
|
|
25
21
|
const handleLaunchCancelAppointmentModal = () => {
|