Skip to content

Commit 6c1873f

Browse files
authored
BN-69 | Add. Expanded ValueSet Data And Custom Styles For AllergiesTable (#31)
* BN-69 | Refactor. Use Expanded ValueSet For Allergen Search * BN-69 | Refactor. Replace Term Medication With Drug * BN-69 | Fix. AllergiesTable Header Order * BN-69 | Refactor. Replace Term Recorder With Recorded By * BN-69 | Add. Set AllergyIntolerance Count As 100 * BN-69 | Add. Allergen Category To Allergies Column * BN-69 | Add. Allergen Severity Tag To Allergies Column * BN-69 | Add. Sort Allergens Based On Severity OnMount * BN-69 | Add. Custom Styling For Certainity * BN-69 | Add. Sort Selected Allergens By Added Order * BN-69 | Remove. Redundant Parent Concept Filter
1 parent b504f78 commit 6c1873f

File tree

17 files changed

+939
-588
lines changed

17 files changed

+939
-588
lines changed

public/locales/locale_en.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"ALLERGY_ALREADY_SELECTED": "Already added",
99
"ALLERGY_LIST_ACTIVE": "Active",
1010
"ALLERGY_LIST_INACTIVE": "Inactive",
11-
"ALLERGY_LIST_PROVIDER": "Provider",
11+
"ALLERGY_LIST_RECORDED_BY": "Recorded By",
1212
"ALLERGY_LIST_RECORDED_DATE": "Recorded Date",
1313
"ALLERGY_LIST_STATUS": "Status",
1414
"ALLERGY_REACTIONS_ARIA_LABEL": "Select reactions for this allergy",
@@ -18,7 +18,7 @@
1818
"ALLERGY_TABLE_NOT_AVAILABLE": "Not available",
1919
"ALLERGY_TYPE_ENVIRONMENT": "Environment",
2020
"ALLERGY_TYPE_FOOD": "Food",
21-
"ALLERGY_TYPE_MEDICATION": "Medication",
21+
"ALLERGY_TYPE_DRUG": "Drug",
2222
"CERTAINITY_CONFIRMED": "Confirmed",
2323
"CERTAINITY_PROVISIONAL": "Provisional",
2424
"CLINICAL_DAYS_TRANSLATION_KEY": "days",

public/locales/locale_es.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"ALLERGY_ALREADY_SELECTED": "Ya agregada",
99
"ALLERGY_LIST_ACTIVE": "Activo",
1010
"ALLERGY_LIST_INACTIVE": "Inactivo",
11-
"ALLERGY_LIST_PROVIDER": "Proveedor",
11+
"ALLERGY_LIST_RECORDED_BY": "Grabado por",
1212
"ALLERGY_LIST_RECORDED_DATE": "Fecha de grabación",
1313
"ALLERGY_LIST_STATUS": "Estado",
1414
"ALLERGY_REACTIONS_ARIA_LABEL": "Seleccionar reacciones para esta alergia",
@@ -18,7 +18,7 @@
1818
"ALLERGY_TABLE_NOT_AVAILABLE": "No disponible",
1919
"ALLERGY_TYPE_ENVIRONMENT": "Ambiental",
2020
"ALLERGY_TYPE_FOOD": "Alimento",
21-
"ALLERGY_TYPE_MEDICATION": "Medicamento",
21+
"ALLERGY_TYPE_DRUG": "Droga",
2222
"CERTAINITY_CONFIRMED": "Confirmado",
2323
"CERTAINITY_PROVISIONAL": "Provisorio",
2424
"CLINICAL_DAYS_TRANSLATION_KEY": "días",

src/components/clinical/forms/allergies/__tests__/AllergiesForm.integration.test.tsx

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -103,61 +103,73 @@ describe('AllergiesForm Integration Tests', () => {
103103
// Mock the API responses for ValueSet endpoints
104104
(api.get as jest.Mock).mockImplementation((url) => {
105105
if (url.includes('ValueSet/162552')) {
106-
// Medication allergens
106+
// Medication allergens - using new expansion.contains format
107107
return Promise.resolve({
108108
resourceType: 'ValueSet',
109-
compose: {
110-
include: [
109+
expansion: {
110+
timestamp: '2025-06-10T04:02:11+00:00',
111+
contains: [
112+
{
113+
code: '162552AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', // Parent concept - will be filtered out
114+
display: 'Reference application common drug allergens',
115+
system: 'http://snomed.info/sct',
116+
},
111117
{
112-
concept: [
113-
{
114-
code: '123',
115-
display: 'Penicillin',
116-
system: 'http://snomed.info/sct',
117-
},
118-
],
118+
code: '123',
119+
display: 'Penicillin',
120+
system: 'http://snomed.info/sct',
121+
},
122+
{
123+
code: 'inactive-med',
124+
display: 'Inactive Medication',
125+
system: 'http://snomed.info/sct',
126+
inactive: true, // Will be filtered out
119127
},
120128
],
121129
},
122130
});
123131
} else if (url.includes('ValueSet/162553')) {
124-
// Food allergens
132+
// Food allergens - using new expansion.contains format
125133
return Promise.resolve({
126134
resourceType: 'ValueSet',
127-
compose: {
128-
include: [
135+
expansion: {
136+
timestamp: '2025-06-10T04:02:11+00:00',
137+
contains: [
138+
{
139+
code: '162553AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', // Parent concept - will be filtered out
140+
display: 'Food allergens parent concept',
141+
system: 'http://snomed.info/sct',
142+
},
129143
{
130-
concept: [
131-
{
132-
code: '456',
133-
display: 'Peanuts',
134-
system: 'http://snomed.info/sct',
135-
},
136-
],
144+
code: '456',
145+
display: 'Peanuts',
146+
system: 'http://snomed.info/sct',
137147
},
138148
],
139149
},
140150
});
141151
} else if (url.includes('ValueSet/162554')) {
142-
// Environment allergens
152+
// Environment allergens - using new expansion.contains format
143153
return Promise.resolve({
144154
resourceType: 'ValueSet',
145-
compose: {
146-
include: [
155+
expansion: {
156+
timestamp: '2025-06-10T04:02:11+00:00',
157+
contains: [
158+
{
159+
code: '162554AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', // Parent concept - will be filtered out
160+
display: 'Environment allergens parent concept',
161+
system: 'http://snomed.info/sct',
162+
},
147163
{
148-
concept: [
149-
{
150-
code: '789',
151-
display: 'Dust',
152-
system: 'http://snomed.info/sct',
153-
},
154-
],
164+
code: '789',
165+
display: 'Dust',
166+
system: 'http://snomed.info/sct',
155167
},
156168
],
157169
},
158170
});
159171
} else if (url.includes('ValueSet/162555')) {
160-
// Reaction concepts
172+
// Reaction concepts - keeping old format as this endpoint hasn't changed
161173
return Promise.resolve({
162174
resourceType: 'ValueSet',
163175
compose: {
@@ -195,7 +207,7 @@ describe('AllergiesForm Integration Tests', () => {
195207
await userEvent.type(searchBox, 'pen');
196208

197209
await waitFor(() => {
198-
expect(screen.getByText('Penicillin [Medication]')).toBeInTheDocument();
210+
expect(screen.getByText('Penicillin [Drug]')).toBeInTheDocument();
199211
expect(screen.getByText('Peanuts [Food]')).toBeInTheDocument();
200212
});
201213
});
@@ -217,10 +229,10 @@ describe('AllergiesForm Integration Tests', () => {
217229
await userEvent.type(searchBox, 'pen');
218230

219231
await waitFor(() => {
220-
expect(screen.getByText('Penicillin [Medication]')).toBeInTheDocument();
232+
expect(screen.getByText('Penicillin [Drug]')).toBeInTheDocument();
221233
});
222234

223-
await userEvent.click(screen.getByText('Penicillin [Medication]'));
235+
await userEvent.click(screen.getByText('Penicillin [Drug]'));
224236

225237
expect(mockStore.addAllergy).toHaveBeenCalledWith(
226238
expect.objectContaining({
@@ -276,7 +288,7 @@ describe('AllergiesForm Integration Tests', () => {
276288
await userEvent.type(searchBox, 'pen');
277289

278290
await waitFor(() => {
279-
expect(screen.getByText('Penicillin [Medication]')).toBeInTheDocument();
291+
expect(screen.getByText('Penicillin [Drug]')).toBeInTheDocument();
280292
});
281293

282294
// Mock the store to return the selected allergy after it's added
@@ -295,7 +307,7 @@ describe('AllergiesForm Integration Tests', () => {
295307
],
296308
});
297309

298-
await userEvent.click(screen.getByText('Penicillin [Medication]'));
310+
await userEvent.click(screen.getByText('Penicillin [Drug]'));
299311
expect(mockStore.addAllergy).toHaveBeenCalledWith(
300312
expect.objectContaining({
301313
uuid: '123',

src/components/clinical/forms/allergies/__tests__/SelectedAllergyItem.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('SelectedAllergyItem', () => {
7171
test('handles different allergy types with i18n translations', () => {
7272
const allergyTypes: { type: AllergenType; i18nKey: string }[] = [
7373
{ type: 'food', i18nKey: 'ALLERGY_TYPE_FOOD' },
74-
{ type: 'medication', i18nKey: 'ALLERGY_TYPE_MEDICATION' },
74+
{ type: 'medication', i18nKey: 'ALLERGY_TYPE_DRUG' },
7575
{ type: 'environment', i18nKey: 'ALLERGY_TYPE_ENVIRONMENT' },
7676
];
7777

src/constants/app.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export const PATIENT_CONDITION_RESOURCE_URL = (patientUUID: string) =>
2121
OPENMRS_FHIR_R4 +
2222
`/Condition?category=${HL7_CONDITION_CATEGORY_CONDITION_CODE}&patient=${patientUUID}`;
2323
export const PATIENT_ALLERGY_RESOURCE_URL = (patientUUID: string) =>
24-
OPENMRS_FHIR_R4 + `/AllergyIntolerance?patient=${patientUUID}`;
24+
OPENMRS_FHIR_R4 +
25+
`/AllergyIntolerance?patient=${patientUUID}&_count=100&_sort=-_lastUpdated`;
2526
export const PATIENT_VISITS_URL = (patientUUID: string) =>
2627
OPENMRS_FHIR_R4 + `/Encounter?subject:Patient=${patientUUID}&_tag=visit`;
2728
export const PATIENT_LAB_INVESTIGATION_RESOURCE_URL = (patientUUID: string) =>
@@ -56,7 +57,7 @@ export const CONCEPT_DETAIL_URL = (uuid: string, locale: string): string =>
5657
OPENMRS_REST_V1 +
5758
`/concept/${uuid}?v=custom:(uuid,setMembers:(uuid,display,retired))&locale=${locale}`;
5859
export const FHIR_VALUESET_URL = (uuid: string) =>
59-
OPENMRS_FHIR_R4 + `/ValueSet/${uuid}`;
60+
OPENMRS_FHIR_R4 + `/ValueSet/${uuid}/$expand`;
6061
export const LOGIN_PATH = '/bahmni/home/index.html#/login';
6162
export const DEFAULT_LOCALE = 'en';
6263
export const LOCALE_STORAGE_KEY = 'NG_TRANSLATE_LANG_KEY';

src/displayControls/allergies/AllergiesTable.tsx

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,26 @@ import { usePatientUUID } from '@hooks/usePatientUUID';
66
import { useAllergies } from '@hooks/useAllergies';
77
import { formatAllergies } from '@services/allergyService';
88
import { FormattedAllergy } from '@types/allergy';
9-
import { formatDateTime } from '@utils/date';
10-
import { generateId, capitalize } from '@utils/common';
9+
import { generateId } from '@utils/common';
10+
import { DotMark } from '@carbon/icons-react';
11+
import {
12+
getCategoryDisplayName,
13+
getSeverityDisplayName,
14+
sortAllergiesBySeverity,
15+
} from '@utils/allergy';
16+
import * as styles from './styles/AllergiesTable.module.scss';
17+
18+
// Helper function to get severity CSS class
19+
const getSeverityClassName = (severity: string): string | undefined => {
20+
switch (severity?.toLowerCase()) {
21+
case 'mild':
22+
return styles.mildSeverity;
23+
case 'moderate':
24+
return styles.moderateSeverity;
25+
case 'severe':
26+
return styles.severeSeverity;
27+
}
28+
};
1129

1230
/**
1331
* Component to display patient allergies in a DataTable with expandable rows
@@ -21,81 +39,78 @@ const AllergiesTable: React.FC = () => {
2139
const headers = useMemo(
2240
() => [
2341
{ key: 'display', header: t('ALLERGEN') },
24-
{ key: 'severity', header: t('SEVERITY') },
2542
{ key: 'manifestation', header: t('REACTIONS') },
43+
{ key: 'recorder', header: t('ALLERGY_LIST_RECORDED_BY') },
2644
{ key: 'status', header: t('ALLERGY_LIST_STATUS') },
27-
{ key: 'recorder', header: t('ALLERGY_LIST_PROVIDER') },
28-
{ key: 'recordedDate', header: t('ALLERGY_LIST_RECORDED_DATE') },
2945
],
3046
[t],
3147
);
3248

3349
const sortable = useMemo(
3450
() => [
3551
{ key: 'display', sortable: true },
36-
{ key: 'severity', sortable: true },
3752
{ key: 'manifestation', sortable: false },
38-
{ key: 'status', sortable: true },
3953
{ key: 'recorder', sortable: true },
40-
{ key: 'recordedDate', sortable: true },
54+
{ key: 'status', sortable: true },
4155
],
4256
[],
4357
);
4458

45-
// Format allergies for display
46-
const formattedAllergies = useMemo(() => {
59+
// Format and sort allergies for display
60+
const displayAllergies = useMemo(() => {
4761
if (!allergies || allergies.length === 0) return [];
48-
return formatAllergies(allergies);
62+
const formatted = formatAllergies(allergies);
63+
return sortAllergiesBySeverity(formatted);
4964
}, [allergies]);
5065

51-
// Create row class names array for styling rows with severe allergies
52-
const rowClassNames = useMemo(() => {
53-
const classNames: Record<string, string> = {};
54-
55-
formattedAllergies.forEach((allergy) => {
56-
if (allergy.id && allergy.severity && allergy.severity === 'severe') {
57-
classNames[allergy.id] = 'criticalCell';
58-
}
59-
});
60-
61-
return classNames;
62-
}, [formattedAllergies]);
63-
6466
// Function to render cell content based on the cell ID
6567
const renderCell = (allergy: FormattedAllergy, cellId: string) => {
6668
switch (cellId) {
6769
case 'display':
68-
return allergy.display;
69-
case 'status':
7070
return (
71-
<Tag type={allergy.status === 'Active' ? 'green' : 'gray'}>
72-
{allergy.status === 'Active'
73-
? t('ALLERGY_LIST_ACTIVE')
74-
: t('ALLERGY_LIST_INACTIVE')}
75-
</Tag>
71+
<>
72+
{`${allergy.display} `}
73+
<div className={styles.allergyCategory}>
74+
[{t(getCategoryDisplayName(allergy.category?.[0]))}] &nbsp;
75+
</div>
76+
{}
77+
<Tag className={getSeverityClassName(allergy.severity!)}>
78+
{t(getSeverityDisplayName(allergy.severity!))}
79+
</Tag>
80+
</>
7681
);
7782
case 'manifestation':
7883
return allergy.reactions
7984
? allergy.reactions
8085
.map((reaction) => reaction.manifestation.join(', '))
8186
.join(', ')
8287
: t('ALLERGY_TABLE_NOT_AVAILABLE');
83-
case 'severity':
84-
return capitalize(allergy.severity || 'Unknown');
8588
case 'recorder':
8689
return allergy.recorder || t('ALLERGY_TABLE_NOT_AVAILABLE');
87-
case 'recordedDate': {
88-
const recordedDate = formatDateTime(allergy.recordedDate || '');
89-
return recordedDate.formattedResult || t('ALLERGY_TABLE_NOT_AVAILABLE');
90-
}
90+
case 'status':
91+
return (
92+
<Tag
93+
type="outline"
94+
renderIcon={DotMark}
95+
className={
96+
allergy.status === 'Active'
97+
? styles.activeStatus
98+
: styles.inactiveStatus
99+
}
100+
>
101+
{allergy.status === 'Active'
102+
? t('ALLERGY_LIST_ACTIVE')
103+
: t('ALLERGY_LIST_INACTIVE')}
104+
</Tag>
105+
);
91106
}
92107
};
93108

94109
// Function to render expanded content for an allergy
95110
const renderExpandedContent = (allergy: FormattedAllergy) => {
96111
if (allergy.note && allergy.note.length > 0) {
97112
return (
98-
<p style={{ padding: '0.5rem' }} key={generateId()}>
113+
<p className={styles.allergiesNote} key={generateId()}>
99114
{allergy.note.join(', ')}
100115
</p>
101116
);
@@ -107,7 +122,7 @@ const AllergiesTable: React.FC = () => {
107122
<div data-testid="allergy-table">
108123
<ExpandableDataTable
109124
tableTitle={t('ALLERGIES_DISPLAY_CONTROL_HEADING')}
110-
rows={formattedAllergies}
125+
rows={displayAllergies}
111126
headers={headers}
112127
sortable={sortable}
113128
renderCell={renderCell}
@@ -116,7 +131,7 @@ const AllergiesTable: React.FC = () => {
116131
error={error}
117132
ariaLabel={t('ALLERGIES_DISPLAY_CONTROL_HEADING')}
118133
emptyStateMessage={t('NO_ALLERGIES')}
119-
rowClassNames={rowClassNames}
134+
className={styles.allergiesTableBody}
120135
/>
121136
</div>
122137
);

0 commit comments

Comments
 (0)