@openmrs/esm-appointments-app 9.2.1-pre.7303 → 9.2.1-pre.7318

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.
Files changed (65) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/dist/1431.js +1 -1
  3. package/dist/1431.js.map +1 -1
  4. package/dist/1559.js +1 -1
  5. package/dist/1559.js.map +1 -1
  6. package/dist/2265.js +1 -0
  7. package/dist/2265.js.map +1 -0
  8. package/dist/{5160.js → 4036.js} +1 -1
  9. package/dist/{5160.js.map → 4036.js.map} +1 -1
  10. package/dist/449.js +1 -1
  11. package/dist/449.js.map +1 -1
  12. package/dist/4515.js +1 -0
  13. package/dist/4515.js.map +1 -0
  14. package/dist/4745.js +1 -1
  15. package/dist/4745.js.map +1 -1
  16. package/dist/4889.js +1 -1
  17. package/dist/4889.js.map +1 -1
  18. package/dist/525.js +1 -1
  19. package/dist/525.js.map +1 -1
  20. package/dist/5666.js +1 -1
  21. package/dist/5666.js.map +1 -1
  22. package/dist/5755.js +1 -1
  23. package/dist/5755.js.map +1 -1
  24. package/dist/592.js +1 -1
  25. package/dist/592.js.map +1 -1
  26. package/dist/6467.js +1 -1
  27. package/dist/6467.js.map +1 -1
  28. package/dist/6886.js +1 -1
  29. package/dist/6886.js.map +1 -1
  30. package/dist/{7565.js → 7294.js} +1 -1
  31. package/dist/7294.js.map +1 -0
  32. package/dist/9712.js +1 -1
  33. package/dist/9712.js.map +1 -1
  34. package/dist/main.js +1 -1
  35. package/dist/main.js.map +1 -1
  36. package/dist/openmrs-esm-appointments-app.js +1 -1
  37. package/dist/openmrs-esm-appointments-app.js.buildmanifest.json +121 -120
  38. package/dist/routes.json +1 -1
  39. package/package.json +1 -1
  40. package/src/appointments/common-components/appointments-table.component.tsx +4 -6
  41. package/src/constants.ts +1 -0
  42. package/src/form/appointments-form.resource.ts +1 -0
  43. package/src/form/appointments-form.test.tsx +111 -71
  44. package/src/form/appointments-form.workspace.tsx +437 -436
  45. package/src/form/exported-appointments-form.workspace.tsx +24 -0
  46. package/src/helpers/functions.ts +72 -25
  47. package/src/index.ts +5 -3
  48. package/src/metrics/metrics-cards/highest-volume-service.extension.tsx +1 -1
  49. package/src/metrics/metrics-cards/metrics-card.component.tsx +1 -1
  50. package/src/metrics/metrics-header.component.tsx +9 -24
  51. package/src/patient-appointments/patient-appointments-action-menu.component.tsx +2 -6
  52. package/src/patient-appointments/patient-appointments-detailed-summary.extension.tsx +176 -15
  53. package/src/patient-appointments/{patient-appointments-base.test.tsx → patient-appointments-detailed-summary.test.tsx} +14 -22
  54. package/src/patient-appointments/patient-appointments-overview.component.tsx +15 -18
  55. package/src/routes.json +22 -7
  56. package/dist/3092.js +0 -1
  57. package/dist/3092.js.map +0 -1
  58. package/dist/7026.js +0 -1
  59. package/dist/7026.js.map +0 -1
  60. package/dist/7565.js.map +0 -1
  61. package/src/hooks/patient-appointment-context.ts +0 -18
  62. package/src/patient-appointments/patient-appointments-base.component.tsx +0 -178
  63. package/src/patient-search/patient-search.component.tsx +0 -33
  64. package/src/patient-search/patient-search.scss +0 -24
  65. /package/src/patient-appointments/{patient-appointments-base.scss → patient-appointments-detailed-summary.scss} +0 -0
@@ -18,13 +18,47 @@ import { useProviders } from '../hooks/useProviders';
18
18
  import type { AppointmentKind, AppointmentStatus } from '../types';
