@kenyaemr/esm-care-panel-app 5.4.1-pre.1917 → 5.4.1-pre.1924

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/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[],"extensions":[{"name":"care-panel-patient-summary","component":"carePanelPatientSummary","slot":"patient-chart-care-panel-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"care-panel-summary-dashboard-link","component":"carePanelSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":3,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-care-panel-dashboard-slot","layoutMode":"anchored","path":"Care panel"}},{"name":"delete-regimen-confirmation-dialog","component":"deleteRegimenConfirmationDialog"},{"name":"hiv-patient-visit-summary-dashboard-link","component":"hivPatientSummaryDashboardLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hiv-patient-summary-slot","path":"HIV Patient Summary","layoutMode":"anchored"}},{"name":"hiv-patient-visit-summary","slot":"patient-chart-hiv-patient-summary-slot","component":"hivPatientSummary","order":3,"online":true,"offline":false}],"workspaces":[{"name":"patient-regimen-workspace","title":"Patient Regimen","component":"regimenFormWorkspace","type":"form","canMaximize":true,"canHide":true,"width":"wider"}],"version":"5.4.1-pre.1917"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[],"extensions":[{"name":"care-panel-patient-summary","component":"carePanelPatientSummary","slot":"patient-chart-care-panel-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"care-panel-summary-dashboard-link","component":"carePanelSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":3,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-care-panel-dashboard-slot","layoutMode":"anchored","path":"Care panel"}},{"name":"delete-regimen-confirmation-dialog","component":"deleteRegimenConfirmationDialog"},{"name":"hiv-patient-visit-summary-dashboard-link","component":"hivPatientSummaryDashboardLink","slot":"hiv-care-and-treatment-slot","meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-hiv-patient-summary-slot","path":"HIV Patient Summary","layoutMode":"anchored"}},{"name":"hiv-patient-visit-summary","slot":"patient-chart-hiv-patient-summary-slot","component":"hivPatientSummary","order":3,"online":true,"offline":false},{"name":"patient-diagnoses","component":"patientDiagnoses","online":true,"offline":true},{"name":"patient-conditions","component":"patientConditions","online":true,"offline":true},{"name":"dispensing-patient-vitals","component":"dispensingPaentientVitals","online":true,"offline":true}],"workspaces":[{"name":"patient-regimen-workspace","title":"Patient Regimen","component":"regimenFormWorkspace","type":"form","canMaximize":true,"canHide":true,"width":"wider"}],"version":"5.4.1-pre.1924"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-care-panel-app",
3
- "version": "5.4.1-pre.1917",
3
+ "version": "5.4.1-pre.1924",
4
4
  "description": "Patient care panels microfrontend for the OpenMRS SPA",
5
5
  "browser": "dist/kenyaemr-esm-care-panel-app.js",
6
6
  "main": "src/index.ts",
@@ -5,6 +5,10 @@ export interface CarePanelConfig {
5
5
  encounterProviderRoleUuid: string;
6
6
  };
7
7
  hivProgramUuid: string;
8
+ dispensingVitalsConcepts: Array<{
9
+ uuid: string;
10
+ display: string;
11
+ }>;
8
12
  }
9
13
 
10
14
  export const configSchema = {
@@ -20,4 +24,14 @@ export const configSchema = {
20
24
  _description: 'HIV Program UUID',
21
25
  _default: 'dfdc6d40-2f2f-463d-ba90-cc97350441a8',
22
26
  },
27
+ dispensingVitalsConcepts: {
28
+ _type: Type.Array,
29
+ _description: 'Uuids of patient vitals concept required for dispensing',
30
+ _default: [
31
+ {
32
+ display: 'Weight (Kg)',
33
+ uuid: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
34
+ },
35
+ ],
36
+ },
23
37
  };
