@openmrs/esm-implementer-tools-app 3.2.0 → 3.2.1-pre.1015
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/217.js +1 -1
- package/dist/217.js.map +1 -1
- package/dist/237.js +1 -1
- package/dist/237.js.map +1 -1
- package/dist/25.js +1 -1
- package/dist/25.js.map +1 -1
- package/dist/307.js +1 -1
- package/dist/307.js.map +1 -1
- package/dist/389.js +2 -0
- package/dist/389.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/597.js +3 -0
- package/dist/{682.js.LICENSE.txt → 597.js.LICENSE.txt} +0 -0
- package/dist/{682.js.map → 597.js.map} +1 -1
- package/dist/664.js +2 -0
- package/dist/664.js.map +1 -0
- package/dist/743.js +2 -0
- package/dist/743.js.map +1 -0
- package/dist/788.js +1 -1
- package/dist/788.js.map +1 -1
- package/dist/839.js +1 -1
- package/dist/839.js.map +1 -1
- package/dist/865.js +2 -0
- package/dist/{185.js.map → 865.js.map} +1 -1
- package/dist/openmrs-esm-implementer-tools-app.js +1 -2
- package/dist/openmrs-esm-implementer-tools-app.js.buildmanifest.json +101 -101
- package/dist/openmrs-esm-implementer-tools-app.js.map +1 -1
- package/dist/openmrs-esm-implementer-tools-app.old +2 -0
- package/jest.config.js +1 -1
- package/package.json +4 -3
- package/src/backend-dependencies/backend-dependencies.component.tsx +1 -1
- package/src/configuration/config-tree-for-module.component.tsx +2 -2
- package/src/configuration/config-tree.component.tsx +22 -25
- package/src/configuration/configuration.component.tsx +20 -21
- package/src/configuration/configuration.styles.scss +70 -0
- package/src/configuration/configuration.test.tsx +19 -13
- package/src/configuration/description.component.tsx +20 -6
- package/src/configuration/description.styles.scss +23 -0
- package/src/configuration/display-value.tsx +21 -9
- package/src/configuration/editable-value.component.tsx +19 -15
- package/src/configuration/{editable-value.styles.css → editable-value.styles.scss} +11 -0
- package/src/configuration/extension-slots-config-tree.tsx +5 -19
- package/src/configuration/value-editor.tsx +9 -13
- package/src/configuration/value-editors/array-editor.tsx +1 -2
- package/src/configuration/value-editors/{concept-search.styles.css → concept-search.styles.scss} +10 -0
- package/src/configuration/value-editors/concept-search.tsx +53 -31
- package/src/configuration/value-editors/extension-slot-add.tsx +9 -4
- package/src/configuration/value-editors/extension-slot-remove.tsx +2 -2
- package/src/configuration/value-editors/person-attribute-search.resource.tsx +18 -0
- package/src/configuration/value-editors/person-attribute-search.tsx +123 -0
- package/src/configuration/value-editors/uuid-search.scss +23 -0
- package/src/configuration/value-editors/value-editor-field.tsx +12 -1
- package/src/declarations.d.tsx +2 -4
- package/src/implementer-tools.button.tsx +3 -7
- package/src/implementer-tools.component.tsx +3 -2
- package/src/implementer-tools.styles.scss +19 -0
- package/src/popup/popup.component.tsx +4 -9
- package/src/popup/{popup.styles.css → popup.styles.scss} +10 -17
- package/src/ui-editor/ui-editor.tsx +12 -14
- package/translations/en.json +7 -1
- package/dist/143.js +0 -2
- package/dist/143.js.map +0 -1
- package/dist/185.js +0 -2
- package/dist/682.js +0 -3
- package/dist/684.js +0 -2
- package/dist/684.js.map +0 -1
- package/dist/728.js +0 -2
- package/dist/728.js.map +0 -1
- package/src/configuration/configuration.styles.css +0 -70
- package/src/configuration/description.styles.css +0 -19
- package/src/implementer-tools.styles.css +0 -10
|
@@ -1,28 +1,42 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import styles from "./description.styles.
|
|
2
|
+
import styles from "./description.styles.scss";
|
|
3
3
|
import { useStore } from "@openmrs/esm-framework";
|
|
4
4
|
import { implementerToolsStore } from "../store";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
5
6
|
|
|
6
7
|
export function Description() {
|
|
8
|
+
const { t } = useTranslation();
|
|
7
9
|
const { activeItemDescription } = useStore(implementerToolsStore);
|
|
8
10
|
return (
|
|
9
11
|
<div>
|
|
10
12
|
{activeItemDescription ? (
|
|
11
13
|
<>
|
|
12
|
-
<
|
|
14
|
+
<h2 className={styles.path}>
|
|
13
15
|
{activeItemDescription.path.slice(1).join(" → ")}
|
|
14
|
-
</
|
|
16
|
+
</h2>
|
|
15
17
|
<p className={styles.description}>
|
|
16
18
|
{activeItemDescription.description}
|
|
17
19
|
</p>
|
|
18
20
|
<p className={styles.source}>
|
|
19
21
|
{activeItemDescription.source === "default" ? (
|
|
20
|
-
<>
|
|
22
|
+
<>
|
|
23
|
+
{t(
|
|
24
|
+
"itemDescriptionSourceDefaultText",
|
|
25
|
+
"The current value is the default."
|
|
26
|
+
)}
|
|
27
|
+
</>
|
|
21
28
|
) : activeItemDescription.source ? (
|
|
22
|
-
<>
|
|
29
|
+
<>
|
|
30
|
+
{t("activeItemSourceText", "The current value comes from ")}
|
|
31
|
+
{activeItemDescription.source}
|
|
32
|
+
</>
|
|
23
33
|
) : null}
|
|
24
34
|
</p>
|
|
25
|
-
{activeItemDescription.value ?
|
|
35
|
+
{activeItemDescription.value ? (
|
|
36
|
+
<h4 className={styles.productiveHeading01}>
|
|
37
|
+
{t("value", "Value")}
|
|
38
|
+
</h4>
|
|
39
|
+
) : null}
|
|
26
40
|
<p className={styles.value}>
|
|
27
41
|
{Array.isArray(activeItemDescription.value)
|
|
28
42
|
? activeItemDescription.value.map((v, i) => (
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@import "../implementer-tools.styles.scss";
|
|
2
|
+
|
|
3
|
+
.path {
|
|
4
|
+
@include carbon--type-style("productive-heading-02");
|
|
5
|
+
margin-bottom: $spacing-03;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.description {
|
|
9
|
+
@include carbon--type-style("body-short-01");
|
|
10
|
+
margin-bottom: $spacing-05;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.source {
|
|
14
|
+
@include carbon--type-style("body-short-01");
|
|
15
|
+
font-style: italic;
|
|
16
|
+
margin-bottom: $spacing-03;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.value {
|
|
20
|
+
font-family: monospace;
|
|
21
|
+
margin-top: $spacing-03;
|
|
22
|
+
margin-left: $spacing-03;
|
|
23
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { StructuredListCell, StructuredListRow } from "carbon-components-react";
|
|
1
2
|
import React from "react";
|
|
3
|
+
import styles from "./configuration.styles.scss";
|
|
2
4
|
|
|
3
5
|
export interface DisplayValueProps {
|
|
4
6
|
value: any;
|
|
@@ -7,26 +9,36 @@ export interface DisplayValueProps {
|
|
|
7
9
|
export function DisplayValue({ value }: DisplayValueProps) {
|
|
8
10
|
// TODO: Make this show the concept name for ConceptUUID type values
|
|
9
11
|
return (
|
|
10
|
-
|
|
12
|
+
<div>
|
|
11
13
|
{Array.isArray(value)
|
|
12
14
|
? typeof value[0] === "object"
|
|
13
15
|
? value.map((v, i) => (
|
|
14
|
-
<
|
|
15
|
-
<
|
|
16
|
-
|
|
16
|
+
<StructuredListRow key={`${v}-${i}`}>
|
|
17
|
+
<StructuredListCell className={styles.smallListCell}>
|
|
18
|
+
{i + 1}
|
|
19
|
+
</StructuredListCell>
|
|
20
|
+
<StructuredListCell className={styles.smallListCell}>
|
|
21
|
+
<DisplayValue value={v} />
|
|
22
|
+
</StructuredListCell>
|
|
23
|
+
</StructuredListRow>
|
|
17
24
|
))
|
|
18
|
-
: value.join(", ")
|
|
25
|
+
: `[ ${value.join(", ")} ]`
|
|
19
26
|
: typeof value === "object" && value !== null
|
|
20
27
|
? Object.entries(value).map(([k, v], i) => (
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
<StructuredListRow key={`${k}-${i}`}>
|
|
29
|
+
<StructuredListCell className={styles.smallListCell}>
|
|
30
|
+
{k}
|
|
31
|
+
</StructuredListCell>
|
|
32
|
+
<StructuredListCell className={styles.smallListCell}>
|
|
33
|
+
<DisplayValue value={v} />
|
|
34
|
+
</StructuredListCell>
|
|
35
|
+
</StructuredListRow>
|
|
24
36
|
))
|
|
25
37
|
: typeof value === "string" || typeof value === "number"
|
|
26
38
|
? value
|
|
27
39
|
: value == null
|
|
28
40
|
? "none"
|
|
29
41
|
: JSON.stringify(value)}
|
|
30
|
-
|
|
42
|
+
</div>
|
|
31
43
|
);
|
|
32
44
|
}
|
|
@@ -3,7 +3,8 @@ import isEqual from "lodash-es/isEqual";
|
|
|
3
3
|
import unset from "lodash-es/unset";
|
|
4
4
|
import cloneDeep from "lodash-es/cloneDeep";
|
|
5
5
|
import Reset16 from "@carbon/icons-react/es/reset/16";
|
|
6
|
-
import
|
|
6
|
+
import Edit16 from "@carbon/icons-react/es/edit/16";
|
|
7
|
+
import styles from "./editable-value.styles.scss";
|
|
7
8
|
import { Button } from "carbon-components-react";
|
|
8
9
|
import {
|
|
9
10
|
ConfigValue,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
import { ValueEditor, CustomValueType } from "./value-editor";
|
|
16
17
|
import { implementerToolsStore, ImplementerToolsStore } from "../store";
|
|
17
18
|
import { DisplayValue } from "./display-value";
|
|
19
|
+
import { useTranslation } from "react-i18next";
|
|
18
20
|
|
|
19
21
|
export interface EditableValueProps {
|
|
20
22
|
path: Array<string>;
|
|
@@ -41,6 +43,7 @@ export default function EditableValue({
|
|
|
41
43
|
const [editing, setEditing] = useState(false);
|
|
42
44
|
const [error, setError] = useState<string | null>(null);
|
|
43
45
|
const activeConfigRef = useRef<HTMLButtonElement>(null);
|
|
46
|
+
const { t } = useTranslation();
|
|
44
47
|
|
|
45
48
|
const closeEditor = () => {
|
|
46
49
|
setEditing(false);
|
|
@@ -114,25 +117,26 @@ export default function EditableValue({
|
|
|
114
117
|
/>
|
|
115
118
|
</>
|
|
116
119
|
) : (
|
|
117
|
-
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}`}
|
|
120
|
+
<div className={styles.elementValue}>
|
|
121
|
+
<DisplayValue value={element._value} />
|
|
122
|
+
<Button
|
|
123
|
+
kind="ghost"
|
|
124
|
+
size="sm"
|
|
125
|
+
iconDescription={t("editValueButtonText", "Edit")}
|
|
124
126
|
onClick={() => setEditing(true)}
|
|
125
127
|
ref={activeConfigRef}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
renderIcon={Edit16}
|
|
129
|
+
hasIconOnly
|
|
130
|
+
/>
|
|
129
131
|
{element._source == "temporary config" ? (
|
|
130
132
|
<Button
|
|
131
|
-
style={{ marginLeft: "1em" }}
|
|
132
133
|
renderIcon={Reset16}
|
|
133
134
|
size="sm"
|
|
134
|
-
kind="
|
|
135
|
-
iconDescription=
|
|
135
|
+
kind="ghost"
|
|
136
|
+
iconDescription={t(
|
|
137
|
+
"resetToDefaultValueButtonText",
|
|
138
|
+
"Reset to default"
|
|
139
|
+
)}
|
|
136
140
|
hasIconOnly
|
|
137
141
|
onClick={() => {
|
|
138
142
|
temporaryConfigStore.setState(
|
|
@@ -144,7 +148,7 @@ export default function EditableValue({
|
|
|
144
148
|
}}
|
|
145
149
|
/>
|
|
146
150
|
) : null}
|
|
147
|
-
|
|
151
|
+
</div>
|
|
148
152
|
)}
|
|
149
153
|
{error && <div className={styles.error}>{error}</div>}
|
|
150
154
|
</div>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import "../implementer-tools.styles.scss";
|
|
2
|
+
|
|
1
3
|
.secretButton {
|
|
2
4
|
background: none;
|
|
3
5
|
color: #474b6b;
|
|
@@ -15,3 +17,12 @@
|
|
|
15
17
|
.overriddenValue {
|
|
16
18
|
color: #2020a0;
|
|
17
19
|
}
|
|
20
|
+
|
|
21
|
+
.elementValue {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: flex-start;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.editValueButton {
|
|
27
|
+
margin-left: $spacing-05;
|
|
28
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import EditableValue from "./editable-value.component";
|
|
3
3
|
import isEqual from "lodash-es/isEqual";
|
|
4
4
|
import {
|
|
5
|
-
extensionStore,
|
|
6
5
|
ExtensionSlotConfigureValueObject,
|
|
7
|
-
|
|
8
|
-
ExtensionStore,
|
|
6
|
+
useAssignedExtensions,
|
|
9
7
|
} from "@openmrs/esm-framework";
|
|
10
8
|
import { ExtensionConfigureTree } from "./extension-configure-tree";
|
|
11
9
|
import { Subtree } from "./layout/subtree.component";
|
|
@@ -43,7 +41,7 @@ export function ExtensionSlotsConfigTree({
|
|
|
43
41
|
<ExtensionSlotConfigTree
|
|
44
42
|
key={slotName}
|
|
45
43
|
config={extensionsConfig?.[slotName]}
|
|
46
|
-
path={[moduleName, "
|
|
44
|
+
path={[moduleName, "extensionSlots", slotName]}
|
|
47
45
|
/>
|
|
48
46
|
))}
|
|
49
47
|
</Subtree>
|
|
@@ -56,28 +54,16 @@ interface ExtensionSlotConfigProps {
|
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
function ExtensionSlotConfigTree({ config, path }: ExtensionSlotConfigProps) {
|
|
59
|
-
const [assignedExtensions, setAssignedExtensions] = useState<Array<string>>(
|
|
60
|
-
[]
|
|
61
|
-
);
|
|
62
57
|
const moduleName = path[0];
|
|
63
58
|
const slotName = path[2];
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
function update(state) {
|
|
67
|
-
setAssignedExtensions(
|
|
68
|
-
state.slots[slotName]?.instances?.[moduleName]?.assignedIds
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
update(extensionStore.getState());
|
|
72
|
-
return extensionStore.subscribe(update);
|
|
73
|
-
}, []);
|
|
59
|
+
const assignedExtensions = useAssignedExtensions(slotName);
|
|
74
60
|
|
|
75
61
|
function setActiveExtensionSlotOnMouseEnter(moduleName, slotName) {
|
|
76
62
|
if (!implementerToolsStore.getState().configPathBeingEdited) {
|
|
77
63
|
implementerToolsStore.setState({
|
|
78
64
|
activeItemDescription: {
|
|
79
65
|
path: [moduleName, slotName],
|
|
80
|
-
value: assignedExtensions,
|
|
66
|
+
value: assignedExtensions.map((e) => e.id),
|
|
81
67
|
},
|
|
82
68
|
});
|
|
83
69
|
}
|
|
@@ -5,7 +5,8 @@ import { Button } from "carbon-components-react";
|
|
|
5
5
|
import { Type } from "@openmrs/esm-framework";
|
|
6
6
|
import { ConfigValueDescriptor } from "./editable-value.component";
|
|
7
7
|
import { ValueEditorField } from "./value-editors/value-editor-field";
|
|
8
|
-
import styles from "./configuration.styles.
|
|
8
|
+
import styles from "./configuration.styles.scss";
|
|
9
|
+
import { useTranslation } from "react-i18next";
|
|
9
10
|
|
|
10
11
|
export type CustomValueType = "add" | "remove" | "order" | "configure";
|
|
11
12
|
|
|
@@ -28,6 +29,7 @@ export function ValueEditor({
|
|
|
28
29
|
}: ValueEditorProps) {
|
|
29
30
|
const ref = useRef<HTMLDivElement>(null);
|
|
30
31
|
const [tmpValue, setTmpValue] = useState(element._value);
|
|
32
|
+
const { t } = useTranslation();
|
|
31
33
|
|
|
32
34
|
const valueType = customType ?? element._type;
|
|
33
35
|
|
|
@@ -58,20 +60,14 @@ export function ValueEditor({
|
|
|
58
60
|
<div className={styles.valueEditorButtons}>
|
|
59
61
|
<Button
|
|
60
62
|
renderIcon={Save16}
|
|
61
|
-
size="sm"
|
|
62
63
|
kind="primary"
|
|
63
|
-
iconDescription="Save"
|
|
64
|
-
hasIconOnly
|
|
65
64
|
onClick={() => handleSave(JSON.stringify(tmpValue))}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
hasIconOnly
|
|
73
|
-
onClick={handleClose}
|
|
74
|
-
/>
|
|
65
|
+
>
|
|
66
|
+
{t("saveValueButtonText", "Save")}
|
|
67
|
+
</Button>
|
|
68
|
+
<Button renderIcon={Close16} kind="secondary" onClick={handleClose}>
|
|
69
|
+
{t("CancelButtonText", "Cancel")}
|
|
70
|
+
</Button>
|
|
75
71
|
</div>
|
|
76
72
|
</div>
|
|
77
73
|
);
|
package/src/configuration/value-editors/{concept-search.styles.css → concept-search.styles.scss}
RENAMED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
@import "../../implementer-tools.styles.scss";
|
|
2
|
+
|
|
3
|
+
.activeConceptUuid {
|
|
4
|
+
@include carbon--type-style("body-short-01");
|
|
5
|
+
color: $ui-05;
|
|
6
|
+
margin-bottom: $spacing-03;
|
|
7
|
+
padding-bottom: $spacing-03;
|
|
8
|
+
border-bottom: 1px solid $ui-03;
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
.autocomplete,
|
|
2
12
|
.autocomplete > [role="combobox"] {
|
|
3
13
|
display: flex;
|
|
@@ -5,17 +5,26 @@ import {
|
|
|
5
5
|
fetchConceptByUuid,
|
|
6
6
|
performConceptSearch,
|
|
7
7
|
} from "./concept-search.resource";
|
|
8
|
-
import styles from "./
|
|
8
|
+
import styles from "./uuid-search.scss";
|
|
9
|
+
import {
|
|
10
|
+
Search,
|
|
11
|
+
StructuredListCell,
|
|
12
|
+
StructuredListRow,
|
|
13
|
+
StructuredListWrapper,
|
|
14
|
+
} from "carbon-components-react";
|
|
15
|
+
import { useTranslation } from "react-i18next";
|
|
9
16
|
|
|
10
17
|
interface ConceptSearchBoxProps {
|
|
18
|
+
value: string;
|
|
11
19
|
setConcept: (concept) => void;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
|
-
export function ConceptSearchBox({ setConcept }: ConceptSearchBoxProps) {
|
|
22
|
+
export function ConceptSearchBox({ setConcept, value }: ConceptSearchBoxProps) {
|
|
15
23
|
const [searchTerm, setSearchTerm] = useState("");
|
|
16
24
|
const [searchResults, setSearchResults] = useState([]);
|
|
17
|
-
const [activeConceptUuid, setActiveConceptUuid] = useState<any>(
|
|
25
|
+
const [activeConceptUuid, setActiveConceptUuid] = useState<any>(value);
|
|
18
26
|
const searchTimeoutInMs = 300;
|
|
27
|
+
const { t } = useTranslation();
|
|
19
28
|
|
|
20
29
|
const id = useMemo(() => uniqueId(), []);
|
|
21
30
|
|
|
@@ -48,42 +57,55 @@ export function ConceptSearchBox({ setConcept }: ConceptSearchBoxProps) {
|
|
|
48
57
|
}, [searchTerm]);
|
|
49
58
|
|
|
50
59
|
return (
|
|
51
|
-
<div
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
<div>
|
|
61
|
+
{activeConceptUuid && (
|
|
62
|
+
<p className={styles.activeUuid}>{activeConceptUuid}</p>
|
|
63
|
+
)}
|
|
64
|
+
<div className={styles.autocomplete}>
|
|
65
|
+
<Search
|
|
66
|
+
id={`searchbox-${id}`}
|
|
67
|
+
labelText=""
|
|
68
|
+
type="text"
|
|
69
|
+
size="sm"
|
|
70
|
+
autoComplete="off"
|
|
71
|
+
autoCapitalize="off"
|
|
72
|
+
aria-autocomplete="list"
|
|
73
|
+
role="combobox"
|
|
74
|
+
aria-label={t("searchConceptHelperText", "Concept Name")}
|
|
75
|
+
aria-controls={`searchbox-${id}`}
|
|
76
|
+
aria-expanded={searchResults.length > 0}
|
|
77
|
+
placeholder={t("searchConceptHelperText", "Concept Name")}
|
|
78
|
+
onChange={($event) => {
|
|
79
|
+
handleSearchTermChange($event.target.value);
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
{!!searchResults.length && (
|
|
83
|
+
<StructuredListWrapper
|
|
84
|
+
selection
|
|
85
|
+
id={`searchbox-${id}`}
|
|
86
|
+
className={styles.listbox}
|
|
87
|
+
>
|
|
88
|
+
{searchResults.map((concept: any) => (
|
|
89
|
+
<StructuredListRow
|
|
72
90
|
key={concept.uuid}
|
|
73
91
|
role="option"
|
|
74
|
-
style={{ padding: "5px" }}
|
|
75
92
|
onClick={() => {
|
|
76
93
|
handleUuidChange(concept);
|
|
77
94
|
}}
|
|
78
95
|
aria-selected="true"
|
|
79
96
|
>
|
|
80
|
-
{
|
|
81
|
-
|
|
97
|
+
<StructuredListCell className={styles.smallListCell}>
|
|
98
|
+
{concept.display}
|
|
99
|
+
</StructuredListCell>
|
|
100
|
+
</StructuredListRow>
|
|
82
101
|
))}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
</StructuredListWrapper>
|
|
103
|
+
)}
|
|
104
|
+
{searchTerm && searchResults && !searchResults.length && (
|
|
105
|
+
<p className={styles.bodyShort01}>
|
|
106
|
+
{t("noConceptsFoundText", "No matching results found")}
|
|
107
|
+
</p>
|
|
108
|
+
)}
|
|
87
109
|
</div>
|
|
88
110
|
</div>
|
|
89
111
|
);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { MultiSelect } from "carbon-components-react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ExtensionInternalStore,
|
|
5
|
+
getExtensionInternalStore,
|
|
6
|
+
} from "@openmrs/esm-framework";
|
|
7
|
+
|
|
8
|
+
const extensionInternalStore = getExtensionInternalStore();
|
|
4
9
|
|
|
5
10
|
export function ExtensionSlotAdd({ value, setValue }) {
|
|
6
11
|
const [availableExtensions, setAvailableExtensions] = useState<Array<string>>(
|
|
@@ -8,11 +13,11 @@ export function ExtensionSlotAdd({ value, setValue }) {
|
|
|
8
13
|
);
|
|
9
14
|
|
|
10
15
|
useEffect(() => {
|
|
11
|
-
function update(state:
|
|
16
|
+
function update(state: ExtensionInternalStore) {
|
|
12
17
|
setAvailableExtensions(Object.keys(state.extensions));
|
|
13
18
|
}
|
|
14
|
-
update(
|
|
15
|
-
return
|
|
19
|
+
update(extensionInternalStore.getState());
|
|
20
|
+
return extensionInternalStore.subscribe(update);
|
|
16
21
|
}, []);
|
|
17
22
|
|
|
18
23
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { MultiSelect } from "carbon-components-react";
|
|
3
|
-
import {
|
|
3
|
+
import { useAssignedExtensions } from "@openmrs/esm-framework";
|
|
4
4
|
|
|
5
5
|
export function ExtensionSlotRemove({
|
|
6
6
|
slotName,
|
|
@@ -8,7 +8,7 @@ export function ExtensionSlotRemove({
|
|
|
8
8
|
value,
|
|
9
9
|
setValue,
|
|
10
10
|
}) {
|
|
11
|
-
const assignedIds =
|
|
11
|
+
const assignedIds = useAssignedExtensions(slotName).map((e) => e.id);
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<MultiSelect.Filterable
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { openmrsFetch } from "@openmrs/esm-framework";
|
|
2
|
+
|
|
3
|
+
export function fetchPersonAttributeTypeByUuid(
|
|
4
|
+
personAttributeTypeUuid: string
|
|
5
|
+
) {
|
|
6
|
+
return openmrsFetch(
|
|
7
|
+
`/ws/rest/v1/personattributetype/${personAttributeTypeUuid}`,
|
|
8
|
+
{
|
|
9
|
+
method: "GET",
|
|
10
|
+
}
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function performPersonAttributeTypeSearch(query: string) {
|
|
15
|
+
return openmrsFetch(`/ws/rest/v1/personattributetype/?q=${query}`, {
|
|
16
|
+
method: "GET",
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo } from "react";
|
|
2
|
+
import debounce from "lodash-es/debounce";
|
|
3
|
+
import uniqueId from "lodash-es/uniqueId";
|
|
4
|
+
import { performPersonAttributeTypeSearch } from "./person-attribute-search.resource";
|
|
5
|
+
import styles from "./uuid-search.scss";
|
|
6
|
+
import { useTranslation } from "react-i18next";
|
|
7
|
+
import {
|
|
8
|
+
Search,
|
|
9
|
+
StructuredListCell,
|
|
10
|
+
StructuredListRow,
|
|
11
|
+
StructuredListWrapper,
|
|
12
|
+
} from "carbon-components-react";
|
|
13
|
+
|
|
14
|
+
interface PersonAttributeTypeSearchBoxProps {
|
|
15
|
+
value: string;
|
|
16
|
+
setPersonAttributeUuid: (personAttributeType) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function PersonAttributeTypeSearchBox({
|
|
20
|
+
setPersonAttributeUuid,
|
|
21
|
+
value,
|
|
22
|
+
}: PersonAttributeTypeSearchBoxProps) {
|
|
23
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
24
|
+
const [searchResults, setSearchResults] = useState([]);
|
|
25
|
+
const [activePersonAttributeUuid, setActivePersonAttributeUuid] =
|
|
26
|
+
useState<any>(value);
|
|
27
|
+
const { t } = useTranslation();
|
|
28
|
+
const searchTimeoutInMs = 300;
|
|
29
|
+
|
|
30
|
+
const id = useMemo(() => uniqueId(), []);
|
|
31
|
+
|
|
32
|
+
const handleUuidChange = (personAttributeType) => {
|
|
33
|
+
setActivePersonAttributeUuid(personAttributeType.uuid);
|
|
34
|
+
resetSearch();
|
|
35
|
+
setPersonAttributeUuid(personAttributeType.uuid);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const resetSearch = () => {
|
|
39
|
+
setSearchTerm("");
|
|
40
|
+
setSearchResults([]);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleSearchTermChange = debounce((searchTerm) => {
|
|
44
|
+
setSearchTerm(searchTerm);
|
|
45
|
+
}, searchTimeoutInMs);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const ac = new AbortController();
|
|
49
|
+
|
|
50
|
+
if (searchTerm && searchTerm.length >= 2) {
|
|
51
|
+
performPersonAttributeTypeSearch(searchTerm).then(
|
|
52
|
+
({ data: { results } }) => {
|
|
53
|
+
setSearchResults(results.slice(0, 9));
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
setSearchResults([]);
|
|
58
|
+
}
|
|
59
|
+
return () => ac.abort();
|
|
60
|
+
}, [searchTerm]);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div>
|
|
64
|
+
{activePersonAttributeUuid && (
|
|
65
|
+
<p className={styles.activeUuid}>{activePersonAttributeUuid}</p>
|
|
66
|
+
)}
|
|
67
|
+
<div className={styles.autocomplete}>
|
|
68
|
+
<Search
|
|
69
|
+
id={`search-input-${id}`}
|
|
70
|
+
labelText=""
|
|
71
|
+
type="text"
|
|
72
|
+
size="sm"
|
|
73
|
+
autoComplete="off"
|
|
74
|
+
autoCapitalize="off"
|
|
75
|
+
aria-autocomplete="list"
|
|
76
|
+
role="combobox"
|
|
77
|
+
aria-label={t(
|
|
78
|
+
"searchPersonAttributeHelperText",
|
|
79
|
+
"Person attribute type name"
|
|
80
|
+
)}
|
|
81
|
+
aria-controls={`searchbox-${id}`}
|
|
82
|
+
aria-expanded={searchResults.length > 0}
|
|
83
|
+
placeholder={t(
|
|
84
|
+
"searchPersonAttributeHelperText",
|
|
85
|
+
"Person attribute type name"
|
|
86
|
+
)}
|
|
87
|
+
onChange={($event) => {
|
|
88
|
+
handleSearchTermChange($event.target.value);
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
{!!searchResults.length && (
|
|
93
|
+
<StructuredListWrapper
|
|
94
|
+
selection
|
|
95
|
+
className={styles.listbox}
|
|
96
|
+
id={`searchbox-${id}`}
|
|
97
|
+
>
|
|
98
|
+
{searchResults.map((personAttributeType: any) => (
|
|
99
|
+
<StructuredListRow
|
|
100
|
+
key={personAttributeType.uuid}
|
|
101
|
+
role="option"
|
|
102
|
+
onClick={() => {
|
|
103
|
+
handleUuidChange(personAttributeType);
|
|
104
|
+
}}
|
|
105
|
+
aria-selected="true"
|
|
106
|
+
>
|
|
107
|
+
<StructuredListCell className={styles.smallListCell}>
|
|
108
|
+
{personAttributeType.display}
|
|
109
|
+
</StructuredListCell>
|
|
110
|
+
</StructuredListRow>
|
|
111
|
+
))}
|
|
112
|
+
</StructuredListWrapper>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
{searchTerm && searchResults && !searchResults.length && (
|
|
116
|
+
<p className={styles.bodyShort01}>
|
|
117
|
+
{t("noPersonAttributeFoundText", "No matching results found")}
|
|
118
|
+
</p>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@import "../../implementer-tools.styles.scss";
|
|
2
|
+
|
|
3
|
+
.activeUuid {
|
|
4
|
+
@include carbon--type-style("body-short-01");
|
|
5
|
+
color: $ui-05;
|
|
6
|
+
margin-bottom: $spacing-03;
|
|
7
|
+
padding-bottom: $spacing-03;
|
|
8
|
+
border-bottom: 1px solid $ui-03;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.autocomplete,
|
|
12
|
+
.autocomplete > [role="combobox"] {
|
|
13
|
+
position: relative;
|
|
14
|
+
width: 20em;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.listbox {
|
|
18
|
+
box-shadow: 0 0 $spacing-03;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.smallListCell {
|
|
22
|
+
padding: $spacing-03;
|
|
23
|
+
}
|