Skip to content

Commit b4e959b

Browse files
BN-84 | Refactor. Consultation Pad To Use Stores And Hooks For State Management (#29)
* BN-84 | Add. Encounter Details store to manage state for BasicForm inputs * BN-84 | Refactor. Basic Form and Consultation Pad to use Encounter Details store * BN-84 | Fix. Tests for Consultation Pad after using Zustand Store * BN-84 | Refactor. Current encounter as active visit * BN-84 | Add. Show loading unitl details of BasicForm is loaded * BN-84 | Add. Missing test scenarios for Diagnosis store * BN-84 | Fix. Incorrect i18n Keys * BN-69 | Refactor. Use consultationDate For Bundle Creation * BN-69 | Add. Error Handling in encounterDetailsStore * BN-84 | Refactor. Handle Error Outside Of User Service * BN-84 | Refactor. Handle Error Outside Of Encounter Concept Service * BN-84 | Refactor. Handle Error Outside Of Location Service * BN-84 | Refactor. Handle Error Outside Of Encounter Hook * BN-84 | Refactor. Handle Error Outside Of ActivePractitioner Hook * BN-84 | Refactor. Handle Error Outside Of ActiveVisit Hook * BN-84 | Add. Inline Loading And Error State For BasicForm * BN-84 | Refactor. Get PatientUUID From Hook For BasicForm * BN-84 | Refactor. Set Pratitioner And User Details In BasicForm * BN-84 | Refactor. Set PatientUUID In BasicForm * BN-84 | Add. Button Disable Option For ActionArea * BN-84 | Refactor. Handle State Outside Of ConsultationPad * BN-84 | Fix. Path Aliases * BN-84 | Refactor. Remove Invalid Text For Each Field * BN-84 | Add. Error State In Consultation Pad * BN-84 | Add. Diagnoses Form Search Error State --------- Co-authored-by: MOHANKUMAR T <mohan13081999@gmail.com>
1 parent f704bc8 commit b4e959b

File tree

50 files changed

+5940
-2135
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+5940
-2135
lines changed

public/locales/locale_en.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
"CONSULTATION_ERROR_INVALID_PRACTITIONER": "Invalid or missing practitioner",
4646
"CONSULTATION_PAD_CANCEL_BUTTON": "Cancel",
4747
"CONSULTATION_PAD_DONE_BUTTON": "Done",
48-
"CONSULTATION_PAD_ERROR": "Something went wrong",
49-
"CONSULTATION_PAD_LOADING": "Loading Consultation Pad",
48+
"CONSULTATION_PAD_ERROR_TITLE": "Something went wrong",
49+
"CONSULTATION_PAD_ERROR_BODY": "An error occurred while loading the consultation pad. Please try again later.",
5050
"CONSULTATION_PAD_TITLE": "New Consultation",
5151
"CONSULTATION_SUBMITTED_SUCCESS_MESSAGE": "Consultation saved successfully",
5252
"CONSULTATION_SUBMITTED_SUCCESS_TITLE": "Success",
@@ -76,9 +76,15 @@
7676
"ERROR_DEFAULT_MESSAGE": "An unexpected error occurred",
7777
"ERROR_DEFAULT_TITLE": "Error",
7878
"ERROR_FETCHING_CONCEPTS": "An unexpected error occurred. Please try again later.",
79+
"ERROR_FETCHING_ENCOUNTER_DETAILS": "Error fetching encounter details",
80+
"ERROR_FETCHING_LOCATIONS_DETAILS": "Error fetching locations details",
81+
"ERROR_FETCHING_PRACTITIONERS_DETAILS": "Error fetching practitioners details",
82+
"ERROR_FETCHING_USER_DETAILS": "Error fetching user details",
83+
"ERROR_INVALID_PATIENT_UUID": "Invalid patient UUID",
7984
"ERROR_LOADING_DASHBOARD": "Error loading dashboard",
8085
"ERROR_NETWORK_MESSAGE": "Unable to connect to the server. Please check your internet connection.",
8186
"ERROR_NETWORK_TITLE": "Network Error",
87+
"ERROR_NO_ACTIVE_VISIT_FOUND": "No active visit found",
8288
"ERROR_NO_DEFAULT_DASHBOARD": "No default dashboard configured",
8389
"ERROR_NOT_FOUND_MESSAGE": "The requested resource was not found.",
8490
"ERROR_NOT_FOUND_TITLE": "Not Found",

public/locales/locale_es.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
"CONSULTATION_ERROR_INVALID_PRACTITIONER": "Profesional médico inválido o faltante",
4646
"CONSULTATION_PAD_CANCEL_BUTTON": "Cancelar",
4747
"CONSULTATION_PAD_DONE_BUTTON": "Hecho",
48-
"CONSULTATION_PAD_ERROR": "Algo salió mal",
49-
"CONSULTATION_PAD_LOADING": "Cargando Panel de Consulta",
48+
"CONSULTATION_PAD_ERROR_TITLE": "Algo salió mal",
49+
"CONSULTATION_PAD_ERROR_BODY": "Se produjo un error al cargar el panel de consulta. Por favor, inténtelo de nuevo más tarde.",
5050
"CONSULTATION_PAD_TITLE": "Nueva Consulta",
5151
"CONSULTATION_SUBMITTED_SUCCESS_MESSAGE": "Consulta salvado correctamente",
5252
"CONSULTATION_SUBMITTED_SUCCESS_TITLE": "Éxito",
@@ -76,9 +76,15 @@
7676
"ERROR_DEFAULT_MESSAGE": "Se produjo un error inesperado",
7777
"ERROR_DEFAULT_TITLE": "Error",
7878
"ERROR_FETCHING_CONCEPTS": "Se produjo un error inesperado. Por favor, inténtelo de nuevo mas tarde.",
79+
"ERROR_FETCHING_ENCOUNTER_DETAILS": "Error al obtener los detalles del encuentro",
80+
"ERROR_FETCHING_LOCATIONS_DETAILS": "Error al obtener detalles de ubicaciones",
81+
"ERROR_FETCHING_PRACTITIONERS_DETAILS": "Error al obtener detalles de profesionales médicos",
82+
"ERROR_FETCHING_USER_DETAILS": "Error al obtener detalles del usuario",
83+
"ERROR_INVALID_PATIENT_UUID": "UUID de paciente inválido",
7984
"ERROR_LOADING_DASHBOARD": "Error al cargar el panel",
8085
"ERROR_NETWORK_MESSAGE": "No se puede conectar al servidor. Por favor, compruebe su conexión a Internet.",
8186
"ERROR_NETWORK_TITLE": "Error de red",
87+
"ERROR_NO_ACTIVE_VISIT_FOUND": "No se encontró visita activa",
8288
"ERROR_NO_DEFAULT_DASHBOARD": "No hay panel predeterminado configurado",
8389
"ERROR_NOT_FOUND_MESSAGE": "No se encontró el recurso solicitado.",
8490
"ERROR_NOT_FOUND_TITLE": "No encontrado",
@@ -93,7 +99,7 @@
9399
"INVALID_RESPONSE": "Se recibió una respuesta no válida del servidor",
94100
"LAB_TEST_ACTIONS": "Acciones",
95101
"LAB_TEST_ERROR_LOADING": "Error al cargar pruebas de laboratorio",
96-
"LAB_TEST_LOADING": "Loading lab tests...",
102+
"LAB_TEST_LOADING": "Cargando pruebas de laboratorio...",
97103
"LAB_TEST_NAME": "Nombre de la prueba",
98104
"LAB_TEST_ORDERED_BY": "Ordenado por",
99105
"LAB_TEST_PANEL": "Panel",

src/__mocks__/consultationPadMocks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const mockPractitioner = {
3838
},
3939
};
4040