@@ -0,0 +1,55 @@
1
+ import { InlineLoading, InlineNotification, Tile } from '@carbon/react';
2
+ import { WarningFilled } from '@carbon/react/icons';
3
+ import React from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { usePatientConditions } from './conditions.resource';
6
+ import styles from './dispensing-patient-details.scss';
7
+
8
+ type PatientConditionsProps = {
9
+ patientUuid: string;
10
+ encounterUuid: string;
11
+ };
12
+
13
+ const PatientConditions: React.FC<PatientConditionsProps> = ({ encounterUuid, patientUuid }) => {
14
+ const { t } = useTranslation();
15
+ const { conditions, error, isLoading, mutate } = usePatientConditions(patientUuid);
16
+
17
+ if (isLoading) {
18
+ return (
19
+ <InlineLoading
20
+ iconDescription="Loading"
21
+ description={t('loadingConditions', 'Loading active Conditions ...')}
22
+ status="active"
23
+ />
24
+ );
25
+ }
26
+
27
+ if (error) {
28
+ return <InlineNotification kind="error" subtitle={t('conditionsError', 'Error loading conditions')} lowContrast />;
29
+ }
30
+
31
+ return (
32
+ <Tile className={styles.container}>
33
+ <div className={styles.content}>
34
+ <div>
35
+ <WarningFilled size={24} className={styles.icon} />
36
+ <p>
37
+ {conditions.length > 0 && (
38
+ <span>
39
+ <span style={{ fontWeight: 'bold' }}>
40
+ {t('activeConditionsCount', '{{ count }} conditions', {
41
+ count: conditions.length,
42
+ })}
43
+ </span>{' '}
44
+ {conditions?.map(({ display }) => display).join(' | ')}
45
+ </span>
46
+ )}
47
+ {conditions.length === 0 && t('noActiveConditions', 'No active Conditions')}
48
+ </p>
49
+ </div>
50
+ </div>
51
+ </Tile>
52
+ );
53
+ };
54
+
55
+ export default PatientConditions;
@@ -0,0 +1,97 @@
1
+ import { fhirBaseUrl, useFhirFetchAll } from '@openmrs/esm-framework';
2
+ import { useMemo } from 'react';
3
+
4
+ export interface Resource {
5
+ resourceType: string;
6
+ id: string;
7
+ meta: ResourceMeta;
8
+ clinicalStatus: ClinicalStatus;
9
+ code: Code;
10
+ subject: Subject;
11
+ onsetDateTime: string;
12
+ recordedDate: string;
13
+ recorder: Recorder;
14
+ }
15
+
16
+ export interface ResourceMeta {
17
+ versionId: string;
18
+ lastUpdated: string;
19
+ tag: Array<ResourceMetaTag>;
20
+ }
21
+
22
+ export interface ResourceMetaTag {
23
+ system: string;
24
+ code: string;
25
+ display: string;
26
+ }
27
+
28
+ export interface ClinicalStatus {
29
+ coding: Array<Coding>;
30
+ }
31
+
32
+ export interface Coding {
33
+ system: string;
34
+ code: string;
35
+ }
36
+
37
+ export interface Code {
38
+ coding: Array<ConditionCoding>;
39
+ text: string;
40
+ }
41
+
42
+ export interface ConditionCoding {
43
+ code: string;
44
+ display?: string;
45
+ system?: string;
46
+ }
47
+
48
+ export interface Subject {
49
+ reference: string;
50
+ type: string;
51
+ display: string;
52
+ }
53
+
54
+ export interface Recorder {
55
+ reference: string;
56
+ type: string;
57
+ display: string;
58
+ }
59
+
60
+ export interface Condition {
61
+ id: string;
62
+ status?: 'active' | 'inactive';
63
+ display: string;
64
+ patient: string;
65
+ onsetDateTime: string;
66
+ recordedDate: string;
67
+ recorder: string;
68
+ }
69
+
70
+ export const usePatientConditions = (patientUuid: string) => {
71
+ const url = `${fhirBaseUrl}/Condition?patient=${patientUuid}&_summary=data`;
72
+ const { data, isLoading, mutate, error } = useFhirFetchAll<Resource>(url);
73
+
74
+ const conditions = useMemo(() => {
75
+ return data?.reduce<Array<Condition>>((prev, entry) => {
76
+ if (entry?.resourceType === 'Condition') {
77
+ const condition: Condition = {
78
+ id: entry.id,
79
+ display: entry?.code?.text,
80
+ onsetDateTime: entry?.onsetDateTime,
81
+ patient: entry?.subject?.display,
82
+ recordedDate: entry?.recordedDate,
83
+ recorder: entry?.recorder?.display,
84
+ status: entry?.clinicalStatus?.coding[0]?.code as any,
85
+ };
86
+ return [...prev, condition];
87
+ }
88
+ return prev;
89
+ }, []);
90
+ }, [data]);
91
+ return {
92
+ conditions: (conditions ?? []).filter((condition) => condition.status === 'active'),
93
+ isLoading,
94
+ error,
95
+ mutate,
96
+ };
97
+ };
@@ -0,0 +1,55 @@
1
+ import { InlineLoading, InlineNotification, Tile } from '@carbon/react';
2
+ import { WarningFilled } from '@carbon/react/icons';
3
+ import React from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { usePatientDiagnosis } from './diagnoses.resource';
6
+ import styles from './dispensing-patient-details.scss';
7
+
8
+ type PatientDiagnosesProps = {
9
+ patientUuid: string;
10
+ encounterUuid: string;
11
+ };
12
+
13
+ const PatientDiagnoses: React.FC<PatientDiagnosesProps> = ({ encounterUuid, patientUuid }) => {
14
+ const { diagnoses, isLoading, error } = usePatientDiagnosis(encounterUuid);
15
+ const { t } = useTranslation();
16
+
17
+ if (isLoading) {
18
+ return (
19
+ <InlineLoading
20
+ iconDescription="Loading"
21
+ description={t('loadingDiagnoses', 'Loading Diagnoses ...')}
22
+ status="active"
23
+ />
24
+ );
25
+ }
26
+
27
+ if (error) {
28
+ return <InlineNotification kind="error" subtitle={t('diagnosesError', 'Error loading diagnoses')} lowContrast />;
29
+ }
30
+
31
+ return (
32
+ <Tile className={styles.container}>
33
+ <div className={styles.content}>
34
+ <div>
35
+ <WarningFilled size={24} className={styles.icon} />
36
+ <p>
37
+ {diagnoses.length > 0 && (
38
+ <span>
39
+ <span style={{ fontWeight: 'bold' }}>
40
+ {t('diagnosesCount', '{{ count }} diagnoses', {
41
+ count: diagnoses.length,
42
+ })}
43
+ </span>{' '}
44
+ {diagnoses?.map(({ text }) => text).join(' | ')}
45
+ </span>
46
+ )}
47
+ {diagnoses.length === 0 && t('noFinalDiagnoses', 'No patient final diagnosis for this visit')}
48
+ </p>
49
+ </div>
50
+ </div>
51
+ </Tile>
52
+ );
53
+ };
54
+
55
+ export default PatientDiagnoses;
@@ -0,0 +1,30 @@
1
+ import { type FetchResponse, openmrsFetch, restBaseUrl, type Visit } from '@openmrs/esm-framework';
2
+ import { useMemo } from 'react';
3
+ import useSWR from 'swr';
4
+
5
+ export const usePatientDiagnosis = (encounterUuid: string) => {
6
+ const customRepresentation =
7
+ 'custom:(uuid,display,visit:(uuid,patient,encounters:(uuid,diagnoses:(uuid,display,certainty,diagnosis:(coded:(uuid,display))),encounterDatetime,encounterType:(uuid,display),encounterProviders:(uuid,display,provider:(uuid,person:(uuid,display)))),location:(uuid,name,display),visitType:(uuid,name,display),startDatetime,stopDatetime))';
8
+ const url = `${restBaseUrl}/encounter/${encounterUuid}?v=${customRepresentation}`;
9
+
10
+ const { data, error, isLoading } = useSWR<FetchResponse<{ visit: Visit }>>(url, openmrsFetch);
11
+
12
+ const diagnoses = useMemo(() => {
13
+ return (
14
+ data?.data?.visit?.encounters?.flatMap(
15
+ (encounter) =>
16
+ encounter.diagnoses.map((diagnosis) => ({
17
+ id: diagnosis.diagnosis.coded.uuid,
18
+ text: diagnosis.display,
19
+ certainty: diagnosis.certainty,
20
+ })) || [],
21
+ ) || []
22
+ );
23
+ }, [data]);
24
+
25
+ return {
26
+ error,
27
+ isLoading,
28
+ diagnoses: (diagnoses ?? []) as Array<{ id: string; text: string; certainty: string }>,
29
+ };
30
+ };
@@ -0,0 +1,47 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/colors';
3
+ @use '@carbon/type';
4
+ @import '~@openmrs/esm-styleguide/src/vars';
5
+
6
+ .container {
7
+ height: layout.$spacing-06;
8
+ padding: 0 layout.$spacing-03 0 0;
9
+ border-left: layout.$spacing-02 solid #f1c21b;
10
+ background-color: rgba(253, 209, 58, 0.3);
11
+ margin: layout.$spacing-03 0 layout.$spacing-03 0;
12
+ width: 100%;
13
+ }
14
+
15
+ .content {
16
+ margin: auto;
17
+ min-width: 100%;
18
+ height: 100%;
19
+ display: flex;
20
+ align-items: center;
21
+
22
+ div {
23
+ align-items: center;
24
+ display: flex;
25
+ flex-direction: row;
26
+ width: 100%;
27
+ }
28
+
29
+ p {
30
+ margin-left: layout.$spacing-03;
31
+ font-size: 0.9rem;
32
+ width: 80%;
33
+ }
34
+
35
+ a {
36
+ margin: auto;
37
+ color: #0f62fe;
38
+ text-decoration: none;
39
+ text-align: right;
40
+ }
41
+ }
42
+
43
+ svg.icon {
44
+ fill: #f1c21b;
45
+ vertical-align: middle;
46
+ margin-left: layout.$spacing-03;
47
+ }
@@ -0,0 +1,132 @@
1
+ import { FetchResponse, fhirBaseUrl, openmrsFetch, parseDate, useConfig } from '@openmrs/esm-framework';
2
+ import useSWR from 'swr';
3
+ import { CarePanelConfig } from '../config-schema';
4
+ import { useMemo } from 'react';
5
+
6
+ export interface Entry {
7
+ fullUrl: string;
8
+ resource: Resource;
9
+ }
10
+
11
+ export interface Resource {
12
+ resourceType: string;
13
+ id: string;
14
+ meta: Meta;
15
+ status: string;
16
+ category: Category[];
17
+ code: Code;
18
+ subject: Subject;
19
+ encounter: Encounter;
20
+ effectiveDateTime: string;
21
+ issued: string;
22
+ valueQuantity: ValueQuantity;
23
+ referenceRange: ReferenceRange[];
24
+ }
25
+
26
+ export interface Meta {
27
+ versionId: string;
28
+ lastUpdated: string;
29
+ tag: Tag[];
30
+ }
31
+
32
+ export interface Tag {
33
+ system: string;
34
+ code: string;
35
+ display: string;
36
+ }
37
+
38
+ export interface Category {
39
+ coding: Coding[];
40
+ }
41
+
42
+ export interface Coding {
43
+ system: string;
44
+ code: string;
45
+ display: string;
46
+ }
47
+
48
+ export interface Code {
49
+ coding: CategoryCoding[];
50
+ text: string;
51
+ }
52
+
53
+ export interface CategoryCoding {
54
+ code: string;
55
+ display?: string;
56
+ system?: string;
57
+ }
58
+
59
+ export interface Subject {
60
+ reference: string;
61
+ type: string;
62
+ display: string;
63
+ }
64
+
65
+ export interface Encounter {
66
+ reference: string;
67
+ type: string;
68
+ }
69
+
70
+ export interface ValueQuantity {
71
+ value: number;
72
+ unit: string;
73
+ system: string;
74
+ code: string;
75
+ }
76
+
77
+ export interface ReferenceRange {
78
+ low: Low;
79
+ high: High;
80
+ type: Type;
81
+ }
82
+
83
+ export interface Low {
84
+ value: number;
85
+ }
86
+
87
+ export interface High {
88
+ value: number;
89
+ }
90
+
91
+ export interface Type {
92
+ coding: Coding3[];
93
+ }
94
+
95
+ export interface Coding3 {
96
+ system: string;
97
+ code: string;
98
+ }
99
+
100
+ type DispensingVitals = {
101
+ display: string;
102
+ uuid: string;
103
+ value: number | string;
104
+ dateRecoded?: Date;
105
+ };
106
+
107
+ export const usePatientVitals = (patientUuid: string, encounterUuid: string) => {
108
+ const { dispensingVitalsConcepts } = useConfig<CarePanelConfig>();
109
+ const urlParams = new URLSearchParams({
110
+ patient: patientUuid,
111
+ code: dispensingVitalsConcepts.map(({ uuid }) => uuid).join(','),
112
+ encounter: encounterUuid,
113
+ });
114
+ const url = `${fhirBaseUrl}/Observation?${urlParams.toString()}`;
115
+ const { data, error, isLoading, mutate } = useSWR<FetchResponse<{ entry: Array<Entry> }>>(url, openmrsFetch);
116
+ const vitals = useMemo<Array<DispensingVitals>>(
117
+ () =>
118
+ (data?.data?.entry ?? []).map((entry) => ({
119
+ display: entry?.resource?.code?.text,
120
+ uuid: entry?.resource?.id,
121
+ value: entry?.resource?.valueQuantity?.value,
122
+ dateRecoded: entry?.resource?.issued ? parseDate(entry?.resource?.issued) : undefined,
123
+ })),
124
+ [data],
125
+ );
126
+ return {
127
+ vitals,
128
+ isLoading,
129
+ mutate,
130
+ error,
131
+ };
132
+ };
@@ -0,0 +1,27 @@
1
+ import { Layer } from '@carbon/react';
2
+ import React from 'react';
3
+ import { usePatientVitals } from './dispensing-patient-vitals.resources';
4
+
5
+ type PatientVitalsProps = {
6
+ patientUuid: string;
7
+ encounterUuid: string;
8
+ };
9
+ const DispensingPatientVitals: React.FC<PatientVitalsProps> = ({ encounterUuid, patientUuid }) => {
10
+ const { vitals, error, isLoading } = usePatientVitals(patientUuid, encounterUuid);
11
+
12
+ if (isLoading || error) {
13
+ return null;
14
+ }
15
+ return (
16
+ <Layer>
17
+ {vitals.map((vital) => (
18
+ <div>
19
+ <strong>{vital?.display}: </strong>
20
+ <span>{vital?.value}</span>
21
+ </div>
22
+ ))}
23
+ </Layer>
24
+ );
25
+ };
26
+
27
+ export default DispensingPatientVitals;
package/src/index.ts CHANGED
@@ -8,6 +8,9 @@ import deleteRegimenConfirmationDialogComponent from './regimen-editor/delete-re
8
8
  import regimenFormComponent from './regimen-editor/regimen-form.component';