19
19
  import AppointmentForm from './appointments-form.workspace';
20
20
 
21
+ const existingAppointment = {
22
+ uuid: 'appointment-uuid',
23
+ appointmentNumber: 'APT-001',
24
+ startDateTime: '2024-01-04T09:30:00.000Z',
25
+ endDateTime: '2024-01-04T10:00:00.000Z',
26
+ appointmentKind: 'Scheduled' as AppointmentKind.SCHEDULED,
27
+ status: 'Scheduled' as AppointmentStatus.SCHEDULED,
28
+ comments: 'Existing appointment note',
29
+ location: { uuid: 'b1a8b05e-3542-4037-bbd3-998ee9c40574', display: 'Inpatient Ward', name: 'Inpatient Ward' },
30
+ service: {
31
+ uuid: 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90',
32
+ name: 'Outpatient',
33
+ appointmentServiceId: 1,
34
+ creatorName: 'Test Creator',
35
+ description: 'Outpatient service',
36
+ endTime: '17:00',
37
+ initialAppointmentStatus: 'Scheduled' as AppointmentStatus.SCHEDULED,
38
+ maxAppointmentsLimit: null,
39
+ startTime: '08:00',
40
+ },
41
+ patient: { uuid: mockPatient.id, name: 'Test Patient', identifier: '12345', identifiers: [] },
42
+ provider: { uuid: 'f9badd80-ab76-11e2-9e96-0800200c9a66', display: 'Dr. Cook' },
43
+ providers: [{ uuid: 'f9badd80-ab76-11e2-9e96-0800200c9a66', response: 'ACCEPTED' }],
44
+ recurring: false,
45
+ voided: false,
46
+ extensions: {},
47
+ teleconsultationLink: null,
48
+ dateAppointmentScheduled: '2024-01-04T00:00:00.000Z',
49
+ };
50
+
21
51
  const defaultProps = {
22
- context: 'creating',
23
52
  closeWorkspace: jest.fn(),
24
- patientUuid: mockPatient.id,
25
- promptBeforeClosing: jest.fn(),
26
- closeWorkspaceWithSavedChanges: jest.fn(),
27
- setTitle: jest.fn(),
53
+ workspaceProps: {
54
+ patientUuid: mockPatient.id,
55
+ },
56
+ windowProps: null,
57
+ groupProps: null,
58
+ workspaceName: 'appointments-form',
59
+ windowName: 'test-window',
60
+ isRootWorkspace: true,
61
+ launchChildWorkspace: jest.fn(),
28
62
  };
29
63
 
30
64
  const mockOpenmrsFetch = jest.mocked(openmrsFetch);
@@ -694,7 +728,7 @@ describe('AppointmentForm', () => {
694
728
  expect(mockSaveAppointment).not.toHaveBeenCalled();
695
729
  });
696
730
 
697
- it('should detect patient double-booking conflicts', async () => {
731
+ it('should detect patient double-booking conflicts when creating new appointment', async () => {
698
732
  const user = userEvent.setup();
699
733
 
700
734
  mockOpenmrsFetch.mockResolvedValue({ data: mockUseAppointmentServiceData } as unknown as FetchResponse);
@@ -703,6 +737,7 @@ describe('AppointmentForm', () => {
703
737
  data: { PATIENT_DOUBLE_BOOKING: true },
704
738
  } as FetchResponse);
705
739
 
740
+ // Render WITHOUT existing appointment (creating mode)
706
741
  renderWithSwr(<AppointmentForm {...defaultProps} />);
707
742
 
708
743
  await waitForLoadingToFinish();
@@ -740,6 +775,7 @@ describe('AppointmentForm', () => {
740
775
  await user.click(saveButton);
741
776
 
742
777
  expect(mockCheckAppointmentConflict).toHaveBeenCalledTimes(1);
778
+ // Should show double-booking error when creating new appointment
743
779
  expect(mockShowSnackbar).toHaveBeenCalledWith({
744
780
  isLowContrast: true,
745
781
  kind: 'error',
@@ -747,43 +783,72 @@ describe('AppointmentForm', () => {
747
783
  });
748
784
  expect(mockSaveAppointment).not.toHaveBeenCalled();
749
785
  });
786
+
787
+ it('should not show double-booking error when editing same appointment', async () => {
788
+ const user = userEvent.setup();
789
+
790
+ mockOpenmrsFetch.mockResolvedValue({ data: mockUseAppointmentServiceData } as unknown as FetchResponse);
791
+ // Backend should exclude the current appointment from conflict check when UUID is sent
792
+ mockCheckAppointmentConflict.mockResolvedValue({
793
+ status: 204,
794
+ data: {},
795
+ } as FetchResponse);
796
+ mockSaveAppointment.mockResolvedValue({ status: 200, statusText: 'Ok' } as FetchResponse);
797
+
798
+ // Render WITH existing appointment (editing mode)
799
+ renderWithSwr(
800
+ <AppointmentForm
801
+ {...defaultProps}
802
+ workspaceProps={{
803
+ ...defaultProps.workspaceProps,
804
+ appointment: existingAppointment,
805
+ }}
806
+ />,
807
+ );
808
+
809
+ await waitForLoadingToFinish();
810
+
811
+ const appointmentNoteTextarea = screen.getByRole('textbox', { name: /write an additional note/i });
812
+ const saveButton = screen.getByRole('button', { name: /save and close/i });
813
+
814
+ // Make a small change
815
+ await user.clear(appointmentNoteTextarea);
816
+ await user.type(appointmentNoteTextarea, 'Updated note');
817
+
818
+ await new Promise((resolve) => setTimeout(resolve, 500));
819
+ await user.click(saveButton);
820
+
821
+ expect(mockCheckAppointmentConflict).toHaveBeenCalledTimes(1);
822
+ // Verify UUID is sent to backend so it can exclude current appointment from conflict check
823
+ expect(mockCheckAppointmentConflict).toHaveBeenCalledWith(
824
+ expect.objectContaining({
825
+ uuid: existingAppointment.uuid,
826
+ }),
827
+ );
828
+ // Should NOT show double-booking error when editing (same appointment)
829
+ expect(mockShowSnackbar).not.toHaveBeenCalledWith(
830
+ expect.objectContaining({
831
+ title: 'Patient already booked for an appointment at this time',
832
+ }),
833
+ );
834
+ // Should proceed with save
835
+ expect(mockSaveAppointment).toHaveBeenCalled();
836
+ });
750
837
  });
751
838
 
752
839
  describe('Edit Mode', () => {
753
840
  it('should pre-populate form with existing appointment data', async () => {
754
- const existingAppointment = {
755
- uuid: 'appointment-uuid',
756
- appointmentNumber: 'APT-001',
757
- startDateTime: '2024-01-04T09:30:00.000Z',
758
- endDateTime: '2024-01-04T10:00:00.000Z',
759
- appointmentKind: 'Scheduled' as AppointmentKind.SCHEDULED,
760
- status: 'Scheduled' as AppointmentStatus.SCHEDULED,
761
- comments: 'Existing appointment note',
762
- location: { uuid: 'b1a8b05e-3542-4037-bbd3-998ee9c40574', display: 'Inpatient Ward', name: 'Inpatient Ward' },
763
- service: {
764
- uuid: 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90',
765
- name: 'Outpatient',
766
- appointmentServiceId: 1,
767
- creatorName: 'Test Creator',
768
- description: 'Outpatient service',
769
- endTime: '17:00',
770
- initialAppointmentStatus: 'Scheduled' as AppointmentStatus.SCHEDULED,
771
- maxAppointmentsLimit: null,
772
- startTime: '08:00',
773
- },
774
- patient: { uuid: mockPatient.id, name: 'Test Patient', identifier: '12345', identifiers: [] },
775
- provider: { uuid: 'f9badd80-ab76-11e2-9e96-0800200c9a66', display: 'Dr. Cook' },
776
- providers: [{ uuid: 'f9badd80-ab76-11e2-9e96-0800200c9a66', response: 'ACCEPTED' }],
777
- recurring: false,
778
- voided: false,
779
- extensions: {},
780
- teleconsultationLink: null,
781
- dateAppointmentScheduled: '2024-01-04T00:00:00.000Z',
782
- };
783
-
784
841
  mockOpenmrsFetch.mockResolvedValue({ data: mockUseAppointmentServiceData } as unknown as FetchResponse);
785
842
 
786
- renderWithSwr(<AppointmentForm {...defaultProps} appointment={existingAppointment} context="editing" />);
843
+ renderWithSwr(
844
+ <AppointmentForm
845
+ {...defaultProps}
846
+ workspaceProps={{
847
+ ...defaultProps.workspaceProps,
848
+ appointment: existingAppointment,
849
+ }}
850
+ />,
851
+ );
787
852
 
788
853
  await waitForLoadingToFinish();
789
854
 
@@ -795,41 +860,19 @@ describe('AppointmentForm', () => {
795
860
 
796
861
  it('should update appointment successfully', async () => {
797
862
  const user = userEvent.setup();
798
- const existingAppointment = {
799
- uuid: 'appointment-uuid',
800
- appointmentNumber: 'APT-001',
801
- startDateTime: '2024-01-04T09:30:00.000Z',
802
- endDateTime: '2024-01-04T10:00:00.000Z',
803
- appointmentKind: 'Scheduled' as AppointmentKind.SCHEDULED,
804
- status: 'Scheduled' as AppointmentStatus.SCHEDULED,
805
- comments: 'Original note',
806
- location: { uuid: 'b1a8b05e-3542-4037-bbd3-998ee9c40574', display: 'Inpatient Ward', name: 'Inpatient Ward' },
807
- service: {
808
- uuid: 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90',
809
- name: 'Outpatient',
810
- appointmentServiceId: 1,
811
- creatorName: 'Test Creator',
812
- description: 'Outpatient service',
813
- endTime: '17:00',
814
- initialAppointmentStatus: 'Scheduled',
815
- maxAppointmentsLimit: null,
816
- startTime: '08:00',
817
- },
818
- patient: { uuid: mockPatient.id, name: 'Test Patient', identifier: '12345', identifiers: [] },
819
- provider: { uuid: 'f9badd80-ab76-11e2-9e96-0800200c9a66', display: 'Dr. Cook' },
820
- providers: [{ uuid: 'f9badd80-ab76-11e2-9e96-0800200c9a66', response: 'ACCEPTED' }],
821
- recurring: false,
822
- voided: false,
823
- extensions: {},
824
- teleconsultationLink: null,
825
- dateAppointmentScheduled: '2024-01-04T00:00:00.000Z',
826
- };
827
863
 
828
864
  mockOpenmrsFetch.mockResolvedValue({ data: mockUseAppointmentServiceData } as unknown as FetchResponse);
829
865
  mockCheckAppointmentConflict.mockResolvedValue({ status: 204, data: {} } as FetchResponse);
830
866
  mockSaveAppointment.mockResolvedValue({ status: 200, statusText: 'Ok' } as FetchResponse);
831
-
832
- renderWithSwr(<AppointmentForm {...defaultProps} appointment={existingAppointment} context="editing" />);
867
+ renderWithSwr(
868
+ <AppointmentForm
869
+ {...defaultProps}
870
+ workspaceProps={{
871
+ ...defaultProps.workspaceProps,
872
+ appointment: existingAppointment,
873
+ }}
874
+ />,
875
+ );
833
876
 
834
877
  await waitForLoadingToFinish();
835
878
 
@@ -897,9 +940,6 @@ describe('AppointmentForm', () => {
897
940
 
898
941
  // Try to cancel
899
942
  await user.click(cancelButton);
900
-
901
- // Should call promptBeforeClosing if there are unsaved changes
902
- expect(defaultProps.promptBeforeClosing).toHaveBeenCalled();
903
943
  });
904
944
  });
905
945
  });