Skip to content

Commit 35fdf87

Browse files
BN-50 | Add. RadiologyInvestigation Display Control (#34)
* BN-50 | Add. Group By Date Util * BN-50 | Refactor. Use GroupByDate Util In DiagnosesService * BN-50 | Add. Radiology Investigation Service * BN-50 | Add. Radiology Investigation Hook * BN-50 | Add. Radiology Investigation Table Display Control * BN-50 | Fix. Remove Timestamp From Date Group Key * BN-50 | Fix. Display Stat As Urgent In UI Co-authored-by: Dev Singh <dev.singh@thoughtworks.com> * BN-50 | Add. RadiologyInvestigation To Patient Dashboard Co-authored-by: Dev Singh <dev.singh@thoughtworks.com> * BN-50 | Add. Integration Test To RadiologyInvestigation Co-authored-by: Dev Singh <dev.singh@thoughtworks.com> * BN-50 | Add. A11y Test For RadiologyInvestigationTable Co-authored-by: Dev Singh <dev.singh@thoughtworks.com> * BN-50 | Fix. Use Aliases For Imports Co-authored-by: Dev Singh <dev.singh@thoughtworks.com> * BN-50 | Refactor. Group Radiology Investigations By Date In Presentation Layer Co-authored-by: Dev Singh <dev.singh@thoughtworks.com> * BN-50 | Refactor. Memoize Sort and Group Fn Co-authored-by: Dev Singh <dev.singh@thoughtworks.com> * BN-50 | Refactor. Group Diagnoses By Date In Presentation Layer Co-authored-by: Dev Singh <dev.singh@thoughtworks.com> * BN-50 | Add. a11y Test For DiagnosesTable --------- Co-authored-by: Dev Singh <dev.singh@thoughtworks.com>
1 parent 48e8c3f commit 35fdf87

30 files changed

+2801
-959
lines changed

public/locales/locale_en.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"ERROR_FETCHING_DIAGNOSES": "Error fetching diagnoses. Please try again later.",
8484
"ERROR_FETCHING_ENCOUNTER_DETAILS": "Error fetching encounter details",
8585
"ERROR_FETCHING_LOCATIONS_DETAILS": "Error fetching locations details",
86+
"ERROR_FETCHING_RADIOLOGY_ORDERS": "Error fetching radiology orders",
8687
"ERROR_FETCHING_PRACTITIONERS_DETAILS": "Error fetching practitioners details",
8788
"ERROR_FETCHING_USER_DETAILS": "Error fetching user details",
8889
"ERROR_INVALID_PATIENT_UUID": "Invalid patient UUID",
@@ -127,10 +128,16 @@
127128
"NO_DIAGNOSES": "No diagnoses recorded",
128129
"NO_MATCHING_ALLERGEN_FOUND": "No matching allergen found",
129130
"NO_MATCHING_DIAGNOSIS_FOUND": "No matching diagnosis found.",
131+
"NO_RADIOLOGY_ORDERS": "No radiology orders found",
130132
"PARTICIPANT": "Participant(s)",
131133
"PATIENT_HEADER_ACTION_AREA_IN_PROGRESS": "Consultation in progress",
132134
"PATIENT_HEADER_LABEL": "Patient Header",
133135
"PATIENT_HEADER_SHOW_ACTION_AREA": "New Consultation",
136+
"RADIOLOGY_ORDERED_BY": "Ordered By",
137+
"RADIOLOGY_INVESTIGATION_HEADING": "Radiology Investigations",
138+
"RADIOLOGY_PRIORITY_URGENT": "Urgent",
139+
"RADIOLOGY_RESULTS": "Results",
140+
"RADIOLOGY_TEST_NAME": "Test Name",
134141
"REACTIONS": "Reaction(s)",
135142
"SELECT_ENCOUNTER_TYPE": "Select encounter type",
136143
"SELECT_LOCATION": "Select location",

public/locales/locale_es.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"ERROR_FETCHING_DIAGNOSES": "Error al obtener datos de diagnósticos. Por favor, inténtelo de nuevo más tarde.",
8484
"ERROR_FETCHING_ENCOUNTER_DETAILS": "Error al obtener los detalles del encuentro",
8585
"ERROR_FETCHING_LOCATIONS_DETAILS": "Error al obtener detalles de ubicaciones",
86+
"ERROR_FETCHING_RADIOLOGY_ORDERS": "Error al obtener órdenes de radiología",
8687
"ERROR_FETCHING_PRACTITIONERS_DETAILS": "Error al obtener detalles de profesionales médicos",
8788
"ERROR_FETCHING_USER_DETAILS": "Error al obtener detalles del usuario",
8889
"ERROR_INVALID_PATIENT_UUID": "UUID de paciente inválido",
@@ -127,10 +128,16 @@
127128
"NO_DIAGNOSES": "No hay diagnósticos registrados",
128129
"NO_MATCHING_ALLERGEN_FOUND": "No se encontró ningún alérgeno coincidente",
129130
"NO_MATCHING_DIAGNOSIS_FOUND": "No se encontraron diagnósticos coincidentes",
131+
"NO_RADIOLOGY_ORDERS": "No se encontraron órdenes de radiología",
130132
"PARTICIPANT": "Partícipe(es)",
131133
"PATIENT_HEADER_ACTION_AREA_IN_PROGRESS": "Consulta en progreso",
132134
"PATIENT_HEADER_LABEL": "Cabecera del Paciente",
133135
"PATIENT_HEADER_SHOW_ACTION_AREA": "Nueva Consulta",
136+
"RADIOLOGY_ORDERED_BY": "Ordenado por",
137+
"RADIOLOGY_INVESTIGATION_HEADING": "Investigación de Radiología",
138+
"RADIOLOGY_PRIORITY_URGENT": "Urgente",
139+
"RADIOLOGY_RESULTS": "Resultados",
140+
"RADIOLOGY_TEST_NAME": "Nombre de la Prueba",
134141
"REACTIONS": "Reacción(es)",
135142
"SELECT_ENCOUNTER_TYPE": "Seleccionar tipo de encuentro",
136143
"SELECT_LOCATION": "Seleccionar ubicación",

src/components/clinical/dashboardSection/DashboardSection.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import { DashboardSectionConfig } from '@types/dashboardConfig';
44
import * as styles from './styles/DashboardSection.module.scss';
55
import AllergiesTable from '@displayControls/allergies/AllergiesTable';
66
import ConditionsTable from '@displayControls/conditions/ConditionsTable';
7-
import { useTranslation } from 'react-i18next';
87
import LabInvestigation from '@displayControls/labinvestigation/LabInvestigationControl';
98
import DiagnosesTable from '@displayControls/diagnoses/DiagnosesTable';
9+
import RadiologyOrdersTable from '@displayControls/radiologyInvestigation/RadiologyInvestigationTable';
10+
import { useTranslation } from 'react-i18next';
1011

1112
export interface DashboardSectionProps {
1213
section: DashboardSectionConfig;
13-
ref: React.RefObject<HTMLDivElement>;
14+
ref: React.RefObject<HTMLDivElement | null>;
1415
}
1516

1617
//TODO: Refactor this to depend on Controls configuration
@@ -27,6 +28,8 @@ const renderSectionContent = (section: DashboardSectionConfig) => {
2728
);
2829
case 'Lab Investigations':
2930
return <LabInvestigation />;
31+
case 'Radiology Investigations':
32+
return <RadiologyOrdersTable />;
3033
default:
3134
return null;
3235
}