41-
export const mockCurrentEncounter = {
41+
export const mockActiveVisit = {
4242
id: 'encounter-1',
4343
type: [
4444
{

src/__mocks__/encounterMocks.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const mockEncounterBundle = {
1+
export const mockVisitBundle = {
22
resourceType: 'Bundle',
33
id: '2c8da098-d083-42bf-a2d4-cc3bb4c67199',
44
meta: {
@@ -227,5 +227,5 @@ export const mockEncounterBundle = {
227227
],
228228
};
229229

230-
// The current active encounter is the one without an end date
231-
export const mockCurrentEncounter = mockEncounterBundle.entry[3].resource;
230+
// The current active visit is the one without an end date
231+
export const mockActiveVisit = mockVisitBundle.entry[3].resource;

src/__mocks__/labInvestigationMocks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
LabTestStatus,
66
LabTestPriority,
77
LabTestsByDate,
8-
} from '@/types/labInvestigation';
8+
} from '@types/labInvestigation';
99

1010
// Mock patient UUID
1111
export const mockPatientUUID = 'test-patient-uuid';

src/components/clinical/consultationPad/ConsultationPad.tsx

Lines changed: 79 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
1-
import React from 'react';
1+
import React, { useEffect } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import ActionArea from '@components/common/actionArea/ActionArea';
4-
import { useCurrentEncounter } from '@hooks/useCurrentEncounter';
5-
import { useActivePractitioner } from '@hooks/useActivePractitioner';
6-
import { useEncounterConcepts } from '@hooks/useEncounterConcepts';
7-
import { useLocations } from '@hooks/useLocations';
8-
import { Column, Grid, Loading, MenuItemDivider } from '@carbon/react';
9-
import * as styles from './styles/ConsultationPad.module.scss';
4+
import { Column, Grid, MenuItemDivider } from '@carbon/react';
105
import BasicForm from '@components/clinical/forms/basic/BasicForm';
116
import DiagnosesForm from '@components/clinical/forms/diagnoses/DiagnosesForm';
127
import AllergiesForm from '@components/clinical/forms/allergies/AllergiesForm';
13-
import { Concept } from '@types/encounterConcepts';
148
import { ConsultationBundle } from '@types/consultationBundle';
159
import {
1610
postConsultationBundle,
1711
createDiagnosisBundleEntries,
1812
createAllergiesBundleEntries,
1913
} from '@services/consultationBundleService';
2014
import useNotification from '@hooks/useNotification';
21-
import { formatDate } from '@utils/date';
2215
import { createEncounterResource } from '@utils/fhir/encounterResourceCreator';
2316
import {
2417
createBundleEntry,
@@ -27,16 +20,14 @@ import {
2720
import { ERROR_TITLES } from '@constants/errors';
2821
import { useDiagnosisStore } from '@stores/diagnosisStore';
2922
import useAllergyStore from '@stores/allergyStore';
23+
import { useEncounterDetailsStore } from '@stores/encounterDetailsStore';
24+
import * as styles from './styles/ConsultationPad.module.scss';
3025

3126
interface ConsultationPadProps {
32-
patientUUID: string;
3327
onClose: () => void;
3428
}
3529

36-
const ConsultationPad: React.FC<ConsultationPadProps> = ({
37-
patientUUID,
38-
onClose,
39-
}) => {
30+
const ConsultationPad: React.FC<ConsultationPadProps> = ({ onClose }) => {
4031
const [isSubmitting, setIsSubmitting] = React.useState(false);
4132

4233
const { t } = useTranslation();
@@ -54,63 +45,55 @@ const ConsultationPad: React.FC<ConsultationPadProps> = ({
5445
validateAllAllergies,
5546
reset: resetAllergies,
5647
} = useAllergyStore();
57-
58-
const {
59-
locations,
60-
loading: loadingLocations,
61-
error: errorLocations,
62-
} = useLocations();
63-
const {
64-
encounterConcepts,
65-
loading: loadingEncounterConcepts,
66-
error: errorEncounterConcepts,
67-
} = useEncounterConcepts();
68-
48+
// Use the encounter details store
6949
const {
50+
activeVisit,
51+
selectedLocation,
52+
selectedEncounterType,
53+
encounterParticipants,
54+
consultationDate,
55+
isEncounterDetailsFormReady,
7056
practitioner,
7157
user,
72-
loading: loadingPractitioner,
73-
error: errorPractitioner,
74-
} = useActivePractitioner();
75-
76-
const {
77-
currentEncounter,
78-
loading: loadingEncounter,
79-
error: errorEncounter,
80-
} = useCurrentEncounter(patientUUID);
81-
82-
const encounterTypeSelected = encounterConcepts?.encounterTypes.find(
83-
(item: Concept) => item.name === 'Consultation',
84-
);
85-
86-
const currentEncounterId = currentEncounter?.type[0]?.coding[0]?.code || '';
87-
88-
const visitTypeSelected = encounterConcepts?.visitTypes.find(
89-
(item: Concept) => item.uuid === currentEncounterId,
90-
);
91-
92-
const formattedDate = formatDate(new Date());
58+
patientUUID,
59+
hasError,
60+
reset: resetEncounterDetails,
61+
} = useEncounterDetailsStore();
62+
63+
// Clean up on unmount
64+
useEffect(() => {
65+
return () => {
66+
resetEncounterDetails();
67+
resetAllergies();
68+
resetDiagnoses();
69+
};
70+
}, [resetEncounterDetails, resetAllergies, resetDiagnoses]);
9371

9472
// Data validation check for consultation submission
9573
const canSubmitConsultation = !!(
9674
patientUUID &&
9775
practitioner &&
9876
practitioner.uuid &&
99-
currentEncounter &&
100-
locations?.length > 0 &&
101-
encounterTypeSelected
77+
activeVisit &&
78+
selectedLocation &&
79+
selectedEncounterType &&
80+
encounterParticipants.length > 0
10281
);
10382

83+
// TODO: Extract Business Logic
84+
// 1. Create a consultationService to handle submission logic
85+
// 2. Extract validation logic into a custom hook
86+
// 3. Create utility functions for bundle creation
10487
const submitConsultation = () => {
10588
const enconterResourceURL = `urn:uuid:${crypto.randomUUID()}`;
10689
const encounterResource = createEncounterResource(
107-
encounterTypeSelected!.uuid,
108-
encounterTypeSelected!.name,
109-
patientUUID,
110-
[practitioner!.uuid],
111-
currentEncounter!.id,
112-
locations[0].uuid,
113-
new Date(),
90+
selectedEncounterType!.uuid,
91+
selectedEncounterType!.name,
92+
patientUUID!,
93+
encounterParticipants.map((p) => p.uuid),
94+
activeVisit!.id,
95+
selectedLocation!.uuid,
96+
consultationDate,
11497
);
11598
const encounterBundleEntry = createBundleEntry(
11699
enconterResourceURL,
@@ -122,6 +105,7 @@ const ConsultationPad: React.FC<ConsultationPadProps> = ({
122105
encounterSubject: encounterResource.subject!,
123106
encounterReference: enconterResourceURL,
124107
practitionerUUID: user!.uuid,
108+
consultationDate,
125109
});
126110

127111
const allergyEntries = createAllergiesBundleEntries({
@@ -154,6 +138,7 @@ const ConsultationPad: React.FC<ConsultationPadProps> = ({
154138
setIsSubmitting(false);
155139
resetDiagnoses();
156140
resetAllergies();
141+
resetEncounterDetails();
157142
addNotification({
158143
title: t('CONSULTATION_SUBMITTED_SUCCESS_TITLE'),
159144
message: t('CONSULTATION_SUBMITTED_SUCCESS_MESSAGE'),
@@ -182,92 +167,50 @@ const ConsultationPad: React.FC<ConsultationPadProps> = ({
182167
onClose();
183168
};
184169

185-
if (
186-
loadingEncounterConcepts ||
187-
loadingLocations ||
188-
loadingPractitioner ||
189-
loadingEncounter ||
190-
isSubmitting
191-
) {
192-
return (
193-
<ActionArea
194-
title={t('CONSULTATION_PAD_TITLE')}
195-
primaryButtonText={t('CONSULTATION_PAD_DONE_BUTTON')}
196-
onPrimaryButtonClick={handleOnPrimaryButtonClick}
197-
secondaryButtonText={t('CONSULTATION_PAD_CANCEL_BUTTON')}
198-
onSecondaryButtonClick={handleOnSecondaryButtonClick}
199-
content={
200-
<Grid>
201-
<Column sm={3} md={8} lg={16} className={styles.loadingContent}>
202-
<Loading
203-
description={t('CONSULTATION_PAD_LOADING')}
204-
withOverlay={false}
205-
/>
206-
</Column>
207-
</Grid>
208-
}
209-
/>
210-
);
211-
}
212-
213-
if (
214-
errorLocations ||
215-
errorEncounterConcepts ||
216-
errorPractitioner ||
217-
errorEncounter ||
218-
!practitioner ||
219-
!patientUUID ||
220-
!encounterConcepts?.encounterTypes ||
221-
!encounterConcepts?.visitTypes ||
222-
!currentEncounter ||
223-
!visitTypeSelected ||
224-
!encounterTypeSelected ||
225-
!locations ||
226-
locations.length === 0 ||
227-
formattedDate.error
228-
) {
229-
return (
230-
<ActionArea
231-
title={t('CONSULTATION_PAD_TITLE')}
232-
primaryButtonText={t('CONSULTATION_PAD_DONE_BUTTON')}
233-
onPrimaryButtonClick={handleOnPrimaryButtonClick}
234-
secondaryButtonText={t('CONSULTATION_PAD_CANCEL_BUTTON')}
235-
onSecondaryButtonClick={handleOnSecondaryButtonClick}
236-
content={
237-
<Grid>
238-
<Column sm={4} md={8} lg={16}>
239-
<h2>{t('CONSULTATION_PAD_ERROR')}</h2>
240-
</Column>
241-
</Grid>
242-
}
243-
/>
244-
);
245-
}
246-
247170
return (
248171
<ActionArea
249-
title={t('CONSULTATION_PAD_TITLE')}
172+
title={hasError ? '' : t('CONSULTATION_PAD_TITLE')}
250173
primaryButtonText={t('CONSULTATION_PAD_DONE_BUTTON')}
251174
onPrimaryButtonClick={handleOnPrimaryButtonClick}
175+
isPrimaryButtonDisabled={
176+
!isEncounterDetailsFormReady || !canSubmitConsultation || isSubmitting
177+
}
252178
secondaryButtonText={t('CONSULTATION_PAD_CANCEL_BUTTON')}
253179
onSecondaryButtonClick={handleOnSecondaryButtonClick}
254180
content={
255-
<>
256-
<BasicForm
257-
practitioner={practitioner}
258-
encounterTypes={encounterConcepts.encounterTypes}
259-
encounterTypeSelected={encounterTypeSelected}
260-
visitTypes={encounterConcepts.visitTypes}
261-
visitTypeSelected={visitTypeSelected}
262-
location={locations[0]}
263-
locationSelected={locations[0]}
264-
defaultDate={formattedDate.formattedResult}
265-
/>
266-
<DiagnosesForm />
267-
<MenuItemDivider />
268-
<AllergiesForm />
269-
<MenuItemDivider />
270-
</>
181+
hasError ? (
182+
<>
183+
<Grid className={styles.emptyState}>
184+
<Column
185+
sm={4}
186+
md={8}
187+
lg={16}
188+
xlg={16}
189+
className={styles.emptyStateTitle}
190+
>
191+
{t('CONSULTATION_PAD_ERROR_TITLE')}
192+
</Column>
193+
<Column
194+
sm={4}
195+
md={8}
196+
lg={16}
197+
xlg={16}
198+
className={styles.emptyStateBody}
199+
>
200+
{t('CONSULTATION_PAD_ERROR_BODY')}
201+
</Column>
202+
</Grid>
203+
</>
204+
) : (
205+
<>
206+
<BasicForm />
207+
<MenuItemDivider />
208+
<DiagnosesForm />
209+
<MenuItemDivider />
210+
<AllergiesForm />
211+
<MenuItemDivider />
212+
</>
213+
)
271214
}
272215
/>
273216
);

0 commit comments

Comments
 (0)