diff --git a/.gitignore b/.gitignore index 84a171bb3..20c829d72 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ results.xml moduleName .env + +# vim +.swp diff --git a/.husky/pre-push b/.husky/pre-push index b48f5ec9f..879f05d90 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -3,4 +3,4 @@ set -e # die on error -# yarn verify +yarn verify diff --git a/.tx/config b/.tx/config index 61326b06a..e00dfb552 100644 --- a/.tx/config +++ b/.tx/config @@ -1,7 +1,7 @@ [main] host = https://www.transifex.com -[o:openmrs:p:openmrs-esm-patient-management:r:esm-active-visits-app] +[o:openmrs:p:openmrs3:r:esm-active-visits-app] file_filter = packages/esm-active-visits-app/translations/.json source_file = packages/esm-active-visits-app/translations/en.json source_lang = en @@ -10,7 +10,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-active-visits-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-appointments-app] +[o:openmrs:p:openmrs3:r:esm-appointments-app] file_filter = packages/esm-appointments-app/translations/.json source_file = packages/esm-appointments-app/translations/en.json source_lang = en @@ -19,7 +19,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-appointments-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-bed-management-app] +[o:openmrs:p:openmrs3:r:esm-bed-management-app] file_filter = packages/esm-bed-management-app/translations/.json source_file = packages/esm-bed-management-app/translations/en.json source_lang = en @@ -28,7 +28,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-bed-management-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-patient-list-management-app] +[o:openmrs:p:openmrs3:r:esm-patient-list-management-app] file_filter = packages/esm-patient-list-management-app/translations/.json source_file = packages/esm-patient-list-management-app/translations/en.json source_lang = en @@ -37,7 +37,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-patient-list-management-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-patient-registration-app] +[o:openmrs:p:openmrs3:r:esm-patient-registration-app] file_filter = packages/esm-patient-registration-app/translations/.json source_file = packages/esm-patient-registration-app/translations/en.json source_lang = en @@ -46,7 +46,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-patient-registration-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-patient-search-app] +[o:openmrs:p:openmrs3:r:esm-patient-search-app] file_filter = packages/esm-patient-search-app/translations/.json source_file = packages/esm-patient-search-app/translations/en.json source_lang = en @@ -55,7 +55,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-patient-search-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-service-queues-app] +[o:openmrs:p:openmrs3:r:esm-service-queues-app] file_filter = packages/esm-service-queues-app/translations/.json source_file = packages/esm-service-queues-app/translations/en.json source_lang = en diff --git a/e2e/commands/cohort-operations.ts b/e2e/commands/cohort-operations.ts index 0c672510e..496038970 100644 --- a/e2e/commands/cohort-operations.ts +++ b/e2e/commands/cohort-operations.ts @@ -1,5 +1,5 @@ -import { APIRequestContext, expect } from '@playwright/test'; -import { Patient } from './patient-operations'; +import { type APIRequestContext, expect } from '@playwright/test'; +import { type Patient } from './patient-operations'; export interface CohortType { uuid: string; diff --git a/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.component.tsx b/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.component.tsx index 5017deea3..f30042b81 100644 --- a/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.component.tsx +++ b/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.component.tsx @@ -22,6 +22,7 @@ const AddPatientToPatientListMenuItem: React.FC dispose(), patientUuid, + size: 'sm', }); closeOverflowMenu(); }, [patientUuid]); diff --git a/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.test.tsx b/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.test.tsx index 7334b4fe4..e4fe52b3d 100644 --- a/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.test.tsx +++ b/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.test.tsx @@ -26,6 +26,10 @@ describe('AddPatientToPatientListMenuItem', () => { await user.click(button); - expect(mockShowModal).toHaveBeenCalledWith('add-patient-to-patient-list-modal', expect.any(Object)); + expect(mockShowModal).toHaveBeenCalledWith('add-patient-to-patient-list-modal', { + closeModal: expect.any(Function), + size: 'sm', + patientUuid: mockPatient.uuid, + }); }); }); diff --git a/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx b/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx index fc040c167..6c6b5b345 100644 --- a/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx +++ b/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx @@ -1,21 +1,11 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react'; import classNames from 'classnames'; +import { mutate } from 'swr'; import { useTranslation } from 'react-i18next'; -import type { TFunction } from 'i18next'; -import useSWR from 'swr'; -import { Button, Checkbox, Pagination, Search, CheckboxSkeleton } from '@carbon/react'; -import { - getDynamicOfflineDataEntries, - putDynamicOfflineData, - syncDynamicOfflineData, - showSnackbar, - toOmrsIsoString, - usePagination, - navigate, - useConfig, -} from '@openmrs/esm-framework'; -import { addPatientToList, getAllPatientLists, getPatientListIdsForPatient } from '../api/api-remote'; -import { type ConfigSchema } from '../config-schema'; +import { Button, Checkbox, CheckboxSkeleton, Pagination, Search, Tile } from '@carbon/react'; +import { navigate, restBaseUrl, showSnackbar, usePagination } from '@openmrs/esm-framework'; +import { type AddablePatientListViewModel } from '../api/types'; +import { useAddablePatientLists } from '../api/api-remote'; import styles from './add-patient.scss'; interface AddPatientProps { @@ -23,26 +13,19 @@ interface AddPatientProps { patientUuid: string; } -interface AddablePatientListViewModel { - addPatient(): Promise; - displayName: string; - checked?: boolean; - id: string; -} - const AddPatient: React.FC = ({ closeModal, patientUuid }) => { const { t } = useTranslation(); + const { data, isLoading } = useAddablePatientLists(patientUuid); const [searchValue, setSearchValue] = useState(''); const [selected, setSelected] = useState>([]); - const { data, isLoading } = useAddablePatientLists(patientUuid); - const handleCreateNewList = () => { + const handleCreateNewList = useCallback(() => { navigate({ to: window.getOpenmrsSpaBase() + 'home/patient-lists?new_cohort=true', }); closeModal(); - }; + }, [closeModal]); const handleSelectionChanged = useCallback((patientListId: string, listSelected: boolean) => { if (listSelected) { @@ -52,34 +35,39 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { } }, []); - const handleSubmit = useCallback(() => { - for (const selectedId of selected) { - const patientList = data.find((list) => list.id === selectedId); - if (!patientList) { - continue; - } + const mutateCohortMembers = useCallback(() => { + const key = `${restBaseUrl}/cohortm/cohortmember?patient=${patientUuid}&v=custom:(uuid,patient:ref,cohort:(uuid,name,startDate,endDate))`; - patientList - .addPatient() - .then(() => - showSnackbar({ - title: t('successfullyAdded', 'Successfully added'), - kind: 'success', - isLowContrast: true, - subtitle: `${t('successAddPatientToList', 'Patient added to list')}: ${patientList.displayName}`, - }), - ) - .catch(() => - showSnackbar({ - title: t('error', 'Error'), - kind: 'error', - subtitle: `${t('errorAddPatientToList', 'Patient not added to list')}: ${patientList.displayName}`, - }), - ); - } + return mutate((k) => typeof k === 'string' && k === key); + }, []); - closeModal(); - }, [data, selected, closeModal, t]); + const handleSubmit = useCallback(() => { + Promise.all( + selected.map((selectedId) => { + const patientList = data.find((list) => list.id === selectedId); + if (!patientList) return Promise.resolve(); + + return patientList + .addPatient() + .then(async () => { + await mutateCohortMembers(); + showSnackbar({ + title: t('successfullyAdded', 'Successfully added'), + kind: 'success', + isLowContrast: true, + subtitle: `${t('successAddPatientToList', 'Patient added to list')}: ${patientList.displayName}`, + }); + }) + .catch(() => { + showSnackbar({ + title: t('error', 'Error'), + kind: 'error', + subtitle: `${t('errorAddPatientToList', 'Patient not added to list')}: ${patientList.displayName}`, + }); + }); + }), + ).finally(closeModal); + }, [data, selected, closeModal, t, patientUuid]); const searchResults = useMemo(() => { if (!data) { @@ -94,7 +82,7 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { return data; }, [searchValue, data]); - const { results, goTo, currentPage, paginated } = usePagination(searchResults, 5); + const { results, goTo, currentPage, paginated } = usePagination(searchResults, 5); useEffect(() => { if (currentPage !== 1) { @@ -105,41 +93,54 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { return (
-

{t('addPatientToList', 'Add patient to list')}

-

+

{t('addPatientToList', 'Add patient to list')}

+

{t('searchForAListToAddThisPatientTo', 'Search for a list to add this patient to.')}

-
- { - setSearchValue(target.value); - }} - value={searchValue} - /> -
+ { + setSearchValue(target.value); + }} + value={searchValue} + />
-

{t('patientLists', 'Patient lists')}

