diff --git a/README.md b/README.md
index f15427537..12021fe08 100644
--- a/README.md
+++ b/README.md
@@ -182,3 +182,14 @@ should be the version number (e.g., `3.2.1`). The creation of the GitHub release
will cause GitHub Actions to publish the packages, completing the release process.
> Don't run `npm publish` or `yarn publish`. Use the above process.
+
+### Bumping the Common Lib version
+
+Make sure to bump the Common Lib version used here each time you cut a release of Patient Chart. Because Common Lib is marked as a peer dependency and a Webpack module federation shared dependency in the [Appointments app](packages/esm-appointments-app/package.json), the copy of the Common Lib that the framework loads is the first one that gets loaded at runtime when frontend modules are registered. If this happens to be a different version than what the Patient Chart expects, you might get some unexpected behavior in the Patient Chart. You can bump the Common Lib version by running the following command:
+
+```sh
+yarn up @openmrs/esm-patient-common-lib
+git checkout package.json
+yarn
+```
+
diff --git a/packages/esm-appointments-app/package.json b/packages/esm-appointments-app/package.json
index 8070749a2..8b8574dbb 100644
--- a/packages/esm-appointments-app/package.json
+++ b/packages/esm-appointments-app/package.json
@@ -44,7 +44,7 @@
},
"peerDependencies": {
"@openmrs/esm-framework": "5.x",
- "@openmrs/esm-patient-common-lib": "7.x",
+ "@openmrs/esm-patient-common-lib": "8.x",
"react": "18.x",
"react-i18next": "11.x",
"react-router-dom": "6.x",
diff --git a/packages/esm-appointments-app/src/calendar/header/calendar-header.component.tsx b/packages/esm-appointments-app/src/calendar/header/calendar-header.component.tsx
index a4a51c0cc..591616d5d 100644
--- a/packages/esm-appointments-app/src/calendar/header/calendar-header.component.tsx
+++ b/packages/esm-appointments-app/src/calendar/header/calendar-header.component.tsx
@@ -19,6 +19,7 @@ const CalendarHeader: React.FC = () => {
= ({ dayOfWeek }) => {
+
+const DaysOfWeekCard: React.FC = ({ dayOfWeek }) => {
const isToday = dayjs(new Date()).format('ddd').toUpperCase() === dayOfWeek;
return (
- {dayOfWeek}
+ {dayOfWeek}
);
};
diff --git a/packages/esm-appointments-app/src/calendar/monthly/days-of-week.scss b/packages/esm-appointments-app/src/calendar/monthly/days-of-week.scss
index f993ed167..a789c4126 100644
--- a/packages/esm-appointments-app/src/calendar/monthly/days-of-week.scss
+++ b/packages/esm-appointments-app/src/calendar/monthly/days-of-week.scss
@@ -10,6 +10,11 @@
width: 100%;
align-items: center;
border: 1px solid colors.$gray-20;
+ border-right: none;
+
+ &:last-child {
+ border-right: 1px solid colors.$gray-20;
+ }
& > span {
font-size: layout.$spacing-05;
diff --git a/packages/esm-appointments-app/src/calendar/monthly/monthly-calendar-view.component.tsx b/packages/esm-appointments-app/src/calendar/monthly/monthly-calendar-view.component.tsx
index fbb75bc2d..6155b14ce 100644
--- a/packages/esm-appointments-app/src/calendar/monthly/monthly-calendar-view.component.tsx
+++ b/packages/esm-appointments-app/src/calendar/monthly/monthly-calendar-view.component.tsx
@@ -4,9 +4,9 @@ import isBetween from 'dayjs/plugin/isBetween';
import { type DailyAppointmentsCountByService } from '../../types';
import { monthDays } from '../../helpers';
import MonthlyViewWorkload from './monthly-workload-view.component';
-import MonthlyHeader from './monthly-header.module';
-import styles from '../appointments-calendar-view-view.scss';
+import MonthlyHeader from './monthly-header.component';
import SelectedDateContext from '../../hooks/selectedDateContext';
+import styles from '../appointments-calendar-view-view.scss';
dayjs.extend(isBetween);
diff --git a/packages/esm-appointments-app/src/calendar/monthly/monthly-header.component.tsx b/packages/esm-appointments-app/src/calendar/monthly/monthly-header.component.tsx
new file mode 100644
index 000000000..d4e8e70e1
--- /dev/null
+++ b/packages/esm-appointments-app/src/calendar/monthly/monthly-header.component.tsx
@@ -0,0 +1,49 @@
+import React, { useCallback, useContext } from 'react';
+import dayjs from 'dayjs';
+import { useTranslation } from 'react-i18next';
+import { Button } from '@carbon/react';
+import { formatDate } from '@openmrs/esm-framework';
+import { omrsDateFormat } from '../../constants';
+import DaysOfWeekCard from './days-of-week.component';
+import SelectedDateContext from '../../hooks/selectedDateContext';
+import styles from './monthly-header.scss';
+
+const DAYS_IN_WEEK = ['SUN', 'MON', 'TUE', 'WED', 'THUR', 'FRI', 'SAT'];
+
+const MonthlyHeader: React.FC = () => {
+ const { t } = useTranslation();
+ const { selectedDate, setSelectedDate } = useContext(SelectedDateContext);
+
+ const handleSelectPrevMonth = useCallback(() => {
+ setSelectedDate(dayjs(selectedDate).subtract(1, 'month').format(omrsDateFormat));
+ }, [selectedDate, setSelectedDate]);
+
+ const handleSelectNextMonth = useCallback(() => {
+ setSelectedDate(dayjs(selectedDate).add(1, 'month').format(omrsDateFormat));
+ }, [selectedDate, setSelectedDate]);
+
+ return (
+ <>
+
+
+ {t('prev', 'Prev')}
+
+ {formatDate(new Date(selectedDate), { day: false, time: false, noToday: true })}
+
+ {t('next', 'Next')}
+
+
+
+ {DAYS_IN_WEEK.map((day) => (
+
+ ))}
+
+ >
+ );
+};
+
+export default MonthlyHeader;
diff --git a/packages/esm-appointments-app/src/calendar/monthly/monthly-header.module.tsx b/packages/esm-appointments-app/src/calendar/monthly/monthly-header.module.tsx
deleted file mode 100644
index ef8fe1bcf..000000000
--- a/packages/esm-appointments-app/src/calendar/monthly/monthly-header.module.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React, { useContext } from 'react';
-import dayjs from 'dayjs';
-import styles from './monthly-header.module.scss';
-import { Button } from '@carbon/react';
-import { useTranslation } from 'react-i18next';
-import DaysOfWeekCard from './days-of-week.component';
-import SelectedDateContext from '../../hooks/selectedDateContext';
-import { omrsDateFormat } from '../../constants';
-
-const monthFormat = 'MMMM, YYYY';
-const daysInWeek = ['SUN', 'MON', 'TUE', 'WED', 'THUR', 'FRI', 'SAT'];
-function MonthlyHeader() {
- const { t } = useTranslation();
- const { selectedDate, setSelectedDate } = useContext(SelectedDateContext);
-
- return (
- <>
-
- setSelectedDate(dayjs(selectedDate).subtract(1, 'month').format(omrsDateFormat))}
- kind="tertiary">
- {t('prev', 'Prev')}
-
- {dayjs(selectedDate).format(monthFormat)}
-
- setSelectedDate(dayjs(selectedDate).add(1, 'month').format(omrsDateFormat))}
- kind="tertiary">
- {t('next', 'Next')}
-
-
-
- {daysInWeek?.map((day, i) => )}
-
- >
- );
-}
-export default MonthlyHeader;
diff --git a/packages/esm-appointments-app/src/calendar/monthly/monthly-header.module.scss b/packages/esm-appointments-app/src/calendar/monthly/monthly-header.scss
similarity index 100%
rename from packages/esm-appointments-app/src/calendar/monthly/monthly-header.module.scss
rename to packages/esm-appointments-app/src/calendar/monthly/monthly-header.scss
diff --git a/packages/esm-appointments-app/src/calendar/monthly/monthly-view-workload.scss b/packages/esm-appointments-app/src/calendar/monthly/monthly-view-workload.scss
index 7a3eaa37e..262dacaa4 100644
--- a/packages/esm-appointments-app/src/calendar/monthly/monthly-view-workload.scss
+++ b/packages/esm-appointments-app/src/calendar/monthly/monthly-view-workload.scss
@@ -13,10 +13,6 @@
text-align: right;
@include type.type-style('body-compact-02');
- &:nth-child(-n + 7) {
- border-top: 1px solid colors.$gray-20;
- }
-
&:nth-child(7n) {
border-right: 1px solid colors.$gray-20;
}
diff --git a/packages/esm-appointments-app/src/form/appointments-form.component.tsx b/packages/esm-appointments-app/src/form/appointments-form.component.tsx
index 879227ddb..b5c8855e1 100644
--- a/packages/esm-appointments-app/src/form/appointments-form.component.tsx
+++ b/packages/esm-appointments-app/src/form/appointments-form.component.tsx
@@ -181,11 +181,22 @@ const AppointmentsForm: React.FC = ({
)
.refine(
(formValues) => {
- const appointmentDate = formValues.appointmentDateTime?.startDate;
- const dateAppointmentScheduled = formValues.dateAppointmentScheduled;
+ const { appointmentDateTime, dateAppointmentScheduled } = formValues;
- if (!appointmentDate || !dateAppointmentScheduled) return true;
- return dateAppointmentScheduled < appointmentDate;
+ const startDate = appointmentDateTime?.startDate;
+
+ if (!startDate || !dateAppointmentScheduled) return true;
+
+ const normalizeDate = (date: Date) => {
+ const normalizedDate = new Date(date);
+ normalizedDate.setHours(0, 0, 0, 0);
+ return normalizedDate;
+ };
+
+ const startDateObj = normalizeDate(startDate);
+ const scheduledDateObj = normalizeDate(dateAppointmentScheduled);
+
+ return scheduledDateObj <= startDateObj;
},
{
path: ['dateAppointmentScheduled'],
@@ -195,6 +206,7 @@ const AppointmentsForm: React.FC = ({
),
},
);
+
type AppointmentFormData = z.infer;
const defaultDateAppointmentScheduled = appointment?.dateAppointmentScheduled
diff --git a/packages/esm-appointments-app/src/hooks/useClinicalMetrics.ts b/packages/esm-appointments-app/src/hooks/useClinicalMetrics.ts
index 0470a972d..9d016ccb4 100644
--- a/packages/esm-appointments-app/src/hooks/useClinicalMetrics.ts
+++ b/packages/esm-appointments-app/src/hooks/useClinicalMetrics.ts
@@ -45,11 +45,13 @@ export function useAllAppointmentsByDate() {
openmrsFetch,
);
- const providersArray = data?.data?.filter(({ providers }) => providers !== null) ?? [];
- const providersCount = uniqBy(
- providersArray.map(({ providers }) => providers).flat(),
- (provider) => provider.uuid,
- ).length;
+ const providersArray = data?.data?.flatMap(({ providers }) => providers ?? []) ?? [];
+
+ const validProviders = providersArray.filter((provider) => provider.response === 'ACCEPTED');
+
+ const uniqueProviders = uniqBy(validProviders, (provider) => provider.uuid);
+ const providersCount = uniqueProviders.length;
+
return {
totalProviders: providersCount ? providersCount : 0,
isLoading,
diff --git a/packages/esm-appointments-app/translations/en.json b/packages/esm-appointments-app/translations/en.json
index 5b39be5f0..666410df3 100644
--- a/packages/esm-appointments-app/translations/en.json
+++ b/packages/esm-appointments-app/translations/en.json
@@ -100,6 +100,8 @@
"location": "Location",
"medications": "Medications",
"missed": "Missed",
+ "next": "Next",
+ "nextMonth": "Next month",
"nextPage": "Next page",
"no": "No",
"noAppointmentsToDisplay": "No appointments to display",
@@ -120,6 +122,8 @@
"patientName": "Patient name",
"patients": "Patients",
"period": "Period",
+ "prev": "Prev",
+ "previousMonth": "Previous month",
"previousPage": "Previous page",
"provider": "Provider",
"providers": "Providers",
diff --git a/packages/esm-bed-management-app/src/bed-administration/bed-administration-form.component.tsx b/packages/esm-bed-management-app/src/bed-administration/bed-administration-form.component.tsx
index 9451566b1..fb19ab3e3 100644
--- a/packages/esm-bed-management-app/src/bed-administration/bed-administration-form.component.tsx
+++ b/packages/esm-bed-management-app/src/bed-administration/bed-administration-form.component.tsx
@@ -118,8 +118,8 @@ const BedAdministrationForm: React.FC = ({
return (
onModalChange(false)} preventCloseOnClickOutside>
-
+
+
+
+ onModalChange(false)} kind="secondary">
+ {t('cancel', 'Cancel')}
+
+
+ {t('save', 'Save')}
+
+
);
};
diff --git a/packages/esm-bed-management-app/src/bed-administration/bed-administration-table.scss b/packages/esm-bed-management-app/src/bed-administration/bed-administration-table.scss
index d7c969ffe..b07e89456 100644
--- a/packages/esm-bed-management-app/src/bed-administration/bed-administration-table.scss
+++ b/packages/esm-bed-management-app/src/bed-administration/bed-administration-table.scss
@@ -6,10 +6,6 @@
border: 1px solid colors.$gray-20;
margin: layout.$spacing-06;
- :global(.cds--modal-content) {
- margin-bottom: unset;
- }
-
@media (min-width: 66rem) {
:global(.cds--modal-container) {
max-height: 100%;
diff --git a/packages/esm-patient-registration-app/package.json b/packages/esm-patient-registration-app/package.json
index c119ee10c..6a2f875ea 100644
--- a/packages/esm-patient-registration-app/package.json
+++ b/packages/esm-patient-registration-app/package.json
@@ -18,7 +18,7 @@
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
"coverage": "yarn test --coverage",
"typescript": "tsc",
- "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.extension.tsx' 'src/**/*modal.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
+ "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.extension.tsx' 'src/**/*modal.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' 'src/patient-registration/validation/patient-registration-validation.ts' --config ../../tools/i18next-parser.config.js"
},
"browserslist": [
"extends browserslist-config-openmrs"
diff --git a/packages/esm-patient-registration-app/src/config-schema.ts b/packages/esm-patient-registration-app/src/config-schema.ts
index 205b9a2a6..1e2cadd78 100644
--- a/packages/esm-patient-registration-app/src/config-schema.ts
+++ b/packages/esm-patient-registration-app/src/config-schema.ts
@@ -37,6 +37,10 @@ export interface RegistrationConfig {
sectionDefinitions: Array;
fieldDefinitions: Array;
fieldConfigurations: {
+ causeOfDeath: {
+ conceptUuid: string;
+ required?: boolean;
+ };
name: {
displayMiddleName: boolean;
allowUnidentifiedPatients: boolean;
@@ -78,6 +82,7 @@ export interface RegistrationConfig {
encounterProviderRoleUuid: string;
registrationFormUuid: string | null;
};
+ freeTextFieldConceptUuid: string;
}
export const builtInSections: Array = [
@@ -87,12 +92,21 @@ export const builtInSections: Array = [
fields: ['name', 'gender', 'dob', 'id'],
},
{ id: 'contact', name: 'Contact Details', fields: ['address', 'phone'] },
- { id: 'death', name: 'Death Info', fields: [] },
+ { id: 'death', name: 'Death Info', fields: ['dateAndTimeOfDeath', 'causeOfDeath'] },
{ id: 'relationships', name: 'Relationships', fields: [] },
];
// These fields are handled specially in field.component.tsx
-export const builtInFields = ['name', 'gender', 'dob', 'id', 'address', 'phone'] as const;
+export const builtInFields = [
+ 'name',
+ 'gender',
+ 'dob',
+ 'id',
+ 'address',
+ 'phone',
+ 'causeOfDeath',
+ 'dateAndTimeOfDeath',
+] as const;
export const esmPatientRegistrationSchema = {
sections: {
@@ -199,6 +213,14 @@ export const esmPatientRegistrationSchema = {
'Definitions for custom fields that can be used in sectionDefinitions. Can also be used to override built-in fields.',
},
fieldConfigurations: {
+ causeOfDeath: {
+ conceptUuid: {
+ _type: Type.ConceptUuid,
+ _description: 'The concept UUID to get cause of death answers',
+ _default: '9272a14b-7260-4353-9e5b-5787b5dead9d',
+ },
+ required: { _type: Type.Boolean, _default: false },
+ },
name: {
displayMiddleName: { _type: Type.Boolean, _default: true },
allowUnidentifiedPatients: {
@@ -359,6 +381,10 @@ export const esmPatientRegistrationSchema = {
'The form UUID to associate with the registration encounter. By default no form will be associated.',
},
},
+ freeTextFieldConceptUuid: {
+ _default: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
+ _type: Type.ConceptUuid,
+ },
_validators: [
validator(
(config: RegistrationConfig) =>
diff --git a/packages/esm-patient-registration-app/src/index.ts b/packages/esm-patient-registration-app/src/index.ts
index d31ceb58a..47a6d4e42 100644
--- a/packages/esm-patient-registration-app/src/index.ts
+++ b/packages/esm-patient-registration-app/src/index.ts
@@ -50,10 +50,7 @@ export const editPatient = getSyncLifecycle(rootComponent, {
export const addPatientLink = getSyncLifecycle(addPatientLinkComponent, options);
-export const cancelPatientEditModal = getAsyncLifecycle(
- () => import('./widgets/cancel-patient-edit.component'),
- options,
-);
+export const cancelPatientEditModal = getAsyncLifecycle(() => import('./widgets/cancel-patient-edit.modal'), options);
export const patientPhotoExtension = getSyncLifecycle(PatientPhotoExtension, options);
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx
new file mode 100644
index 000000000..e764d0e81
--- /dev/null
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx
@@ -0,0 +1,98 @@
+import React, { useMemo } from 'react';
+import classNames from 'classnames';
+import { Field, useField } from 'formik';
+import { useTranslation } from 'react-i18next';
+import { InlineNotification, Layer, Select, SelectItem, SelectSkeleton, TextInput } from '@carbon/react';
+import { useConfig } from '@openmrs/esm-framework';
+import { type RegistrationConfig } from '../../../config-schema';
+import { useConceptAnswers } from '../field.resource';
+import styles from '../field.scss';
+
+export const CauseOfDeathField: React.FC = () => {
+ const { t } = useTranslation();
+ const { fieldConfigurations, freeTextFieldConceptUuid } = useConfig();
+ const [deathCause, deathCauseMeta] = useField('deathCause');
+
+ const conceptUuid = fieldConfigurations?.causeOfDeath?.conceptUuid;
+ const required = fieldConfigurations?.causeOfDeath?.required;
+
+ const {
+ data: conceptAnswers,
+ isLoading: isLoadingConceptAnswers,
+ error: errorLoadingConceptAnswers,
+ } = useConceptAnswers(conceptUuid);
+
+ const answers = useMemo(() => {
+ if (!isLoadingConceptAnswers && conceptAnswers) {
+ return conceptAnswers.map((answer) => ({ ...answer, label: answer.display }));
+ }
+ return [];
+ }, [conceptAnswers, isLoadingConceptAnswers]);
+
+ if (isLoadingConceptAnswers) {
+ return (
+
+
{t('causeOfDeathInputLabel', 'Cause of death')}
+
+
+ );
+ }
+
+ return (
+
+
{t('causeOfDeathInputLabel', 'Cause of death')}
+ {errorLoadingConceptAnswers || !conceptUuid ? (
+
+ ) : (
+ <>
+
+ {({ field, form: { touched, errors }, meta }) => {
+ return (
+
+
+
+ {answers.map((answer) => (
+
+ ))}
+
+
+ );
+ }}
+
+ {deathCause.value === freeTextFieldConceptUuid && (
+
+
+ {({ field, form: { touched, errors }, meta }) => {
+ return (
+
+
+
+ );
+ }}
+
+
+ )}
+ >
+ )}
+
+ );
+};
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx
new file mode 100644
index 000000000..0a27740f4
--- /dev/null
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx
@@ -0,0 +1,84 @@
+import React, { useCallback, useContext } from 'react';
+import classNames from 'classnames';
+import dayjs from 'dayjs';
+import { Layer, SelectItem, TimePicker, TimePickerSelect } from '@carbon/react';
+import { useTranslation } from 'react-i18next';
+import { useField } from 'formik';
+import { OpenmrsDatePicker } from '@openmrs/esm-framework';
+import { PatientRegistrationContext } from '../../patient-registration-context';
+import type { FormValues } from '../../patient-registration.types';
+import styles from '../field.scss';
+
+export const DateAndTimeOfDeathField: React.FC = () => {
+ const { t } = useTranslation();
+
+ return (
+
+
{t('deathDateInputLabel', 'Date of Death')}
+
+
+
+
+
+ );
+};
+
+function DeathDateField() {
+ const { values, setFieldValue } = useContext(PatientRegistrationContext);
+ const [deathDate, deathDateMeta] = useField('deathDate');
+ const { t } = useTranslation();
+ const today = dayjs().hour(23).minute(59).second(59).toDate();
+ const onDateChange = useCallback(
+ (selectedDate: Date) => {
+ setFieldValue(
+ 'deathDate',
+ selectedDate ? dayjs(selectedDate).hour(0).minute(0).second(0).millisecond(0).toDate() : undefined,
+ );
+ },
+ [deathDate],
+ );
+
+ return (
+
+
+
+ );
+}
+
+function DeathTimeField() {
+ const { t } = useTranslation();
+ const [deathTimeField, deathTimeMeta] = useField('deathTime');
+ const [deathTimeFormatField, deathTimeFormatMeta] = useField('deathTimeFormat');
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx
index f3c21415c..7b1500bf2 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx
@@ -88,7 +88,7 @@ export const DobField: React.FC = () => {
{(allowEstimatedBirthDate || dobUnknown) && (
- {t('dobToggleLabelText', 'Date of Birth Known?')}
+ {t('dobToggleLabelText', 'Date of birth known?')}
@@ -104,7 +104,7 @@ export const DobField: React.FC = () => {
{...birthdate}
onChange={onDateChange}
maxDate={today}
- labelText={t('dateOfBirthLabelText', 'Date of Birth')}
+ labelText={t('dateOfBirthLabelText', 'Date of birth')}
isInvalid={!!(birthdateMeta.touched && birthdateMeta.error)}
invalidText={t(birthdateMeta.error)}
value={birthdate.value}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/field.component.tsx
index bdadee3d4..8c6baef18 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/field/field.component.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/field.component.tsx
@@ -1,12 +1,14 @@
import React from 'react';
-import { NameField } from './name/name-field.component';
-import { GenderField } from './gender/gender-field.component';
-import { Identifiers } from './id/id-field.component';
-import { DobField } from './dob/dob.component';
import { reportError, useConfig } from '@openmrs/esm-framework';
import { builtInFields, type RegistrationConfig } from '../../config-schema';
-import { CustomField } from './custom-field.component';
import { AddressComponent } from './address/address-field.component';
+import { CauseOfDeathField } from './cause-of-death/cause-of-death.component';
+import { CustomField } from './custom-field.component';
+import { DateAndTimeOfDeathField } from './date-and-time-of-death/date-and-time-of-death.component';
+import { DobField } from './dob/dob.component';
+import { GenderField } from './gender/gender-field.component';
+import { Identifiers } from './id/id-field.component';
+import { NameField } from './name/name-field.component';
import { PhoneField } from './phone/phone-field.component';
export interface FieldProps {
@@ -35,6 +37,10 @@ export function Field({ name }: FieldProps) {
return ;
case 'dob':
return ;
+ case 'dateAndTimeOfDeath':
+ return ;
+ case 'causeOfDeath':
+ return ;
case 'address':
return ;
case 'id':
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/field.resource.ts b/packages/esm-patient-registration-app/src/patient-registration/field/field.resource.ts
index 217fdefc5..9d549fc47 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/field/field.resource.ts
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/field.resource.ts
@@ -1,6 +1,7 @@
-import { type FetchResponse, openmrsFetch, showSnackbar, restBaseUrl } from '@openmrs/esm-framework';
+import { type FetchResponse, openmrsFetch, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
import useSWRImmutable from 'swr/immutable';
import { type ConceptAnswers, type ConceptResponse } from '../patient-registration.types';
+import { useMemo } from 'react';
export function useConcept(conceptUuid: string): { data: ConceptResponse; isLoading: boolean } {
const shouldFetch = typeof conceptUuid === 'string' && conceptUuid !== '';
@@ -15,10 +16,15 @@ export function useConcept(conceptUuid: string): { data: ConceptResponse; isLoad
kind: 'error',
});
}
- return { data: data?.data, isLoading };
+ const results = useMemo(() => ({ data: data?.data, isLoading }), [data, isLoading]);
+ return results;
}
-export function useConceptAnswers(conceptUuid: string): { data: Array; isLoading: boolean } {
+export function useConceptAnswers(conceptUuid: string): {
+ data: Array;
+ isLoading: boolean;
+ error: Error;
+} {
const shouldFetch = typeof conceptUuid === 'string' && conceptUuid !== '';
const { data, error, isLoading } = useSWRImmutable, Error>(
shouldFetch ? `${restBaseUrl}/concept/${conceptUuid}` : null,
@@ -31,5 +37,6 @@ export function useConceptAnswers(conceptUuid: string): { data: Array ({ data: data?.data?.answers, isLoading, error }), [isLoading, error, data]);
+ return results;
}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/field.scss b/packages/esm-patient-registration-app/src/patient-registration/field/field.scss
index fe29c1bc1..7f3595392 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/field/field.scss
+++ b/packages/esm-patient-registration-app/src/patient-registration/field/field.scss
@@ -64,8 +64,30 @@
}
.sexField,
-.dobField {
+.dobField,
+.dodField {
margin-bottom: layout.$spacing-05;
+
+ span {
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: space-between;
+ align-items: start;
+ }
+}
+
+.nonCodedCauseOfDeath {
+ margin-top: layout.$spacing-04;
+}
+
+.timeOfDeathContainer {
+ display: flex;
+ align-items: center;
+}
+
+.timeOfDeathField {
+ flex: none;
+ margin-left: layout.$spacing-02;
}
.dobContentSwitcherLabel {
diff --git a/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts b/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts
index 56b4e528f..ae625319e 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts
+++ b/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts
@@ -20,7 +20,10 @@ const formValues: FormValues = {
telephoneNumber: '',
isDead: false,
deathDate: 'string',
+ deathTime: '',
+ deathTimeFormat: 'AM',
deathCause: 'string',
+ nonCodedCauseOfDeath: '',
relationships: [],
address: {
address1: '',
diff --git a/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts b/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts
index 2019fc4ad..b682d038f 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts
+++ b/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts
@@ -1,23 +1,24 @@
import {
type FetchResponse,
- type Session,
- type StyleguideConfigObject,
getConfig,
openmrsFetch,
queueSynchronizationItem,
restBaseUrl,
+ type Session,
+ type StyleguideConfigObject,
+ toOmrsIsoString,
} from '@openmrs/esm-framework';
import { patientRegistration } from '../constants';
import {
- type FormValues,
type AttributeValue,
- type PatientUuidMapType,
- type Patient,
type CapturePhotoProps,
+ type Encounter,
+ type FormValues,
+ type Patient,
type PatientIdentifier,
type PatientRegistration,
+ type PatientUuidMapType,
type RelationshipValue,
- type Encounter,
} from './patient-registration.types';
import {
addPatientIdentifier,
@@ -25,14 +26,16 @@ import {
deletePersonName,
deleteRelationship,
generateIdentifier,
+ getDatetime,
+ saveEncounter,
savePatient,
savePatientPhoto,
saveRelationship,
- updateRelationship,
updatePatientIdentifier,
- saveEncounter,
+ updateRelationship,
} from './patient-registration.resource';
import { type RegistrationConfig } from '../config-schema';
+import dayjs from 'dayjs';
export type SavePatientForm = (
isNewPatient: boolean,
@@ -62,7 +65,7 @@ export class FormManager {
) => {
const syncItem: PatientRegistration = {
fhirPatient: FormManager.mapPatientToFhirPatient(
- FormManager.getPatientToCreate(isNewPatient, values, patientUuidMap, initialAddressFieldValues, []),
+ FormManager.getPatientToCreate(isNewPatient, values, patientUuidMap, initialAddressFieldValues, [], config),
),
_patientRegistrationData: {
isNewPatient,
@@ -115,6 +118,7 @@ export class FormManager {
patientUuidMap,
initialAddressFieldValues,
patientIdentifiers,
+ config,
);
FormManager.getDeletedNames(values.patientUuid, patientUuidMap).forEach(async (name) => {
@@ -297,6 +301,7 @@ export class FormManager {
patientUuidMap: PatientUuidMapType,
initialAddressFieldValues: Record,
identifiers: Array,
+ config?: RegistrationConfig,
): Patient {
let birthdate;
if (values.birthdate instanceof Date) {
@@ -317,7 +322,7 @@ export class FormManager {
birthdateEstimated: values.birthdateEstimated,
attributes: FormManager.getPatientAttributes(isNewPatient, values, patientUuidMap),
addresses: [values.address],
- ...FormManager.getPatientDeathInfo(values),
+ ...FormManager.getPatientDeathInfo(values, config),
},
identifiers,
};
@@ -376,12 +381,22 @@ export class FormManager {
return attributes;
}
- static getPatientDeathInfo(values: FormValues) {
- const { isDead, deathDate, deathCause } = values;
+ static getPatientDeathInfo(values: FormValues, config?: RegistrationConfig) {
+ const { isDead, deathDate, deathTime, deathTimeFormat, deathCause, nonCodedCauseOfDeath } = values;
+
+ if (!isDead) {
+ return {
+ dead: false,
+ };
+ }
+ const dateTimeOfDeath = toOmrsIsoString(getDatetime(deathDate, deathTime, deathTimeFormat));
+
return {
- dead: isDead,
- deathDate: isDead ? deathDate : undefined,
- causeOfDeath: isDead ? deathCause : undefined,
+ dead: true,
+ deathDate: dateTimeOfDeath,
+ ...(deathCause === config?.freeTextFieldConceptUuid
+ ? { causeOfDeathNonCoded: nonCodedCauseOfDeath, causeOfDeath: null }
+ : { causeOfDeath: deathCause, causeOfDeathNonCoded: null }),
};
}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/input/input.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/input/input.component.tsx
index c2a31ab8f..fa6edca8a 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/input/input.component.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/input/basic-input/input/input.component.tsx
@@ -66,7 +66,7 @@ export interface TextInputProps
* `true` to use the light version. For use on $ui-01 backgrounds only.
* Don't use this to make tile background color same as container background color.
* 'The `light` prop for `TextInput` has ' +
- 'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'
+ 'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'
*/
light?: boolean;
@@ -145,6 +145,10 @@ export const Input: React.FC = ({ checkWarning, ...props }) => {
t('invalidEmail')
t('numberInNameDubious')
t('yearsEstimateRequired')
+ t('deathdayIsRequired', 'Death date is required when the patient is marked as deceased.')
+ t('deathdayInvalidDate', 'Date of death is invalid')
+ t('deathCauseRequired', 'Cause of death is required')
+ t('nonCodedCauseOfDeathRequired', 'Non-coded cause of death is required')
*/
const value = field.value || '';
diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx
index 79f76218b..c87f5e31d 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx
@@ -25,7 +25,10 @@ export const dummyFormValues: FormValues = {
telephoneNumber: '0800001066',
isDead: false,
deathDate: '',
+ deathTime: '',
+ deathTimeFormat: 'AM',
deathCause: '',
+ nonCodedCauseOfDeath: '',
relationships: [],
address: {
address1: 'Bom Jesus Street',
diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-context.ts b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-context.ts
index d3e46eca2..2f29433f0 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-context.ts
+++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-context.ts
@@ -1,7 +1,7 @@
import { useConfig } from '@openmrs/esm-framework';
import { createContext, type SetStateAction } from 'react';
import { type RegistrationConfig } from '../config-schema';
-import { type FormValues, type CapturePhotoProps } from './patient-registration.types';
+import { type CapturePhotoProps, type FormValues } from './patient-registration.types';
export interface PatientRegistrationContextProps {
currentPhoto: string;
@@ -9,11 +9,12 @@ export interface PatientRegistrationContextProps {
inEditMode: boolean;
initialFormValues: FormValues;
isOffline: boolean;
- setCapturePhotoProps(value: SetStateAction): void;
- setFieldValue(field: string, value: any, shouldValidate?: boolean): void;
setInitialFormValues?: React.Dispatch>;
validationSchema: any;
values: FormValues;
+ setCapturePhotoProps(value: SetStateAction): void;
+ setFieldValue(field: string, value: any, shouldValidate?: boolean): void;
+ setFieldTouched(field: string, isTouched?: any, shouldValidate?: boolean): void;
}
export const PatientRegistrationContext = createContext(undefined);
diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-hooks.ts b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-hooks.ts
index 8768fc8a0..ff50d4b20 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-hooks.ts
+++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-hooks.ts
@@ -1,11 +1,11 @@
import {
type FetchResponse,
- type OpenmrsResource,
getSynchronizationItems,
openmrsFetch,
+ type OpenmrsResource,
+ restBaseUrl,
useConfig,
usePatient,
- restBaseUrl,
} from '@openmrs/esm-framework';
import camelCase from 'lodash-es/camelCase';
import { type Dispatch, useEffect, useMemo, useState } from 'react';
@@ -14,12 +14,12 @@ import { v4 } from 'uuid';
import { type RegistrationConfig } from '../config-schema';
import { patientRegistration } from '../constants';
import {
+ type Encounter,
type FormValues,
+ type PatientIdentifierResponse,
type PatientRegistration,
type PatientUuidMapType,
type PersonAttributeResponse,
- type PatientIdentifierResponse,
- type Encounter,
} from './patient-registration.types';
import {
getAddressFieldValuesFromFhirPatient,
@@ -32,7 +32,9 @@ import { useInitialPatientRelationships } from './section/patient-relationships/
import dayjs from 'dayjs';
export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch] {
+ const { freeTextFieldConceptUuid } = useConfig();
const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(patientUuid);
+ const { data: deathInfo, isLoading: isLoadingDeathInfo } = useInitialPersonDeathInfo(patientUuid);
const { data: attributes, isLoading: isLoadingAttributes } = useInitialPersonAttributes(patientUuid);
const { data: identifiers, isLoading: isLoadingIdentifiers } = useInitialPatientIdentifiers(patientUuid);
const { data: relationships, isLoading: isLoadingRelationships } = useInitialPatientRelationships(patientUuid);
@@ -54,8 +56,11 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
birthdateEstimated: false,
telephoneNumber: '',
isDead: false,
- deathDate: '',
+ deathDate: undefined,
+ deathTime: undefined,
+ deathTimeFormat: 'AM',
deathCause: '',
+ nonCodedCauseOfDeath: '',
relationships: [],
identifiers: {},
address: {},
@@ -96,6 +101,25 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
})();
}, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
+ // Set initial patient death info
+ useEffect(() => {
+ if (!isLoadingDeathInfo && deathInfo?.dead) {
+ const deathDatetime = deathInfo.deathDate || null;
+ const deathDate = deathDatetime ? new Date(deathDatetime) : undefined;
+ const time = deathDate ? dayjs(deathDate).format('hh:mm') : undefined;
+ const timeFormat = deathDate ? (dayjs(deathDate).hour() >= 12 ? 'PM' : 'AM') : 'AM';
+ setInitialFormValues((initialFormValues) => ({
+ ...initialFormValues,
+ isDead: deathInfo.dead || false,
+ deathDate: deathDate,
+ deathTime: time,
+ deathTimeFormat: timeFormat,
+ deathCause: deathInfo.causeOfDeathNonCoded ? freeTextFieldConceptUuid : deathInfo.causeOfDeath?.uuid,
+ nonCodedCauseOfDeath: deathInfo.causeOfDeathNonCoded,
+ }));
+ }
+ }, [isLoadingDeathInfo, deathInfo, setInitialFormValues]);
+
// Set initial patient relationships
useEffect(() => {
if (!isLoadingRelationships && relationships) {
@@ -279,6 +303,32 @@ function useInitialPersonAttributes(personUuid: string) {
return result;
}
+interface DeathInfoResults {
+ uuid: string;
+ display: string;
+ causeOfDeath: OpenmrsResource | null;
+ dead: boolean;
+ deathDate: string;
+ causeOfDeathNonCoded: string | null;
+}
+
+function useInitialPersonDeathInfo(personUuid: string) {
+ const { data, error, isLoading } = useSWR, Error>(
+ !!personUuid
+ ? `${restBaseUrl}/person/${personUuid}?v=custom:(uuid,display,causeOfDeath,dead,deathDate,causeOfDeathNonCoded)`
+ : null,
+ openmrsFetch,
+ );
+
+ const result = useMemo(() => {
+ return {
+ data: data?.data,
+ isLoading,
+ };
+ }, [data, error]);
+ return result;
+}
+
function getPatientAttributeUuidMapForPatient(attributes: Array) {
const attributeUuidMap = {};
attributes.forEach((attribute) => {
diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.ts b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.ts
index 57dddc818..3aa777a2c 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.ts
+++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.ts
@@ -3,11 +3,11 @@ import camelCase from 'lodash-es/camelCase';
import { parseDate } from '@openmrs/esm-framework';
import {
type AddressValidationSchemaType,
+ type Encounter,
type FormValues,
type PatientIdentifier,
- type PatientUuidMapType,
type PatientIdentifierValue,
- type Encounter,
+ type PatientUuidMapType,
} from './patient-registration.types';
export function parseAddressTemplateXml(addressTemplate: string) {
@@ -47,6 +47,7 @@ export function parseAddressTemplateXml(addressTemplate: string) {
addressValidationSchema,
};
}
+
export function parseAddressTemplateXmlOld(addressTemplate: string) {
const templateXmlDoc = new DOMParser().parseFromString(addressTemplate, 'text/xml');
const nameMappings = templateXmlDoc.querySelector('nameMappings').querySelectorAll('property');
@@ -123,11 +124,6 @@ export function getFormValuesFromFhirPatient(patient: fhir.Patient) {
result.birthdate = patient.birthDate ? parseDate(patient.birthDate) : undefined;
result.telephoneNumber = patient.telecom ? patient.telecom[0].value : '';
- if (patient.deceasedBoolean || patient.deceasedDateTime) {
- result.isDead = true;
- result.deathDate = patient.deceasedDateTime ? patient.deceasedDateTime.split('T')[0] : '';
- }
-
return {
...result,
...patient.identifier.map((identifier) => {
diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.component.tsx
index abc3abac5..2db6ba576 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.component.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.component.tsx
@@ -1,20 +1,20 @@
-import React, { useState, useEffect, useContext, useMemo, useRef } from 'react';
+import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import classNames from 'classnames';
-import { Button, Link, InlineLoading } from '@carbon/react';
+import { Button, InlineLoading, Link } from '@carbon/react';
import { XAxis } from '@carbon/react/icons';
import { useLocation, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { Formik, Form, type FormikHelpers } from 'formik';
+import { Form, Formik, type FormikHelpers } from 'formik';
import {
createErrorHandler,
+ interpolateUrl,
showSnackbar,
useConfig,
- interpolateUrl,
usePatient,
usePatientPhoto,
} from '@openmrs/esm-framework';
import { getValidationSchema } from './validation/patient-registration-validation';
-import { type FormValues, type CapturePhotoProps } from './patient-registration.types';
+import { type CapturePhotoProps, type FormValues } from './patient-registration.types';
import { PatientRegistrationContext } from './patient-registration-context';
import { type SavePatientForm, SavePatientTransactionManager } from './form-manager';
import { DummyDataInput } from './input/dummy-data/dummy-data-input.component';
@@ -132,16 +132,23 @@ export const PatientRegistration: React.FC = ({ savePa
}
};
+ const getDescription = (errors) => {
+ return (
+
+ {Object.keys(errors).map((error, index) => {
+ return {t(`${error}LabelText`, error)} ;
+ })}
+
+ );
+ };
+
const displayErrors = (errors) => {
if (errors && typeof errors === 'object' && !!Object.keys(errors).length) {
- Object.keys(errors).forEach((error) => {
- showSnackbar({
- subtitle: t(`${error}LabelText`, error),
- title: t('incompleteForm', 'The following field has errors:'),
- kind: 'warning',
- isLowContrast: true,
- timeoutInMs: 5000,
- });
+ showSnackbar({
+ isLowContrast: true,
+ kind: 'warning',
+ title: t('fieldsWithErrors', 'The following fields have errors:'),
+ subtitle: <>{getDescription(errors)}>,
});
}
};
@@ -205,6 +212,7 @@ export const PatientRegistration: React.FC = ({ savePa
values: props.values,
inEditMode,
setFieldValue: props.setFieldValue,
+ setFieldTouched: props.setFieldTouched,
setCapturePhotoProps,
currentPhoto: photo?.imageSrc,
isOffline,
diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.resource.ts b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.resource.ts
index a1c766054..b18b2f985 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.resource.ts
+++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.resource.ts
@@ -1,5 +1,6 @@
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import { type Patient, type Relationship, type PatientIdentifier, type Encounter } from './patient-registration.types';
+import dayjs from 'dayjs';
export const uuidIdentifier = '05a29f94-c0ed-11e2-94be-8c13b969e334';
export const uuidTelephoneNumber = '14d4f066-15f5-102d-96e4-000c29c2a5d7';
@@ -188,3 +189,10 @@ export async function deletePatientIdentifier(patientUuid: string, patientIdenti
signal: abortController.signal,
});
}
+
+export function getDatetime(date: Date | string, time: string, timeFormat: 'AM' | 'PM') {
+ const datetime = new Date(date);
+ const [hours, minutes] = time.split(':').map(Number);
+ const fullHours = timeFormat === 'PM' ? (hours % 12) + 12 : hours % 12;
+ return dayjs(datetime).hour(fullHours).minute(minutes).second(0).millisecond(0).toDate();
+}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx
index 3c376d752..f2c93fd80 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx
@@ -14,7 +14,7 @@ import {
import { mockedAddressTemplate } from '__mocks__';
import { mockPatient } from 'tools';
import { saveEncounter, savePatient } from './patient-registration.resource';
-import { type RegistrationConfig, esmPatientRegistrationSchema } from '../config-schema';
+import { esmPatientRegistrationSchema, type RegistrationConfig } from '../config-schema';
import type { AddressTemplate, Encounter } from './patient-registration.types';
import { ResourcesContext } from '../offline.resources';
import { FormManager } from './form-manager';
@@ -167,6 +167,9 @@ let mockOpenmrsConfig: RegistrationConfig = {
searchAddressByLevel: true,
},
},
+ causeOfDeath: {
+ conceptUuid: 'cause-of-death-concept-uuid',
+ },
},
links: {
submitButton: '#',
@@ -407,7 +410,7 @@ describe('Updating an existing patient record', () => {
const givenNameInput: HTMLInputElement = screen.getByLabelText(/First Name/);
const familyNameInput: HTMLInputElement = screen.getByLabelText(/Family Name/);
const middleNameInput: HTMLInputElement = screen.getByLabelText(/Middle Name/);
- const dateOfBirthInput: HTMLInputElement = screen.getByLabelText('Date of Birth');
+ const dateOfBirthInput: HTMLInputElement = screen.getByLabelText(/Date of Birth/i);
const genderInput: HTMLInputElement = screen.getByLabelText(/Male/);
// assert initial values
@@ -443,7 +446,10 @@ describe('Updating an existing patient record', () => {
birthdate: new Date('1972-04-04T00:00:00.000Z'),
birthdateEstimated: false,
deathCause: '',
- deathDate: '',
+ nonCodedCauseOfDeath: '',
+ deathDate: undefined,
+ deathTime: undefined,
+ deathTimeFormat: 'AM',
familyName: 'Smith',
gender: expect.stringMatching(/male/i),
givenName: 'Eric',
diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.types.ts b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.types.ts
index 35b6a4e2a..a185b16e0 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.types.ts
+++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.types.ts
@@ -167,7 +167,9 @@ export interface FormValues {
birthdate: Date | string;
birthdateEstimated: boolean;
deathCause: string;
- deathDate: string;
+ deathDate: string | Date;
+ deathTime: string;
+ deathTimeFormat: 'AM' | 'PM';
familyName: string;
gender: string;
givenName: string;
@@ -177,6 +179,7 @@ export interface FormValues {
isDead: boolean;
middleName: string;
monthsEstimated: number;
+ nonCodedCauseOfDeath: string;
obs?: {
[conceptUuid: string]: string;
};
diff --git a/packages/esm-patient-registration-app/src/patient-registration/section/death-info/death-info-section.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/section/death-info/death-info-section.component.tsx
index 6b33cf3b0..3deb60b82 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/section/death-info/death-info-section.component.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/section/death-info/death-info-section.component.tsx
@@ -1,30 +1,35 @@
-import React from 'react';
-import classNames from 'classnames';
+import React, { useContext } from 'react';
import { useTranslation } from 'react-i18next';
-import { Input } from '../../input/basic-input/input/input.component';
-import { SelectInput } from '../../input/basic-input/select/select-input.component';
+import { Checkbox, Layer } from '@carbon/react';
+import { useField } from 'formik';
+import { Field } from '../../field/field.component';
import { PatientRegistrationContext } from '../../patient-registration-context';
import styles from './../section.scss';
-export const DeathInfoSection = () => {
- const { values } = React.useContext(PatientRegistrationContext);
+export interface DeathInfoSectionProps {
+ fields: Array;
+}
+
+export const DeathInfoSection: React.FC = ({ fields }) => {
const { t } = useTranslation();
+ const { values, setFieldValue } = useContext(PatientRegistrationContext);
+ const [deathDate, deathDateMeta] = useField('deathDate');
+ const today = new Date();
return (
);
diff --git a/packages/esm-patient-registration-app/src/patient-registration/section/death-info/death-info-section.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/section/death-info/death-info-section.test.tsx
index dc7bb909d..08824c88a 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/section/death-info/death-info-section.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/section/death-info/death-info-section.test.tsx
@@ -1,10 +1,10 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
-import { Formik, Form } from 'formik';
+import { Form, Formik } from 'formik';
import { initialFormValues } from '../../patient-registration.component';
-import { DeathInfoSection } from './death-info-section.component';
import { type FormValues } from '../../patient-registration.types';
import { PatientRegistrationContext } from '../../patient-registration-context';
+import { DeathInfoSection } from './death-info-section.component';
const initialContextValues = {
currentPhoto: 'data:image/png;base64,1234567890',
@@ -29,7 +29,7 @@ describe('Death info section', () => {
-
+
,
@@ -40,16 +40,6 @@ describe('Death info section', () => {
renderDeathInfoSection(true);
expect(screen.getByRole('region', { name: /death info section/i })).toBeInTheDocument();
- expect(screen.getByRole('heading', { name: /death info/i })).toBeInTheDocument();
- expect(screen.getByRole('textbox', { name: /is dead \(optional\)/i })).toBeInTheDocument();
- expect(screen.getByRole('textbox', { name: /date of death \(optional\)/i })).toBeInTheDocument();
- expect(screen.getByRole('combobox', { name: /cause of death \(optional\)/i })).toBeInTheDocument();
- });
-
- it('has the correct number of inputs if is dead is not checked', async () => {
- renderDeathInfoSection(false);
-
- expect(screen.queryByRole('textbox', { name: /date of death \(optional\)/i })).not.toBeInTheDocument();
- expect(screen.queryByRole('combobox', { name: /cause of death \(optional\)/i })).not.toBeInTheDocument();
+ expect(screen.getByRole('checkbox', { name: /is dead/i })).toBeInTheDocument();
});
});
diff --git a/packages/esm-patient-registration-app/src/patient-registration/section/section.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/section/section.component.tsx
index 1f3429c67..5b8fec26a 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/section/section.component.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/section/section.component.tsx
@@ -14,7 +14,7 @@ export function Section({ sectionDefinition }: SectionProps) {
case 'demographics':
return ;
case 'death':
- return ;
+ return ;
case 'relationships':
return ;
default: // includes 'contact'
diff --git a/packages/esm-patient-registration-app/src/patient-registration/section/section.scss b/packages/esm-patient-registration-app/src/patient-registration/section/section.scss
index ee749ce6e..a1eb191c1 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/section/section.scss
+++ b/packages/esm-patient-registration-app/src/patient-registration/section/section.scss
@@ -1,3 +1,4 @@
+@use '@carbon/colors';
@use '@carbon/layout';
@use '@carbon/type';
@use '@openmrs/esm-styleguide/src/vars' as *;
@@ -14,3 +15,7 @@
color: $ui-04;
cursor: pointer;
}
+
+.isDeadFieldContainer {
+ margin-bottom: layout.$spacing-05;
+}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.test.ts
similarity index 98%
rename from packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.test.tsx
rename to packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.test.ts
index 699642d3c..f9c70c9b0 100644
--- a/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.test.tsx
+++ b/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.test.ts
@@ -35,6 +35,8 @@ describe('Patient registration validation', () => {
additionalGivenName: '',
birthdate: new Date('1990-01-01'),
birthdateEstimated: false,
+ isDead: false,
+ causeOfDeath: null,
deathDate: null,
email: 'john.doe@example.com',
familyName: 'Doe',
@@ -177,6 +179,6 @@ describe('Patient registration validation', () => {
deathDate: new Date('2100-01-01'),
};
const validationError = await validateFormValues(invalidFormValues);
- expect(validationError.errors).toContain('deathdayNotInTheFuture');
+ expect(validationError.errors).toContain('deathDateInFuture');
});
});
diff --git a/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.ts b/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.ts
new file mode 100644
index 000000000..ebb5fc835
--- /dev/null
+++ b/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.ts
@@ -0,0 +1,121 @@
+import dayjs from 'dayjs';
+import * as Yup from 'yup';
+import mapValues from 'lodash/mapValues';
+import { translateFrom } from '@openmrs/esm-framework';
+import { type RegistrationConfig } from '../../config-schema';
+import { type FormValues } from '../patient-registration.types';
+import { getDatetime } from '../patient-registration.resource';
+
+const t = (key: string, value: string) => translateFrom('@openmrs/esm-framework', key, value);
+
+export function getValidationSchema(config: RegistrationConfig) {
+ return Yup.object({
+ givenName: Yup.string().required(t('givenNameRequired', 'Given name is required')),
+ familyName: Yup.string().required(t('familyNameRequired', 'Family name is required')),
+ additionalGivenName: Yup.string().when('addNameInLocalLanguage', {
+ is: true,
+ then: Yup.string().required(t('givenNameRequired', 'Given name is required')),
+ otherwise: Yup.string().notRequired(),
+ }),
+ additionalFamilyName: Yup.string().when('addNameInLocalLanguage', {
+ is: true,
+ then: Yup.string().required(t('familyNameRequired', 'Family name is required')),
+ otherwise: Yup.string().notRequired(),
+ }),
+ gender: Yup.string()
+ .oneOf(
+ config.fieldConfigurations.gender.map((g) => g.value),
+ t('genderUnspecified', 'Gender unspecified'),
+ )
+ .required(t('genderRequired', 'Gender is required')),
+ birthdate: Yup.date().when('birthdateEstimated', {
+ is: false,
+ then: Yup.date()
+ .required(t('birthdayRequired', 'Birthday is required'))
+ .max(Date(), t('birthdayNotInTheFuture', 'Birthday cannot be in future'))
+ .nullable(),
+ otherwise: Yup.date().nullable(),
+ }),
+ yearsEstimated: Yup.number().when('birthdateEstimated', {
+ is: true,
+ then: Yup.number()
+ .required(t('yearsEstimateRequired', 'Estimated years required'))
+ .min(0, t('negativeYears', 'Estimated years cannot be negative')),
+ otherwise: Yup.number().nullable(),
+ }),
+ monthsEstimated: Yup.number().min(0, t('negativeMonths', 'Estimated months cannot be negative')),
+ isDead: Yup.boolean(),
+ deathDate: Yup.date()
+ .when('isDead', {
+ is: true,
+ then: Yup.date().required(t('deathDateRequired', 'Death date is required')),
+ otherwise: Yup.date().nullable(),
+ })
+ .max(new Date(), 'deathDateInFuture')
+ .test(
+ 'deathDate-after-birthdate',
+ t('deathdayInvalidDate', 'Death date and time cannot be before the birthday'),
+ function (value) {
+ const { birthdate } = this.parent;
+ if (birthdate && value) {
+ return dayjs(value).isAfter(birthdate);
+ }
+ return true;
+ },
+ )
+ .test('deathDate-before-today', t('deathDateInFuture', 'Death date cannot be in future'), function (value) {
+ const { deathTime, deathTimeFormat } = this.parent;
+ if (value && deathTime && deathTimeFormat && /^(1[0-2]|0?[1-9]):([0-5]?[0-9])$/.test(deathTime)) {
+ return dayjs(getDatetime(value, deathTime, deathTimeFormat)).isBefore(dayjs());
+ }
+ return true;
+ }),
+ deathTime: Yup.string()
+ .when('isDead', {
+ is: true,
+ then: Yup.string().required(t('deathTimeRequired', 'Death time is required')),
+ otherwise: Yup.string().nullable(),
+ })
+ .matches(/^(1[0-2]|0?[1-9]):([0-5]?[0-9])$/, t('deathTimeInvalid', "Time doesn't match the format 'hh:mm'")),
+
+ deathTimeFormat: Yup.string()
+ .when('isDead', {
+ is: true,
+ then: Yup.string().required(t('deathTimeFormatRequired', 'Time format is required')),
+ otherwise: Yup.string().nullable(),
+ })
+ .oneOf(['AM', 'PM'], t('deathTimeFormatInvalid', 'Time format is invalid')),
+
+ deathCause: Yup.string().when('isDead', {
+ is: true,
+ then: Yup.string().required(t('deathCauseRequired', 'Cause of death is required')),
+ otherwise: Yup.string().nullable(),
+ }),
+ nonCodedCauseOfDeath: Yup.string().when(['isDead', 'deathCause'], {
+ is: (isDead, deathCause) => isDead && deathCause === config.freeTextFieldConceptUuid,
+ then: Yup.string().required(t('nonCodedCauseOfDeathRequired', 'Cause of death is required')),
+ otherwise: Yup.string().nullable(),
+ }),
+ email: Yup.string().optional().email(t('invalidEmail', 'Invalid email')),
+ identifiers: Yup.lazy((obj: FormValues['identifiers']) =>
+ Yup.object(
+ mapValues(obj, () =>
+ Yup.object({
+ required: Yup.bool(),
+ identifierValue: Yup.string().when('required', {
+ is: true,
+ then: Yup.string().required(t('identifierValueRequired', 'Identifier value is required')),
+ otherwise: Yup.string().notRequired(),
+ }),
+ }),
+ ),
+ ),
+ ),
+ relationships: Yup.array().of(
+ Yup.object().shape({
+ relatedPersonUuid: Yup.string().required(),
+ relationshipType: Yup.string().required(),
+ }),
+ ),
+ });
+}
diff --git a/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.tsx b/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.tsx
deleted file mode 100644
index adba6a859..000000000
--- a/packages/esm-patient-registration-app/src/patient-registration/validation/patient-registration-validation.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import * as Yup from 'yup';
-import mapValues from 'lodash/mapValues';
-import { type FormValues } from '../patient-registration.types';
-import { type RegistrationConfig } from '../../config-schema';
-
-export function getValidationSchema(config: RegistrationConfig) {
- return Yup.object({
- givenName: Yup.string().required('givenNameRequired'),
- familyName: Yup.string().required('familyNameRequired'),
- additionalGivenName: Yup.string().when('addNameInLocalLanguage', {
- is: true,
- then: Yup.string().required('givenNameRequired'),
- otherwise: Yup.string().notRequired(),
- }),
- additionalFamilyName: Yup.string().when('addNameInLocalLanguage', {
- is: true,
- then: Yup.string().required('familyNameRequired'),
- otherwise: Yup.string().notRequired(),
- }),
- gender: Yup.string()
- .oneOf(
- config.fieldConfigurations.gender.map((g) => g.value),
- 'genderUnspecified',
- )
- .required('genderRequired'),
- birthdate: Yup.date().when('birthdateEstimated', {
- is: false,
- then: Yup.date().required('birthdayRequired').max(Date(), 'birthdayNotInTheFuture').nullable(),
- otherwise: Yup.date().nullable(),
- }),
- yearsEstimated: Yup.number().when('birthdateEstimated', {
- is: true,
- then: Yup.number().required('yearsEstimateRequired').min(0, 'negativeYears'),
- otherwise: Yup.number().nullable(),
- }),
- monthsEstimated: Yup.number().min(0, 'negativeMonths'),
- deathDate: Yup.date().max(Date(), 'deathdayNotInTheFuture').nullable(),
- email: Yup.string().optional().email('invalidEmail'),
- identifiers: Yup.lazy((obj: FormValues['identifiers']) =>
- Yup.object(
- mapValues(obj, () =>
- Yup.object({
- required: Yup.bool(),
- identifierValue: Yup.string().when('required', {
- is: true,
- then: Yup.string().required('identifierValueRequired'),
- otherwise: Yup.string().notRequired(),
- }),
- }),
- ),
- ),
- ),
- relationships: Yup.array().of(
- Yup.object().shape({
- relatedPersonUuid: Yup.string().required(),
- relationshipType: Yup.string().required(),
- }),
- ),
- });
-}
diff --git a/packages/esm-patient-registration-app/src/routes.json b/packages/esm-patient-registration-app/src/routes.json
index 3910bdbf1..8e55b60df 100644
--- a/packages/esm-patient-registration-app/src/routes.json
+++ b/packages/esm-patient-registration-app/src/routes.json
@@ -25,12 +25,6 @@
"online": true,
"offline": true
},
- {
- "component": "cancelPatientEditModal",
- "name": "cancel-patient-edit-modal",
- "online": true,
- "offline": true
- },
{
"component": "patientPhotoExtension",
"name": "patient-photo-widget",
@@ -58,5 +52,11 @@
"online": true,
"offline": true
}
+ ],
+ "modals": [
+ {
+ "name": "cancel-patient-edit-modal",
+ "component": "cancelPatientEditModal"
+ }
]
}
diff --git a/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.component.tsx b/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.component.tsx
deleted file mode 100644
index e7d1b86b9..000000000
--- a/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.component.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import { Button } from '@carbon/react';
-import { useTranslation } from 'react-i18next';
-
-interface CancelPatientEditProps {
- close(): void;
- onConfirm(): void;
-}
-
-const CancelPatientEdit: React.FC = ({ close, onConfirm }) => {
- const { t } = useTranslation();
- return (
- <>
-
-
{t('discardModalHeader', 'Confirm Discard Changes')}
-
-
-
- {t(
- 'discardModalBody',
- "The changes you made to this patient's details have not been saved. Discard changes?",
- )}
-
-
-
-
- {t('cancel', 'Cancel')}
-
-
- {t('discard', 'Discard')}
-
-
- >
- );
-};
-
-export default CancelPatientEdit;
diff --git a/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.modal.tsx b/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.modal.tsx
new file mode 100644
index 000000000..26ffd7b59
--- /dev/null
+++ b/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.modal.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
+
+interface CancelPatientEditPropsModal {
+ close(): void;
+ onConfirm(): void;
+}
+
+const CancelPatientEditModal: React.FC = ({ close, onConfirm }) => {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+ {t('confirmDiscardChangesBody', 'Your unsaved changes will be lost if you proceed to discard the form')}.
+
+
+
+ {t('cancel', 'Cancel')}
+
+
+ {t('discard', 'Discard')}
+
+
+ >
+ );
+};
+
+export default CancelPatientEditModal;
diff --git a/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.test.tsx b/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.test.tsx
index e454fd16d..40380a3ca 100644
--- a/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.test.tsx
+++ b/packages/esm-patient-registration-app/src/widgets/cancel-patient-edit.test.tsx
@@ -1,15 +1,14 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { screen, render } from '@testing-library/react';
-import CancelPatientEdit from './cancel-patient-edit.component';
+import CancelPatientEdit from './cancel-patient-edit.modal';
-describe('CancelPatientEdit component', () => {
+describe('CancelPatientEdit modal', () => {
const mockClose = jest.fn();
const mockOnConfirm = jest.fn();
it('renders the modal and triggers close and onConfirm functions', async () => {
const user = userEvent.setup();
-
render( );
const cancelButton = screen.getByRole('button', { name: /Cancel/i });
diff --git a/packages/esm-patient-registration-app/translations/en.json b/packages/esm-patient-registration-app/translations/en.json
index ed4e4dfc4..9a7e5b919 100644
--- a/packages/esm-patient-registration-app/translations/en.json
+++ b/packages/esm-patient-registration-app/translations/en.json
@@ -3,69 +3,83 @@
"addressHeader": "Address",
"allFieldsRequiredText": "All fields are required unless marked optional",
"autoGeneratedPlaceholderText": "Auto-generated",
- "birthdayNotInTheFuture": "Birthday cannot be in the future",
+ "birthdayNotInTheFuture": "Birthday cannot be in future",
"birthdayRequired": "Birthday is required",
"birthFieldLabelText": "Birth",
"cancel": "Cancel",
- "causeOfDeathInputLabel": "Cause of Death",
+ "causeOfDeathInputLabel": "Cause of death",
"closeOverlay": "Close overlay",
"codedPersonAttributeAnswerSetEmpty": "The coded person attribute field '{{codedPersonAttributeFieldId}}' has been defined with an answer concept set UUID '{{answerConceptSetUuid}}' that does not have any concept answers.",
"codedPersonAttributeAnswerSetInvalid": "The coded person attribute field '{{codedPersonAttributeFieldId}}' has been defined with an invalid answer concept set UUID '{{answerConceptSetUuid}}'.",
"codedPersonAttributeNoAnswerSet": "The person attribute field '{{codedPersonAttributeFieldId}}' is of type 'coded' but has been defined without an answer concept set UUID. The 'answerConceptSetUuid' key is required.",
"configure": "Configure",
"configureIdentifiers": "Configure identifiers",
+ "confirmDiscardChangesBody": "Your unsaved changes will be lost if you proceed to discard the form",
+ "confirmDiscardChangesTitle": "Are you sure you want to discard these changes?",
"confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?",
"contactSection": "Contact Details",
"createNewPatient": "Create new patient",
- "dateOfBirthLabelText": "Date of Birth",
- "deathDateInputLabel": "Date of Death",
- "deathdayNotInTheFuture": "Death day cannot be in the future",
+ "dateOfBirthLabelText": "Date of birth",
+ "deathCauseRequired": "Cause of death is required",
+ "deathDateInFuture": "Death date cannot be in future",
+ "deathDateInputLabel": "Date of death",
+ "deathDateRequired": "Death date is required",
+ "deathdayInvalidDate": "Death date and time cannot be before the birthday",
+ "deathdayIsRequired": "Death date is required when the patient is marked as deceased.",
+ "deathdayNotInTheFuture": "",
"deathSection": "Death Info",
+ "deathTimeFormatInvalid": "Time format is invalid",
+ "deathTimeFormatRequired": "Time format is required",
+ "deathTimeInvalid": "Time doesn't match the format 'hh:mm'",
+ "deathTimeRequired": "Death time is required",
"deleteIdentifierModalHeading": "Remove identifier?",
"deleteIdentifierModalText": " has a value of ",
"deleteIdentifierTooltip": "Delete",
"deleteRelationshipTooltipText": "Delete",
"demographicsSection": "Basic Info",
"discard": "Discard",
- "discardModalBody": "The changes you made to this patient's details have not been saved. Discard changes?",
- "discardModalHeader": "Confirm Discard Changes",
"dobToggleLabelText": "Date of Birth Known?",
"editIdentifierTooltip": "Edit",
"editPatientDetails": "Edit patient details",
"editPatientDetailsBreadcrumb": "Edit patient details",
+ "enterNonCodedCauseOfDeath": "Enter non-coded cause of death",
"error": "Error",
+ "errorFetchingCodedCausesOfDeath": "Error fetching coded causes of death",
"errorFetchingOrderedFields": "Error occured fetching ordered fields for address hierarchy",
"estimatedAgeInMonthsLabelText": "Estimated age in months",
"estimatedAgeInYearsLabelText": "Estimated age in years",
"familyNameLabelText": "Family Name",
"familyNameRequired": "Family name is required",
"female": "Female",
+ "fieldsWithErrors": "The following fields have errors: ",
"fullNameLabelText": "Full Name",
"genderLabelText": "Sex",
"genderRequired": "Gender is required",
- "genderUnspecified": "Gender is not specified",
+ "genderUnspecified": "Gender unspecified",
"givenNameLabelText": "First Name",
"givenNameRequired": "Given name is required",
"identifierValueRequired": "Identifier value is required",
"idFieldLabelText": "Identifiers",
"IDInstructions": "Select the identifiers you'd like to add for this patient:",
- "incompleteForm": "Incomplete form",
- "invalidEmail": "A valid email has to be given",
+ "invalidEmail": "Invalid email",
"invalidInput": "Invalid Input",
- "isDeadInputLabel": "Is Dead",
+ "isDeadInputLabel": "Is dead",
"jumpTo": "Jump to",
"male": "Male",
"middleNameLabelText": "Middle Name",
- "negativeMonths": "Negative months",
- "negativeYears": "Negative years",
+ "negativeMonths": "Estimated months cannot be negative",
+ "negativeYears": "Estimated years cannot be negative",
"no": "No",
+ "nonCodedCauseOfDeath": "Non-coded cause of death",
+ "nonCodedCauseOfDeathRequired": "Cause of death is required",
"numberInNameDubious": "Number in name is dubious",
"obsFieldUnknownDatatype": "Concept for obs field '{{fieldDefinitionId}}' has unknown datatype '{{datatypeName}}'",
"optional": "optional",
"other": "Other",
"patientNameKnown": "Patient's Name is Known?",
"patientRegistrationBreadcrumb": "Patient Registration",
- "registerPatient": "Register Patient",
+ "refreshOrContactAdmin": "Try refreshing the page or contact your system administrator",
+ "registerPatient": "Register patient",
"registerPatientSuccessSnackbarSubtitle": "The patient can now be found by searching for them using their name or ID number",
"registerPatientSuccessSnackbarTitle": "New Patient Created",
"registrationErrorSnackbarTitle": "Patient Registration Failed",
@@ -75,7 +89,7 @@
"relationshipRemovedText": "Relationship removed",
"relationshipsSection": "Relationships",
"relationshipToPatient": "Relationship to patient",
- "relativeFullNameLabelText": "Related person",
+ "relativeFullNameLabelText": "Full name",
"relativeNamePlaceholder": "Firstname Familyname",
"removeIdentifierButton": "Remove Identifier",
"resetIdentifierTooltip": "Reset",
@@ -85,15 +99,16 @@
"selectAnOption": "Select an option",
"sexFieldLabelText": "Sex",
"source": "Source",
- "stroke": "Stroke",
"submitting": "Submitting",
- "unableToFetch": "Unable to fetch person attribute type {{personattributetype}}",
+ "timeFormat": "Time Format",
+ "timeOfDeathInputLabel": "Time of death (hh:mm)",
+ "unableToFetch": "Unable to fetch person attribute type - {{personattributetype}}",
"unknown": "Unknown",
"unknownPatientAttributeType": "Patient attribute type has unknown format {{personAttributeTypeFormat}}",
- "updatePatient": "Update Patient",
+ "updatePatient": "Update patient",
"updatePatientErrorSnackbarTitle": "Patient Details Update Failed",
"updatePatientSuccessSnackbarSubtitle": "The patient's information has been successfully updated",
"updatePatientSuccessSnackbarTitle": "Patient Details Updated",
- "yearsEstimateRequired": "Years estimate required",
+ "yearsEstimateRequired": "Estimated years required",
"yes": "Yes"
}
diff --git a/packages/esm-ward-app/.fetch.swp b/packages/esm-ward-app/.fetch.swp
new file mode 100644
index 000000000..9a648bb5e
Binary files /dev/null and b/packages/esm-ward-app/.fetch.swp differ
diff --git a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx
index ee1a2e1d3..93c5274f8 100644
--- a/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx
+++ b/packages/esm-ward-app/src/ward-patient-card/row-elements/ward-patient-coded-obs-tags.tsx
@@ -39,7 +39,7 @@ const WardPatientCodedObsTags: React.FC = ({ confi
} else {
const obsToDisplay = data?.filter((o) => {
const matchVisit = o.encounter.visit?.uuid == visit?.uuid;
- return matchVisit || visit == null; // TODO: remove visit == null hack when server API supports returning visit
+ return matchVisit;
});
const summaryLabelToDisplay = summaryLabel != null ? t(summaryLabel) : obsToDisplay?.[0]?.concept?.display;
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 bfbbfc088..ef6ff8f86 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
@@ -8,6 +8,7 @@ import { useAdmissionLocation } from '../hooks/useAdmissionLocation';
import { mockAdmissionLocation, mockInpatientAdmissions } from '__mocks__';
import { useInpatientAdmission } from '../hooks/useInpatientAdmission';
import useWardLocation from '../hooks/useWardLocation';
+import { screen } from '@testing-library/react';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
@@ -68,9 +69,9 @@ describe('Ward Metrics', () => {
invalidLocation: true,
});
const bedMetrics = getWardMetrics(mockWardBeds);
- const { getByText } = renderWithSwr( );
+ renderWithSwr( );
for (let [key, value] of Object.entries(bedMetrics)) {
- expect(getByText(value)).toBeInTheDocument();
+ expect(screen.getByText(value)).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 525014c92..3bc67ced8 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
@@ -1,23 +1,20 @@
-import React from 'react';
import { InlineNotification } from '@carbon/react';
import {
- ExtensionSlot,
- openmrsFetch,
useAppContext,
useDefineAppContext,
- WorkspaceContainer,
+ WorkspaceContainer
} from '@openmrs/esm-framework';
+import React, { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import EmptyBedSkeleton from '../beds/empty-bed-skeleton';
import UnassignedPatient from '../beds/unassigned-patient.component';
import useWardLocation from '../hooks/useWardLocation';
-import { type WardPatientGroupDetails, type WardPatient } from '../types';
+import { useWardPatientGrouping } from '../hooks/useWardPatientGrouping';
+import { type WardPatient, type WardPatientGroupDetails } from '../types';
import WardViewHeader from '../ward-view-header/ward-view-header.component';
import WardBed from './ward-bed.component';
import { bedLayoutToBed } from './ward-view.resource';
import styles from './ward-view.scss';
-import { useWardPatientGrouping } from '../hooks/useWardPatientGrouping';
-import useSWR from 'swr';
const WardView = () => {
const response = useWardLocation();
@@ -46,22 +43,54 @@ const WardView = () => {
const WardViewMain = () => {
const { location } = useWardLocation();
+ const { t } = useTranslation();
const wardPatientsGrouping = useAppContext('ward-patients-group');
const { bedLayouts, wardAdmittedPatientsWithBed, wardUnassignedPatientsList } = wardPatientsGrouping ?? {};
const { isLoading: isLoadingAdmissionLocation, error: errorLoadingAdmissionLocation } =
wardPatientsGrouping?.admissionLocationResponse ?? {};
- const { isLoading: isLoadingInpatientAdmissions, error: errorLoadingInpatientAdmissions } =
- wardPatientsGrouping?.inpatientAdmissionResponse ?? {};
+ const {
+ isLoading: isLoadingInpatientAdmissions,
+ error: errorLoadingInpatientAdmissions,
+ hasMore: hasMoreInpatientAdmissions,
+ loadMore: loadMoreInpatientAdmissions
+ } = wardPatientsGrouping?.inpatientAdmissionResponse ?? {};
- const { t } = useTranslation();
+ const scrollToLoadMoreTrigger = useRef(null);
+ useEffect(
+ function scrollToLoadMore() {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ if (hasMoreInpatientAdmissions && !errorLoadingInpatientAdmissions && !isLoadingInpatientAdmissions) {
+ loadMoreInpatientAdmissions();
+ }
+ }
+ });
+ },
+ { threshold: 1 },
+ );
+
+ if (scrollToLoadMoreTrigger.current) {
+ observer.observe(scrollToLoadMoreTrigger.current);
+ }
+ return () => {
+ if (scrollToLoadMoreTrigger.current) {
+ observer.unobserve(scrollToLoadMoreTrigger.current);
+ }
+ };
+ },
+ [scrollToLoadMoreTrigger, hasMoreInpatientAdmissions, errorLoadingInpatientAdmissions, loadMoreInpatientAdmissions],
+ );
if (!wardPatientsGrouping) return <>>;
+
const wardBeds = bedLayouts?.map((bedLayout) => {
const { patients } = bedLayout;
const bed = bedLayoutToBed(bedLayout);
const wardPatients: WardPatient[] = patients.map((patient): WardPatient => {
- const inpatientAdmission = wardAdmittedPatientsWithBed?.get(patient.uuid);
+ const inpatientAdmission = wardAdmittedPatientsWithBed.get(patient.uuid);
if (inpatientAdmission) {
const { patient, visit, currentInpatientRequest } = inpatientAdmission;
return { patient, visit, bed, inpatientAdmission, inpatientRequest: currentInpatientRequest || null };
@@ -125,6 +154,7 @@ const WardViewMain = () => {
subtitle={errorLoadingInpatientAdmissions?.message}
/>
)}
+
);
};
diff --git a/packages/esm-ward-app/src/ward-view/ward-view.resource.ts b/packages/esm-ward-app/src/ward-view/ward-view.resource.ts
index 7b9bd7b09..b7c39ed8e 100644
--- a/packages/esm-ward-app/src/ward-view/ward-view.resource.ts
+++ b/packages/esm-ward-app/src/ward-view/ward-view.resource.ts
@@ -31,7 +31,7 @@ export function getWardMetrics(beds: Bed[]): WardMetrics {
freeBeds: '--',
capacity: '--',
};
- if (!beds?.length) return bedMetrics;
+ if (beds == null || beds.length == 0) return bedMetrics;
const total = beds.length;
const occupiedBeds = beds.filter((bed) => bed.status === 'OCCUPIED');
const patients = occupiedBeds.length;
diff --git a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx
index ed7410683..0a239a1ba 100644
--- a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx
+++ b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card-header.component.tsx
@@ -1,12 +1,11 @@
import { ExtensionSlot, formatDatetime, getLocale } from '@openmrs/esm-framework';
import classNames from 'classnames';
import React from 'react';
-import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component';
-import styles from './admission-request-card.scss';
-import type WardPatientCard from '../../ward-patient-card/ward-patient-card.component';
import { useCurrentWardCardConfig } from '../../hooks/useCurrentWardCardConfig';
-import { WardPatientCardElement } from '../../ward-patient-card/ward-patient-card-element.component';
import WardPatientName from '../../ward-patient-card/row-elements/ward-patient-name';
+import { WardPatientCardElement } from '../../ward-patient-card/ward-patient-card-element.component';
+import type WardPatientCard from '../../ward-patient-card/ward-patient-card.component';
+import styles from './admission-request-card.scss';
const AdmissionRequestCardHeader: WardPatientCard = (wardPatient) => {
const { inpatientRequest } = wardPatient;
@@ -42,7 +41,7 @@ const AdmissionRequestCardHeader: WardPatientCard = (wardPatient) => {
);
diff --git a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.scss b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.scss
index 067cf4be9..b7496b63c 100644
--- a/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.scss
+++ b/packages/esm-ward-app/src/ward-workspace/admission-request-card/admission-request-card.scss
@@ -25,10 +25,14 @@
}
}
-.admissionRequestCardRow {
- padding: layout.$spacing-05;
- margin: layout.$spacing-03;
- background-color: white;
+.admissionRequestCardExtensionSlot {
+ display: none;
+
+ &:has(div:not(:empty)) {
+ display: block;
+ margin: layout.$spacing-03 0;
+ background-color: white;
+ }
}
.admissionEncounterDetails {
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 f8e767ff3..959017213 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
@@ -5,6 +5,7 @@ import { renderWithSwr } from '../../../../../tools';
import AdmitPatientFormWorkspace from './admit-patient-form.workspace';
import {
mockAdmissionLocation,
+ mockInpatientAdmissions,
mockInpatientRequest,
mockLocationInpatientWard,
mockPatientAlice,
@@ -12,10 +13,13 @@ import {
import type { DispositionType } from '../../types';
import type { AdmitPatientFormWorkspaceProps } from './types';
import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
-import { openmrsFetch, provide, showSnackbar, useFeatureFlag, useSession } from '@openmrs/esm-framework';
+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';
jest.mock('../../hooks/useAdmissionLocation', () => ({
useAdmissionLocation: jest.fn(),
@@ -29,14 +33,43 @@ jest.mock('../../hooks/useInpatientRequest', () => ({
useInpatientRequest: jest.fn(),
}));
+jest.mock('../../hooks/useWardPatientGrouping', () => ({
+ useWardPatientGrouping: jest.fn(),
+}));
+
+jest.mock('../../hooks/useInpatientAdmission', () => ({
+ useInpatientAdmission: jest.fn(),
+}));
+
+const inpatientAdmissionsUuidMap = getInpatientAdmissionsUuidMap(mockInpatientAdmissions);
+
const mockedUseInpatientRequest = jest.mocked(useInpatientRequest);
const mockedUseEmrConfiguration = jest.mocked(useEmrConfiguration);
const mockedUseWardLocation = jest.mocked(useWardLocation);
const mockedOpenmrsFetch = jest.mocked(openmrsFetch);
-const mockedUseAdmissionLocation = jest.mocked(useAdmissionLocation);
+const mockedUseAdmissionLocation = 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 mockInpatientAdmissionResponse = jest.mocked(useInpatientAdmission).mockReturnValue({
+ error: undefined,
+ mutate: jest.fn(),
+ isValidating: false,
+ isLoading: false,
+ inpatientAdmissions: mockInpatientAdmissions,
+});
+const mockWardPatientGroupDetails = jest.mocked(useWardPatientGrouping).mockReturnValue({
+ admissionLocationResponse: mockedUseAdmissionLocation(),
+ inpatientAdmissionResponse: mockInpatientAdmissionResponse(),
+ ...createAndGetWardPatientGrouping(mockInpatientAdmissions, mockAdmissionLocation, inpatientAdmissionsUuidMap),
+});
+jest.mocked(useAppContext).mockReturnValue(mockWardPatientGroupDetails());
const mockWorkspaceProps: AdmitPatientFormWorkspaceProps = {
patient: mockPatientAlice,
@@ -56,13 +89,7 @@ const mockedMutateInpatientRequest = jest.fn();
describe('Testing AdmitPatientForm', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockedUseAdmissionLocation.mockReturnValue({
- isLoading: false,
- isValidating: false,
- admissionLocation: mockAdmissionLocation,
- mutate: jest.fn(),
- error: undefined,
- });
+
mockedUseSession.mockReturnValue({
currentProvider: {
uuid: 'current-provider-uuid',
@@ -157,30 +184,15 @@ describe('Testing AdmitPatientForm', () => {
it('should render admit patient form if bed management module is present, but no beds are configured', () => {
mockedUseFeatureFlag.mockReturnValue(true);
- mockedUseAdmissionLocation.mockReturnValueOnce({
- isLoading: false,
- isValidating: false,
- admissionLocation: {
- ...mockAdmissionLocation,
- totalBeds: 0,
- bedLayouts: [],
- },
- mutate: jest.fn(),
- error: null,
- });
+ const replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
+ // @ts-i
renderAdmissionForm();
expect(screen.getByText('Select a bed')).toBeInTheDocument();
expect(screen.getByText('No beds configured for Inpatient Ward location')).toBeInTheDocument();
+ replacedProperty.restore();
});
it('should submit the form, create encounter and submit bed', async () => {
- mockedUseAdmissionLocation.mockReturnValueOnce({
- isLoading: false,
- isValidating: false,
- admissionLocation: mockAdmissionLocation,
- mutate: jest.fn(),
- error: null,
- });
// @ts-ignore - we only need these two keys for now
mockedOpenmrsFetch.mockResolvedValue({
ok: true,
@@ -290,17 +302,7 @@ describe('Testing AdmitPatientForm', () => {
});
it('should admit patient if no beds are configured', async () => {
- mockedUseAdmissionLocation.mockReturnValueOnce({
- isLoading: false,
- isValidating: false,
- admissionLocation: {
- ...mockAdmissionLocation,
- totalBeds: 0,
- bedLayouts: [],
- },
- mutate: jest.fn(),
- error: null,
- });
+ const replacedProperty = jest.replaceProperty(mockWardPatientGroupDetails(), 'bedLayouts', []);
// @ts-ignore - we only need these two keys for now
mockedOpenmrsFetch.mockResolvedValue({
ok: true,
@@ -337,5 +339,6 @@ describe('Testing AdmitPatientForm', () => {
subtitle: 'Patient admitted successfully to Inpatient Ward',
title: 'Patient admitted successfully',
});
+ replacedProperty.restore();
});
});
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 cc8d4845a..5799d0bde 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
@@ -4,11 +4,10 @@ import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useTranslation } from 'react-i18next';
import { Button, ButtonSet, Column, Dropdown, DropdownSkeleton, Form, InlineNotification, Row } from '@carbon/react';
-import { showSnackbar, useFeatureFlag, useSession } from '@openmrs/esm-framework';
+import { showSnackbar, useAppContext, useFeatureFlag, useSession } from '@openmrs/esm-framework';
import { filterBeds } from '../../ward-view/ward-view.resource';
-import type { BedLayout } from '../../types';
+import type { BedLayout, WardPatientGroupDetails } from '../../types';
import { assignPatientToBed, createEncounter } from '../../ward.resource';
-import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
import { useInpatientRequest } from '../../hooks/useInpatientRequest';
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
import useWardLocation from '../../hooks/useWardLocation';
@@ -29,8 +28,9 @@ const AdmitPatientFormWorkspace: React.FC
= ({
const { mutate: mutateInpatientRequest } = useInpatientRequest();
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
const [showErrorNotifications, setShowErrorNotifications] = useState(false);
- const { isLoading, admissionLocation, mutate: mutateAdmissionLocation } = useAdmissionLocation();
- const beds = useMemo(() => (isLoading ? [] : filterBeds(admissionLocation)), [admissionLocation]);
+ const wardPatientGrouping = useAppContext('ward-patients-group');
+ const { isLoading, mutate: mutateAdmissionLocation } = wardPatientGrouping?.admissionLocationResponse ?? {};
+ const beds = isLoading ? [] : wardPatientGrouping?.bedLayouts ?? [];
const isBedManagementModuleInstalled = useFeatureFlag('bedmanagement-module');
const getBedRepresentation = useCallback((bedLayout: BedLayout) => {
const bedNumber = bedLayout.bedNumber;
@@ -175,6 +175,7 @@ const AdmitPatientFormWorkspace: React.FC = ({
setIsSubmitting(false);
}, []);
+ if (!wardPatientGrouping) return <>>;
return (
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 9643e2370..23e68198f 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,14 +1,13 @@
import React, { useCallback, useState } from 'react';
-import { ExtensionSlot, showSnackbar, useSession } from '@openmrs/esm-framework';
+import { ExtensionSlot, showSnackbar, useAppContext, useSession } from '@openmrs/esm-framework';
import { Button, ButtonSet, InlineNotification } from '@carbon/react';
import { useTranslation } from 'react-i18next';
import styles from './patient-discharge.scss';
import WardPatientWorkspaceBanner from '../patient-banner/patient-banner.component';
-import type { WardPatientWorkspaceProps } from '../../types';
+import {type WardPatientGroupDetails, type WardPatientWorkspaceProps } from '../../types';
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
import { createEncounter, removePatientFromBed } from '../../ward.resource';
import useWardLocation from '../../hooks/useWardLocation';
-import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
import { useInpatientRequest } from '../../hooks/useInpatientRequest';
import { Exit } from '@carbon/react/icons';
@@ -19,7 +18,8 @@ export default function PatientDischargeWorkspace(props: WardPatientWorkspacePro
const { currentProvider } = useSession();
const { location } = useWardLocation();
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
- const { mutate: mutateAdmissionLocation } = useAdmissionLocation();
+ const wardGroupingDetails = useAppContext
('ward-patients-group');
+ const { mutate: mutateAdmissionLocation } = wardGroupingDetails?.admissionLocationResponse ?? {};
const { mutate: mutateInpatientRequest } = useInpatientRequest();
const submitDischarge = useCallback(() => {
@@ -74,6 +74,7 @@ export default function PatientDischargeWorkspace(props: WardPatientWorkspacePro
mutateInpatientRequest,
]);
+ if (!wardGroupingDetails) return <>>;
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 728c84813..9e387c015 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,15 +1,14 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styles from './patient-transfer-swap.scss';
-import { useAdmissionLocation } from '../../hooks/useAdmissionLocation';
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, WardPatientWorkspaceProps } from '../../types';
+import type { BedLayout, WardPatientGroupDetails, WardPatientWorkspaceProps } from '../../types';
import { assignPatientToBed, createEncounter } from '../../ward.resource';
import useEmrConfiguration from '../../hooks/useEmrConfiguration';
-import { showSnackbar, useSession } from '@openmrs/esm-framework';
+import { showSnackbar, useAppContext, useSession } from '@openmrs/esm-framework';
import useWardLocation from '../../hooks/useWardLocation';
import { useInpatientRequest } from '../../hooks/useInpatientRequest';
import {
@@ -31,12 +30,12 @@ export default function PatientBedSwapForm({
const { patient } = wardPatient;
const { t } = useTranslation();
const [showErrorNotifications, setShowErrorNotifications] = useState(false);
- const { isLoading, admissionLocation } = useAdmissionLocation();
const { emrConfiguration, isLoadingEmrConfiguration, errorFetchingEmrConfiguration } = useEmrConfiguration();
const [isSubmitting, setIsSubmitting] = useState(false);
const { currentProvider } = useSession();
const { location } = useWardLocation();
- const { mutate: mutateAdmissionLocation } = useAdmissionLocation();
+ const wardGroupingDetails = useAppContext('ward-patients-group');
+ const { isLoading, mutate: mutateAdmissionLocation } = wardGroupingDetails?.admissionLocationResponse ?? {};
const { mutate: mutateInpatientRequest } = useInpatientRequest();
const zodSchema = useMemo(
@@ -72,7 +71,7 @@ export default function PatientBedSwapForm({
[t],
);
- const beds = useMemo(() => (admissionLocation ? filterBeds(admissionLocation) : []), [admissionLocation]);
+ const beds = wardGroupingDetails?.bedLayouts ?? [];
const bedDetails = useMemo(
() =>
beds.map((bed) => {
@@ -148,6 +147,7 @@ export default function PatientBedSwapForm({
setShowErrorNotifications(true);
}, []);
+ if (!wardGroupingDetails) return <>>;
return (
emrConfiguration?.dispositions.filter(({ type }) => type === 'TRANSFER'),
[emrConfiguration],
);
- const { mutate: mutateAdmissionLocation } = useAdmissionLocation();
+ const wardGroupingDetails = useAppContext('ward-patients-group');
+ const { mutate: mutateAdmissionLocation } = wardGroupingDetails?.admissionLocationResponse ?? {};
const { mutate: mutateInpatientRequest } = useInpatientRequest();
const zodSchema = useMemo(
@@ -154,6 +154,7 @@ export default function PatientTransferForm({
setShowErrorNotifications(true);
}, []);
+ if (!wardGroupingDetails) return <>>;
return (
=0.6.2 <2.0.0, source-map-js@npm:^1.0.2":
- version: 1.0.2
- resolution: "source-map-js@npm:1.0.2"
- checksum: 10/38e2d2dd18d2e331522001fc51b54127ef4a5d473f53b1349c5cca2123562400e0986648b52e9407e348eaaed53bce49248b6e2641e6d793ca57cb2c360d6d51
- languageName: node
- linkType: hard
-
-"source-map-js@npm:^1.2.0":
+"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.2.0":
version: 1.2.0
resolution: "source-map-js@npm:1.2.0"
checksum: 10/74f331cfd2d121c50790c8dd6d3c9de6be21926de80583b23b37029b0f37aefc3e019fa91f9a10a5e120c08135297e1ecf312d561459c45908cb1e0e365f49e5
@@ -16399,17 +16306,7 @@ __metadata:
languageName: node
linkType: hard
-"swc-loader@npm:^0.2.3":
- version: 0.2.3
- resolution: "swc-loader@npm:0.2.3"
- peerDependencies:
- "@swc/core": ^1.2.147
- webpack: ">=2"
- checksum: 10/010d84d399525c0185d36d62c86c55ae017e7a90046bc8a39be4b7e07526924037868049f6037bc966da98151cb2600934b96a66279b742d3c413a718b427251
- languageName: node
- linkType: hard
-
-"swc-loader@npm:^0.2.6":
+"swc-loader@npm:^0.2.3, swc-loader@npm:^0.2.6":
version: 0.2.6
resolution: "swc-loader@npm:0.2.6"
dependencies:
@@ -16421,19 +16318,7 @@ __metadata:
languageName: node
linkType: hard
-"swr@npm:^2.0.1":
- version: 2.2.4
- resolution: "swr@npm:2.2.4"
- dependencies:
- client-only: "npm:^0.0.1"
- use-sync-external-store: "npm:^1.2.0"
- peerDependencies:
- react: ^16.11.0 || ^17.0.0 || ^18.0.0
- checksum: 10/feb2fb5d3feb5accf93ce81108d659b941f9df6114d7744ccd148349255ed4d072e228e269d5aa3f81e4198cc120672929f5d1709cd52169d8e279314a5af4fd
- languageName: node
- linkType: hard
-
-"swr@npm:^2.2.5":
+"swr@npm:^2.0.1, swr@npm:^2.2.5":
version: 2.2.5
resolution: "swr@npm:2.2.5"
dependencies:
@@ -17133,20 +17018,6 @@ __metadata:
languageName: node
linkType: hard
-"update-browserslist-db@npm:^1.0.9":
- version: 1.0.10
- resolution: "update-browserslist-db@npm:1.0.10"
- dependencies:
- escalade: "npm:^3.1.1"
- picocolors: "npm:^1.0.0"
- peerDependencies:
- browserslist: ">= 4.21.0"
- bin:
- browserslist-lint: cli.js
- checksum: 10/2c88096ca99918efc77a514458c4241b3f2a8e7882aa91b97251231240c30c71e82cb2043aaf12e40eba6bebda3369010e180a58bc11bbd0bca29094945c31cb
- languageName: node
- linkType: hard
-
"update-browserslist-db@npm:^1.1.0":
version: 1.1.0
resolution: "update-browserslist-db@npm:1.1.0"
@@ -17524,21 +17395,6 @@ __metadata:
languageName: node
linkType: hard
-"webpack-dev-middleware@npm:^5.3.1":
- version: 5.3.3
- resolution: "webpack-dev-middleware@npm:5.3.3"
- dependencies:
- colorette: "npm:^2.0.10"
- memfs: "npm:^3.4.3"
- mime-types: "npm:^2.1.31"
- range-parser: "npm:^1.2.1"
- schema-utils: "npm:^4.0.0"
- peerDependencies:
- webpack: ^4.0.0 || ^5.0.0
- checksum: 10/31a2f7a11e58a76bdcde1eb8da310b6643844d9b442f9916f48be5b46c103f23490c393c32a9af501ce68226fbb018b811f5a956635ed60a03f9481a4bcd6c76
- languageName: node
- linkType: hard
-
"webpack-dev-middleware@npm:^5.3.4":
version: 5.3.4
resolution: "webpack-dev-middleware@npm:5.3.4"
@@ -17554,54 +17410,7 @@ __metadata:
languageName: node
linkType: hard
-"webpack-dev-server@npm:^4.15.1":
- version: 4.15.1
- resolution: "webpack-dev-server@npm:4.15.1"
- dependencies:
- "@types/bonjour": "npm:^3.5.9"
- "@types/connect-history-api-fallback": "npm:^1.3.5"
- "@types/express": "npm:^4.17.13"
- "@types/serve-index": "npm:^1.9.1"
- "@types/serve-static": "npm:^1.13.10"
- "@types/sockjs": "npm:^0.3.33"
- "@types/ws": "npm:^8.5.5"
- ansi-html-community: "npm:^0.0.8"
- bonjour-service: "npm:^1.0.11"
- chokidar: "npm:^3.5.3"
- colorette: "npm:^2.0.10"
- compression: "npm:^1.7.4"
- connect-history-api-fallback: "npm:^2.0.0"
- default-gateway: "npm:^6.0.3"
- express: "npm:^4.17.3"
- graceful-fs: "npm:^4.2.6"
- html-entities: "npm:^2.3.2"
- http-proxy-middleware: "npm:^2.0.3"
- ipaddr.js: "npm:^2.0.1"
- launch-editor: "npm:^2.6.0"
- open: "npm:^8.0.9"
- p-retry: "npm:^4.5.0"
- rimraf: "npm:^3.0.2"
- schema-utils: "npm:^4.0.0"
- selfsigned: "npm:^2.1.1"
- serve-index: "npm:^1.9.1"
- sockjs: "npm:^0.3.24"
- spdy: "npm:^4.0.2"
- webpack-dev-middleware: "npm:^5.3.1"
- ws: "npm:^8.13.0"
- peerDependencies:
- webpack: ^4.37.0 || ^5.0.0
- peerDependenciesMeta:
- webpack:
- optional: true
- webpack-cli:
- optional: true
- bin:
- webpack-dev-server: bin/webpack-dev-server.js
- checksum: 10/fd6dfb6c71eb94696b21930ea4c2f25e95ba85fac1bbc15aa5d03af0a90712eba057901fa9131ed3e901665c95b2379208279aca61e9c48e7cda276c3caa95dd
- languageName: node
- linkType: hard
-
-"webpack-dev-server@npm:^4.15.2":
+"webpack-dev-server@npm:^4.15.1, webpack-dev-server@npm:^4.15.2":
version: 4.15.2
resolution: "webpack-dev-server@npm:4.15.2"
dependencies: