Skip to content

Commit

Permalink
(feat) Add functionality to allow deleting and editing an allergy (op…
Browse files Browse the repository at this point in the history
…enmrs#1703)

* Add functionality to allow deleting and editing an allergy

(feat) O3-2760: Add a `closeWorkspaceWithSavedChanges` function to the workspace API (openmrs#1689)

* Added new function 'closeWorkspaceWithSavedChanges' to close workspace after form is saved

* Passing 'closeWorkspaceWithSavedChanges' as workspace props

(docs) Amend steps for updating core libraries in README (openmrs#1690)

(chore) Bump @openmrs/ngx-formentry (openmrs#1684)

(fix) O3 2804: Reuse the ResponsiveWrapper component from esm-framework (openmrs#1673)

* (feat) Reuse ResponsiveWrapper component from esm-framework

* feat: Added Layer in visit-notes-form

* Fix styleguide imports by bumping both openmrs tooling and framework

* Misc fixes

---------

Co-authored-by: Dennis Kigen <kigen.work@gmail.com>

(refactor) O3-2815: Replace usages of `/ws/rest/v1` with `restBaseUrl` (openmrs#1661)

* (Refactor)O3-2815: Replace usages of '/ws/rest/v1' with restBaseUrl

* Fixup

---------

Co-authored-by: jwnasambu <wamalwa1844.com>
Co-authored-by: Dennis Kigen <kigen.work@gmail.com>

(feat) Add ability to cancel orders (openmrs#1640)

* (feat) cancel order

* clean up

* pr changes, reload fixes and clean up

* translations

* wip cancel order via action

* mutate orders on cancel

* order mutations and pr comments

* fixes to labs

* button text change

* Fixup

* More tweaks

---------

Co-authored-by: Dennis Kigen <kigen.work@gmail.com>

(chore) Add generic slot to Visit Form (openmrs#1688)

* (chore) Add billing slot to Start Visit Form

* Add generic visit attribute slot

* remove slot from config

---------

Co-authored-by: CynthiaKamau <cynthiakamau54@gmail.com>

(chore) Bump Angular form engine (openmrs#1694)

(feat) Makes orders widget expandable and adds detailed order view (openmrs#1696)

* (feat) Expandable orders widget

* adds order item cards when expanded

* changes button to tooltip

(fix) O3-2629: Submit button on Allergy Form remains disabled when filling allergen and severity before reactions (openmrs#1699)

fix the allergy reaction dependency

(feat) Hide bottom navigation on tablets and phones when workspace is active (openmrs#1695)

(feat) Relocate Load More button in visits section (openmrs#1702)

(chore) Relocate Load More button in visits section

add closeWorkspaceWithSavedChanges argument to Allergy form

parameterised allergy reaction should be checked correctly

maintain non-coded allergen array

remove test

(chore) Prettier should only list files that don't formatting config

(feat) Add config to handle bill submission in start visit form (openmrs#1700)

Upgrade peter-evans/create-pull-request

See peter-evans/create-pull-request#2790

(chore) Update translations from Transifex (openmrs#1683)

add test

fix added test

* clean deletePatientAllergy function

* Update packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.component.tsx

Co-authored-by: Dennis Kigen <kigen.work@gmail.com>

* Update packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.test.tsx

Co-authored-by: Dennis Kigen <kigen.work@gmail.com>

* Update packages/esm-patient-allergies-app/src/allergies/allergies-form/allergy-form.resource.ts

Co-authored-by: Dennis Kigen <kigen.work@gmail.com>

* (chore) Bump @openmrs/ngx-formentry

* make default allergy loading more clean

* fix linting

---------

Co-authored-by: Dennis Kigen <kigen.work@gmail.com>
  • Loading branch information
2 people authored and usamaidrsk committed Mar 11, 2024
1 parent 68346cc commit e731eb3
Show file tree
Hide file tree
Showing 12 changed files with 391 additions and 44 deletions.
15 changes: 15 additions & 0 deletions __mocks__/allergies.mock.ts
Expand Up @@ -827,3 +827,18 @@ export const mockAllergies = [
reactionSeverity: 'Severe',
},
];

export const mockAllergy = {
clinicalStatus: 'Active',
criticality: 'high',
display: 'ACE inhibitors',
id: 'acf497ae-1f75-436c-ad27-b8a0dec390cd',
lastUpdated: '2024-02-28T11:41:58.000+00:00',
note: 'sample allergy note',
reactionManifestations: ['Anaphylaxis', 'Headache'],
reactionSeverity: undefined,
reactionToSubstance: undefined,
recordedBy: 'Super User',
recordedDate: '2024-02-23T13:45:08+00:00',
recorderType: 'Practitioner',
};
@@ -0,0 +1,60 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Layer, OverflowMenu, OverflowMenuItem } from '@carbon/react';
import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib';
import { showModal, useLayoutType } from '@openmrs/esm-framework';
import { type Allergy } from './allergy-intolerance.resource';
import styles from './allergies-action-menu.scss';
import { patientAllergiesFormWorkspace } from '../constants';

interface allergiesActionMenuProps {
allergy: Allergy;
patientUuid?: string;
}

export const AllergiesActionMenu = ({ allergy, patientUuid }: allergiesActionMenuProps) => {
const { t } = useTranslation();
const isTablet = useLayoutType() === 'tablet';

const launchEditAllergiesForm = useCallback(() => {
launchPatientWorkspace(patientAllergiesFormWorkspace, {
workspaceTitle: t('editAllergy', 'Edit an Allergy'),
allergy,
formContext: 'editing',
});
}, [allergy, t]);

const launchDeleteAllergyDialog = (allergyId: string) => {
const dispose = showModal('allergy-delete-confirmation-dialog', {
closeDeleteModal: () => dispose(),
allergyId,
patientUuid,
});
};

return (
<Layer className={styles.layer}>
<OverflowMenu
aria-label={t('editOrDeleteAllergy', 'Edit or delete allergy')}
size={isTablet ? 'lg' : 'sm'}
flipped
align="left"
>
<OverflowMenuItem
className={styles.menuItem}
id="editAllergy"
onClick={launchEditAllergiesForm}
itemText={t('edit', 'Edit')}
/>
<OverflowMenuItem
className={styles.menuItem}
id="deleteAllergy"
itemText={t('delete', 'Delete')}
onClick={() => launchDeleteAllergyDialog(allergy.id)}
isDelete
hasDivider
/>
</OverflowMenu>
</Layer>
);
};
@@ -0,0 +1,11 @@
.layer {
height: 100%;

:global(.cds--overflow-menu) {
min-height: unset;
}
}

.menuItem {
max-width: none;
}
Expand Up @@ -21,6 +21,7 @@ import { useAllergies } from './allergy-intolerance.resource';
import { patientAllergiesFormWorkspace } from '../constants';
import styles from './allergies-detailed-summary.scss';
import { ReactionSeverity } from '../types';
import { AllergiesActionMenu } from './allergies-action-menu.component';

interface AllergiesDetailedSummaryProps {
patient: fhir.Patient;
Expand Down Expand Up @@ -110,6 +111,7 @@ const AllergiesDetailedSummary: React.FC<AllergiesDetailedSummaryProps> = ({ pat
{header.header?.content ?? header.header}
</TableHeader>
))}
<TableHeader />
</TableRow>
</TableHead>
<TableBody>
Expand All @@ -118,6 +120,12 @@ const AllergiesDetailedSummary: React.FC<AllergiesDetailedSummaryProps> = ({ pat
{row.cells.map((cell) => (
<TableCell key={cell.id}>{cell.value?.content ?? cell.value}</TableCell>
))}
<TableCell className="cds--table-column-menu">
<AllergiesActionMenu
patientUuid={patient.id}
allergy={allergies.find((allergy) => allergy.id == row.id)}
/>
</TableCell>
</TableRow>
))}
</TableBody>
Expand Down
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { type TFunction, useTranslation } from 'react-i18next';
import {
Button,
ButtonSet,
Expand All @@ -19,7 +19,7 @@ import {
} from '@carbon/react';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { type Control, Controller, useForm, type UseFormSetValue } from 'react-hook-form';
import { type Control, Controller, useForm, type UseFormSetValue, type UseFormGetValues } from 'react-hook-form';
import {
ExtensionSlot,
type FetchResponse,
Expand All @@ -36,8 +36,9 @@ import {
saveAllergy,
useAllergens,
useAllergicReactions,
updatePatientAllergy,
} from './allergy-form.resource';
import { useAllergies } from '../allergy-intolerance.resource';
import { type Allergy, useAllergies } from '../allergy-intolerance.resource';
import { AllergenType } from '../../types';
import styles from './allergy-form.scss';

Expand Down Expand Up @@ -65,8 +66,14 @@ type AllergyFormData = {
comment: string;
};

function AllergyForm(props: DefaultWorkspaceProps) {
const { closeWorkspace, patientUuid, promptBeforeClosing, closeWorkspaceWithSavedChanges } = props;
interface AllergyFormProps extends DefaultWorkspaceProps {
allergy?: Allergy;
formContext: 'creating' | 'editing';
}

function AllergyForm(props: AllergyFormProps) {
const { closeWorkspace, patientUuid, allergy, formContext, promptBeforeClosing, closeWorkspaceWithSavedChanges } =
props;
const { t } = useTranslation();
const { concepts } = useConfig();
const isTablet = useLayoutType() === 'tablet';
Expand All @@ -82,23 +89,73 @@ function AllergyForm(props: DefaultWorkspaceProps) {
const [isDisabled, setIsDisabled] = useState(true);
const { mutate } = useAllergies(patientUuid);

const getDefaultSeverityUUID = (severity) => {
switch (severity) {
case 'mild':
return mildReactionUuid;
case 'moderate':
return moderateReactionUuid;
case 'severe':
return severeReactionUuid;
default:
return null;
}
};

const getDefaultAllergicReactions = () => {
return allergicReactions?.map((reaction) => {
return allergy?.reactionManifestations?.includes(reaction.display) ? reaction.uuid : '';
});
};

const setDefaultNonCodedAllergen = (defaultAllergy) => {
const codedAllergenDisplays = allergens?.map((allergen) => allergen?.display);
if (!codedAllergenDisplays?.includes(allergy?.display)) {
defaultAllergy.allergen = { uuid: otherConceptUuid, display: t('other', 'Other'), type: AllergenType?.OTHER };
defaultAllergy.nonCodedAllergen = allergy?.display;
}
};

const setDefaultNonCodedReactions = (defaultAllergy) => {
const allergicReactionDisplays = allergicReactions?.map((reaction) => reaction?.display);
allergy?.reactionManifestations?.forEach((reaction) => {
if (!allergicReactionDisplays?.includes(reaction)) {
defaultAllergy.nonCodedAllergicReaction = reaction;
defaultAllergy.allergicReactions?.splice(defaultAllergy.allergicReactions?.length - 1, 1, otherConceptUuid);
}
});
};

const getDefaultAllergy = (allergy: Allergy, formContext) => {
const defaultAllergy = {
allergen: null,
nonCodedAllergen: '',
allergicReactions: [],
nonCodedAllergicReaction: '',
severityOfWorstReaction: null,
comment: '',
};
if (formContext === 'editing') {
defaultAllergy.allergen = allergens?.find((a) => allergy?.display === a?.display);
defaultAllergy.allergicReactions = getDefaultAllergicReactions();
defaultAllergy.severityOfWorstReaction = getDefaultSeverityUUID(allergy?.reactionSeverity);
defaultAllergy.comment = allergy?.note !== '--' ? allergy?.note : '';
setDefaultNonCodedAllergen(defaultAllergy);
setDefaultNonCodedReactions(defaultAllergy);
}
return defaultAllergy;
};
const {
control,
handleSubmit,
watch,
getValues,
setValue,
formState: { isDirty },
} = useForm<AllergyFormData>({
mode: 'all',
resolver: zodResolver(allergyFormSchema),
defaultValues: {
allergen: null,
nonCodedAllergen: '',
allergicReactions: [],
nonCodedAllergicReaction: '',
severityOfWorstReaction: null,
comment: '',
},
values: getDefaultAllergy(allergy, formContext),
});

useEffect(() => {
Expand All @@ -110,8 +167,8 @@ function AllergyForm(props: DefaultWorkspaceProps) {
const selectedSeverityOfWorstReaction = watch('severityOfWorstReaction');
const selectednonCodedAllergen = watch('nonCodedAllergen');
const selectedNonCodedAllergicReaction = watch('nonCodedAllergicReaction');
const reactionsValidation = selectedAllergicReactions?.some((item) => item !== '');

const reactionsValidation = selectedAllergicReactions.some((item) => item !== '');
useEffect(() => {
if (!!selectedAllergen && reactionsValidation && !!selectedSeverityOfWorstReaction) setIsDisabled(false);
else setIsDisabled(true);
Expand All @@ -137,7 +194,8 @@ function AllergyForm(props: DefaultWorkspaceProps) {
} = data;

const selectedAllergicReactions = allergicReactions.filter((value) => value !== '');
let payload: NewAllergy = {

let patientAllergy: NewAllergy = {
allergen:
allergen.uuid == otherConceptUuid
? {
Expand All @@ -160,30 +218,55 @@ function AllergyForm(props: DefaultWorkspaceProps) {
}),
};
const abortController = new AbortController();
saveAllergy(payload, patientUuid, abortController)
.then(
(response: FetchResponse) => {
if (response.status === 201) {
mutate();
closeWorkspaceWithSavedChanges();
showSnackbar({
isLowContrast: true,
kind: 'success',
title: t('allergySaved', 'Allergy saved'),
subtitle: t('allergyNowVisible', 'It is now visible on the Allergies page'),
});
}
},
(err) => {
showSnackbar({
title: t('allergySaveError', 'Error saving allergy'),
kind: 'error',
isLowContrast: false,
subtitle: err?.message,
});
},
)
.finally(() => abortController.abort());
formContext === 'editing'
? updatePatientAllergy(patientAllergy, patientUuid, allergy?.id, abortController)
.then(
(response: FetchResponse) => {
if (response.status === 200) {
mutate();
closeWorkspace({ ignoreChanges: true });
showSnackbar({
isLowContrast: true,
kind: 'success',
title: t('allergyUpdated', 'Allergy updated'),
subtitle: t('allergyNowVisible', 'It is now visible on the Allergies page'),
});
}
},
(err) => {
showSnackbar({
title: t('allergySaveError', 'Error saving allergy'),
kind: 'error',
isLowContrast: false,
subtitle: err?.message,
});
},
)
.finally(() => abortController.abort())
: saveAllergy(patientAllergy, patientUuid, abortController)
.then(
(response: FetchResponse) => {
if (response.status === 201) {
mutate();
closeWorkspace({ ignoreChanges: true });
showSnackbar({
isLowContrast: true,
kind: 'success',
title: t('allergySaved', 'Allergy saved'),
subtitle: t('allergyNowVisible', 'It is now visible on the Allergies page'),
});
}
},
(err) => {
showSnackbar({
title: t('allergySaveError', 'Error saving allergy'),
kind: 'error',
isLowContrast: false,
subtitle: err?.message,
});
},
)
.finally(() => abortController.abort());
},
[otherConceptUuid, patientUuid, closeWorkspaceWithSavedChanges, t, mutate],
);
Expand Down Expand Up @@ -370,7 +453,10 @@ function AllergicReactionsField({
methods: { control, setValue },
}: {
allergicReactions: AllergicReaction[];
methods: { control: Control<AllergyFormData>; setValue: UseFormSetValue<AllergyFormData> };
methods: {
control: Control<AllergyFormData>;
setValue: UseFormSetValue<AllergyFormData>;
};
}) {
const handleAllergicReactionChange = useCallback(
(onChange, checked, id, index) => {
Expand Down
Expand Up @@ -143,3 +143,19 @@ export function saveAllergy(payload: NewAllergy, patientUuid: string, abortContr
signal: abortController.signal,
});
}

export function updatePatientAllergy(
payload: NewAllergy,
patientUuid: string,
allergenUuid: string,
abortController: AbortController,
) {
return openmrsFetch(`${restBaseUrl}/patient/${patientUuid}/allergy/${allergenUuid}`, {
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
body: payload,
signal: abortController.signal,
});
}

0 comments on commit e731eb3

Please sign in to comment.