{!isLoading && results ? ( results.length > 0 ? ( - results.map((patientList) => ( -
- handleSelectionChanged(patientList.id, e.target.checked)} - checked={patientList.checked || selected.includes(patientList.id)} - disabled={patientList.checked} - labelText={patientList.displayName} - id={patientList.id} - /> -
- )) + <> +

{t('patientLists', 'Patient lists')}

+ {results.map((patientList) => ( +
+ handleSelectionChanged(patientList.id, e.target.checked)} + checked={patientList.checked || selected.includes(patientList.id)} + disabled={patientList.checked} + labelText={patientList.displayName} + id={patientList.id} + /> +
+ ))} + ) : ( -

{t('noPatientListFound', 'No patient list found')}

+
+ +
+

{t('noMatchingListsFound', 'No matching lists found')}

+

+ {t('trySearchingForADifferentList', 'Try searching for a different list')} + — or — + +

+
+
+
) ) : ( <> @@ -180,7 +181,7 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => {
)}
-
@@ -196,76 +197,4 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { ); }; -// This entire modal is a little bit special since it not only displays the "real" patient lists (i.e. data from -// the cohorts/backend), but also a fake patient list which doesn't really exist in the backend: -// The offline patient list. -// When a patient is added to the offline list, that patient should become available offline, i.e. -// a dynamic offline data entry must be created. -// This is why the following abstracts away the differences between the real and the fake patient lists. -// The component doesn't really care about which is which - the only thing that matters is that the -// data can be fetched and that there is an "add patient" function. - -export function useAddablePatientLists(patientUuid: string) { - const { t } = useTranslation(); - const config = useConfig() as ConfigSchema; - return useSWR(['addablePatientLists', patientUuid], async () => { - // Using Promise.allSettled instead of Promise.all here because some distros might not have the - // cohort module installed, leading to the real patient list call failing. - // In that case we still want to show fake lists and *not* error out here. - const [fakeLists, realLists] = await Promise.allSettled([ - findFakePatientListsWithoutPatient(patientUuid, t), - findRealPatientListsWithoutPatient(patientUuid, config.myListCohortTypeUUID, config.systemListCohortTypeUUID), - ]); - - return [ - ...(fakeLists.status === 'fulfilled' ? fakeLists.value : []), - ...(realLists.status === 'fulfilled' ? realLists.value : []), - ]; - }); -} - -async function findRealPatientListsWithoutPatient( - patientUuid: string, - myListCohortUUID, - systemListCohortType, -): Promise> { - const [allLists, listsIdsOfThisPatient] = await Promise.all([ - getAllPatientLists({}, myListCohortUUID, systemListCohortType), - getPatientListIdsForPatient(patientUuid), - ]); - - return allLists.map((list) => ({ - id: list.id, - displayName: list.display, - checked: listsIdsOfThisPatient.includes(list.id), - async addPatient() { - await addPatientToList({ - cohort: list.id, - patient: patientUuid, - startDate: toOmrsIsoString(new Date()), - }); - }, - })); -} - -async function findFakePatientListsWithoutPatient( - patientUuid: string, - t: TFunction, -): Promise> { - const offlinePatients = await getDynamicOfflineDataEntries('patient'); - const isPatientOnOfflineList = offlinePatients.some((x) => x.identifier === patientUuid); - return isPatientOnOfflineList - ? [] - : [ - { - id: 'fake-offline-patient-list', - displayName: t('offlinePatients', 'Offline patients'), - async addPatient() { - await putDynamicOfflineData('patient', patientUuid); - await syncDynamicOfflineData('patient', patientUuid); - }, - }, - ]; -} - export default AddPatient; diff --git a/packages/esm-patient-list-management-app/src/add-patient/add-patient.scss b/packages/esm-patient-list-management-app/src/add-patient/add-patient.scss index 6d985f25e..0b4fdaab5 100644 --- a/packages/esm-patient-list-management-app/src/add-patient/add-patient.scss +++ b/packages/esm-patient-list-management-app/src/add-patient/add-patient.scss @@ -1,9 +1,11 @@ +@use '@carbon/colors'; @use '@carbon/layout'; @use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .modalContent { width: 100%; - background-color: #f4f4f4; + background-color: $ui-02; } .modalHeader { @@ -18,6 +20,16 @@ padding-bottom: 0.875rem; } +.header { + @include type.type-style('heading-03'); + margin: layout.$spacing-05 0; +} + +.subheader { + @include type.type-style('body-01'); + margin: layout.$spacing-05 0; +} + .pagination { width: 100%; overflow: hidden; @@ -30,6 +42,11 @@ position: relative; } +.search { + background-color: colors.$white; + margin-bottom: 0.875rem; +} + .itemsCountDisplay { position: absolute; top: 0; @@ -37,7 +54,7 @@ height: layout.$spacing-09; display: flex; align-items: center; - color: #525252; + color: colors.$gray-70; } .pagination > div:first-child { @@ -47,8 +64,22 @@ .buttonSet { display: flex; - justify-content: space-between; - align-items: flex-start; + align-items: center; + justify-content: center; + width: 100%; + + .createButton { + flex: 2; + } + + div { + flex: 1; + display: flex; + + > button { + flex: 1; + } + } } .productiveHeading03 { @@ -58,3 +89,41 @@ .bodyLong01 { @include type.type-style('body-01'); } + +.tileContainer { + background-color: $ui-02; + padding: layout.$spacing-09 0; + margin-bottom: layout.$spacing-05; +} + +.tile { + margin: auto; + width: fit-content; +} + +.tileContent { + display: flex; + flex-direction: column; + align-items: center; +} + +.content { + @include type.type-style('heading-compact-02'); + color: $text-02; + margin-bottom: layout.$spacing-03; +} + +.helper { + @include type.type-style('body-compact-01'); + color: $text-02; +} + +.actionText { + @include type.type-style('body-01'); + color: $text-02; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: layout.$spacing-05; +} diff --git a/packages/esm-patient-list-management-app/src/add-patient/add-patient.test.tsx b/packages/esm-patient-list-management-app/src/add-patient/add-patient.test.tsx new file mode 100644 index 000000000..c9bcf398f --- /dev/null +++ b/packages/esm-patient-list-management-app/src/add-patient/add-patient.test.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; +import { navigate } from '@openmrs/esm-framework'; +import { useAddablePatientLists } from '../api/api-remote'; +import { mockPatient } from '__mocks__'; +import AddPatient from './add-patient.component'; + +const mockNavigate = jest.mocked(navigate); +const mockUseAddablePatientLists = jest.mocked(useAddablePatientLists); +const mockCloseModal = jest.fn(); + +jest.mock('../api/api-remote', () => ({ + useAddablePatientLists: jest.fn(), +})); + +describe('AddPatient', () => { + beforeEach(() => { + mockUseAddablePatientLists.mockReturnValue({ + data: [ + { id: 'list1', displayName: 'List 1', addPatient: jest.fn() }, + { id: 'list2', displayName: 'List 2', addPatient: jest.fn() }, + ], + isLoading: false, + error: null, + mutate: jest.fn(), + isValidating: false, + }); + }); + + it('renders the Add Patient to List modal', () => { + render(); + + expect(screen.getByRole('heading', { name: /add patient to list/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /search for a list to add this patient to/i })).toBeInTheDocument(); + expect(screen.getByRole('searchbox', { name: /search for a list/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /clear search input/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /create new patient list/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /add to list/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument(); + expect(screen.getByRole('checkbox', { name: /list 1/i })).toBeInTheDocument(); + expect(screen.getByRole('checkbox', { name: /list 2/i })).toBeInTheDocument(); + }); + + it('allows selecting and deselecting patient lists', async () => { + const user = userEvent.setup(); + render(); + + const checkbox1 = screen.getByLabelText('List 1'); + const checkbox2 = screen.getByLabelText('List 2'); + + await user.click(checkbox1); + expect(checkbox1).toBeChecked(); + + await user.click(checkbox2); + expect(checkbox2).toBeChecked(); + + await user.click(checkbox1); + expect(checkbox1).not.toBeChecked(); + }); + + it('filters patient lists based on search input', async () => { + const user = userEvent.setup(); + render(); + + const searchInput = screen.getByRole('searchbox', { name: /search for a list/i }); + await user.type(searchInput, 'Bananarama'); + expect(screen.getByText(/no matching lists found/i)).toBeInTheDocument(); + expect(screen.getByText(/try searching for a different list/i)).toBeInTheDocument(); + expect(screen.getAllByRole('button', { name: /create new patient list/i })).toHaveLength(2); + + await user.clear(searchInput); + await user.type(searchInput, 'List 1'); + + expect(screen.getByText('List 1')).toBeInTheDocument(); + expect(screen.queryByText('List 2')).not.toBeInTheDocument(); + }); + + it('clicking the "Create new list" button opens the create list form', async () => { + const user = userEvent.setup(); + render(); + + const createNewListButton = screen.getByRole('button', { name: /create new patient list/i }); + await user.click(createNewListButton); + + expect(mockNavigate).toHaveBeenCalledWith({ + to: window.getOpenmrsSpaBase() + 'home/patient-lists?new_cohort=true', + }); + }); + + it('clicking the "Cancel" button closes the modal', async () => { + const user = userEvent.setup(); + render(); + + const cancelButton = screen.getByRole('button', { name: /cancel/i }); + await user.click(cancelButton); + + expect(mockCloseModal).toHaveBeenCalled(); + }); +}); diff --git a/packages/esm-patient-list-management-app/src/api/api-remote.ts b/packages/esm-patient-list-management-app/src/api/api-remote.ts index 5e36dcf8c..dd75a8b1b 100644 --- a/packages/esm-patient-list-management-app/src/api/api-remote.ts +++ b/packages/esm-patient-list-management-app/src/api/api-remote.ts @@ -1,6 +1,22 @@ -import { type LoggedInUser, openmrsFetch, refetchCurrentUser, restBaseUrl, fhirBaseUrl } from '@openmrs/esm-framework'; +import { useTranslation } from 'react-i18next'; +import { type TFunction } from 'i18next'; +import useSWR from 'swr'; +import { + type LoggedInUser, + openmrsFetch, + refetchCurrentUser, + restBaseUrl, + fhirBaseUrl, + getDynamicOfflineDataEntries, + syncDynamicOfflineData, + putDynamicOfflineData, + toOmrsIsoString, + useConfig, +} from '@openmrs/esm-framework'; +import { type ConfigSchema } from '../config-schema'; import { type AddPatientData, + type AddablePatientListViewModel, type CohortResponse, type NewCohortData, type NewCohortDataPayload, @@ -9,8 +25,8 @@ import { type OpenmrsCohortRef, type PatientListFilter, type PatientListMember, - PatientListType, type PatientListUpdate, + PatientListType, } from './types'; export const cohortUrl = `${restBaseUrl}/cohortm`; @@ -209,3 +225,75 @@ export async function getPatientListName(patientListUuid: string) { console.error('Error resolving patient list name: ', error); } } + +export async function findRealPatientListsWithoutPatient( + patientUuid: string, + myListCohortUUID: string, + systemListCohortType: string, +): Promise> { + const [allLists, listsIdsOfThisPatient] = await Promise.all([ + getAllPatientLists({}, myListCohortUUID, systemListCohortType), + getPatientListIdsForPatient(patientUuid), + ]); + + return allLists.map((list) => ({ + id: list.id, + displayName: list.display, + checked: listsIdsOfThisPatient.includes(list.id), + async addPatient() { + await addPatientToList({ + cohort: list.id, + patient: patientUuid, + startDate: toOmrsIsoString(new Date()), + }); + }, + })); +} + +export async function findFakePatientListsWithoutPatient( + patientUuid: string, + t: TFunction, +): Promise> { + const offlinePatients = await getDynamicOfflineDataEntries('patient'); + const isPatientOnOfflineList = offlinePatients.some((x) => x.identifier === patientUuid); + return isPatientOnOfflineList + ? [] + : [ + { + id: 'fake-offline-patient-list', + displayName: t('offlinePatients', 'Offline patients'), + async addPatient() { + await putDynamicOfflineData('patient', patientUuid); + await syncDynamicOfflineData('patient', patientUuid); + }, + }, + ]; +} + +// This entire model is a little bit special since it not only displays the "real" patient lists (i.e. data from +// the cohorts/backend), but also a fake patient list which doesn't really exist in the backend: +// The offline patient list. +// When a patient is added to the offline list, that patient should become available offline, i.e. +// a dynamic offline data entry must be created. +// This is why the following abstracts away the differences between the real and the fake patient lists. +// The component doesn't really care about which is which - the only thing that matters is that the +// data can be fetched and that there is an "add patient" function. + +export function useAddablePatientLists(patientUuid: string) { + const { t } = useTranslation(); + const config = useConfig(); + return useSWR(['addablePatientLists', patientUuid], async () => { + // Using Promise.allSettled instead of Promise.all here because some distros might not have the + // cohort module installed, leading to the real patient list call failing. + // In that case we still want to show fake lists and *not* error out here. + const [fakeLists, realLists] = await Promise.allSettled([ + findFakePatientListsWithoutPatient(patientUuid, t), + findRealPatientListsWithoutPatient(patientUuid, config.myListCohortTypeUUID, config.systemListCohortTypeUUID), + ]); + + return [ + ...(fakeLists.status === 'fulfilled' ? fakeLists.value : []), + ...(realLists.status === 'fulfilled' ? realLists.value : []), + ]; + }); +} diff --git a/packages/esm-patient-list-management-app/src/api/types.ts b/packages/esm-patient-list-management-app/src/api/types.ts index 65cca2835..0ca7a4882 100644 --- a/packages/esm-patient-list-management-app/src/api/types.ts +++ b/packages/esm-patient-list-management-app/src/api/types.ts @@ -7,6 +7,13 @@ export enum PatientListType { ALL = 'All', } +export interface AddablePatientListViewModel { + addPatient(): Promise; + displayName: string; + checked?: boolean; + id: string; +} + export interface PatientList { id: string; display: string; diff --git a/packages/esm-patient-list-management-app/src/routes.json b/packages/esm-patient-list-management-app/src/routes.json index eea291a3d..99932d660 100644 --- a/packages/esm-patient-list-management-app/src/routes.json +++ b/packages/esm-patient-list-management-app/src/routes.json @@ -23,14 +23,16 @@ "name": "list-details-table", "component": "listDetailsTable" }, - { - "name": "add-patient-to-patient-list-modal", - "component": "addPatientToListModal" - }, { "name": "add-patient-to-patient-list-button", "slot": "patient-actions-slot", "component": "addPatientToPatientListMenuItem" } + ], + "modals": [ + { + "name": "add-patient-to-patient-list-modal", + "component": "addPatientToListModal" + } ] } diff --git a/packages/esm-patient-list-management-app/translations/en.json b/packages/esm-patient-list-management-app/translations/en.json index 6f65985df..03432c1a8 100644 --- a/packages/esm-patient-list-management-app/translations/en.json +++ b/packages/esm-patient-list-management-app/translations/en.json @@ -47,11 +47,10 @@ "newPatientListNameLabel": "List name", "nextPage": "Next page", "noMatchingLists": "No matching lists to display", + "noMatchingListsFound": "No matching lists found", "noMatchingPatients": "No matching patients to display", "noOfPatients": "No. of patients", - "noPatientListFound": "No patient list found", "noPatientsInList": "There are no patients in this list", - "offlinePatients": "Offline patients", "openPatientList": "Add to list", "patientListMemberCount_one": "This list has {{count}} patient", "patientListMemberCount_other": "This list has {{count}} patients", @@ -77,6 +76,7 @@ "successfullyAdded": "Successfully added", "systemDefined": "system-defined", "systemLists": "System lists", + "trySearchingForADifferentList": "Try searching for a different list", "unstarList": "Unstar list", "updated": "Updated", "userDefined": "user-defined" diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx index 90ce72354..3a3166ceb 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx @@ -100,10 +100,10 @@ const IdentifierInput: React.FC = ({ patientIdentifier, fi ) : (