src/components/clinical/dashboardSection/__tests__/DashboardSection.test.tsx

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ jest.mock('react-i18next', () => ({
2121
}),
2222
}));
2323

24+
// Mock CSS modules
25+
jest.mock('../styles/DashboardSection.module.scss', () => ({
26+
sectionTitle: 'sectionTitle',
27+
sectionTile: 'sectionTile',
28+
}));
29+
2430
// Mock the display control components
2531
jest.mock('@displayControls/allergies/AllergiesTable', () => ({
2632
__esModule: true,
@@ -39,9 +45,19 @@ jest.mock('@displayControls/labinvestigation/LabInvestigationControl', () => ({
3945

4046
jest.mock('@displayControls/diagnoses/DiagnosesTable', () => ({
4147
__esModule: true,
42-
default: () => <div data-testid="diagnoses-table">Lab Investigation</div>,
48+
default: () => <div data-testid="diagnoses-table">Diagnoses Table</div>,
4349
}));
4450

51+
jest.mock(
52+
'@displayControls/radiologyInvestigation/RadiologyInvestigationTable',
53+
() => ({
54+
__esModule: true,
55+
default: () => (
56+
<div data-testid="radiology-orders-table">Radiology Orders Table</div>
57+
),
58+
}),
59+
);
60+
4561
describe('DashboardSection Component', () => {
4662
const mockSection: DashboardSectionConfig = {
4763
id: 'test-section-id',
@@ -51,7 +67,7 @@ describe('DashboardSection Component', () => {
5167
};
5268

5369
// Handle for forwardRef in tests
54-
const mockRef = jest.fn();
70+
const mockRef = React.createRef<HTMLDivElement>();
5571

5672
it('renders with the correct section name', () => {
5773
render(<DashboardSection section={mockSection} ref={mockRef} />);
@@ -80,9 +96,9 @@ describe('DashboardSection Component', () => {
8096
});
8197

8298
it('accepts a ref prop', () => {
83-
const mockRefFn = jest.fn();
99+
const testRef = React.createRef<HTMLDivElement>();
84100

85-
render(<DashboardSection section={mockSection} ref={mockRefFn} />);
101+
render(<DashboardSection section={mockSection} ref={testRef} />);
86102

87103
// In a real component, we'd check ref.current
88104
// For our mocked component, we just verify the ref was passed
@@ -121,7 +137,7 @@ describe('DashboardSection Component', () => {
121137
expect(screen.getByTestId('allergies-table')).toBeInTheDocument();
122138
});
123139

124-
it('renders ConditionsTable when section name is Conditions', () => {
140+
it('renders both ConditionsTable and DiagnosesTable when section name is Conditions', () => {
125141
const conditionsSection: DashboardSectionConfig = {
126142
id: 'conditions-id',
127143
name: 'Conditions',
@@ -132,6 +148,7 @@ describe('DashboardSection Component', () => {
132148
render(<DashboardSection section={conditionsSection} ref={mockRef} />);
133149

134150
expect(screen.getByTestId('conditions-table')).toBeInTheDocument();
151+
expect(screen.getByTestId('diagnoses-table')).toBeInTheDocument();
135152
});
136153

137154
it('renders LabInvestigation when section name is Lab Investigations', () => {
@@ -149,6 +166,19 @@ describe('DashboardSection Component', () => {
149166
expect(screen.getByTestId('lab-investigation')).toBeInTheDocument();
150167
});
151168

169+
it('renders RadiologyOrdersTable when section name is Radiology Investigations', () => {
170+
const radiologySection: DashboardSectionConfig = {
171+
id: 'radiology-id',
172+
name: 'Radiology Investigations',
173+
icon: 'test-icon',
174+
controls: [],
175+
};
176+
177+
render(<DashboardSection section={radiologySection} ref={mockRef} />);
178+
179+
expect(screen.getByTestId('radiology-orders-table')).toBeInTheDocument();
180+
});
181+
152182
it('renders no content for unknown section types', () => {
153183
const unknownSection: DashboardSectionConfig = {
154184
id: 'unknown-section-id',

src/constants/app.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//TODO: Move Display Control URLs to a separate file
12
import {
23
HL7_CONDITION_CATEGORY_CONDITION_CODE,
34
HL7_CONDITION_CATEGORY_DIAGNOSIS_CODE,
@@ -11,6 +12,10 @@ const OPENMRS_REST_V1 = '/openmrs/ws/rest/v1';
1112
export const BAHMNI_USER_COOKIE_NAME = 'bahmni.user';
1213
export const BAHMNI_USER_LOCATION_COOKIE_NAME = 'bahmni.user.location';
1314

15+
//TODO: When we work on taking values dynamically, we need to remove the hardcoded value of LAB_ORDER_TYPE_UUID */
16+
export const LAB_ORDER_TYPE_UUID = 'd3560b17-5e07-11ef-8f7c-0242ac120002';
17+
export const RADIOLOGY_ORDER_TYPE_UUID = 'd3561dc0-5e07-11ef-8f7c-0242ac120002';
18+
1419
export const CONFIG_TRANSLATIONS_URL_TEMPLATE = (lang: string) =>
1520
`/bahmni_config/openmrs/i18n/clinical/locale_${lang}.json`;
1621
export const BUNDLED_TRANSLATIONS_URL_TEMPLATE = (lang: string) =>
@@ -36,6 +41,9 @@ export const PATIENT_DIAGNOSIS_RESOURCE_URL = (patientUUID: string) =>
3641
export const CLINICAL_CONFIG_URL =
3742
'/bahmni_config/openmrs/apps/clinical/v2/app.json';
3843
export const LOCATION_RESOURCE_URL = OPENMRS_REST_V1 + '/location';
44+
export const PATIENT_RADIOLOGY_RESOURCE_URL = (patientUUID: string) =>
45+
OPENMRS_FHIR_R4 +
46+
`/ServiceRequest?category=${RADIOLOGY_ORDER_TYPE_UUID}&patient=${patientUUID}&_count=100&_sort=-_lastUpdated&numberOfVisits=5`;
3947
export const ENCOUNTER_CONCEPTS_URL =
4048
OPENMRS_REST_V1 +
4149
'/bahmnicore/config/bahmniencounter?callerContext=REGISTRATION_CONCEPTS';
@@ -64,5 +72,3 @@ export const LOCALE_STORAGE_KEY = 'NG_TRANSLATE_LANG_KEY';
6472
export const CLINICAL_NAMESPACE = 'clinical';
6573
export const BAHMNI_HOME_PATH = '/bahmni/home/index.html';
6674
export const BAHMNI_CLINICAL_PATH = '/bahmni/clinical/index.html';
67-
//TODO: When we work on taking values dynamically, we need to remove the hardcoded value of LAB_ORDER_TYPE_UUID */
68-
export const LAB_ORDER_TYPE_UUID = 'd3560b17-5e07-11ef-8f7c-0242ac120002';

src/constants/date.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export const DATE_FORMAT = 'dd/MM/yyyy';
22
export const FULL_MONTH_DATE_FORMAT = 'MMMM dd, yyyy';
33
export const DATE_TIME_FORMAT = 'dd/MM/yyyy HH:mm';
4+
export const ISO_DATE_FORMAT = 'yyyy-MM-dd';

src/displayControls/diagnoses/DiagnosesTable.tsx

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import React, { useMemo } from 'react';
1+
import React, { useMemo, useCallback } from 'react';
22
import { Tag, Tile, DataTableSkeleton } from '@carbon/react';
33
import { useTranslation } from 'react-i18next';
44
import { ExpandableDataTable } from '@components/common/expandableDataTable/ExpandableDataTable';
55
import { useDiagnoses } from '@hooks/useDiagnoses';
6-
import { FormattedDiagnosis } from '@types/diagnosis';
6+
import { Diagnosis } from '@types/diagnosis';
77
import { formatDate } from '@utils/date';
8-
import { FULL_MONTH_DATE_FORMAT } from '@constants/date';
8+
import { FULL_MONTH_DATE_FORMAT, ISO_DATE_FORMAT } from '@constants/date';
99
import { sortDiagnosesByCertainty } from '@utils/diagnosis';
10+
import { groupByDate } from '@utils/common';
1011
import * as styles from './styles/DiagnosesTable.module.scss';
1112

1213
/**
@@ -34,16 +35,19 @@ const DiagnosesTable: React.FC = () => {
3435
[],
3536
);
3637

37-
// Sort diagnoses within each date group by certainty: confirmed → provisional
38-
const sortedDiagnoses = useMemo(() => {
39-
return diagnoses.map((diagnosisByDate) => ({
40-
...diagnosisByDate,
41-
diagnoses: sortDiagnosesByCertainty(diagnosisByDate.diagnoses),
38+
const processedDiagnoses = useMemo(() => {
39+
const grouped = groupByDate(diagnoses, (diagnosis) => {
40+
const result = formatDate(diagnosis.recordedDate, ISO_DATE_FORMAT);
41+
return result.formattedResult;
42+
});
43+
44+
return grouped.map((group) => ({
45+
date: group.date,
46+
diagnoses: sortDiagnosesByCertainty(group.items),
4247
}));
4348
}, [diagnoses]);
4449

45-
// Function to render cell content based on the cell ID
46-
const renderCell = (diagnosis: FormattedDiagnosis, cellId: string) => {
50+
const renderCell = useCallback((diagnosis: Diagnosis, cellId: string) => {
4751
switch (cellId) {
4852
case 'display':
4953
return (
@@ -65,7 +69,7 @@ const DiagnosesTable: React.FC = () => {
6569
case 'recorder':
6670
return diagnosis.recorder || t('DIAGNOSIS_TABLE_NOT_AVAILABLE');
6771
}
68-
};
72+
}, []);
6973

7074
return (
7175
<Tile
@@ -93,7 +97,7 @@ const DiagnosesTable: React.FC = () => {
9397
{!loading && !error && diagnoses.length === 0 && (
9498
<p className={styles.diagnosesTableBodyError}>{t('NO_DIAGNOSES')}</p>
9599
)}
96-
{sortedDiagnoses.map((diagnosisByDate, index) => {
100+
{processedDiagnoses.map((diagnosisByDate, index) => {
97101
const { date, diagnoses: diagnosisList } = diagnosisByDate;
98102

99103
// Format the date for display

0 commit comments

Comments
 (0)