9
9
  import CarePanelDashboard from './care-panel-dashboard/care-panel-dashboard.component';
10
10
  import PatientSummary from './patient-summary/patient-summary.component';
11
+ import PatientDiagnoses from './dispensing-patient-details/diagnoses.component';
12
+ import PatientConditions from './dispensing-patient-details/conditions.component';
13
+ import DispensingPatientVitals from './dispensing-patient-details/patient-vitals.component';
11
14
 
12
15
  const moduleName = '@kenyaemr/esm-care-panel-app';
13
16
 
@@ -45,3 +48,8 @@ export const hivPatientSummaryDashboardLink = getSyncLifecycle(
45
48
  );
46
49
  export const hivPatientSummary = getSyncLifecycle(PatientSummary, options);
47
50
  export const regimenFormWorkspace = getSyncLifecycle(regimenFormComponent, options);
51
+
52
+ // TODO Clean when community version gets merged
53
+ export const patientDiagnoses = getSyncLifecycle(PatientDiagnoses, options);
54
+ export const patientConditions = getSyncLifecycle(PatientConditions, options);
55
+ export const dispensingPaentientVitals = getSyncLifecycle(DispensingPatientVitals, options);
package/src/routes.json CHANGED
@@ -50,14 +50,32 @@
50
50
  "order": 3,
51
51
  "online": true,
52
52
  "offline": false
53
+ },
54
+ {
55
+ "name": "patient-diagnoses",
56
+ "component": "patientDiagnoses",
57
+ "online": true,
58
+ "offline": true
59
+ },
60
+ {
61
+ "name": "patient-conditions",
62
+ "component": "patientConditions",
63
+ "online": true,
64
+ "offline": true
65
+ },
66
+ {
67
+ "name": "dispensing-patient-vitals",
68
+ "component": "dispensingPaentientVitals",
69
+ "online": true,
70
+ "offline": true
53
71
  }
54
72
  ],
55
73
  "workspaces": [
56
74
  {
57
75
  "name": "patient-regimen-workspace",
58
76
  "title": "Patient Regimen",
59
- "component":"regimenFormWorkspace",
60
- "type":"form",
77
+ "component": "regimenFormWorkspace",
78
+ "type": "form",
61
79
  "canMaximize": true,
62
80
  "canHide": true,
63
81
  "width": "wider"