@kenyaemr/esm-pharmacy-app 5.2.1-pre.724
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 +32 -0
- package/LICENSE.md +401 -0
- package/README.md +5 -0
- package/dist/102.js +1 -0
- package/dist/102.js.map +1 -0
- package/dist/130.js +2 -0
- package/dist/130.js.LICENSE.txt +3 -0
- package/dist/130.js.map +1 -0
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/255.js +2 -0
- package/dist/255.js.LICENSE.txt +9 -0
- package/dist/255.js.map +1 -0
- package/dist/303.js +1 -0
- package/dist/303.js.map +1 -0
- package/dist/341.js +1 -0
- package/dist/341.js.map +1 -0
- package/dist/368.js +1 -0
- package/dist/368.js.map +1 -0
- package/dist/383.js +1 -0
- package/dist/383.js.map +1 -0
- package/dist/419.js +1 -0
- package/dist/419.js.map +1 -0
- package/dist/515.js +1 -0
- package/dist/515.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/591.js +2 -0
- package/dist/591.js.LICENSE.txt +32 -0
- package/dist/591.js.map +1 -0
- package/dist/682.js +2 -0
- package/dist/682.js.LICENSE.txt +35 -0
- package/dist/682.js.map +1 -0
- package/dist/729.js +1 -0
- package/dist/729.js.map +1 -0
- package/dist/75.js +1 -0
- package/dist/75.js.map +1 -0
- package/dist/784.js +2 -0
- package/dist/784.js.LICENSE.txt +9 -0
- package/dist/784.js.map +1 -0
- package/dist/883.js +1 -0
- package/dist/883.js.map +1 -0
- package/dist/kenyaemr-esm-pharmacy-app.js +1 -0
- package/dist/kenyaemr-esm-pharmacy-app.js.buildmanifest.json +556 -0
- package/dist/kenyaemr-esm-pharmacy-app.js.map +1 -0
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/jest.config.js +8 -0
- package/package.json +55 -0
- package/src/autosuggest/autosuggest.component.tsx +187 -0
- package/src/autosuggest/autosuggest.scss +62 -0
- package/src/config-schema.ts +43 -0
- package/src/declarations.d.ts +6 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/usePharmacy.ts +17 -0
- package/src/hooks/usePharmacyPatients.ts +48 -0
- package/src/hooks/usePharmacyUsers.ts +43 -0
- package/src/hooks/useUserMappedPharmacies.ts +47 -0
- package/src/index.ts +41 -0
- package/src/pharamacy-forms/delete-confirm-dialog.modal.tsx +26 -0
- package/src/pharamacy-forms/pharamacy-assignment-form.workspace.tsx +121 -0
- package/src/pharamacy-forms/pharmacy-assignment-form.scss +125 -0
- package/src/pharmacy-component/pharmacy.component.tsx +14 -0
- package/src/pharmacy-detail/pharmacy-detail.component.tsx +14 -0
- package/src/pharmacy-detail-header/pharmacy-detail-header.component.tsx +41 -0
- package/src/pharmacy-header/pharmacy-header.component.tsx +36 -0
- package/src/pharmacy-header/pharmacy-header.scss +71 -0
- package/src/pharmacy-header/pharmacy-illustration.component.tsx +13 -0
- package/src/pharmacy-left-panel/pharmacy-left-panel-link.component.tsx +40 -0
- package/src/pharmacy-tables/pharmacy-patients.component.tsx +221 -0
- package/src/pharmacy-tables/pharmacy-table.component.tsx +164 -0
- package/src/pharmacy-tables/pharmacy-tables.scss +49 -0
- package/src/pharmacy-tables/pharmacy-users.component.tsx +197 -0
- package/src/pharmacy-tabs/pharmacy-tabs-component.tsx +28 -0
- package/src/pharmacy-tabs/pharmacy-tabs.scss +28 -0
- package/src/pharmacy.resources.ts +55 -0
- package/src/root.component.tsx +19 -0
- package/src/root.scss +15 -0
- package/src/root.test.tsx +5 -0
- package/src/routes.json +38 -0
- package/src/setup-tests.ts +1 -0
- package/src/types/index.ts +67 -0
- package/translations/en.json +27 -0
- package/tsconfig.json +5 -0
- package/webpack.config.js +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kenyaemr/esm-pharmacy-app",
|
|
3
|
+
"version": "5.2.1-pre.724",
|
|
4
|
+
"description": "pharmacy app for KenyaEMR",
|
|
5
|
+
"browser": "dist/kenyaemr-esm-pharmacy-app.js",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"source": true,
|
|
8
|
+
"license": "MPL-2.0",
|
|
9
|
+
"homepage": "https://github.com/palladiumkenya/kenyaemr-esm-core#readme",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "openmrs develop",
|
|
12
|
+
"serve": "webpack serve --mode=development",
|
|
13
|
+
"debug": "npm run serve",
|
|
14
|
+
"build": "webpack --mode production",
|
|
15
|
+
"analyze": "webpack --mode=production --env.analyze=true",
|
|
16
|
+
"lint": "eslint src --ext ts,tsx",
|
|
17
|
+
"typescript": "tsc",
|
|
18
|
+
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js",
|
|
19
|
+
"test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests",
|
|
20
|
+
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js",
|
|
21
|
+
"coverage": "yarn test --coverage"
|
|
22
|
+
},
|
|
23
|
+
"browserslist": [
|
|
24
|
+
"extends browserslist-config-openmrs"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"openmrs"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/palladiumkenya/kenyaemr-esm-core#readme"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/palladiumkenya/kenyaemr-esm-core/issues"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@carbon/react": "^1.42.1",
|
|
41
|
+
"lodash-es": "^4.17.15",
|
|
42
|
+
"react-to-print": "^2.14.13"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@openmrs/esm-framework": "5.x",
|
|
46
|
+
"react": "^18.1.0",
|
|
47
|
+
"react-i18next": "11.x",
|
|
48
|
+
"react-router-dom": "6.x",
|
|
49
|
+
"swr": "2.x"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"webpack": "^5.74.0"
|
|
53
|
+
},
|
|
54
|
+
"gitHead": "26fcec8e422b36202ce9874565ac44d9326f94da"
|
|
55
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import React, { type HTMLAttributes, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Layer, Search, type SearchProps } from '@carbon/react';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import styles from './autosuggest.scss';
|
|
5
|
+
|
|
6
|
+
// FIXME Temporarily included types from Carbon
|
|
7
|
+
type InputPropsBase = Omit<HTMLAttributes<HTMLInputElement>, 'onChange'>;
|
|
8
|
+
|
|
9
|
+
interface SearchProps extends InputPropsBase {
|
|
10
|
+
/**
|
|
11
|
+
* Specify an optional value for the `autocomplete` property on the underlying
|
|
12
|
+
* `<input>`, defaults to "off"
|
|
13
|
+
*/
|
|
14
|
+
autoComplete?: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Specify an optional className to be applied to the container node
|
|
18
|
+
*/
|
|
19
|
+
className?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Specify a label to be read by screen readers on the "close" button
|
|
23
|
+
*/
|
|
24
|
+
closeButtonLabelText?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Optionally provide the default value of the `<input>`
|
|
28
|
+
*/
|
|
29
|
+
defaultValue?: string | number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Specify whether the `<input>` should be disabled
|
|
33
|
+
*/
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Specify whether or not ExpandableSearch should render expanded or not
|
|
38
|
+
*/
|
|
39
|
+
isExpanded?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Specify a custom `id` for the input
|
|
43
|
+
*/
|
|
44
|
+
id?: string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Provide the label text for the Search icon
|
|
48
|
+
*/
|
|
49
|
+
labelText: React.ReactNode;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Optional callback called when the search value changes.
|
|
53
|
+
*/
|
|
54
|
+
onChange?(e: { target: HTMLInputElement; type: 'change' }): void;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Optional callback called when the search value is cleared.
|
|
58
|
+
*/
|
|
59
|
+
onClear?(): void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Optional callback called when the magnifier icon is clicked in ExpandableSearch.
|
|
63
|
+
*/
|
|
64
|
+
onExpand?(e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Provide an optional placeholder text for the Search.
|
|
68
|
+
* Note: if the label and placeholder differ,
|
|
69
|
+
* VoiceOver on Mac will read both
|
|
70
|
+
*/
|
|
71
|
+
placeholder?: string;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Rendered icon for the Search.
|
|
75
|
+
* Can be a React component class
|
|
76
|
+
*/
|
|
77
|
+
renderIcon?: React.ComponentType | React.FunctionComponent;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Specify the role for the underlying `<input>`, defaults to `searchbox`
|
|
81
|
+
*/
|
|
82
|
+
role?: string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Specify the size of the Search
|
|
86
|
+
*/
|
|
87
|
+
size?: 'sm' | 'md' | 'lg';
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Optional prop to specify the type of the `<input>`
|
|
91
|
+
*/
|
|
92
|
+
type?: string;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Specify the value of the `<input>`
|
|
96
|
+
*/
|
|
97
|
+
value?: string | number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface AutosuggestProps extends SearchProps {
|
|
101
|
+
getDisplayValue: Function;
|
|
102
|
+
getFieldValue: Function;
|
|
103
|
+
getSearchResults: (query: string) => Promise<any>;
|
|
104
|
+
onSuggestionSelected: (field: string, value: string) => void;
|
|
105
|
+
invalid?: boolean | undefined;
|
|
106
|
+
invalidText?: string | undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const Autosuggest: React.FC<AutosuggestProps> = ({
|
|
110
|
+
getDisplayValue,
|
|
111
|
+
getFieldValue,
|
|
112
|
+
getSearchResults,
|
|
113
|
+
onSuggestionSelected,
|
|
114
|
+
invalid,
|
|
115
|
+
invalidText,
|
|
116
|
+
...searchProps
|
|
117
|
+
}) => {
|
|
118
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
119
|
+
const searchBox = useRef(null);
|
|
120
|
+
const wrapper = useRef(null);
|
|
121
|
+
const { id: name, labelText } = searchProps;
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
document.addEventListener('mousedown', handleClickOutsideComponent);
|
|
125
|
+
|
|
126
|
+
return () => {
|
|
127
|
+
document.removeEventListener('mousedown', handleClickOutsideComponent);
|
|
128
|
+
};
|
|
129
|
+
}, [wrapper]);
|
|
130
|
+
|
|
131
|
+
const handleClickOutsideComponent = (e) => {
|
|
132
|
+
if (wrapper.current && !wrapper.current.contains(e.target)) {
|
|
133
|
+
setSuggestions([]);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
138
|
+
const query = e.target.value;
|
|
139
|
+
onSuggestionSelected(name, undefined);
|
|
140
|
+
|
|
141
|
+
if (query) {
|
|
142
|
+
getSearchResults(query).then((suggestions) => {
|
|
143
|
+
setSuggestions(suggestions);
|
|
144
|
+
});
|
|
145
|
+
} else {
|
|
146
|
+
setSuggestions([]);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const handleClear = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
151
|
+
onSuggestionSelected(name, undefined);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const handleClick = (index: number) => {
|
|
155
|
+
const display = getDisplayValue(suggestions[index]);
|
|
156
|
+
const value = getFieldValue(suggestions[index]);
|
|
157
|
+
searchBox.current.value = display;
|
|
158
|
+
onSuggestionSelected(name, value);
|
|
159
|
+
setSuggestions([]);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className={styles.autocomplete} ref={wrapper}>
|
|
164
|
+
<label className="cds--label">{labelText}</label>
|
|
165
|
+
<Layer className={classNames({ [styles.invalid]: invalid })}>
|
|
166
|
+
<Search
|
|
167
|
+
id="autosuggest"
|
|
168
|
+
onChange={handleChange}
|
|
169
|
+
onClear={handleClear}
|
|
170
|
+
ref={searchBox}
|
|
171
|
+
className={styles.autocompleteSearch}
|
|
172
|
+
{...searchProps}
|
|
173
|
+
/>
|
|
174
|
+
</Layer>
|
|
175
|
+
{suggestions.length > 0 && (
|
|
176
|
+
<ul className={styles.suggestions}>
|
|
177
|
+
{suggestions.map((suggestion, index) => (
|
|
178
|
+
<li key={index} onClick={(e) => handleClick(index)} role="presentation">
|
|
179
|
+
{getDisplayValue(suggestion)}
|
|
180
|
+
</li>
|
|
181
|
+
))}
|
|
182
|
+
</ul>
|
|
183
|
+
)}
|
|
184
|
+
{invalid ? <label className={classNames(styles.invalidMsg)}>{invalidText}</label> : <></>}
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/spacing';
|
|
2
|
+
@use '@carbon/styles/scss/type';
|
|
3
|
+
@import '~@openmrs/esm-styleguide/src/vars';
|
|
4
|
+
|
|
5
|
+
.label01 {
|
|
6
|
+
@include type.type-style('label-01');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.suggestions {
|
|
10
|
+
position: relative;
|
|
11
|
+
border-top-width: 0;
|
|
12
|
+
list-style: none;
|
|
13
|
+
margin-top: 0;
|
|
14
|
+
max-height: 143px;
|
|
15
|
+
overflow-y: auto;
|
|
16
|
+
padding-left: 0;
|
|
17
|
+
width: 100%;
|
|
18
|
+
position: absolute;
|
|
19
|
+
left: 0;
|
|
20
|
+
background-color: #fff;
|
|
21
|
+
margin-bottom: 20px;
|
|
22
|
+
z-index: 99;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.suggestions li {
|
|
26
|
+
padding: spacing.$spacing-05;
|
|
27
|
+
line-height: 1.29;
|
|
28
|
+
color: #525252;
|
|
29
|
+
border-bottom: 1px solid #8d8d8d;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.suggestions li:hover {
|
|
33
|
+
background-color: #e5e5e5;
|
|
34
|
+
color: #161616;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.suggestions li:not(:last-of-type) {
|
|
39
|
+
border-bottom: 1px solid #999;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.autocomplete {
|
|
43
|
+
position: relative;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.autocompleteSearch {
|
|
47
|
+
width: 100%;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.suggestions a {
|
|
51
|
+
color: inherit;
|
|
52
|
+
text-decoration: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.invalid input {
|
|
56
|
+
outline: 2px solid var(--cds-support-error, #da1e28);
|
|
57
|
+
outline-offset: -2px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.invalidMsg {
|
|
61
|
+
color: var(--cds-text-error, #da1e28);
|
|
62
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Type, validator } from '@openmrs/esm-framework';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This is the config schema. It expects a configuration object which
|
|
5
|
+
* looks like this:
|
|
6
|
+
*
|
|
7
|
+
* ```json
|
|
8
|
+
* { "casualGreeting": true, "whoToGreet": ["Mom"] }
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* In OpenMRS Microfrontends, all config parameters are optional. Thus,
|
|
12
|
+
* all elements must have a reasonable default. A good default is one
|
|
13
|
+
* that works well with the reference application.
|
|
14
|
+
*
|
|
15
|
+
* To understand the schema below, please read the configuration system
|
|
16
|
+
* documentation:
|
|
17
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/config
|
|
18
|
+
* Note especially the section "How do I make my module configurable?"
|
|
19
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/config?id=im-developing-an-esm-module-how-do-i-make-it-configurable
|
|
20
|
+
* and the Schema Reference
|
|
21
|
+
* https://openmrs.github.io/openmrs-esm-core/#/main/config?id=schema-reference
|
|
22
|
+
*/
|
|
23
|
+
export const configSchema = {
|
|
24
|
+
casualGreeting: {
|
|
25
|
+
_type: Type.Boolean,
|
|
26
|
+
_default: false,
|
|
27
|
+
_description: 'Whether to use a casual greeting (or a formal one).',
|
|
28
|
+
},
|
|
29
|
+
whoToGreet: {
|
|
30
|
+
_type: Type.Array,
|
|
31
|
+
_default: ['World'],
|
|
32
|
+
_description: 'Who should be greeted. Names will be separated by a comma and space.',
|
|
33
|
+
_elements: {
|
|
34
|
+
_type: Type.String,
|
|
35
|
+
},
|
|
36
|
+
_validators: [validator((v) => v.length > 0, 'At least one person must be greeted.')],
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type Config = {
|
|
41
|
+
casualGreeting: boolean;
|
|
42
|
+
whoToGreet: Array<string>;
|
|
43
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as useUserMappedPharmacies } from './useUserMappedPharmacies';
|
|
2
|
+
export { default as usePharmacy } from './usePharmacy';
|
|
3
|
+
export { default as usePharmacyPatients } from './usePharmacyPatients';
|
|
4
|
+
export { default as usePharmacyUsers } from './usePharmacyUsers';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { Pharmacy } from '../types';
|
|
4
|
+
|
|
5
|
+
const usePharmacy = (pharmacyUuid: string) => {
|
|
6
|
+
const customeRepresentation = 'full';
|
|
7
|
+
const url = `${restBaseUrl}/location/${pharmacyUuid}?v=${customeRepresentation}`;
|
|
8
|
+
const { isLoading, error, data } = useSWR<FetchResponse<Pharmacy>>(url, openmrsFetch);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
isLoading,
|
|
12
|
+
error,
|
|
13
|
+
pharmacy: data?.data,
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default usePharmacy;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { FetchResponse, formatDatetime, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { PharamacyPatientMapping, PharmacyPatient } from '../types';
|
|
4
|
+
|
|
5
|
+
const usePharmacyPatients = (pharmacyUuid: string) => {
|
|
6
|
+
const url = `${restBaseUrl}/datafilter/search?type=org.openmrs.Patient`;
|
|
7
|
+
|
|
8
|
+
const fetchPharmacyPatients = async (url: string) => {
|
|
9
|
+
const abortController = new AbortController();
|
|
10
|
+
|
|
11
|
+
const response = await openmrsFetch(url, {
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
entityIdentifier: '',
|
|
14
|
+
entityType: 'org.openmrs.Patient',
|
|
15
|
+
basisIdentifier: pharmacyUuid,
|
|
16
|
+
basisType: 'org.openmrs.Location',
|
|
17
|
+
}),
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
signal: abortController.signal,
|
|
23
|
+
});
|
|
24
|
+
return response;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const { data, isLoading, error } = useSWR<FetchResponse<Array<PharamacyPatientMapping>>>(url, fetchPharmacyPatients);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
isLoading,
|
|
31
|
+
error,
|
|
32
|
+
patients: (data?.data ?? []).map(extractData),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const extractData = (mapping: PharamacyPatientMapping) => {
|
|
37
|
+
return {
|
|
38
|
+
name: mapping.entity.name,
|
|
39
|
+
uuid: mapping.entity.uuid,
|
|
40
|
+
age: mapping.entity.age,
|
|
41
|
+
gender: mapping.entity.gender,
|
|
42
|
+
telephoneContact: mapping.entity['Telephone contact'],
|
|
43
|
+
openmrsId: mapping.entity.OpenMRSID,
|
|
44
|
+
dateMapped: formatDatetime(new Date(mapping.dateCreated), { mode: 'standard', noToday: true }),
|
|
45
|
+
} as PharmacyPatient;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default usePharmacyPatients;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { FetchResponse, formatDatetime, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { PhamarcyUserMapping, PharmacyUser } from '../types';
|
|
4
|
+
|
|
5
|
+
const usePharmacyUsers = (pharmacyUuid: string) => {
|
|
6
|
+
const customPresentation = 'full';
|
|
7
|
+
const url = `${restBaseUrl}/datafilter/search?type=org.openmrs.User`;
|
|
8
|
+
const fetchPharmacyUsers = async (url: string) => {
|
|
9
|
+
const abortController = new AbortController();
|
|
10
|
+
|
|
11
|
+
const response = await openmrsFetch(url, {
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
entityIdentifier: '',
|
|
14
|
+
entityType: 'org.openmrs.User',
|
|
15
|
+
basisIdentifier: pharmacyUuid,
|
|
16
|
+
basisType: 'org.openmrs.Location',
|
|
17
|
+
}),
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
signal: abortController.signal,
|
|
23
|
+
});
|
|
24
|
+
return response;
|
|
25
|
+
};
|
|
26
|
+
const { data, isLoading, error } = useSWR<FetchResponse<PhamarcyUserMapping[]>>(url, fetchPharmacyUsers);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
isLoading,
|
|
30
|
+
error,
|
|
31
|
+
users: (data?.data ?? []).map(extractData),
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const extractData = (mapping: PhamarcyUserMapping) => {
|
|
36
|
+
return {
|
|
37
|
+
name: mapping.entity.name,
|
|
38
|
+
uuid: mapping.entity.uuid,
|
|
39
|
+
dateMapped: formatDatetime(new Date(mapping.dateCreated), { mode: 'standard', noToday: true }),
|
|
40
|
+
} as PharmacyUser;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default usePharmacyUsers;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { FetchResponse, formatDatetime, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWR from 'swr';
|
|
3
|
+
import { PhamarcyUserMapping, Pharmacy } from '../types';
|
|
4
|
+
|
|
5
|
+
const extractPharmacyData = (mapping: PhamarcyUserMapping) => {
|
|
6
|
+
return {
|
|
7
|
+
uuid: mapping.basis.uuid,
|
|
8
|
+
name: mapping.basis.name,
|
|
9
|
+
dateMaped: formatDatetime(new Date(mapping.dateCreated), { mode: 'standard', noToday: true }),
|
|
10
|
+
cityVillage: mapping.basis.cityVillage,
|
|
11
|
+
countyDistrict: mapping.basis.countyDistrict,
|
|
12
|
+
mflCode: mapping.basis['Master Facility Code'],
|
|
13
|
+
} as Pharmacy;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const useUserMappedPharmacies = (userUuid: string) => {
|
|
17
|
+
const url = `${restBaseUrl}/datafilter/search`;
|
|
18
|
+
|
|
19
|
+
const fetchUserPharmacies = async (url: string) => {
|
|
20
|
+
const abortController = new AbortController();
|
|
21
|
+
|
|
22
|
+
const response = await openmrsFetch(url, {
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
entityIdentifier: userUuid,
|
|
25
|
+
entityType: 'org.openmrs.User',
|
|
26
|
+
basisIdentifier: '',
|
|
27
|
+
basisType: 'org.openmrs.Location',
|
|
28
|
+
}),
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
},
|
|
33
|
+
signal: abortController.signal,
|
|
34
|
+
});
|
|
35
|
+
return response;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const { isLoading, error, data } = useSWR<FetchResponse<PhamarcyUserMapping[]>>(url, fetchUserPharmacies);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
isLoading,
|
|
42
|
+
error,
|
|
43
|
+
pharmacies: (data?.data ?? []).map(extractPharmacyData),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default useUserMappedPharmacies;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getAsyncLifecycle, defineConfigSchema, getSyncLifecycle, registerBreadcrumbs } from '@openmrs/esm-framework';
|
|
2
|
+
import { configSchema } from './config-schema';
|
|
3
|
+
import { createLeftPanelLink } from './pharmacy-left-panel/pharmacy-left-panel-link.component';
|
|
4
|
+
const moduleName = '@kenyaemr/esm-pharmacy-app';
|
|
5
|
+
|
|
6
|
+
const options = {
|
|
7
|
+
featureName: 'esm-pharmacy-app',
|
|
8
|
+
moduleName,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
|
|
12
|
+
|
|
13
|
+
export function startupApp() {
|
|
14
|
+
const pharmacyBasepath = `${window.spaBase}/home/pharmacy`;
|
|
15
|
+
|
|
16
|
+
defineConfigSchema(moduleName, configSchema);
|
|
17
|
+
registerBreadcrumbs([
|
|
18
|
+
{
|
|
19
|
+
title: 'pharmacy',
|
|
20
|
+
path: pharmacyBasepath,
|
|
21
|
+
parent: `${window.spaBase}/home`,
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const root = getAsyncLifecycle(() => import('./root.component'), options);
|
|
27
|
+
export const pharmacyAssignmentForm = getAsyncLifecycle(
|
|
28
|
+
() => import('./pharamacy-forms/pharamacy-assignment-form.workspace'),
|
|
29
|
+
options,
|
|
30
|
+
);
|
|
31
|
+
export const pharmacyDeleteConfirmDialog = getAsyncLifecycle(
|
|
32
|
+
() => import('./pharamacy-forms/delete-confirm-dialog.modal'),
|
|
33
|
+
options,
|
|
34
|
+
);
|
|
35
|
+
export const pharmacyDashboardLink = getSyncLifecycle(
|
|
36
|
+
createLeftPanelLink({
|
|
37
|
+
name: 'pharmacy',
|
|
38
|
+
title: 'Community Pharmacy',
|
|
39
|
+
}),
|
|
40
|
+
options,
|
|
41
|
+
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Button, ButtonSet, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
|
|
5
|
+
const DeleteConfirmDialog = ({ onClose, onDelete }) => {
|
|
6
|
+
const { t } = useTranslation();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<React.Fragment>
|
|
10
|
+
<ModalHeader closeModal={onClose}>{t('warning', 'Warning!')}</ModalHeader>
|
|
11
|
+
<ModalBody>Are you sure you want to revoke access?</ModalBody>
|
|
12
|
+
<ModalFooter>
|
|
13
|
+
<ButtonSet>
|
|
14
|
+
<Button kind="secondary" onClick={onClose}>
|
|
15
|
+
Cancel
|
|
16
|
+
</Button>
|
|
17
|
+
<Button kind="primary" onClick={onDelete}>
|
|
18
|
+
Revoke
|
|
19
|
+
</Button>
|
|
20
|
+
</ButtonSet>
|
|
21
|
+
</ModalFooter>
|
|
22
|
+
</React.Fragment>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default DeleteConfirmDialog;
|