{identifierName}

-

+

{autoGeneration ? t('autoGeneratedPlaceholderText', 'Auto-generated') : identifierValue}

- + {/* This is added for any error descriptions */} {!!(identifierFieldMeta.touched && identifierFieldMeta.error) && ( {identifierFieldMeta.error && t(identifierFieldMeta.error)} diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx index 1cd2b9436..2647c6ee3 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx @@ -1,29 +1,31 @@ /* eslint-disable testing-library/no-node-access */ import React from 'react'; import { render, screen } from '@testing-library/react'; -import { Formik, Form } from 'formik'; -import { type PatientIdentifierType } from '../../../patient-registration.types'; -import { initialFormValues } from '../../../patient-registration.component'; +import { Form, Formik } from 'formik'; +import { ResourcesContext, type Resources } from '../../../../offline.resources'; +import { + PatientRegistrationContext, + type PatientRegistrationContextProps, +} from '../../../patient-registration-context'; +import type { AddressTemplate, FormValues, PatientIdentifierValue } from '../../../patient-registration.types'; import IdentifierInput from './identifier-input.component'; +import userEvent from '@testing-library/user-event'; -// TODO: Fix this test -xdescribe('identifier input', () => { - const openmrsID = { - name: 'OpenMRS ID', +const predefinedAddressTemplate = { + uuid: 'test-address-template-uuid', + property: 'layout.address.format', + description: 'Test Address Template', + display: + 'Layout - Address Format = \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n address1\n address2\n cityVillage stateProvince country postalCode\n \n ', + value: + '\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n address1 address2\r\n cityVillage stateProvince postalCode\r\n country\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ', +}; + +const mockIdentifierTypes = [ + { fieldName: 'openMrsId', - required: true, - uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', - format: null, - isPrimary: true, + format: '', identifierSources: [ - { - uuid: '691eed12-c0f1-11e2-94be-8c13b969e334', - name: 'Generator 1 for OpenMRS ID', - autoGenerationOption: { - manualEntryEnabled: false, - automaticGenerationEnabled: true, - }, - }, { uuid: '01af8526-cea4-4175-aa90-340acb411771', name: 'Generator 2 for OpenMRS ID', @@ -33,72 +35,121 @@ xdescribe('identifier input', () => { }, }, ], - autoGenerationSource: null, - }; + isPrimary: true, + name: 'OpenMRS ID', + required: true, + uniquenessBehavior: 'UNIQUE' as const, + uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', + }, +]; - const setupIdentifierInput = async (identifierType: PatientIdentifierType) => { - initialFormValues['source-for-' + identifierType.fieldName] = identifierType.identifierSources[0].name; +const mockResourcesContextValue: Resources = { + addressTemplate: predefinedAddressTemplate as unknown as AddressTemplate, + currentSession: { + authenticated: true, + sessionId: 'JSESSION', + currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' }, + }, + relationshipTypes: [], + identifierTypes: [...mockIdentifierTypes], +}; +const mockContextValues: PatientRegistrationContextProps = { + currentPhoto: '', + inEditMode: false, + identifierTypes: [], + initialFormValues: {} as FormValues, + isOffline: false, + setCapturePhotoProps: jest.fn(), + setFieldValue: jest.fn(), + setInitialFormValues: jest.fn(), + setFieldTouched: jest.fn(), + validationSchema: null, + values: {} as FormValues, +}; + +describe('identifier input', () => { + const fieldName = 'openMrsId'; + const openmrsID = { + identifierTypeUuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', + initialValue: '', + identifierValue: '', + identifierName: 'OpenMRS ID', + selectedSource: { + uuid: '01af8526-cea4-4175-aa90-340acb411771', + name: 'Generator 2 for OpenMRS ID', + autoGenerationOption: { + manualEntryEnabled: true, + automaticGenerationEnabled: true, + }, + }, + autoGeneration: false, + preferred: true, + required: true, + }; + + const setupIdentifierInput = async (patientIdentifier: PatientIdentifierValue) => { render( - -
- - -
, + + +
+ + + +
+
+
, ); - const identifierInput = screen.getByLabelText(identifierType.fieldName) as HTMLInputElement; - let identifierSourceSelectInput = screen.getByLabelText('source-for-' + identifierType.fieldName); + + let identifierLabel: HTMLParagraphElement; + let identifierInput: HTMLInputElement; + if (patientIdentifier.autoGeneration) { + identifierLabel = screen.getByTestId('identifier-label'); + identifierInput = screen.getByTestId('identifier-input'); + } else { + identifierLabel = screen.getByText(patientIdentifier.identifierName); + identifierInput = screen.getByLabelText(patientIdentifier.identifierName); + } return { + identifierLabel, identifierInput, - identifierSourceSelectInput, }; }; - it('exists', async () => { - const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID); - - expect(identifierInput.type).toBe('text'); - expect(identifierSourceSelectInput.type).toBe('select-one'); + it('shows the identifier input', async () => { + const { identifierInput } = await setupIdentifierInput(openmrsID); + expect(identifierInput).toBeInTheDocument(); }); - it('has correct props for identifier source select input', async () => { - const { identifierSourceSelectInput } = await setupIdentifierInput(openmrsID); + describe('Auto-generated identifier', () => { + openmrsID.autoGeneration = true; - expect(identifierSourceSelectInput.childElementCount).toBe(3); - expect(identifierSourceSelectInput.value).toBe('Generator 1 for OpenMRS ID'); - }); + it('hides the input when the identifier is auto-generated', async () => { + const { identifierInput } = await setupIdentifierInput(openmrsID); + expect(identifierInput.type).toBe('hidden'); + }); - it('has correct props for identifier input', async () => { - const { identifierInput } = await setupIdentifierInput(openmrsID); - expect(identifierInput.placeholder).toBe('Auto-generated'); - expect(identifierInput.disabled).toBe(true); - }); + it("displays 'Auto-Generated' when the indentifier has auto generation", async () => { + const { identifierLabel, identifierInput } = await setupIdentifierInput(openmrsID); + expect(identifierLabel.innerHTML).toBe('Auto-generated'); + expect(identifierInput.disabled).toBe(true); + }); - it('text input should not be disabled if manual entry is enabled', async () => { - // setup - openmrsID.identifierSources[0].autoGenerationOption.manualEntryEnabled = true; - // replay - const { identifierInput } = await setupIdentifierInput(openmrsID); - expect(identifierInput.placeholder).toBe('Auto-generated'); - expect(identifierInput.disabled).toBe(false); - }); + it('displays an edit button when there is an initial value', async () => { + // setup + openmrsID.required = false; + openmrsID.initialValue = '1002UU9'; + // replay + await setupIdentifierInput(openmrsID); + expect(screen.getByText('Edit')).toBeInTheDocument(); + }); - it('should not render select widget if auto-entry is false', async () => { - // setup - openmrsID.identifierSources = [ - { - uuid: '691eed12-c0f1-11e2-94be-8c13b969e334', - name: 'Generator 1 for OpenMRS ID', - autoGenerationOption: { - manualEntryEnabled: true, - automaticGenerationEnabled: false, - }, - }, - ]; - // replay - const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID); - expect(identifierInput.placeholder).toBe('Enter identifier'); - expect(identifierInput.disabled).toBe(false); - expect(identifierSourceSelectInput).toBe(undefined); + it('displays a delete button when the identifier is not a default type', async () => { + // setup + openmrsID.required = false; + // replay + await setupIdentifierInput(openmrsID); + expect(screen.getByText('Delete')).toBeInTheDocument(); + }); }); }); diff --git a/packages/esm-ward-app/.fetch.swp b/packages/esm-ward-app/.fetch.swp deleted file mode 100644 index 9a648bb5e..000000000 Binary files a/packages/esm-ward-app/.fetch.swp and /dev/null differ diff --git a/packages/esm-ward-app/mock.tsx b/packages/esm-ward-app/mock.tsx new file mode 100644 index 000000000..d9b40cb80 --- /dev/null +++ b/packages/esm-ward-app/mock.tsx @@ -0,0 +1,54 @@ +import { mockAdmissionLocation, mockInpatientAdmissions, mockInpatientRequest } from '__mocks__'; +import { useAdmissionLocation } from './src/hooks/useAdmissionLocation'; +import { useInpatientAdmission } from './src/hooks/useInpatientAdmission'; +import { createAndGetWardPatientGrouping } from './src/ward-view/ward-view.resource'; +import { useInpatientRequest } from './src/hooks/useInpatientRequest'; +import { useWardPatientGrouping } from './src/hooks/useWardPatientGrouping'; + +jest.mock('./src/hooks/useAdmissionLocation', () => ({ + useAdmissionLocation: jest.fn(), +})); +jest.mock('./src/hooks/useInpatientAdmission', () => ({ + useInpatientAdmission: jest.fn(), +})); +jest.mock('./src/hooks/useInpatientRequest', () => ({ + useInpatientRequest: jest.fn(), +})); +jest.mock('./src/hooks/useWardPatientGrouping', () => ({ + useWardPatientGrouping: jest.fn(), +})); +const mockAdmissionLocationResponse = jest.mocked(useAdmissionLocation).mockReturnValue({ + error: undefined, + mutate: jest.fn(), + isValidating: false, + isLoading: false, + admissionLocation: mockAdmissionLocation, +}); +const mockInpatientAdmissionResponse = jest.mocked(useInpatientAdmission).mockReturnValue({ + data: mockInpatientAdmissions, + hasMore: false, + loadMore: jest.fn(), + isValidating: false, + isLoading: false, + error: undefined, + mutate: jest.fn(), + totalCount: mockInpatientAdmissions.length, +}); + +const mockInpatientRequestResponse = jest.mocked(useInpatientRequest).mockReturnValue({ + inpatientRequests: mockInpatientRequest, + hasMore: false, + loadMore: jest.fn(), + isValidating: false, + isLoading: false, + error: undefined, + mutate: jest.fn(), + totalCount: mockInpatientRequest.length, +}); + +export const mockWardPatientGroupDetails = jest.mocked(useWardPatientGrouping).mockReturnValue({ + admissionLocationResponse: mockAdmissionLocationResponse(), + inpatientAdmissionResponse: mockInpatientAdmissionResponse(), + inpatientRequestResponse: mockInpatientRequestResponse(), + ...createAndGetWardPatientGrouping(mockInpatientAdmissions, mockAdmissionLocation, mockInpatientRequest), +}); diff --git a/packages/esm-ward-app/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx b/packages/esm-ward-app/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx new file mode 100644 index 000000000..d5faca5b7 --- /dev/null +++ b/packages/esm-ward-app/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Document } from '@carbon/react/icons'; +import { useTranslation } from 'react-i18next'; +import { ActionMenuButton, launchWorkspace, useWorkspaces } from '@openmrs/esm-framework'; +import type { WardPatientWorkspaceProps } from '../types'; + +const ClinicalFormsWorkspaceSideRailIcon: React.FC = () => { + const { t } = useTranslation(); + const { workspaces } = useWorkspaces(); + + const formEntryWorkspaces = workspaces.filter((w) => w.name === 'ward-patient-form-entry-workspace'); + const recentlyOpenedForm = formEntryWorkspaces[0]; + + const isClinicalFormOpen = formEntryWorkspaces?.length >= 1; + + const launchPatientWorkspaceCb = () => { + if (isClinicalFormOpen) { + launchWorkspace('ward-patient-form-entry-workspace', { + workspaceTitle: recentlyOpenedForm?.additionalProps?.['workspaceTitle'], + }); + } else { + launchWorkspace('ward-patient-clinical-forms-workspace'); + } + }; + + return ( + } + label={t('clinicalForms', 'Clinical forms')} + iconDescription={t('clinicalForms', 'Clinical forms')} + handler={launchPatientWorkspaceCb} + type="ward-patient-clinical-form" + /> + ); +}; + +export default ClinicalFormsWorkspaceSideRailIcon; diff --git a/packages/esm-ward-app/src/hooks/useBeds.ts b/packages/esm-ward-app/src/hooks/useBeds.ts index 9be7e1b31..ca85b9554 100644 --- a/packages/esm-ward-app/src/hooks/useBeds.ts +++ b/packages/esm-ward-app/src/hooks/useBeds.ts @@ -1,5 +1,4 @@ -import { openmrsFetch, restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework'; -import useSWR from 'swr'; +import { restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework'; import { type Bed, type BedStatus } from '../types/index'; interface BedSearchCriteria { diff --git a/packages/esm-ward-app/src/index.ts b/packages/esm-ward-app/src/index.ts index 2d6832298..2d56c54f8 100644 --- a/packages/esm-ward-app/src/index.ts +++ b/packages/esm-ward-app/src/index.ts @@ -107,6 +107,16 @@ export const patientDischargeWorkspaceSideRailIcon = getAsyncLifecycle( options, ); +export const patientClinicalFormsWorkspace = getAsyncLifecycle( + () => import('./ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace'), + options, +); + +export const clinicalFormWorkspaceSideRailIcon = getAsyncLifecycle( + () => import('./action-menu-buttons/clinical-forms-workspace-siderail.component'), + options, +); + export function startupApp() { registerBreadcrumbs([]); defineConfigSchema(moduleName, configSchema); diff --git a/packages/esm-ward-app/src/routes.json b/packages/esm-ward-app/src/routes.json index 77f9d532a..7323ee840 100644 --- a/packages/esm-ward-app/src/routes.json +++ b/packages/esm-ward-app/src/routes.json @@ -55,6 +55,11 @@ "slot": "action-menu-ward-patient-items-slot", "component": "patientDischargeWorkspaceSideRailIcon" }, + { + "name": "clinical-forms-workspace-siderail-button", + "component": "clinicalFormWorkspaceSideRailIcon", + "slot": "action-menu-ward-patient-items-slot" + }, { "component": "admissionRequestNoteRowExtension", "name": "admission-request-note-card-row", @@ -122,6 +127,15 @@ "type": "ward-patient-discharge", "hasOwnSidebar": true, "sidebarFamily": "ward-patient" + }, + { + "name": "ward-patient-clinical-forms-workspace", + "component": "patientClinicalFormsWorkspace", + "title": "clinicalForms", + "type": "ward-patient-clinical-forms", + "hasOwnSidebar": true, + "sidebarFamily": "ward-patient", + "width": "wider" } ] } diff --git a/packages/esm-ward-app/src/ward-patient-card/ward-patient-resource.ts b/packages/esm-ward-app/src/ward-patient-card/ward-patient-resource.ts index 47438d6da..588c72af4 100644 --- a/packages/esm-ward-app/src/ward-patient-card/ward-patient-resource.ts +++ b/packages/esm-ward-app/src/ward-patient-card/ward-patient-resource.ts @@ -1,6 +1,8 @@ import { launchWorkspace } from '@openmrs/esm-framework'; import { type WardPatient, type WardPatientWorkspaceProps } from '../types'; +//To keep track of current patient when clicked on ward-patient-card and pass +//it as a prop to launch workspace let wardPatient: WardPatient = null; export function setWardPatient(currentWardPatient: WardPatient) { wardPatient = currentWardPatient; diff --git a/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.component.tsx b/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.component.tsx index 66e7dbcab..0adc7f73f 100644 --- a/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.component.tsx +++ b/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.component.tsx @@ -1,13 +1,14 @@ -import React from 'react'; -import { Movement } from '@carbon/react/icons'; import { Button, InlineNotification } from '@carbon/react'; -import { ArrowRightIcon, isDesktop, launchWorkspace, useLayoutType } from '@openmrs/esm-framework'; -import { useInpatientRequest } from '../hooks/useInpatientRequest'; +import { Movement } from '@carbon/react/icons'; +import { ArrowRightIcon, isDesktop, launchWorkspace, useAppContext, useLayoutType } from '@openmrs/esm-framework'; +import React from 'react'; import { useTranslation } from 'react-i18next'; +import { type WardPatientGroupDetails } from '../types'; import styles from './admission-requests.scss'; const AdmissionRequestsBar = () => { - const { inpatientRequests, isLoading, error } = useInpatientRequest(['ADMIT', 'TRANSFER']); + const wardPatientGrouping = useAppContext('ward-patients-group'); + const { inpatientRequests, isLoading, error } = wardPatientGrouping?.inpatientRequestResponse ?? {}; const { t } = useTranslation(); const layout = useLayoutType(); diff --git a/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.test.tsx b/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.test.tsx index fbdae6b20..b659b7cb6 100644 --- a/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.test.tsx +++ b/packages/esm-ward-app/src/ward-view-header/admission-requests-bar.test.tsx @@ -1,28 +1,15 @@ -import React from 'react'; -import userEvent from '@testing-library/user-event'; +import { launchWorkspace, useAppContext } from '@openmrs/esm-framework'; import { screen } from '@testing-library/react'; -import { launchWorkspace } from '@openmrs/esm-framework'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; import { renderWithSwr } from 'tools'; -import { mockInpatientRequest } from '__mocks__'; -import { useInpatientRequest } from '../hooks/useInpatientRequest'; +import { mockWardPatientGroupDetails } from '../../mock'; import AdmissionRequestsBar from './admission-requests-bar.component'; -jest.mock('../hooks/useInpatientRequest', () => ({ - useInpatientRequest: jest.fn(), -})); - -const mockInpatientRequestResponse = { - error: undefined, - mutate: jest.fn(), - isValidating: false, - isLoading: false, - inpatientRequests: [mockInpatientRequest], -}; - -jest.mocked(useInpatientRequest).mockReturnValue(mockInpatientRequestResponse); +jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails()); describe('Admission Requests Button', () => { - it('call launch workspace when clicked on manage button', async () => { + it('should launch workspace when clicked on manage button', async () => { const user = userEvent.setup(); renderWithSwr(); @@ -30,7 +17,7 @@ describe('Admission Requests Button', () => { expect(launchWorkspace).toHaveBeenCalled(); }); - it('there should be one admission request', () => { + it('should have one admission request', () => { renderWithSwr(); expect(screen.getByText('1 admission request')).toBeInTheDocument(); diff --git a/packages/esm-ward-app/src/ward-view-header/ward-metrics.component.tsx b/packages/esm-ward-app/src/ward-view-header/ward-metrics.component.tsx index 87005bab2..0008158d5 100644 --- a/packages/esm-ward-app/src/ward-view-header/ward-metrics.component.tsx +++ b/packages/esm-ward-app/src/ward-view-header/ward-metrics.component.tsx @@ -16,12 +16,11 @@ const wardMetrics = [{ name: 'patients' }, { name: 'freeBeds' }, { name: 'capaci const WardMetrics = () => { const { location } = useWardLocation(); + const { beds, isLoading, error } = useBeds({ locationUuid: location.uuid }); const { t } = useTranslation(); const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module'); const wardPatientGroup = useAppContext('ward-patients-group'); - const { admissionLocationResponse, inpatientAdmissionResponse, inpatientRequestResponse, bedLayouts } = - wardPatientGroup || {}; - const { isLoading, error } = admissionLocationResponse ?? {}; + const { admissionLocationResponse, inpatientAdmissionResponse, inpatientRequestResponse } = wardPatientGroup || {}; const isDataLoading = admissionLocationResponse?.isLoading || inpatientAdmissionResponse?.isLoading || @@ -35,8 +34,7 @@ const WardMetrics = () => { description: error.message, }); } - - const wardMetricValues = getWardMetrics(bedLayouts, wardPatientGroup); + const wardMetricValues = getWardMetrics(beds, wardPatientGroup); return (
{isBedManagementModuleInstalled ? ( diff --git a/packages/esm-ward-app/src/ward-view-header/ward-metrics.test.tsx b/packages/esm-ward-app/src/ward-view-header/ward-metrics.test.tsx index 557b5bdea..6cd228dac 100644 --- a/packages/esm-ward-app/src/ward-view-header/ward-metrics.test.tsx +++ b/packages/esm-ward-app/src/ward-view-header/ward-metrics.test.tsx @@ -1,6 +1,8 @@ import React from 'react'; import WardMetrics from './ward-metrics.component'; import { renderWithSwr } from '../../../../tools/test-utils'; +import { useBeds } from '../hooks/useBeds'; +import { mockWardBeds } from '../../../../__mocks__/wardBeds.mock'; import { createAndGetWardPatientGrouping, getInpatientAdmissionsUuidMap, @@ -12,6 +14,7 @@ import { useInpatientAdmission } from '../hooks/useInpatientAdmission'; import useWardLocation from '../hooks/useWardLocation'; import { screen } from '@testing-library/react'; import { useAppContext } from '@openmrs/esm-framework'; +import { mockWardPatientGroupDetails } from '../../mock'; const wardMetrics = [ { name: 'patients', key: 'patients', defaultTranslation: 'Patients' }, @@ -33,59 +36,29 @@ jest.mock('../hooks/useWardLocation', () => }), ); -const mockUseWardLocation = jest.mocked(useWardLocation); - jest.mock('../hooks/useBeds', () => ({ useBeds: jest.fn(), })); -jest.mock('../hooks/useAdmissionLocation', () => ({ - useAdmissionLocation: jest.fn(), -})); -jest.mock('../hooks/useInpatientAdmission', () => ({ - useInpatientAdmission: jest.fn(), -})); - -jest.mock('../hooks/useInpatientRequest', () => ({ - useInpatientRequest: jest.fn(), -})); - -const mockAdmissionLocationResponse = jest.mocked(useAdmissionLocation).mockReturnValue({ - error: undefined, - mutate: jest.fn(), - isValidating: false, - isLoading: false, - admissionLocation: mockAdmissionLocation, -}); -const mockInpatientAdmissionResponse = jest.mocked(useInpatientAdmission).mockReturnValue({ +jest.mocked(useBeds).mockReturnValue({ error: undefined, mutate: jest.fn(), isValidating: false, isLoading: false, - inpatientAdmissions: mockInpatientAdmissions, + beds: mockWardBeds, + totalCount: mockWardBeds.length, + hasMore: false, + loadMore: jest.fn(), }); -const inpatientAdmissionsUuidMap = getInpatientAdmissionsUuidMap(mockInpatientAdmissions); -const mockWardPatientGroupDetails = { - admissionLocationResponse: mockAdmissionLocationResponse(), - inpatientAdmissionResponse: mockInpatientAdmissionResponse(), - ...createAndGetWardPatientGrouping(mockInpatientAdmissions, mockAdmissionLocation, mockInpatientRequest), -}; -jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails); +jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails()); describe('Ward Metrics', () => { it('Should display metrics of in the ward ', () => { - mockUseWardLocation.mockReturnValueOnce({ - location: null, - isLoadingLocation: false, - errorFetchingLocation: null, - invalidLocation: true, - }); - const { bedLayouts } = mockWardPatientGroupDetails; - const bedMetrics = getWardMetrics(bedLayouts, mockWardPatientGroupDetails); + const bedMetrics = getWardMetrics(mockWardBeds, mockWardPatientGroupDetails()); renderWithSwr(); for (let [key, value] of Object.entries(bedMetrics)) { const fieldName = wardMetrics.find((metric) => metric.name == key)?.defaultTranslation; - expect(screen.getByText(fieldName)).toBeInTheDocument(); + expect(screen.getByText(fieldName!)).toBeInTheDocument(); } }); }); diff --git a/packages/esm-ward-app/src/ward-view/ward-view.component.tsx b/packages/esm-ward-app/src/ward-view/ward-view.component.tsx index d3946363d..9a74c1dfd 100644 --- a/packages/esm-ward-app/src/ward-view/ward-view.component.tsx +++ b/packages/esm-ward-app/src/ward-view/ward-view.component.tsx @@ -56,6 +56,7 @@ const WardViewMain = () => { hasMore: hasMoreInpatientAdmissions, loadMore: loadMoreInpatientAdmissions, } = wardPatientsGrouping?.inpatientAdmissionResponse ?? {}; + const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module'); const scrollToLoadMoreTrigger = useRef(null); useEffect( @@ -127,7 +128,7 @@ const WardViewMain = () => { return (
{wardBeds} - {bedLayouts?.length == 0 && ( + {bedLayouts?.length == 0 && isBedManagementModuleInstalled && ( bl.bedId) .sort((bedA, bedB) => collator.compare(bedA.bedNumber, bedB.bedNumber)); - return bedLayouts; } //TODO: This implementation will change when the api is ready -export function getWardMetrics(bedLayouts: BedLayout[], wardPatientGroup: WardPatientGroupDetails): WardMetrics { +export function getWardMetrics(beds: Bed[], wardPatientGroup: WardPatientGroupDetails): WardMetrics { const bedMetrics = { patients: '--', freeBeds: '--', capacity: '--', }; - if (bedLayouts == null || bedLayouts.length == 0) return bedMetrics; - const total = bedLayouts.length; - const occupiedBeds = bedLayouts.filter((bed) => bed.patients.length); + if (beds == null || beds.length == 0) return bedMetrics; + const total = beds.length; + const occupiedBeds = beds.filter((bed) => bed.status === 'OCCUPIED'); const patients = occupiedBeds.length; const freeBeds = total - patients; const capacity = total != 0 ? Math.trunc((wardPatientGroup.totalPatientsCount / total) * 100) : 0; @@ -73,6 +72,7 @@ export function createAndGetWardPatientGrouping( const wardUnadmittedPatientsWithBed = new Map(); const bedLayouts = admissionLocation && filterBeds(admissionLocation); const allWardPatientUuids = new Set(); + let wardPatientPendingCount = 0; bedLayouts?.map((bedLayout) => { const { patients } = bedLayout; diff --git a/packages/esm-ward-app/src/ward-view/ward-view.test.tsx b/packages/esm-ward-app/src/ward-view/ward-view.test.tsx index fa8620a5e..c95835af8 100644 --- a/packages/esm-ward-app/src/ward-view/ward-view.test.tsx +++ b/packages/esm-ward-app/src/ward-view/ward-view.test.tsx @@ -3,21 +3,16 @@ import { getDefaultsFromConfigSchema, useAppContext, useConfig, - useFeatureFlag + useFeatureFlag, } from '@openmrs/esm-framework'; import { screen } from '@testing-library/react'; -import { mockAdmissionLocation, mockInpatientAdmissions, mockInpatientRequest } from '__mocks__'; import React from 'react'; import { useParams } from 'react-router-dom'; import { renderWithSwr } from 'tools'; +import { mockWardPatientGroupDetails } from '../../mock'; import { configSchema } from '../config-schema'; -import { useAdmissionLocation } from '../hooks/useAdmissionLocation'; -import { useInpatientAdmission } from '../hooks/useInpatientAdmission'; -import { useInpatientRequest } from '../hooks/useInpatientRequest'; import useWardLocation from '../hooks/useWardLocation'; -import { useWardPatientGrouping } from '../hooks/useWardPatientGrouping'; import WardView from './ward-view.component'; -import { createAndGetWardPatientGrouping } from './ward-view.resource'; jest.mocked(useConfig).mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), @@ -42,54 +37,6 @@ jest.mock('react-router-dom', () => ({ })); const mockUseParams = useParams as jest.Mock; -jest.mock('../hooks/useAdmissionLocation', () => ({ - useAdmissionLocation: jest.fn(), -})); -jest.mock('../hooks/useInpatientAdmission', () => ({ - useInpatientAdmission: jest.fn(), -})); -jest.mock('../hooks/useInpatientRequest', () => ({ - useInpatientRequest: jest.fn(), -})); -jest.mock('../hooks/useWardPatientGrouping', () => ({ - useWardPatientGrouping: jest.fn(), -})); -const mockAdmissionLocationResponse = jest.mocked(useAdmissionLocation).mockReturnValue({ - error: undefined, - mutate: jest.fn(), - isValidating: false, - isLoading: false, - admissionLocation: mockAdmissionLocation, -}); -const mockInpatientAdmissionResponse = jest.mocked(useInpatientAdmission).mockReturnValue({ - data: mockInpatientAdmissions, - hasMore: false, - loadMore: jest.fn(), - isValidating: false, - isLoading: false, - error: undefined, - mutate: jest.fn(), - totalCount: 1 -}); - -const mockInpatientRequestResponse = jest.mocked(useInpatientRequest).mockReturnValue({ - inpatientRequests: mockInpatientRequest, - hasMore: false, - loadMore: jest.fn(), - isValidating: false, - isLoading: false, - error: undefined, - mutate: jest.fn(), - totalCount: 1 -}) - -const mockWardPatientGroupDetails = jest.mocked(useWardPatientGrouping).mockReturnValue({ - admissionLocationResponse: mockAdmissionLocationResponse(), - inpatientAdmissionResponse: mockInpatientAdmissionResponse(), - inpatientRequestResponse: mockInpatientRequestResponse(), - ...createAndGetWardPatientGrouping(mockInpatientAdmissions, mockAdmissionLocation, mockInpatientRequest), -}); - jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails()); const intersectionObserverMock = () => ({ @@ -98,6 +45,8 @@ const intersectionObserverMock = () => ({ window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock); describe('WardView', () => { + let replacedProperty: jest.ReplaceProperty | null = null; + it('renders the session location when no location provided in URL', () => { renderWithSwr(); const header = screen.getByRole('heading', { name: 'mock location' }); @@ -145,38 +94,29 @@ describe('WardView', () => { expect(invalidText).toBeInTheDocument(); }); - it('screen should render warning if backend module installed and no beds configured', () => { + it('should render warning if backend module installed and no beds configured', () => { // override the default response so that no beds are returned - jest.mocked(useAdmissionLocation).mockReturnValue({ - error: undefined, - mutate: jest.fn(), - isValidating: false, - isLoading: false, - admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] }, - }); - const replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []); + replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []); - mockUseFeatureFlag.mockReturnValueOnce(true); + mockUseFeatureFlag.mockReturnValue(true); renderWithSwr(); const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location'); expect(noBedsConfiguredForThisLocation).toBeInTheDocument(); - replacedProperty.restore(); }); - it('screen not should render warning if backend module installed and no beds configured', () => { + it('should not render warning if backend module installed and no beds configured', () => { // override the default response so that no beds are returned - jest.mocked(useAdmissionLocation).mockReturnValue({ - error: undefined, - mutate: jest.fn(), - isValidating: false, - isLoading: false, - admissionLocation: { ...mockAdmissionLocation, bedLayouts: [] }, - }); - mockUseFeatureFlag.mockReturnValueOnce(false); + replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []); + mockUseFeatureFlag.mockReturnValue(false); renderWithSwr(); const noBedsConfiguredForThisLocation = screen.queryByText('No beds configured for this location'); expect(noBedsConfiguredForThisLocation).not.toBeInTheDocument(); }); + + afterEach(() => { + replacedProperty?.restore(); + replacedProperty = null; + }); }); diff --git a/packages/esm-ward-app/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx b/packages/esm-ward-app/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx index 73caccdba..86a91746a 100644 --- a/packages/esm-ward-app/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx +++ b/packages/esm-ward-app/src/ward-workspace/admission-request-workspace/admission-requests.workspace.tsx @@ -9,6 +9,8 @@ import { type InpatientRequest } from '../../types'; interface AdmissionRequestsWorkspaceProps {} const AdmissionRequestsWorkspace: React.FC = () => { + // note: useAppContext() does not work here for some reason, so we call `useInpatientRequest` + // directly. See: https://openmrs.atlassian.net/browse/O3-4020 const { inpatientRequests, isLoading: isLoadingInpatientRequests, diff --git a/packages/esm-ward-app/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx b/packages/esm-ward-app/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx index e2d4ab634..210b8b5e2 100644 --- a/packages/esm-ward-app/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx +++ b/packages/esm-ward-app/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.test.tsx @@ -1,25 +1,17 @@ -import React from 'react'; +import { openmrsFetch, showSnackbar, useAppContext, useFeatureFlag, useSession } from '@openmrs/esm-framework'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { mockAdmissionLocation, mockLocationInpatientWard, mockPatientAlice } from '../../../../../__mocks__'; import { renderWithSwr } from '../../../../../tools'; -import AdmitPatientFormWorkspace from './admit-patient-form.workspace'; -import { - mockAdmissionLocation, - mockInpatientAdmissions, - mockInpatientRequest, - mockLocationInpatientWard, - mockPatientAlice, -} from '../../../../../__mocks__'; -import type { DispositionType } from '../../types'; -import type { AdmitPatientFormWorkspaceProps } from './types'; +import { mockWardPatientGroupDetails } from '../../../mock'; import { useAdmissionLocation } from '../../hooks/useAdmissionLocation'; -import { openmrsFetch, provide, showSnackbar, useAppContext, useFeatureFlag, useSession } from '@openmrs/esm-framework'; import useEmrConfiguration from '../../hooks/useEmrConfiguration'; -import useWardLocation from '../../hooks/useWardLocation'; import { useInpatientRequest } from '../../hooks/useInpatientRequest'; -import { useWardPatientGrouping } from '../../hooks/useWardPatientGrouping'; -import { getInpatientAdmissionsUuidMap, createAndGetWardPatientGrouping } from '../../ward-view/ward-view.resource'; -import { useInpatientAdmission } from '../../hooks/useInpatientAdmission'; +import useWardLocation from '../../hooks/useWardLocation'; +import type { DispositionType } from '../../types'; +import AdmitPatientFormWorkspace from './admit-patient-form.workspace'; +import type { AdmitPatientFormWorkspaceProps } from './types'; jest.mock('../../hooks/useAdmissionLocation', () => ({ useAdmissionLocation: jest.fn(), @@ -41,48 +33,12 @@ jest.mock('../../hooks/useInpatientAdmission', () => ({ useInpatientAdmission: jest.fn(), })); -const mockedUseInpatientRequest = jest.mocked(useInpatientRequest); const mockedUseEmrConfiguration = jest.mocked(useEmrConfiguration); const mockedUseWardLocation = jest.mocked(useWardLocation); const mockedOpenmrsFetch = jest.mocked(openmrsFetch); -const mockedUseAdmissionLocationResponse = jest.mocked(useAdmissionLocation).mockReturnValue({ - isLoading: false, - isValidating: false, - admissionLocation: mockAdmissionLocation, - mutate: jest.fn(), - error: undefined, -}); const mockedUseFeatureFlag = jest.mocked(useFeatureFlag); const mockedShowSnackbar = jest.mocked(showSnackbar); const mockedUseSession = jest.mocked(useSession); -const mockedInpatientAdmissionResponse = jest.mocked(useInpatientAdmission).mockReturnValue({ - error: undefined, - mutate: jest.fn(), - isValidating: false, - isLoading: false, - data: mockInpatientAdmissions, - totalCount: 1, - hasMore: false, - loadMore: jest.fn(), -}); - -const mockInpatientRequestResponse = jest.mocked(useInpatientRequest).mockReturnValue({ - inpatientRequests: mockInpatientRequest, - hasMore: false, - loadMore: jest.fn(), - isValidating: false, - isLoading: false, - error: undefined, - mutate: jest.fn(), - totalCount: 1, -}); - -const mockWardPatientGroupDetails = jest.mocked(useWardPatientGrouping).mockReturnValue({ - admissionLocationResponse: mockedUseAdmissionLocationResponse(), - inpatientAdmissionResponse: mockedInpatientAdmissionResponse(), - inpatientRequestResponse: mockInpatientRequestResponse(), - ...createAndGetWardPatientGrouping(mockInpatientAdmissions, mockAdmissionLocation, mockInpatientRequest), -}); jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails()); @@ -102,8 +58,6 @@ function renderAdmissionForm(dispositionType: DispositionType = 'ADMIT') { renderWithSwr(); } -const mockedMutateInpatientRequest = jest.fn(); - describe('Testing AdmitPatientForm', () => { beforeEach(() => { jest.clearAllMocks(); @@ -136,16 +90,6 @@ describe('Testing AdmitPatientForm', () => { }, mutateEmrConfiguration: jest.fn(), }); - mockedUseInpatientRequest.mockReturnValue({ - mutate: mockedMutateInpatientRequest, - error: undefined, - inpatientRequests: mockInpatientRequest, - isLoading: false, - isValidating: false, - totalCount: 1, - hasMore: false, - loadMore: jest.fn(), - }); mockedUseWardLocation.mockReturnValue({ location: mockLocationInpatientWard, invalidLocation: false, diff --git a/packages/esm-ward-app/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx b/packages/esm-ward-app/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx index 1f892fd25..63c0139db 100644 --- a/packages/esm-ward-app/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx +++ b/packages/esm-ward-app/src/ward-workspace/admit-patient-form-workspace/admit-patient-form.workspace.tsx @@ -28,6 +28,7 @@ const AdmitPatientFormWorkspace: React.FC = ({ const wardPatientGrouping = useAppContext('ward-patients-group'); const { isLoading, mutate: mutateAdmissionLocation } = wardPatientGrouping?.admissionLocationResponse ?? {}; const { mutate: mutateInpatientRequest } = wardPatientGrouping?.inpatientRequestResponse ?? {}; + const { mutate: mutateInpatientAdmission } = wardPatientGrouping?.inpatientAdmissionResponse ?? {}; const beds = isLoading ? [] : wardPatientGrouping?.bedLayouts ?? []; const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module'); const getBedRepresentation = useCallback((bedLayout: BedLayout) => { @@ -139,9 +140,6 @@ const AdmitPatientFormWorkspace: React.FC = ({ }), }); } - mutateAdmissionLocation(); - mutateInpatientRequest(); - closeWorkspaceWithSavedChanges(); } }, () => { @@ -153,13 +151,14 @@ const AdmitPatientFormWorkspace: React.FC = ({ 'Patient admitted successfully but fail to assign bed to patient', ), }); - mutateAdmissionLocation(); - mutateInpatientRequest(); - closeWorkspaceWithSavedChanges(); }, ) .finally(() => { setIsSubmitting(false); + mutateAdmissionLocation(); + mutateInpatientRequest(); + mutateInpatientAdmission(); + closeWorkspaceWithSavedChanges(); }); }, [ @@ -172,6 +171,7 @@ const AdmitPatientFormWorkspace: React.FC = ({ currentProvider, mutateAdmissionLocation, mutateInpatientRequest, + mutateInpatientAdmission, ], ); diff --git a/packages/esm-ward-app/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx b/packages/esm-ward-app/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx new file mode 100644 index 000000000..9d76e742a --- /dev/null +++ b/packages/esm-ward-app/src/ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace.tsx @@ -0,0 +1,23 @@ +import React, { useMemo } from 'react'; +import { ExtensionSlot } from '@openmrs/esm-framework'; +import type { WardPatientWorkspaceProps } from '../../types'; + +const WardPatientClinicalFormsWorkspace: React.FC = (props) => { + const { wardPatient, ...restWorkspaceProps } = props; + const patientUuid = wardPatient?.patient?.uuid; + + const clinicalFormsExtensionState = useMemo( + () => ({ + patientUuid, + clinicalFormsWorkspaceName: 'ward-patient-clinical-forms-workspace', + formEntryWorkspaceName: 'ward-patient-form-entry-workspace', + htmlFormEntryWorkspaceName: 'ward-patient-html-form-entry-workspace', + ...restWorkspaceProps, + }), + [patientUuid, restWorkspaceProps], + ); + + return ; +}; + +export default WardPatientClinicalFormsWorkspace; diff --git a/packages/esm-ward-app/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx b/packages/esm-ward-app/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx index 23e68198f..dc74293b7 100644 --- a/packages/esm-ward-app/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx +++ b/packages/esm-ward-app/src/ward-workspace/patient-discharge/patient-discharge.workspace.tsx @@ -1,15 +1,14 @@ -import React, { useCallback, useState } from 'react'; -import { ExtensionSlot, showSnackbar, useAppContext, useSession } from '@openmrs/esm-framework'; import { Button, ButtonSet, InlineNotification } from '@carbon/react'; +import { Exit } from '@carbon/react/icons'; +import { ExtensionSlot, showSnackbar, useAppContext, useSession } from '@openmrs/esm-framework'; +import React, { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import styles from './patient-discharge.scss'; -import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component'; -import {type WardPatientGroupDetails, type WardPatientWorkspaceProps } from '../../types'; import useEmrConfiguration from '../../hooks/useEmrConfiguration'; -import { createEncounter, removePatientFromBed } from '../../ward.resource'; import useWardLocation from '../../hooks/useWardLocation'; -import { useInpatientRequest } from '../../hooks/useInpatientRequest'; -import { Exit } from '@carbon/react/icons'; +import { type WardPatientGroupDetails, type WardPatientWorkspaceProps } from '../../types'; +import { createEncounter, removePatientFromBed } from '../../ward.resource'; +import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component'; +import styles from './patient-discharge.scss'; export default function PatientDischargeWorkspace(props: WardPatientWorkspaceProps) { const { wardPatient, closeWorkspaceWithSavedChanges } = props; @@ -20,7 +19,8 @@ export default function PatientDischargeWorkspace(props: WardPatientWorkspacePro const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration(); const wardGroupingDetails = useAppContext('ward-patients-group'); const { mutate: mutateAdmissionLocation } = wardGroupingDetails?.admissionLocationResponse ?? {}; - const { mutate: mutateInpatientRequest } = useInpatientRequest(); + const { mutate: mutateInpatientRequest } = wardGroupingDetails?.inpatientRequestResponse ?? {}; + const { mutate: mutateInpatientAdmission } = wardGroupingDetails?.inpatientAdmissionResponse ?? {}; const submitDischarge = useCallback(() => { setIsSubmitting(true); @@ -51,9 +51,6 @@ export default function PatientDischargeWorkspace(props: WardPatientWorkspacePro title: t('patientWasDischarged', 'Patient was discharged'), kind: 'success', }); - closeWorkspaceWithSavedChanges(); - mutateAdmissionLocation(); - mutateInpatientRequest(); } }) .catch((err: Error) => { @@ -63,7 +60,13 @@ export default function PatientDischargeWorkspace(props: WardPatientWorkspacePro kind: 'error', }); }) - .finally(() => setIsSubmitting(false)); + .finally(() => { + setIsSubmitting(false); + closeWorkspaceWithSavedChanges(); + mutateAdmissionLocation(); + mutateInpatientRequest(); + mutateInpatientAdmission(); + }); }, [ currentProvider, location, @@ -72,6 +75,7 @@ export default function PatientDischargeWorkspace(props: WardPatientWorkspacePro wardPatient?.bed?.uuid, mutateAdmissionLocation, mutateInpatientRequest, + mutateInpatientAdmission, ]); if (!wardGroupingDetails) return <>; diff --git a/packages/esm-ward-app/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx b/packages/esm-ward-app/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx index 9e387c015..2776c34ee 100644 --- a/packages/esm-ward-app/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx +++ b/packages/esm-ward-app/src/ward-workspace/patient-transfer-bed-swap/patient-bed-swap-form.component.tsx @@ -1,16 +1,3 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import styles from './patient-transfer-swap.scss'; -import { z } from 'zod'; -import { useTranslation } from 'react-i18next'; -import { Controller, useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { filterBeds } from '../../ward-view/ward-view.resource'; -import type { BedLayout, WardPatientGroupDetails, WardPatientWorkspaceProps } from '../../types'; -import { assignPatientToBed, createEncounter } from '../../ward.resource'; -import useEmrConfiguration from '../../hooks/useEmrConfiguration'; -import { showSnackbar, useAppContext, useSession } from '@openmrs/esm-framework'; -import useWardLocation from '../../hooks/useWardLocation'; -import { useInpatientRequest } from '../../hooks/useInpatientRequest'; import { Button, ButtonSet, @@ -20,7 +7,18 @@ import { RadioButtonGroup, RadioButtonSkeleton, } from '@carbon/react'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { showSnackbar, useAppContext, useSession } from '@openmrs/esm-framework'; import classNames from 'classnames'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { z } from 'zod'; +import useEmrConfiguration from '../../hooks/useEmrConfiguration'; +import useWardLocation from '../../hooks/useWardLocation'; +import type { BedLayout, WardPatientGroupDetails, WardPatientWorkspaceProps } from '../../types'; +import { assignPatientToBed, createEncounter } from '../../ward.resource'; +import styles from './patient-transfer-swap.scss'; export default function PatientBedSwapForm({ promptBeforeClosing, @@ -36,7 +34,8 @@ export default function PatientBedSwapForm({ const { location } = useWardLocation(); const wardGroupingDetails = useAppContext('ward-patients-group'); const { isLoading, mutate: mutateAdmissionLocation } = wardGroupingDetails?.admissionLocationResponse ?? {}; - const { mutate: mutateInpatientRequest } = useInpatientRequest(); + const { mutate: mutateInpatientRequest } = wardGroupingDetails?.inpatientRequestResponse ?? {}; + const { mutate: mutateInpatientAdmission } = wardGroupingDetails?.inpatientAdmissionResponse ?? {}; const zodSchema = useMemo( () => @@ -112,9 +111,6 @@ export default function PatientBedSwapForm({ bedNumber: bedSelected.bedNumber, }), }); - mutateAdmissionLocation(); - mutateInpatientRequest(); - closeWorkspaceWithSavedChanges(); } }) .catch((error: Error) => { @@ -123,12 +119,13 @@ export default function PatientBedSwapForm({ title: t('errorAssigningBedToPatient', 'Error assigning bed to patient'), subtitle: error?.message, }); - mutateAdmissionLocation(); - mutateInpatientRequest(); - closeWorkspaceWithSavedChanges(); }) .finally(() => { setIsSubmitting(false); + mutateAdmissionLocation(); + mutateInpatientRequest(); + mutateInpatientAdmission(); + closeWorkspaceWithSavedChanges(); }); }, [ @@ -140,6 +137,7 @@ export default function PatientBedSwapForm({ beds, mutateAdmissionLocation, mutateInpatientRequest, + mutateInpatientAdmission, ], ); diff --git a/packages/esm-ward-app/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx b/packages/esm-ward-app/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx index 67dcbc78f..ef5ec74a5 100644 --- a/packages/esm-ward-app/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx +++ b/packages/esm-ward-app/src/ward-workspace/patient-transfer-bed-swap/patient-transfer-request-form.component.tsx @@ -1,18 +1,17 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Button, ButtonSet, Form, InlineNotification, RadioButton, RadioButtonGroup, TextArea } from '@carbon/react'; +import { zodResolver } from '@hookform/resolvers/zod'; import { ResponsiveWrapper, showSnackbar, useAppContext, useSession } from '@openmrs/esm-framework'; -import styles from './patient-transfer-swap.scss'; +import classNames from 'classnames'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { z } from 'zod'; -import { Controller, useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import LocationSelector from '../../location-selector/location-selector.component'; import useEmrConfiguration from '../../hooks/useEmrConfiguration'; -import { createEncounter } from '../../ward.resource'; import useWardLocation from '../../hooks/useWardLocation'; +import LocationSelector from '../../location-selector/location-selector.component'; import type { ObsPayload, WardPatientGroupDetails, WardPatientWorkspaceProps } from '../../types'; -import { useInpatientRequest } from '../../hooks/useInpatientRequest'; -import classNames from 'classnames'; -import { Button, ButtonSet, Form, InlineNotification, RadioButton, RadioButtonGroup, TextArea } from '@carbon/react'; +import { createEncounter } from '../../ward.resource'; +import styles from './patient-transfer-swap.scss'; export default function PatientTransferForm({ closeWorkspaceWithSavedChanges, @@ -32,7 +31,8 @@ export default function PatientTransferForm({ ); const wardGroupingDetails = useAppContext('ward-patients-group'); const { mutate: mutateAdmissionLocation } = wardGroupingDetails?.admissionLocationResponse ?? {}; - const { mutate: mutateInpatientRequest } = useInpatientRequest(); + const { mutate: mutateInpatientAdmission } = wardGroupingDetails?.inpatientAdmissionResponse ?? {}; + const { mutate: mutateInpatientRequest } = wardGroupingDetails?.inpatientRequestResponse ?? {}; const zodSchema = useMemo( () => @@ -124,9 +124,6 @@ export default function PatientTransferForm({ title: t('patientTransferRequestCreated', 'Patient transfer request created'), kind: 'success', }); - closeWorkspaceWithSavedChanges(); - mutateAdmissionLocation(); - mutateInpatientRequest(); }) .catch((err: Error) => { showSnackbar({ @@ -135,7 +132,13 @@ export default function PatientTransferForm({ kind: 'error', }); }) - .finally(() => setIsSubmitting(false)); + .finally(() => { + setIsSubmitting(false); + closeWorkspaceWithSavedChanges(); + mutateAdmissionLocation(); + mutateInpatientAdmission(); + mutateInpatientRequest(); + }); }, [ setShowErrorNotifications, @@ -145,6 +148,7 @@ export default function PatientTransferForm({ patient?.uuid, dispositionsWithTypeTransfer, mutateAdmissionLocation, + mutateInpatientAdmission, mutateInpatientRequest, ], ); diff --git a/packages/esm-ward-app/translations/en.json b/packages/esm-ward-app/translations/en.json index db470028f..4d6b54aef 100644 --- a/packages/esm-ward-app/translations/en.json +++ b/packages/esm-ward-app/translations/en.json @@ -12,6 +12,7 @@ "capacity": "Capacity", "capacityMetricValue": "{{ metricValue }} %", "chooseAnOption": "Choose an option", + "clinicalForms": "Clinical forms", "clinicalNoteLabel": "Write your notes", "discharge": "Discharge", "empty": "Empty", diff --git a/yarn.lock b/yarn.lock index ce4013798..b45786535 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2731,9 +2731,9 @@ __metadata: languageName: unknown linkType: soft -"@openmrs/esm-api@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-api@npm:5.8.2-pre.2301" +"@openmrs/esm-api@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-api@npm:5.8.2-pre.2357" dependencies: "@types/fhir": "npm:0.0.31" lodash-es: "npm:^4.17.21" @@ -2742,17 +2742,17 @@ __metadata: "@openmrs/esm-error-handling": 5.x "@openmrs/esm-navigation": 5.x "@openmrs/esm-offline": 5.x - checksum: 10/bbd3f213126c6d0bd35025734e4ca8a33b0b58a6de568d85a4fb4d8f29178d992cf0f2fa95cb34e6cecc4617b63c8fbe888ddf538eb8fb9aae25725e73a26096 + checksum: 10/5beb2657f8de7d716d6b7358e748ccd12c5f6db7201de6f3a9ed765f0c71ce61c07ecd245e65c7070f98535b95fe4d0be08a017851280d99d1bac1330aaabab4 languageName: node linkType: hard -"@openmrs/esm-app-shell@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-app-shell@npm:5.8.2-pre.2301" +"@openmrs/esm-app-shell@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-app-shell@npm:5.8.2-pre.2357" dependencies: "@carbon/react": "npm:~1.37.0" - "@openmrs/esm-framework": "npm:5.8.2-pre.2301" - "@openmrs/esm-styleguide": "npm:5.8.2-pre.2301" + "@openmrs/esm-framework": "npm:5.8.2-pre.2357" + "@openmrs/esm-styleguide": "npm:5.8.2-pre.2357" dayjs: "npm:^1.10.4" dexie: "npm:^3.0.3" html-webpack-plugin: "npm:^5.5.0" @@ -2777,7 +2777,7 @@ __metadata: workbox-strategies: "npm:^6.1.5" workbox-webpack-plugin: "npm:^6.1.5" workbox-window: "npm:^6.1.5" - checksum: 10/944108f9e23d4160fe4dca93f1c76d966f4da34d3fd9bcf2dcd637352fbf40ac78fbdcbaeb5f740840bdc762ef9e69c468ab66347360eb5bc7ab646880e5152c + checksum: 10/6e7639f0b012083ed5da1ff8a03ced2194f64e3fb84390c6782ed5a1a234f44144b99752109ede3ab64de2b87c0f7a737d3bff49eacd4fc73af4c8f50903046c languageName: node linkType: hard @@ -2817,9 +2817,9 @@ __metadata: languageName: unknown linkType: soft -"@openmrs/esm-config@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-config@npm:5.8.2-pre.2301" +"@openmrs/esm-config@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-config@npm:5.8.2-pre.2357" dependencies: ramda: "npm:^0.26.1" peerDependencies: @@ -2827,44 +2827,44 @@ __metadata: "@openmrs/esm-state": 5.x "@openmrs/esm-utils": 5.x single-spa: 5.x - checksum: 10/7fab46244194420279d8cd29a550074c1d4a1df267366415725571c9117ba81a12a94079ea671ba36297edf9d8da8099bc3d9592edf0ae2f6178331847e20564 + checksum: 10/1e58072dc594017faa6d9aecaec240ba5764d229f131cb5c9fb2f9578cc63e36978b123f39283cfaed5cc85278bd00d280e7ee076aed23dd1c575ca80fd86513 languageName: node linkType: hard -"@openmrs/esm-context@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-context@npm:5.8.2-pre.2301" +"@openmrs/esm-context@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-context@npm:5.8.2-pre.2357" dependencies: immer: "npm:^10.0.4" peerDependencies: "@openmrs/esm-globals": 5.x "@openmrs/esm-state": 5.x - checksum: 10/11358a0b49b67305548b6ad3217eb88d28da691b8ab8ef824efd839674955e69601b967d99e0077a0d3db1b324e1fd9c018217dd05a10058bc0fcb4c8776dc6c + checksum: 10/01367aa44fe03381a4f1c95511c97e2d3727b753f57a4f93f98e42e93eca83494d3a2dff805fa6c843cfd9a73ad1ccfead1dc8ce66441a5e3a70e23c83279904 languageName: node linkType: hard -"@openmrs/esm-dynamic-loading@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-dynamic-loading@npm:5.8.2-pre.2301" +"@openmrs/esm-dynamic-loading@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-dynamic-loading@npm:5.8.2-pre.2357" peerDependencies: "@openmrs/esm-globals": 5.x "@openmrs/esm-translations": 5.x - checksum: 10/1a1d9609cc783fd5caebc7578a86f409073b22a4293ddd91c69c01d13bb04cd524cff0cf20a8ae1d65e7afb22c0dc8ae175964abecfd4e030b75d9a5685318e9 + checksum: 10/2dcf45519b3a9f35b924b59b018c65d06e6580385fd9716ae6fe1d9e795777db732e50dca60cbade291f26d585c8041586a98032bd35f0562c2989a978e2304f languageName: node linkType: hard -"@openmrs/esm-error-handling@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-error-handling@npm:5.8.2-pre.2301" +"@openmrs/esm-error-handling@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-error-handling@npm:5.8.2-pre.2357" peerDependencies: "@openmrs/esm-globals": 5.x - checksum: 10/84194574e19800050720e4a781ffe43a021e0efefd23063ad279e5159e965fa4781d4192cefc8df9e69d0f7627ac1a6ce8914d82d7f4d587d024d93e7e160de5 + checksum: 10/14b71821ace3504fdfc92f990617e7f1ce0752a80a242d77cb9dc512c2e3a4b8ab1be49ed17f7523587502a7ced11644e0d649bd97981fe94f6e76a5314cb6b4 languageName: node linkType: hard -"@openmrs/esm-expression-evaluator@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-expression-evaluator@npm:5.8.2-pre.2301" +"@openmrs/esm-expression-evaluator@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-expression-evaluator@npm:5.8.2-pre.2357" dependencies: "@jsep-plugin/arrow": "npm:^1.0.5" "@jsep-plugin/new": "npm:^1.0.3" @@ -2873,13 +2873,13 @@ __metadata: "@jsep-plugin/template": "npm:^1.0.4" "@jsep-plugin/ternary": "npm:^1.1.3" jsep: "npm:^1.3.9" - checksum: 10/fe519437b5faa69d59a540953c391a8a77e2f44aa5590e8832a07cb3ef239f1b29521acf50b7a5498ab5c86ca52f7ed62aaecc50418bc12e2ee59c18e70c145d + checksum: 10/28e5f2e180e36e7003379b3ac43775f42403a65344b1a62c8fcde4122e1ad7fd43aab9dc95907ac70839c33e11aa3d27222834377e4ff7bbef73cb2d998e1aa4 languageName: node linkType: hard -"@openmrs/esm-extensions@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-extensions@npm:5.8.2-pre.2301" +"@openmrs/esm-extensions@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-extensions@npm:5.8.2-pre.2357" dependencies: lodash-es: "npm:^4.17.21" peerDependencies: @@ -2890,44 +2890,44 @@ __metadata: "@openmrs/esm-state": 5.x "@openmrs/esm-utils": 5.x single-spa: 5.x - checksum: 10/65d82442b8508470fe36773f8b587f6c6cd14285969482d2e85713ff85f01364dd3c80f0030135a92aa355c3e1e703d1c58f341a3262ef2f43440891873f9e36 + checksum: 10/18d3b95e18864e5e2bdb58a3c270637a6328ffdd44cd9558edf38239a2626ed3cc5adc5e8c12aed6568879ed1925cb6087bbb7a5a8bbe66126fbb105b35a038c languageName: node linkType: hard -"@openmrs/esm-feature-flags@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-feature-flags@npm:5.8.2-pre.2301" +"@openmrs/esm-feature-flags@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-feature-flags@npm:5.8.2-pre.2357" dependencies: ramda: "npm:^0.26.1" peerDependencies: "@openmrs/esm-globals": 5.x "@openmrs/esm-state": 5.x single-spa: 5.x - checksum: 10/4645abfd0520c806c294a00e6061b18442cb36c6dc435869472c8529c7fba2aa6f66b5e2b6314e220fbe859becce3d7f6749c57a76020136fe52b2296e4e4f3b - languageName: node - linkType: hard - -"@openmrs/esm-framework@npm:5.8.2-pre.2301, @openmrs/esm-framework@npm:next": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-framework@npm:5.8.2-pre.2301" - dependencies: - "@openmrs/esm-api": "npm:5.8.2-pre.2301" - "@openmrs/esm-config": "npm:5.8.2-pre.2301" - "@openmrs/esm-context": "npm:5.8.2-pre.2301" - "@openmrs/esm-dynamic-loading": "npm:5.8.2-pre.2301" - "@openmrs/esm-error-handling": "npm:5.8.2-pre.2301" - "@openmrs/esm-expression-evaluator": "npm:5.8.2-pre.2301" - "@openmrs/esm-extensions": "npm:5.8.2-pre.2301" - "@openmrs/esm-feature-flags": "npm:5.8.2-pre.2301" - "@openmrs/esm-globals": "npm:5.8.2-pre.2301" - "@openmrs/esm-navigation": "npm:5.8.2-pre.2301" - "@openmrs/esm-offline": "npm:5.8.2-pre.2301" - "@openmrs/esm-react-utils": "npm:5.8.2-pre.2301" - "@openmrs/esm-routes": "npm:5.8.2-pre.2301" - "@openmrs/esm-state": "npm:5.8.2-pre.2301" - "@openmrs/esm-styleguide": "npm:5.8.2-pre.2301" - "@openmrs/esm-translations": "npm:5.8.2-pre.2301" - "@openmrs/esm-utils": "npm:5.8.2-pre.2301" + checksum: 10/8634e3e77babdaf7fe60985e4fac032a44a2f4ee8275269feb35370c3aeb9e2add504691c545e6b78db6a5bd1db5ccdfe7c356ab10e981f402f80d2aeee44432 + languageName: node + linkType: hard + +"@openmrs/esm-framework@npm:5.8.2-pre.2357, @openmrs/esm-framework@npm:next": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-framework@npm:5.8.2-pre.2357" + dependencies: + "@openmrs/esm-api": "npm:5.8.2-pre.2357" + "@openmrs/esm-config": "npm:5.8.2-pre.2357" + "@openmrs/esm-context": "npm:5.8.2-pre.2357" + "@openmrs/esm-dynamic-loading": "npm:5.8.2-pre.2357" + "@openmrs/esm-error-handling": "npm:5.8.2-pre.2357" + "@openmrs/esm-expression-evaluator": "npm:5.8.2-pre.2357" + "@openmrs/esm-extensions": "npm:5.8.2-pre.2357" + "@openmrs/esm-feature-flags": "npm:5.8.2-pre.2357" + "@openmrs/esm-globals": "npm:5.8.2-pre.2357" + "@openmrs/esm-navigation": "npm:5.8.2-pre.2357" + "@openmrs/esm-offline": "npm:5.8.2-pre.2357" + "@openmrs/esm-react-utils": "npm:5.8.2-pre.2357" + "@openmrs/esm-routes": "npm:5.8.2-pre.2357" + "@openmrs/esm-state": "npm:5.8.2-pre.2357" + "@openmrs/esm-styleguide": "npm:5.8.2-pre.2357" + "@openmrs/esm-translations": "npm:5.8.2-pre.2357" + "@openmrs/esm-utils": "npm:5.8.2-pre.2357" dayjs: "npm:^1.10.7" peerDependencies: dayjs: 1.x @@ -2938,35 +2938,35 @@ __metadata: rxjs: 6.x single-spa: 5.x swr: 2.x - checksum: 10/894e88bb2752d2e9488c58891f38c6b572eb7e70c925dfdd4777205f2d6faea31a5ca05d22618ba5ad991454b6a463e3e33fc9122d78729d25c90cd8cae073a2 + checksum: 10/358e9d37819afc6f30a23068cca3bc5af4e659f958751f100b1f0777516925b3de6170acba17437a9c74252a113b8b9981b108bf1b522c504cd056ca0dd3dafd languageName: node linkType: hard -"@openmrs/esm-globals@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-globals@npm:5.8.2-pre.2301" +"@openmrs/esm-globals@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-globals@npm:5.8.2-pre.2357" dependencies: "@types/fhir": "npm:0.0.31" peerDependencies: single-spa: 5.x - checksum: 10/6b782bedac684f1874788470a1feae357673e22e1669d75f7c2b63cc7f6ba830be2288dd40399ec957baa731dc372de48e94d515a557eb965a913b23539c8254 + checksum: 10/ee335b3fdb8f24a58c2d3c77e3f68ffc5e7a1958da40c89f9740c3ea4ad9ecbc430fbbd32e8e884ae84e2c8551b44742fcf4bc5493d2ff5ef6104787ffa6eea2 languageName: node linkType: hard -"@openmrs/esm-navigation@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-navigation@npm:5.8.2-pre.2301" +"@openmrs/esm-navigation@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-navigation@npm:5.8.2-pre.2357" dependencies: path-to-regexp: "npm:6.1.0" peerDependencies: "@openmrs/esm-state": 5.x - checksum: 10/efa74c0db00ff3b54640df06ac5f0a8cc71fcfa6ad920a368c0081cf05c70438947050752d5f7e38734ef2f47d9da4b3b537fb4a37908345baa425476d5fd2e6 + checksum: 10/790b6e6f61b68d9f3baa64d13a4ce800fe99724224656cf9a45f1e23cbf9d16fbbaf76e954d2c4c480332cc9f0760c8ed09f8da5af47a02a2930917b3abad523 languageName: node linkType: hard -"@openmrs/esm-offline@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-offline@npm:5.8.2-pre.2301" +"@openmrs/esm-offline@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-offline@npm:5.8.2-pre.2357" dependencies: dexie: "npm:^3.0.3" lodash-es: "npm:^4.17.21" @@ -2977,7 +2977,7 @@ __metadata: "@openmrs/esm-globals": 5.x "@openmrs/esm-state": 5.x rxjs: 6.x - checksum: 10/2fd84cbe7cbdc63414bba254cb4eec22e6963fcc81f3485fe23c5be995575192e97cf1128d8f76d422306019d50cf840ef7e815ea6dfe2603998044a6a27959d + checksum: 10/3855e79d36f6a422df2c9df00737cf0cb488ad53f0f4ecce530c70dfd2de0cbd87097567aa5a50d2ba8ae31f58590de1e9ae6bd5caacf596aea2e060926657f5 languageName: node linkType: hard @@ -3118,9 +3118,9 @@ __metadata: languageName: unknown linkType: soft -"@openmrs/esm-react-utils@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-react-utils@npm:5.8.2-pre.2301" +"@openmrs/esm-react-utils@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-react-utils@npm:5.8.2-pre.2357" dependencies: lodash-es: "npm:^4.17.21" single-spa-react: "npm:^6.0.0" @@ -3141,13 +3141,13 @@ __metadata: react-i18next: 11.x rxjs: 6.x swr: 2.x - checksum: 10/3d43e199a108f54f5c2bb2424cbbaefa1ba25f8619b3a9210f0830e6ab7d85c564ce5d4aca829ab857d677ee410402042542d420c39fa06f871ab18f5546f01c + checksum: 10/3b62ac16b17757fb44bd149f899bd5852728c3033964e64bb4607eddd269b57ffca0a2125f2b1c7806f3e193cab2d6d54f83ba8dbb1fc5781c08b70fd01ab052 languageName: node linkType: hard -"@openmrs/esm-routes@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-routes@npm:5.8.2-pre.2301" +"@openmrs/esm-routes@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-routes@npm:5.8.2-pre.2357" peerDependencies: "@openmrs/esm-config": 5.x "@openmrs/esm-dynamic-loading": 5.x @@ -3156,7 +3156,7 @@ __metadata: "@openmrs/esm-globals": 5.x "@openmrs/esm-utils": 5.x single-spa: 6.x - checksum: 10/b9134ee4bdd3dd41240d60005c66424ad551e92fb742a3ede9686485cc03d060400eea005b2a76e1e1879026099c9f383eada1f2b7b2b49a248dd85b246b5976 + checksum: 10/7516ddd0966d911c6966be1775e7f198f5baba403cffea105ef62c00f588a034b83558d70beaa56d2a817a00f520a12f4c54c99196877aa63ce8ef0d2a8f58e6 languageName: node linkType: hard @@ -3176,20 +3176,20 @@ __metadata: languageName: unknown linkType: soft -"@openmrs/esm-state@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-state@npm:5.8.2-pre.2301" +"@openmrs/esm-state@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-state@npm:5.8.2-pre.2357" dependencies: zustand: "npm:^4.5.5" peerDependencies: "@openmrs/esm-globals": 5.x - checksum: 10/1a98ad66e06e4028b087cc3673af01cf6e6fb6db1001661552ca1d73b14b32f6d3af55aa71852e01c27566ac2062af395f2b95388daf4c98689908a11367e0fe + checksum: 10/48ed490b71e97b638d77513800e2963f1937b7ebb693631b36927d4a10bd00d2e642fc8c90dc179ef97ce2310b8c8b6eed61f5b3e6cf7df43a35717b22459a93 languageName: node linkType: hard -"@openmrs/esm-styleguide@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-styleguide@npm:5.8.2-pre.2301" +"@openmrs/esm-styleguide@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-styleguide@npm:5.8.2-pre.2357" dependencies: "@carbon/charts": "npm:^1.12.0" "@carbon/react": "npm:~1.37.0" @@ -3213,24 +3213,24 @@ __metadata: react-dom: 18.x react-i18next: 11.x rxjs: 6.x - checksum: 10/98fdfd406d450672113cc148a15e9f43643f55afd7d13a0d1e5be6de83f595b8c151efe99f0594b87b80da016a097e4cf11e3bc32a67e6262030ba9f081da3a0 + checksum: 10/87207473d5d61b42ef886d2e64040a206b059e215a4cee5280a6ee60fafaacbf48bff539d1460211d00e02cf86558c61d0aebba1b3f7331af4489327523b3d11 languageName: node linkType: hard -"@openmrs/esm-translations@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-translations@npm:5.8.2-pre.2301" +"@openmrs/esm-translations@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-translations@npm:5.8.2-pre.2357" dependencies: i18next: "npm:21.10.0" peerDependencies: i18next: 21.x - checksum: 10/7a23953f91233afabaeec232788185bfc1e3a526409d2ef01b47a2278a205316ea6db65f091656c08146a7824aa0c0cba83dc64fc5a7daf3394e90be588d7181 + checksum: 10/3eebbddfe7d538be7ef80f75483e002f83659fe6ad8f6b171a960ecd12d0553b446d218c115903b5f4e157c29add7c32079a79c067ba1628077937c5a52003a8 languageName: node linkType: hard -"@openmrs/esm-utils@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/esm-utils@npm:5.8.2-pre.2301" +"@openmrs/esm-utils@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/esm-utils@npm:5.8.2-pre.2357" dependencies: "@formatjs/intl-durationformat": "npm:^0.2.4" "@internationalized/date": "npm:^3.5.5" @@ -3240,7 +3240,7 @@ __metadata: dayjs: 1.x i18next: 21.x rxjs: 6.x - checksum: 10/fd73d5a4d3c1edab03c50366f9212cdcb81a5950bbcfae1f0af0ea70607b8884e735086b2cbff98cd307220bf0ec86bce55552123e94c0cc9ef989019d2a4880 + checksum: 10/f3800809b060e57bf9c44a5a35c3ddc225d8ea3784eb9539feda3900dccb47aac9e4279e7c6e4c463025355e2d638bef213211cd6f8cfc727024d1b07864fdcd languageName: node linkType: hard @@ -3260,9 +3260,9 @@ __metadata: languageName: unknown linkType: soft -"@openmrs/webpack-config@npm:5.8.2-pre.2301": - version: 5.8.2-pre.2301 - resolution: "@openmrs/webpack-config@npm:5.8.2-pre.2301" +"@openmrs/webpack-config@npm:5.8.2-pre.2357": + version: 5.8.2-pre.2357 + resolution: "@openmrs/webpack-config@npm:5.8.2-pre.2357" dependencies: "@swc/core": "npm:^1.3.58" clean-webpack-plugin: "npm:^4.0.0" @@ -3280,7 +3280,7 @@ __metadata: webpack-stats-plugin: "npm:^1.0.3" peerDependencies: webpack: 5.x - checksum: 10/25beffc311742423935c0858f668caf1f4f1492f6fc1a3d8d396e0629df814e057d1f36ac78c2e751dc176dda40c6f3fb7e430c7f099ec83e2a50337346ad56a + checksum: 10/d2248f3edb2376a4c1c1602e53be56b2524476e979dac510c58c5d81dd1381eba8b0b4ff19f86837e5a9f3c3abfd4cf39704ecf7be18dc7d9f9e8a186a10cebf languageName: node linkType: hard @@ -13493,11 +13493,11 @@ __metadata: linkType: hard "openmrs@npm:next": - version: 5.8.2-pre.2301 - resolution: "openmrs@npm:5.8.2-pre.2301" + version: 5.8.2-pre.2357 + resolution: "openmrs@npm:5.8.2-pre.2357" dependencies: - "@openmrs/esm-app-shell": "npm:5.8.2-pre.2301" - "@openmrs/webpack-config": "npm:5.8.2-pre.2301" + "@openmrs/esm-app-shell": "npm:5.8.2-pre.2357" + "@openmrs/webpack-config": "npm:5.8.2-pre.2357" "@pnpm/npm-conf": "npm:^2.1.0" "@swc/core": "npm:^1.3.58" autoprefixer: "npm:^10.4.20" @@ -13536,7 +13536,7 @@ __metadata: yargs: "npm:^17.6.2" bin: openmrs: ./dist/cli.js - checksum: 10/ec87076b2e16003f719125930ed1b84905a5a769362c06da8c13adac169423f124e1f861f9aff08cb7d32de75344736ee0e6796edebe2319d790f418a3e0a191 + checksum: 10/6f9965138b98028dabadfb45ed6d4fb1e1e9fe9b69307970d615361ff38707aa18c03305aaa5d95a8ee892e22ace1c1556a55cf66de2ee12724507d8af55cb22 languageName: node linkType: hard