@palladium-ethiopia/esm-clinical-workflow-app 5.4.2-pre.20 → 5.4.2-pre.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/dist/152.js +1 -1
- package/dist/152.js.map +1 -1
- package/dist/164.js +1 -0
- package/dist/164.js.map +1 -0
- package/dist/208.js +1 -1
- package/dist/208.js.map +1 -1
- package/dist/209.js +1 -1
- package/dist/209.js.map +1 -1
- package/dist/363.js +1 -1
- package/dist/363.js.map +1 -1
- package/dist/534.js +1 -0
- package/dist/534.js.map +1 -0
- package/dist/677.js +1 -1
- package/dist/677.js.map +1 -1
- package/dist/689.js +1 -1
- package/dist/689.js.map +1 -1
- package/dist/712.js +1 -1
- package/dist/712.js.map +1 -1
- package/dist/771.js +1 -1
- package/dist/771.js.map +1 -1
- package/dist/825.js +1 -0
- package/dist/825.js.map +1 -0
- package/dist/914.js +37 -0
- package/dist/914.js.map +1 -0
- package/dist/926.js +17 -0
- package/dist/926.js.map +1 -0
- package/dist/ethiopia-esm-clinical-workflow-app.js +5 -5
- package/dist/ethiopia-esm-clinical-workflow-app.js.buildmanifest.json +144 -144
- package/dist/ethiopia-esm-clinical-workflow-app.js.map +1 -1
- package/dist/main.js +34 -8
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/config-schema.ts +98 -0
- package/src/index.ts +32 -1
- package/src/patient-chart/clinical-views/hooks/useEncountersByVisit.ts +13 -0
- package/src/patient-chart/constants.ts +11 -0
- package/src/patient-chart/visit/visit-history-table/diagnosis-tags.component.tsx +43 -0
- package/src/patient-chart/visit/visit-history-table/diagnosis-tags.module.scss +57 -0
- package/src/patient-chart/visit/visit-history-table/visit-actions-cell.component.tsx +20 -0
- package/src/patient-chart/visit/visit-history-table/visit-actions-cell.scss +4 -0
- package/src/patient-chart/visit/visit-history-table/visit-date-cell.component.tsx +19 -0
- package/src/patient-chart/visit/visit-history-table/visit-diagnoses-cell-with-certainty.component.tsx +31 -0
- package/src/patient-chart/visit/visit-history-table/visit-diagnoses-cell-with-certainty.module.scss +16 -0
- package/src/patient-chart/visit/visit-history-table/visit-history-table.component.tsx +144 -0
- package/src/patient-chart/visit/visit-history-table/visit-history-table.scss +25 -0
- package/src/patient-chart/visit/visit-history-table/visit-type-cell.component.tsx +15 -0
- package/src/patient-chart/visit/visits-widget/encounter-observations/encounter-observations.component.tsx +67 -0
- package/src/patient-chart/visit/visits-widget/encounter-observations/index.ts +3 -0
- package/src/patient-chart/visit/visits-widget/encounter-observations/styles.scss +22 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/all-encounters-table.component.tsx +44 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/encounters-table.component.tsx +388 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/encounters-table.resource.ts +97 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/encounters-table.scss +113 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/encounters-table/visit-encounters-table.component.tsx +42 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/medications-summary.component.tsx +157 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/notes-summary.component.tsx +34 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/tests-summary.component.tsx +16 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/visit-actions-cell.scss +4 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/visit-summary.component.tsx +176 -0
- package/src/patient-chart/visit/visits-widget/past-visits-components/visit-summary.scss +72 -0
- package/src/patient-chart/visit/visits-widget/single-visit-details/visit-timeline/visit-timeline.component.tsx +94 -0
- package/src/patient-chart/visit/visits-widget/single-visit-details/visit-timeline/visit-timeline.scss +60 -0
- package/src/patient-chart/visit/visits-widget/visit-detail-overview.component.tsx +50 -0
- package/src/patient-chart/visit/visits-widget/visit-detail-overview.scss +262 -0
- package/src/patient-chart/visit/visits-widget/visit.resource.tsx +144 -0
- package/src/patient-notes/types/index.ts +194 -0
- package/src/patient-notes/visit-note-action-button.extension.tsx +28 -0
- package/src/patient-notes/visit-note-config-schema.ts +38 -0
- package/src/patient-notes/visit-notes-form-shadow.workspace.tsx +963 -0
- package/src/patient-notes/visit-notes-form.scss +453 -0
- package/src/patient-notes/visit-notes.resource.ts +113 -0
- package/src/routes.json +23 -0
- package/translations/am.json +168 -0
- package/translations/en.json +168 -0
- package/dist/410.js +0 -1
- package/dist/410.js.map +0 -1
- package/dist/484.js +0 -11
- package/dist/484.js.map +0 -1
- package/dist/540.js +0 -1
- package/dist/540.js.map +0 -1
- package/dist/545.js +0 -43
- package/dist/545.js.map +0 -1
package/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[],"extensions":[{"name":"clinical-workflow-triage-dashboard-link","component":"triageDashboardLink","slot":"homepage-dashboard-slot","order":0,"meta":{"name":"clinical-workflow-triage","slot":"clinical-workflow-triage-dashboard-slot","title":"clinical-workflow-triage"}},{"name":"clinical-workflow-triage-dashboard","component":"triageDashboard","slot":"clinical-workflow-triage-dashboard-slot"},{"name":"ewf-mru-dashboard-link","component":"mruLeftPanelLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"mru","title":"MRU","path":"mru","slot":"mru-dashboard-slot"}},{"name":"ewf-mru-dashboard","component":"mruDashboard","slot":"mru-dashboard-slot","online":true,"offline":false},{"name":"patient-scoreboard-link","component":"patientScoreboardLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"patient-scoreboard","title":"Patient Scoreboard","path":"patient-scoreboard","slot":"patient-scoreboard-slot"}},{"name":"patient-scoreboard","component":"patientScoreboard","slot":"patient-scoreboard-slot","online":true,"offline":false}],"workspaces":[{"name":"triage-workspace","component":"triageWorkspace","title":"Triage Form","canMaximize":true,"type":"clinical-form"},{"name":"patient-registration-workspace","component":"patientRegistrationWorkspace","title":"Patient Registration","canMaximize":true,"type":"registration-form"},{"name":"billing-information-workspace","component":"billingInformationWorkspace","title":"Billing Information Workspace","type":"clinical-form"}],"workspaces2":[{"name":"clinical-workflow-patient-form-entry-workspace","component":"@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace","window":"clinical-workflow-window"}],"workspaceWindows2":[{"name":"clinical-workflow-window","group":"clinical-workflow-group","width":"wider","canMaximize":true}],"workspaceGroups2":[{"name":"clinical-workflow-group","overlay":true,"persistence":"closable"}],"version":"5.4.2-pre.
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"pages":[],"extensions":[{"name":"clinical-workflow-triage-dashboard-link","component":"triageDashboardLink","slot":"homepage-dashboard-slot","order":0,"meta":{"name":"clinical-workflow-triage","slot":"clinical-workflow-triage-dashboard-slot","title":"clinical-workflow-triage"}},{"name":"clinical-workflow-triage-dashboard","component":"triageDashboard","slot":"clinical-workflow-triage-dashboard-slot"},{"name":"ewf-mru-dashboard-link","component":"mruLeftPanelLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"mru","title":"MRU","path":"mru","slot":"mru-dashboard-slot"}},{"name":"ewf-mru-dashboard","component":"mruDashboard","slot":"mru-dashboard-slot","online":true,"offline":false},{"name":"patient-scoreboard-link","component":"patientScoreboardLink","slot":"homepage-dashboard-slot","order":1,"meta":{"name":"patient-scoreboard","title":"Patient Scoreboard","path":"patient-scoreboard","slot":"patient-scoreboard-slot"}},{"name":"patient-scoreboard","component":"patientScoreboard","slot":"patient-scoreboard-slot","online":true,"offline":false},{"name":"past-visits-detail-overview-shadow","slot":"patient-chart-encounters-dashboard-slot","component":"pastVisitsDetailOverviewShadow","order":0,"meta":{"title":"Visits","view":"visits"},"online":true,"offline":true}],"workspaces":[{"name":"triage-workspace","component":"triageWorkspace","title":"Triage Form","canMaximize":true,"type":"clinical-form"},{"name":"patient-registration-workspace","component":"patientRegistrationWorkspace","title":"Patient Registration","canMaximize":true,"type":"registration-form"},{"name":"billing-information-workspace","component":"billingInformationWorkspace","title":"Billing Information Workspace","type":"clinical-form"}],"workspaces2":[{"name":"clinical-workflow-patient-form-entry-workspace","component":"@openmrs/esm-patient-forms-app#exportedPatientFormEntryWorkspace","window":"clinical-workflow-window"},{"name":"visit-notes-form-shadow-workspace","component":"visitNotesFormWorkspace","window":"visit-note-shadow"}],"workspaceWindows2":[{"name":"clinical-workflow-window","group":"clinical-workflow-group","width":"wider","canMaximize":true},{"name":"visit-note-shadow","group":"patient-chart","icon":"visitNoteActionButton","order":3}],"workspaceGroups2":[{"name":"clinical-workflow-group","overlay":true,"persistence":"closable"}],"version":"5.4.2-pre.26"}
|
package/package.json
CHANGED
package/src/config-schema.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from '@openmrs/esm-framework';
|
|
2
|
+
import notesConfigSchema, { type VisitNoteConfigObject } from './patient-notes/visit-note-config-schema';
|
|
2
3
|
|
|
3
4
|
export const configSchema = {
|
|
4
5
|
triageLocationForms: {
|
|
@@ -46,6 +47,54 @@ export const configSchema = {
|
|
|
46
47
|
_description: 'Patient attribute type UUID for Medico Legal Cases',
|
|
47
48
|
_default: '',
|
|
48
49
|
},
|
|
50
|
+
diagnosisConceptClass: {
|
|
51
|
+
_type: Type.UUID,
|
|
52
|
+
_default: '8d4918b0-c2cc-11de-8d13-0010c6dffd0f',
|
|
53
|
+
_description: 'The concept class UUID for diagnoses',
|
|
54
|
+
},
|
|
55
|
+
isPrimaryDiagnosisRequired: {
|
|
56
|
+
_type: Type.Boolean,
|
|
57
|
+
_default: true,
|
|
58
|
+
_description: 'Indicates whether a primary diagnosis is required when submitting a visit note',
|
|
59
|
+
},
|
|
60
|
+
visitNoteConfig: notesConfigSchema,
|
|
61
|
+
disableEmptyTabs: {
|
|
62
|
+
_type: Type.Boolean,
|
|
63
|
+
_default: false,
|
|
64
|
+
_description: 'Disable notes/tests/medications/encounters tabs when empty',
|
|
65
|
+
},
|
|
66
|
+
encounterEditableDuration: {
|
|
67
|
+
_type: Type.Number,
|
|
68
|
+
_default: 0,
|
|
69
|
+
_description:
|
|
70
|
+
'The number of minutes an encounter is editable after it is created. 0 means the encounter is editable forever.',
|
|
71
|
+
},
|
|
72
|
+
encounterEditableDurationOverridePrivileges: {
|
|
73
|
+
_type: Type.Array,
|
|
74
|
+
_elements: {
|
|
75
|
+
_type: Type.String,
|
|
76
|
+
},
|
|
77
|
+
_default: [],
|
|
78
|
+
_description:
|
|
79
|
+
'The privileges that allow users to edit encounters even after the editable duration (set by `encounterEditableDuration`) has expired. Any privilege in the list is sufficient to edit the encounter.',
|
|
80
|
+
},
|
|
81
|
+
notesConceptUuids: {
|
|
82
|
+
_type: Type.Array,
|
|
83
|
+
_elements: {
|
|
84
|
+
_type: Type.ConceptUuid,
|
|
85
|
+
},
|
|
86
|
+
_default: ['162169AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
|
|
87
|
+
},
|
|
88
|
+
showAllEncountersTab: {
|
|
89
|
+
_type: Type.Boolean,
|
|
90
|
+
_description: 'Shows the All Encounters Tab of Patient Visits section in Patient Chart',
|
|
91
|
+
_default: true,
|
|
92
|
+
},
|
|
93
|
+
drugOrderTypeUUID: {
|
|
94
|
+
_type: Type.UUID,
|
|
95
|
+
_description: "UUID for the 'Drug' order type to fetch medications",
|
|
96
|
+
_default: '131168f4-15f5-102d-96e4-000c29c2a5d7',
|
|
97
|
+
},
|
|
49
98
|
};
|
|
50
99
|
|
|
51
100
|
export type ClinicalWorkflowConfig = {
|
|
@@ -67,3 +116,52 @@ export type ClinicalWorkflowConfig = {
|
|
|
67
116
|
defaultIdentifierTypeUuid: string;
|
|
68
117
|
medicoLegalCasesAttributeTypeUuid: string;
|
|
69
118
|
};
|
|
119
|
+
|
|
120
|
+
export interface VisitNoteConfig {
|
|
121
|
+
diagnosisConceptClass: string;
|
|
122
|
+
isPrimaryDiagnosisRequired: boolean;
|
|
123
|
+
visitNoteConfig: VisitNoteConfigObject;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface ChartConfig {
|
|
127
|
+
defaultFacilityUrl: string;
|
|
128
|
+
disableChangingVisitLocation: boolean;
|
|
129
|
+
disableEmptyTabs: boolean;
|
|
130
|
+
encounterEditableDuration: number;
|
|
131
|
+
encounterEditableDurationOverridePrivileges: Array<string>;
|
|
132
|
+
freeTextFieldConceptUuid: string;
|
|
133
|
+
logo: {
|
|
134
|
+
alt: string;
|
|
135
|
+
name: string;
|
|
136
|
+
src: string;
|
|
137
|
+
};
|
|
138
|
+
notesConceptUuids: string[];
|
|
139
|
+
offlineVisitTypeUuid: string;
|
|
140
|
+
restrictByVisitLocationTag: boolean;
|
|
141
|
+
showAllEncountersTab: boolean;
|
|
142
|
+
showRecommendedVisitTypeTab: boolean;
|
|
143
|
+
showServiceQueueFields: boolean; // used by extension from esm-service-queues-app
|
|
144
|
+
showUpcomingAppointments: boolean; // used by extension from esm-appointments-app
|
|
145
|
+
visitTypeResourceUrl: string;
|
|
146
|
+
visitAttributeTypes: Array<{
|
|
147
|
+
displayInThePatientBanner: boolean;
|
|
148
|
+
required: boolean;
|
|
149
|
+
showWhenExpression?: string;
|
|
150
|
+
uuid: string;
|
|
151
|
+
}>;
|
|
152
|
+
visitDiagnosisConceptUuid: string;
|
|
153
|
+
requireActiveVisitForEncounterTile: boolean;
|
|
154
|
+
trueConceptUuid: string;
|
|
155
|
+
falseConceptUuid: string;
|
|
156
|
+
tileDefinitions: Array<{
|
|
157
|
+
title: string;
|
|
158
|
+
columns: Array<{
|
|
159
|
+
title: string;
|
|
160
|
+
concept: string;
|
|
161
|
+
encounterType: string;
|
|
162
|
+
hasSummary?: boolean;
|
|
163
|
+
}>;
|
|
164
|
+
}>;
|
|
165
|
+
otherConceptUuid: string;
|
|
166
|
+
drugOrderTypeUUID: string;
|
|
167
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineConfigSchema, getAsyncLifecycle, getSyncLifecycle } from '@openmrs/esm-framework';
|
|
1
|
+
import { defineConfigSchema, getAsyncLifecycle, getGlobalStore, getSyncLifecycle } from '@openmrs/esm-framework';
|
|
2
2
|
import { createDashboardLink } from './createDashboardLink';
|
|
3
3
|
import { configSchema } from './config-schema';
|
|
4
4
|
import { dashboardMeta } from './dashboard.meta';
|
|
@@ -6,6 +6,8 @@ import MRUDashboard from './mru/dashboard.component';
|
|
|
6
6
|
import { spaBasePath } from './constants';
|
|
7
7
|
import BillingInformationWorkspace from './mru/billing-information/billing-information.workspace';
|
|
8
8
|
import PatientScoreboard from './patient-scoreboard/patient-scoreboard.component';
|
|
9
|
+
import visitNotesActionButtonExtension from './patient-notes/visit-note-action-button.extension';
|
|
10
|
+
import pastVisitsOverviewComponent from './patient-chart/visit/visits-widget/visit-detail-overview.component';
|
|
9
11
|
|
|
10
12
|
const moduleName = '@ethiopia/esm-clinical-workflow-app';
|
|
11
13
|
|
|
@@ -16,6 +18,23 @@ const options = {
|
|
|
16
18
|
|
|
17
19
|
export function startupApp() {
|
|
18
20
|
defineConfigSchema(moduleName, configSchema);
|
|
21
|
+
|
|
22
|
+
// Remove the core visit-note workspace window since we're providing a custom one
|
|
23
|
+
const workspace2Store = getGlobalStore<any>('workspace2');
|
|
24
|
+
const removeCoreButton = () => {
|
|
25
|
+
const state = workspace2Store.getState();
|
|
26
|
+
if (state.registeredWindowsByName['visit-note']) {
|
|
27
|
+
const newWindows = { ...state.registeredWindowsByName };
|
|
28
|
+
delete newWindows['visit-note'];
|
|
29
|
+
workspace2Store.setState({
|
|
30
|
+
registeredWindowsByName: newWindows,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Try immediately and also subscribe to handle cases where the core module registers later
|
|
36
|
+
removeCoreButton();
|
|
37
|
+
workspace2Store.subscribe(removeCoreButton);
|
|
19
38
|
}
|
|
20
39
|
|
|
21
40
|
export const root = getAsyncLifecycle(() => import('./root.component'), options);
|
|
@@ -49,3 +68,15 @@ export const patientScoreboardLink = getSyncLifecycle(
|
|
|
49
68
|
);
|
|
50
69
|
|
|
51
70
|
export const patientScoreboard = getSyncLifecycle(PatientScoreboard, options);
|
|
71
|
+
|
|
72
|
+
export const visitNoteActionButton = getSyncLifecycle(visitNotesActionButtonExtension, options);
|
|
73
|
+
|
|
74
|
+
export const visitNotesFormWorkspace = getAsyncLifecycle(
|
|
75
|
+
() => import('./patient-notes/visit-notes-form-shadow.workspace'),
|
|
76
|
+
options,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
export const pastVisitsDetailOverviewShadow = getSyncLifecycle(pastVisitsOverviewComponent, {
|
|
80
|
+
featureName: 'visits-detail-overview',
|
|
81
|
+
moduleName,
|
|
82
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Encounter, restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
export function useEncountersByVisit(patientUuid: string, visitUuid: string) {
|
|
4
|
+
const customRepresentation =
|
|
5
|
+
'custom:(uuid,encounterType:(uuid,display),encounterProviders:(provider:(person:(display))),encounterDatetime,visit:(uuid))';
|
|
6
|
+
const url = `${restBaseUrl}/encounter?patient=${patientUuid}&order=desc&visit=${visitUuid}&v=${customRepresentation}`;
|
|
7
|
+
const { data: encounters, ...rest } = useOpenmrsFetchAll<Encounter>(url);
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
encounters,
|
|
11
|
+
...rest,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const clinicalFormsWorkspace = 'clinical-forms-workspace';
|
|
2
|
+
export const formEntryWorkspace = 'patient-form-entry-workspace';
|
|
3
|
+
export const spaRoot = window['getOpenmrsSpaBase']();
|
|
4
|
+
export const basePath = '/patient/:patientUuid/chart';
|
|
5
|
+
export const dashboardPath = `${basePath}/:view/*`;
|
|
6
|
+
export const spaBasePath = `${window.spaBase}${basePath}`;
|
|
7
|
+
export const moduleName = '@openmrs/esm-patient-chart-app';
|
|
8
|
+
export const patientChartWorkspaceSlot = 'patient-chart-workspace-slot';
|
|
9
|
+
export const patientChartWorkspaceHeaderSlot = 'patient-chart-workspace-header-slot';
|
|
10
|
+
export const omrsDateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZZ';
|
|
11
|
+
export const jsonSchemaResourceName = 'JSON schema';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tag } from '@carbon/react';
|
|
3
|
+
import { CheckmarkFilled, Help } from '@carbon/react/icons';
|
|
4
|
+
import { type Diagnosis } from '@openmrs/esm-emr-api';
|
|
5
|
+
import styles from './diagnosis-tags.module.scss';
|
|
6
|
+
|
|
7
|
+
interface DiagnosisTagsProps {
|
|
8
|
+
diagnoses: Array<Diagnosis>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const DiagnosisTags: React.FC<DiagnosisTagsProps> = ({ diagnoses = [] }) => {
|
|
12
|
+
if (!diagnoses || diagnoses.length === 0) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className={styles.diagnosesText}>
|
|
18
|
+
{diagnoses.map((diagnosis, index) => {
|
|
19
|
+
const { uuid, display, certainty = 'PROVISIONAL', rank } = diagnosis;
|
|
20
|
+
const color = rank === 1 ? 'red' : 'blue';
|
|
21
|
+
const displayText = display.length > 30 ? `${display.substring(0, 30)}...` : display;
|
|
22
|
+
const certaintyText = certainty === 'CONFIRMED' ? 'Confirmed' : 'Presumed';
|
|
23
|
+
const fullTooltip = `${display} (${certaintyText})`;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
/* The title goes here on the wrapper */
|
|
27
|
+
<div key={uuid || index} className={styles.tagWrapper} title={fullTooltip}>
|
|
28
|
+
<Tag className={styles.tag} type={color}>
|
|
29
|
+
<span className={styles.tagContent}>
|
|
30
|
+
<span className={styles.textLabel}>{displayText}</span>
|
|
31
|
+
{certainty === 'CONFIRMED' ? (
|
|
32
|
+
<CheckmarkFilled size={14} className={`${styles.tagIcon} ${styles.confirmedIcon}`} />
|
|
33
|
+
) : (
|
|
34
|
+
<Help size={14} className={`${styles.tagIcon} ${styles.presumedIcon}`} />
|
|
35
|
+
)}
|
|
36
|
+
</span>
|
|
37
|
+
</Tag>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
})}
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
@use '@carbon/colors';
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/type';
|
|
4
|
+
|
|
5
|
+
.diagnosesText {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-wrap: wrap;
|
|
8
|
+
gap: layout.$spacing-03;
|
|
9
|
+
margin-bottom: layout.$spacing-05;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.tagWrapper {
|
|
13
|
+
display: inline-flex;
|
|
14
|
+
/* Makes the wrapper behave like the tag for the mouse */
|
|
15
|
+
height: fit-content;
|
|
16
|
+
width: fit-content;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.tag {
|
|
20
|
+
max-width: layout.rem(320px);
|
|
21
|
+
margin: 0 !important;
|
|
22
|
+
/* Prevents internal Carbon elements from stealing hover event */
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
|
|
25
|
+
:global(.cds--tag__label) {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
max-width: 100%;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.tagContent {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: layout.$spacing-02;
|
|
36
|
+
width: 100%;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.textLabel {
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
text-overflow: ellipsis;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tagIcon {
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.confirmedIcon {
|
|
52
|
+
color: colors.$green-60;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.presumedIcon {
|
|
56
|
+
color: colors.$orange-60;
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ExtensionSlot, type Visit } from '@openmrs/esm-framework';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styles from './visit-actions-cell.scss';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
visit: Visit;
|
|
7
|
+
patient: fhir.Patient;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const VisitActionsCell: React.FC<Props> = ({ visit, patient }) => {
|
|
11
|
+
return (
|
|
12
|
+
<ExtensionSlot
|
|
13
|
+
name="visit-detail-overview-actions"
|
|
14
|
+
className={styles.visitActions}
|
|
15
|
+
state={{ patientUuid: visit.patient.uuid, patient, visit, compact: true }}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default VisitActionsCell;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { formatDate, type Visit } from '@openmrs/esm-framework';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
visit: Visit;
|
|
7
|
+
patient: fhir.Patient;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const VisitDateCell: React.FC<Props> = ({ visit }) => {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
const { startDatetime, stopDatetime } = visit;
|
|
13
|
+
const fromDate = formatDate(new Date(startDatetime));
|
|
14
|
+
const toDate = stopDatetime ? formatDate(new Date(stopDatetime)) : null;
|
|
15
|
+
|
|
16
|
+
return <>{toDate ? t('fromDateToDate', '{{fromDate}} - {{toDate}}', { fromDate, toDate }) : fromDate}</>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default VisitDateCell;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tag } from '@carbon/react';
|
|
3
|
+
import { type Visit } from '@openmrs/esm-framework';
|
|
4
|
+
import { DiagnosisTags } from './diagnosis-tags.component';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import styles from './visit-diagnoses-cell-with-certainty.module.scss';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
visit: Visit;
|
|
10
|
+
patient: fhir.Patient;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const VisitDiagnosisCell: React.FC<Props> = ({ visit }) => {
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const diagnoses = visit.encounters
|
|
16
|
+
.flatMap((enc) => enc.diagnoses)
|
|
17
|
+
.filter((d) => !d.voided)
|
|
18
|
+
.sort((a, b) => a.rank - b.rank);
|
|
19
|
+
|
|
20
|
+
if (!diagnoses.length) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className={styles.cell}>
|
|
26
|
+
<DiagnosisTags diagnoses={diagnoses} />
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default VisitDiagnosisCell;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import {
|
|
4
|
+
DataTable,
|
|
5
|
+
DataTableSkeleton,
|
|
6
|
+
Pagination,
|
|
7
|
+
Table,
|
|
8
|
+
TableBody,
|
|
9
|
+
TableCell,
|
|
10
|
+
TableContainer,
|
|
11
|
+
TableExpandedRow,
|
|
12
|
+
TableExpandHeader,
|
|
13
|
+
TableExpandRow,
|
|
14
|
+
TableHead,
|
|
15
|
+
TableHeader,
|
|
16
|
+
TableRow,
|
|
17
|
+
} from '@carbon/react';
|
|
18
|
+
import { ErrorState, isDesktop, useLayoutType } from '@openmrs/esm-framework';
|
|
19
|
+
import { EmptyState } from '@openmrs/esm-patient-common-lib';
|
|
20
|
+
import { usePaginatedVisits } from '../visits-widget/visit.resource';
|
|
21
|
+
import VisitActionsCell from './visit-actions-cell.component';
|
|
22
|
+
import VisitDateCell from './visit-date-cell.component';
|
|
23
|
+
import VisitDiagnosisCell from './visit-diagnoses-cell-with-certainty.component';
|
|
24
|
+
import VisitSummary from '../visits-widget/past-visits-components/visit-summary.component';
|
|
25
|
+
import VisitTypeCell from './visit-type-cell.component';
|
|
26
|
+
import styles from './visit-history-table.scss';
|
|
27
|
+
|
|
28
|
+
interface VisitHistoryTableProps {
|
|
29
|
+
patientUuid: string;
|
|
30
|
+
patient: fhir.Patient;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* This show a list of visit histories in the visit tab in patient chart
|
|
35
|
+
*/
|
|
36
|
+
const VisitHistoryTable: React.FC<VisitHistoryTableProps> = ({ patientUuid, patient }) => {
|
|
37
|
+
const defaultPageSize = 10;
|
|
38
|
+
const [pageSize, setPageSize] = useState(defaultPageSize);
|
|
39
|
+
const pageSizes = [10, 20, 30, 40, 50];
|
|
40
|
+
|
|
41
|
+
const { data: visits, currentPage, error, isLoading, totalCount, goTo } = usePaginatedVisits(patientUuid, pageSize);
|
|
42
|
+
const { t } = useTranslation();
|
|
43
|
+
const desktopLayout = isDesktop(useLayoutType());
|
|
44
|
+
|
|
45
|
+
// TODO: make this configurable
|
|
46
|
+
const columns = [
|
|
47
|
+
{ key: 'visitDate', header: t('date', 'Date'), CellComponent: VisitDateCell },
|
|
48
|
+
{ key: 'visitType', header: t('visitType', 'Visit type'), CellComponent: VisitTypeCell },
|
|
49
|
+
{ key: 'diagnoses', header: t('diagnoses', 'Diagnoses'), CellComponent: VisitDiagnosisCell },
|
|
50
|
+
{ key: 'actions', header: '', CellComponent: VisitActionsCell },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const layout = useLayoutType();
|
|
54
|
+
|
|
55
|
+
const rowData = visits?.map((visit) => {
|
|
56
|
+
const row: Record<string, JSX.Element | string> = { id: visit.uuid };
|
|
57
|
+
for (const { key, CellComponent } of columns) {
|
|
58
|
+
row[key] = <CellComponent key={key} visit={visit} patient={patient} />;
|
|
59
|
+
}
|
|
60
|
+
return row;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (isLoading) {
|
|
64
|
+
return <DataTableSkeleton role="progressbar" compact={isDesktop(layout)} zebra />;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (error) {
|
|
68
|
+
return <ErrorState error={error} headerTitle={t('pastVisits', 'Past visits')} />;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (visits.length === 0) {
|
|
72
|
+
return (
|
|
73
|
+
<div className={styles.emptyStateContainer}>
|
|
74
|
+
<EmptyState headerTitle={t('pastVisits', 'Past visits')} displayText={t('visits', 'visits')} />
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return (
|
|
79
|
+
<div className={styles.container}>
|
|
80
|
+
{/* @ts-ignore */}
|
|
81
|
+
<DataTable headers={columns} rows={rowData} size={desktopLayout ? 'sm' : 'lg'} useZebraStyles>
|
|
82
|
+
{({ rows, headers, getTableProps, getHeaderProps, getExpandHeaderProps, getRowProps, getExpandedRowProps }) => (
|
|
83
|
+
<>
|
|
84
|
+
<TableContainer>
|
|
85
|
+
<Table {...getTableProps()}>
|
|
86
|
+
<TableHead>
|
|
87
|
+
<TableRow>
|
|
88
|
+
<TableExpandHeader enableToggle {...getExpandHeaderProps()} />
|
|
89
|
+
{headers.map((header) => (
|
|
90
|
+
<TableHeader
|
|
91
|
+
{...getHeaderProps({
|
|
92
|
+
header,
|
|
93
|
+
className: header.key === 'actions' ? styles.actionsColumn : '',
|
|
94
|
+
})}>
|
|
95
|
+
{header.header}
|
|
96
|
+
</TableHeader>
|
|
97
|
+
))}
|
|
98
|
+
</TableRow>
|
|
99
|
+
</TableHead>
|
|
100
|
+
<TableBody>
|
|
101
|
+
{rows.map((row, i) => {
|
|
102
|
+
const visit = visits[i];
|
|
103
|
+
return (
|
|
104
|
+
<React.Fragment key={row.id}>
|
|
105
|
+
<TableExpandRow {...getRowProps({ row })}>
|
|
106
|
+
{row.cells.map((cell) => {
|
|
107
|
+
return <TableCell key={cell.id}>{cell?.value}</TableCell>;
|
|
108
|
+
})}
|
|
109
|
+
</TableExpandRow>
|
|
110
|
+
{row.isExpanded ? (
|
|
111
|
+
<TableExpandedRow {...getExpandedRowProps({ row })} colSpan={headers.length + 2}>
|
|
112
|
+
<VisitSummary visit={visit} patientUuid={patientUuid} />
|
|
113
|
+
</TableExpandedRow>
|
|
114
|
+
) : (
|
|
115
|
+
<TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} />
|
|
116
|
+
)}
|
|
117
|
+
</React.Fragment>
|
|
118
|
+
);
|
|
119
|
+
})}
|
|
120
|
+
</TableBody>
|
|
121
|
+
</Table>
|
|
122
|
+
</TableContainer>
|
|
123
|
+
<Pagination
|
|
124
|
+
forwardText={t('nextPage', 'Next page')}
|
|
125
|
+
backwardText={t('previousPage', 'Previous page')}
|
|
126
|
+
page={currentPage}
|
|
127
|
+
pageSize={pageSize}
|
|
128
|
+
pageSizes={pageSizes}
|
|
129
|
+
totalItems={totalCount}
|
|
130
|
+
onChange={({ pageSize, page }) => {
|
|
131
|
+
setPageSize(pageSize);
|
|
132
|
+
if (page !== currentPage) {
|
|
133
|
+
goTo(page);
|
|
134
|
+
}
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
</>
|
|
138
|
+
)}
|
|
139
|
+
</DataTable>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default VisitHistoryTable;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
@use '@carbon/layout';
|
|
2
|
+
@use '@openmrs/esm-styleguide/src/vars' as *;
|
|
3
|
+
|
|
4
|
+
.container {
|
|
5
|
+
margin: layout.$spacing-05 0;
|
|
6
|
+
border: 1px solid $ui-03;
|
|
7
|
+
|
|
8
|
+
// fixes padding of encounters table within the expanded visit table row
|
|
9
|
+
:global(tr.cds--parent-row.cds--expandable-row + tr[data-child-row] td) {
|
|
10
|
+
padding-inline-start: layout.$spacing-05;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.emptyStateContainer {
|
|
15
|
+
text-align: center;
|
|
16
|
+
margin: layout.$spacing-05 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.actionsColumn {
|
|
20
|
+
width: 1% !important; // fixes the width of action column to fit content
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.hiddenRow {
|
|
24
|
+
display: none;
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Visit } from '@openmrs/esm-framework';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
visit: Visit;
|
|
6
|
+
patient: fhir.Patient;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const VisitTypeCell: React.FC<Props> = ({ visit }) => {
|
|
10
|
+
const { visitType } = visit;
|
|
11
|
+
|
|
12
|
+
return <>{visitType.display}</>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default VisitTypeCell;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { SkeletonText } from '@carbon/react';
|
|
4
|
+
import { type Obs, useConfig } from '@openmrs/esm-framework';
|
|
5
|
+
import styles from './styles.scss';
|
|
6
|
+
|
|
7
|
+
interface EncounterObservationsProps {
|
|
8
|
+
observations: Array<Obs>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const EncounterObservations: React.FC<EncounterObservationsProps> = ({ observations }) => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const { obsConceptUuidsToHide = [] } = useConfig();
|
|
14
|
+
|
|
15
|
+
function getAnswerFromDisplay(display: string): string {
|
|
16
|
+
const colonIndex = display.indexOf(':');
|
|
17
|
+
if (colonIndex === -1) {
|
|
18
|
+
return '';
|
|
19
|
+
} else {
|
|
20
|
+
return display.substring(colonIndex + 1).trim();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const filteredObservations = !!obsConceptUuidsToHide.length
|
|
25
|
+
? observations?.filter((obs) => {
|
|
26
|
+
return !obsConceptUuidsToHide.includes(obs?.concept?.uuid);
|
|
27
|
+
})
|
|
28
|
+
: observations;
|
|
29
|
+
|
|
30
|
+
if (!filteredObservations || filteredObservations.length == 0) {
|
|
31
|
+
return (
|
|
32
|
+
<div className={styles.observation}>
|
|
33
|
+
<p>{t('noObservationsFound', 'No observations found')}</p>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className={styles.observation}>
|
|
40
|
+
{filteredObservations?.map((obs, index) => {
|
|
41
|
+
if (obs.groupMembers) {
|
|
42
|
+
return (
|
|
43
|
+
<React.Fragment key={index}>
|
|
44
|
+
<span className={styles.parentConcept}>{obs.concept.display}</span>
|
|
45
|
+
<span />
|
|
46
|
+
{obs.groupMembers.map((member) => (
|
|
47
|
+
<React.Fragment key={index}>
|
|
48
|
+
<span className={styles.childConcept}>{member.concept.display}</span>
|
|
49
|
+
<span>{getAnswerFromDisplay(member.display)}</span>
|
|
50
|
+
</React.Fragment>
|
|
51
|
+
))}
|
|
52
|
+
</React.Fragment>
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
return (
|
|
56
|
+
<React.Fragment key={index}>
|
|
57
|
+
<span>{obs.concept.display}</span>
|
|
58
|
+
<span>{getAnswerFromDisplay(obs.display)}</span>
|
|
59
|
+
</React.Fragment>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
})}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default EncounterObservations;
|